mirror of
https://github.com/BoostIO/BoostNote-App.git
synced 2024-10-04 16:18:06 +03:00
Introduce eslint and fix lint errors
This commit is contained in:
parent
2758ad5381
commit
d358daf9db
27
.eslintrc
Normal file
27
.eslintrc
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "react-hooks"],
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
770
package-lock.json
generated
770
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@
|
||||
"pack": "env-cmd ./.env build --dir",
|
||||
"dist": "env-cmd ./.env build",
|
||||
"shasum": "shasum -a 256 dist/mac/Inpad-$npm_package_version.dmg",
|
||||
"lint": "tslint -p . src/**/*.ts{,x}",
|
||||
"lint": "eslint src/**/*.ts{,x}",
|
||||
"format": "prettier --write \"src/**/*\"",
|
||||
"webpack": "NODE_ENV=development webpack-dev-server --config webpack.config.js",
|
||||
"build": "NODE_ENV=production webpack --mode production",
|
||||
@ -57,7 +57,14 @@
|
||||
"@types/webpack": "^4.4.17",
|
||||
"@types/webpack-dev-server": "^3.1.1",
|
||||
"@types/webpack-env": "^1.13.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.2.0",
|
||||
"@typescript-eslint/parser": "^2.2.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"eslint": "^6.3.0",
|
||||
"eslint-config-prettier": "^6.2.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.14.3",
|
||||
"eslint-plugin-react-hooks": "^2.0.1",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.14",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^24.9.0",
|
||||
|
@ -9,11 +9,14 @@ import ContextMenu from './ContextMenu'
|
||||
import Dialog from './Dialog/Dialog'
|
||||
import { useDb } from '../lib/db'
|
||||
|
||||
export default () => {
|
||||
const App = () => {
|
||||
const db = useDb()
|
||||
useEffect(() => {
|
||||
db.initialize()
|
||||
}, [])
|
||||
useEffect(
|
||||
() => {
|
||||
db.initialize()
|
||||
},
|
||||
[db]
|
||||
)
|
||||
return (
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<StyledAppContainer>
|
||||
@ -32,3 +35,5 @@ export default () => {
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useCallback } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import StorageItem from './StorageItem/StorageItem'
|
||||
import { useRouter } from '../../lib/router'
|
||||
import SotrageCreateForm from './StorageCreateForm'
|
||||
@ -6,49 +6,21 @@ import { StyledSideNavContainer, StyledStorageList } from './styled'
|
||||
import { useDb } from '../../lib/db'
|
||||
|
||||
export default () => {
|
||||
const db = useDb()
|
||||
const {
|
||||
createStorage,
|
||||
renameStorage,
|
||||
removeStorage,
|
||||
createFolder,
|
||||
removeFolder,
|
||||
storageMap
|
||||
} = useDb()
|
||||
const router = useRouter()
|
||||
|
||||
const storageEntries = useMemo(
|
||||
() => {
|
||||
return Object.entries(db.storageMap)
|
||||
return Object.entries(storageMap)
|
||||
},
|
||||
[db.storageMap]
|
||||
)
|
||||
|
||||
const createStorage = useCallback(
|
||||
async (storageName: string) => {
|
||||
await db.createStorage(storageName)
|
||||
},
|
||||
[db.createStorage]
|
||||
)
|
||||
|
||||
const renameStorage = useCallback(
|
||||
async (storageId: string, name: string) => {
|
||||
await db.renameStorage(storageId, name)
|
||||
},
|
||||
[db.renameStorage]
|
||||
)
|
||||
|
||||
const removeStorage = useCallback(
|
||||
async (storageId: string) => {
|
||||
await db.removeStorage(storageId)
|
||||
},
|
||||
[db.removeStorage]
|
||||
)
|
||||
|
||||
const createFolder = useCallback(
|
||||
async (storageId: string, pathname: string) => {
|
||||
await db.createFolder(storageId, pathname)
|
||||
},
|
||||
[db.createFolder]
|
||||
)
|
||||
|
||||
const removeFolder = useCallback(
|
||||
async (storageId: string, pathname: string) => {
|
||||
await db.removeFolder(storageId, pathname)
|
||||
},
|
||||
[db.removeFolder]
|
||||
[storageMap]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
type SotrageCreateFormProps = {
|
||||
createStorage: (storageName: string) => Promise<void>
|
||||
createStorage: (storageName: string) => Promise<any>
|
||||
}
|
||||
|
||||
type SotrageCreateFormState = {
|
||||
@ -36,12 +36,12 @@ export default class SotrageCreateForm extends React.Component<
|
||||
<div>
|
||||
<label>New storage</label>
|
||||
<input
|
||||
type="text"
|
||||
type='text'
|
||||
ref={this.nameInputRef}
|
||||
value={this.state.name}
|
||||
onChange={this.updateName}
|
||||
/>
|
||||
<button type="submit" onClick={this.createStorage}>
|
||||
<button type='submit' onClick={this.createStorage}>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@ type FolderItemProps = {
|
||||
}
|
||||
|
||||
export default (props: FolderItemProps) => {
|
||||
const dialog = useDialog()
|
||||
const { prompt, messageBox } = useDialog()
|
||||
const contextMenu = useContextMenu()
|
||||
const { storageId, folder, active, removeFolder, createFolder } = props
|
||||
const openContextMenu = useCallback(
|
||||
@ -28,7 +28,7 @@ export default (props: FolderItemProps) => {
|
||||
type: MenuTypes.Normal,
|
||||
label: 'New Folder',
|
||||
onClick: async () => {
|
||||
dialog.prompt({
|
||||
prompt({
|
||||
title: 'Create a Folder',
|
||||
message: 'Enter the path where do you want to create a folder',
|
||||
iconType: DialogIconTypes.Question,
|
||||
@ -46,7 +46,7 @@ export default (props: FolderItemProps) => {
|
||||
label: 'Remove Folder',
|
||||
enabled: !folderIsRootFolder,
|
||||
onClick: () => {
|
||||
dialog.messageBox({
|
||||
messageBox({
|
||||
title: `Remove "${folder.pathname}" folder`,
|
||||
message: 'All notes and subfolders will be deleted.',
|
||||
iconType: DialogIconTypes.Warning,
|
||||
@ -63,7 +63,15 @@ export default (props: FolderItemProps) => {
|
||||
}
|
||||
])
|
||||
},
|
||||
[dialog.messageBox, contextMenu.popup, createFolder, removeFolder]
|
||||
[
|
||||
folder.pathname,
|
||||
prompt,
|
||||
messageBox,
|
||||
contextMenu,
|
||||
createFolder,
|
||||
storageId,
|
||||
removeFolder
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -25,7 +25,7 @@ type StorageItemProps = {
|
||||
}
|
||||
|
||||
export default (props: StorageItemProps) => {
|
||||
const dialog = useDialog()
|
||||
const { prompt, messageBox } = useDialog()
|
||||
const contextMenu = useContextMenu()
|
||||
const {
|
||||
id,
|
||||
@ -63,7 +63,7 @@ export default (props: StorageItemProps) => {
|
||||
type: MenuTypes.Normal,
|
||||
label: 'New Folder',
|
||||
onClick: async () => {
|
||||
dialog.prompt({
|
||||
prompt({
|
||||
title: 'Create a Folder',
|
||||
message: 'Enter the path where do you want to create a folder',
|
||||
iconType: DialogIconTypes.Question,
|
||||
@ -80,7 +80,7 @@ export default (props: StorageItemProps) => {
|
||||
type: MenuTypes.Normal,
|
||||
label: 'Rename Storage',
|
||||
onClick: async () => {
|
||||
dialog.prompt({
|
||||
prompt({
|
||||
title: `Rename "${storageName}" storage`,
|
||||
message: 'Enter new name for the storage',
|
||||
iconType: DialogIconTypes.Question,
|
||||
@ -97,7 +97,7 @@ export default (props: StorageItemProps) => {
|
||||
type: MenuTypes.Normal,
|
||||
label: 'Remove Storage',
|
||||
onClick: async () => {
|
||||
dialog.messageBox({
|
||||
messageBox({
|
||||
title: `Remove "${storageName}" storage`,
|
||||
message: 'All notes and folders will be deleted.',
|
||||
iconType: DialogIconTypes.Warning,
|
||||
@ -114,7 +114,16 @@ export default (props: StorageItemProps) => {
|
||||
}
|
||||
])
|
||||
},
|
||||
[contextMenu.popup, dialog.prompt, dialog.messageBox, storageName, id]
|
||||
[
|
||||
contextMenu,
|
||||
prompt,
|
||||
messageBox,
|
||||
createFolder,
|
||||
id,
|
||||
storageName,
|
||||
renameStorage,
|
||||
removeStorage
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -8,7 +8,7 @@ export const menuMargin = 5
|
||||
export const menuVerticalPadding = 4
|
||||
export const menuZIndex = 9000
|
||||
|
||||
function createContextMenuStore(): ContextMenuContext {
|
||||
function useContextMenuStore(): ContextMenuContext {
|
||||
const [closed, setClosed] = useState(true)
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||
const [menuItems, setMenuItems] = useState<MenuItem[]>([])
|
||||
@ -55,4 +55,4 @@ function createContextMenuStore(): ContextMenuContext {
|
||||
export const {
|
||||
StoreProvider: ContextMenuProvider,
|
||||
useStore: useContextMenu
|
||||
} = createStoreContext(createContextMenuStore, 'context menu')
|
||||
} = createStoreContext(useContextMenuStore, 'context menu')
|
||||
|
@ -4,7 +4,7 @@ import { getFolderId, getTagId, generateNoteId, getNow } from './utils'
|
||||
import { NoteDoc, FolderDoc, ExceptRev } from './types'
|
||||
|
||||
let noteDbCount = 0
|
||||
async function prepareNoteDb(shouldInit: boolean = true): Promise<NoteDb> {
|
||||
async function prepareNoteDb(shouldInit = true): Promise<NoteDb> {
|
||||
const id = `dummy${++noteDbCount}`
|
||||
const pouchDb = new PouchDB(id, {
|
||||
adapter: 'memory'
|
||||
|
@ -165,7 +165,7 @@ export function createDbStoreCreator(
|
||||
})
|
||||
)
|
||||
},
|
||||
[storageMap, router.pathname]
|
||||
[storageMap, router]
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -11,7 +11,7 @@ export * from './types'
|
||||
|
||||
let id = 0
|
||||
|
||||
function createDialog(): DialogContext {
|
||||
function useDialogStore(): DialogContext {
|
||||
const [data, setData] = useState<DialogData | null>(null)
|
||||
const prompt = useCallback((options: PromptDialogOptions) => {
|
||||
setData({
|
||||
@ -42,4 +42,4 @@ function createDialog(): DialogContext {
|
||||
export const {
|
||||
StoreProvider: DialogProvider,
|
||||
useStore: useDialog
|
||||
} = createStoreContext(createDialog, 'dialog')
|
||||
} = createStoreContext(useDialogStore, 'dialog')
|
||||
|
@ -1,155 +0,0 @@
|
||||
import unified from 'unified'
|
||||
import parse from 'remark-parse'
|
||||
import frontmatter from 'remark-frontmatter'
|
||||
import parseYaml from 'remark-parse-yaml'
|
||||
import { getMetaData, getTitleFromNode, getTagsFromNode } from './markdown'
|
||||
|
||||
const processor = unified()
|
||||
.use(parse)
|
||||
.use(frontmatter)
|
||||
.use(parseYaml)
|
||||
|
||||
function parseAndTransform(value: string) {
|
||||
const parsedNode = processor.parse(value)
|
||||
const transformedNode = processor.runSync(parsedNode)
|
||||
return transformedNode
|
||||
}
|
||||
|
||||
describe('markdown', () => {
|
||||
describe('getMetaData', () => {
|
||||
it('returns meta data from string', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const value = [
|
||||
'---',
|
||||
'title: yaml title',
|
||||
'tags: [test, tags]',
|
||||
'---',
|
||||
'',
|
||||
'# test',
|
||||
''
|
||||
].join('\n')
|
||||
|
||||
// When
|
||||
const metaData = getMetaData(value)
|
||||
|
||||
// Then
|
||||
expect(metaData).toEqual({
|
||||
title: 'yaml title',
|
||||
tags: ['test', 'tags']
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTitleFromNode', () => {
|
||||
it('returns title from content of the first heading node if there is no yaml title', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const node = parseAndTransform([
|
||||
'---',
|
||||
'otherValue: test',
|
||||
'---',
|
||||
'',
|
||||
'# heading title',
|
||||
'',
|
||||
'## another heading',
|
||||
''
|
||||
].join('\n'))
|
||||
|
||||
// When
|
||||
const title = getTitleFromNode(node)
|
||||
|
||||
// Then
|
||||
expect(title).toBe('heading title')
|
||||
})
|
||||
|
||||
it('returns title from content of the first line if there is not heading node nor yaml title', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const node = parseAndTransform([
|
||||
'---',
|
||||
'otherValue: test',
|
||||
'---',
|
||||
'',
|
||||
'no title',
|
||||
''
|
||||
].join('\n'))
|
||||
|
||||
// When
|
||||
const title = getTitleFromNode(node)
|
||||
|
||||
// Then
|
||||
expect(title).toBe('no title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTagsFromNode', () => {
|
||||
it('parses tags as string', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const node = parseAndTransform([
|
||||
'---',
|
||||
'tags: test',
|
||||
'---',
|
||||
''
|
||||
].join('\n'))
|
||||
|
||||
// When
|
||||
const tags = getTagsFromNode(node)
|
||||
|
||||
// Then
|
||||
expect(tags).toEqual(['test'])
|
||||
})
|
||||
|
||||
it('parses tags as string array', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const node = parseAndTransform([
|
||||
'---',
|
||||
'tags: [test, tags, 123]',
|
||||
'---',
|
||||
''
|
||||
].join('\n'))
|
||||
|
||||
// When
|
||||
const tags = getTagsFromNode(node)
|
||||
|
||||
// Then
|
||||
expect(tags).toEqual(['test', 'tags', '123'])
|
||||
})
|
||||
|
||||
it('ignores non string value in array', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const node = parseAndTransform([
|
||||
'---',
|
||||
'tags: [test, [123]]',
|
||||
'---',
|
||||
''
|
||||
].join('\n'))
|
||||
|
||||
// When
|
||||
const tags = getTagsFromNode(node)
|
||||
|
||||
// Then
|
||||
expect(tags).toEqual(['test'])
|
||||
})
|
||||
|
||||
it('ignores duplicated entity', () => {
|
||||
// Given
|
||||
// prettier-ignore
|
||||
const node = parseAndTransform([
|
||||
'---',
|
||||
'tags: [test, test]',
|
||||
'---',
|
||||
''
|
||||
].join('\n'))
|
||||
|
||||
// When
|
||||
const tags = getTagsFromNode(node)
|
||||
|
||||
// Then
|
||||
expect(tags).toEqual(['test'])
|
||||
})
|
||||
})
|
||||
})
|
@ -1,93 +0,0 @@
|
||||
import unified from 'unified'
|
||||
import parse from 'remark-parse'
|
||||
import frontmatter from 'remark-frontmatter'
|
||||
import parseYaml from 'remark-parse-yaml'
|
||||
import visit from 'unist-util-visit'
|
||||
import convertMdastToString from 'mdast-util-to-string'
|
||||
import { pipe, filter, map, uniq } from 'ramda'
|
||||
|
||||
const processor = unified()
|
||||
.use(parse)
|
||||
.use(frontmatter)
|
||||
.use(parseYaml)
|
||||
|
||||
function hasYamlNode(node: any) {
|
||||
if (node.children[0] == null) return false
|
||||
return node.children[0].type === 'yaml'
|
||||
}
|
||||
|
||||
function hasYamlTitle(node: any) {
|
||||
if (!hasYamlNode(node)) return false
|
||||
return node.children[0].data.parsedValue.title != null
|
||||
}
|
||||
|
||||
function hasYamlTags(node: any) {
|
||||
if (!hasYamlNode(node)) return false
|
||||
return node.children[0].data.parsedValue.tags != null
|
||||
}
|
||||
|
||||
interface MetaData {
|
||||
title: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export function getMetaData(value: string): MetaData {
|
||||
const parsedNode = processor.parse(value)
|
||||
const transformedNode = processor.runSync(parsedNode)
|
||||
const title = getTitleFromNode(transformedNode)
|
||||
const tags = getTagsFromNode(transformedNode)
|
||||
|
||||
return {
|
||||
title,
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
||||
export function getTitleFromNode(node: any): string {
|
||||
if (hasYamlTitle(node)) {
|
||||
return node.children[0]!.data.parsedValue.title
|
||||
}
|
||||
|
||||
let title: string = ''
|
||||
visit(node, 'heading', (headingNode: any) => {
|
||||
title = convertMdastToString(headingNode)
|
||||
if (title.length > 0) {
|
||||
return visit.EXIT
|
||||
} else {
|
||||
return visit.CONTINUE
|
||||
}
|
||||
})
|
||||
if (title.length > 0) return title
|
||||
|
||||
visit(node, 'text', (literalNode: any) => {
|
||||
title = convertMdastToString(literalNode)
|
||||
if (title.length > 0) {
|
||||
return visit.EXIT
|
||||
} else {
|
||||
return visit.CONTINUE
|
||||
}
|
||||
})
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
export function getTagsFromNode(node: any): string[] {
|
||||
if (!hasYamlTags(node)) return []
|
||||
|
||||
const unknownTags: unknown = node.children[0].data.parsedValue.tags
|
||||
if (isStringOrNumber(unknownTags)) return [unknownTags.toString()]
|
||||
if (Array.isArray(unknownTags)) {
|
||||
return filterTags(unknownTags)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const filterTags = pipe(
|
||||
filter(isStringOrNumber),
|
||||
map(value => value.toString()),
|
||||
uniq
|
||||
)
|
||||
|
||||
function isStringOrNumber(value: any): value is string | number {
|
||||
return typeof value === 'string' || typeof value === 'number'
|
||||
}
|
@ -7,6 +7,7 @@ import path from 'path'
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import { createStoreContext } from '../utils/context'
|
||||
import React, { useState, useEffect, useCallback, FC } from 'react'
|
||||
import { omit } from 'ramda'
|
||||
|
||||
export const history = createBrowserHistory()
|
||||
|
||||
@ -43,10 +44,10 @@ function normalizePathname(pathname: string): string {
|
||||
return normalizedPathname
|
||||
}
|
||||
|
||||
function normalizeLocation({ pathname, key, ...otherProps }: Location) {
|
||||
function normalizeLocation({ pathname, ...otherProps }: Location) {
|
||||
return {
|
||||
pathname: normalizePathname(pathname),
|
||||
...otherProps
|
||||
...omit(['key'], otherProps)
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +64,7 @@ export interface RouterStore {
|
||||
|
||||
const initialLocation = normalizeLocation(history.location)
|
||||
|
||||
function createRouteStore(): RouterStore {
|
||||
function useRouteStore(): RouterStore {
|
||||
const [location, setLocation] = useState(initialLocation)
|
||||
|
||||
useEffect(() => {
|
||||
@ -83,8 +84,8 @@ function createRouteStore(): RouterStore {
|
||||
const go = useCallback((count: number) => {
|
||||
history.go(count)
|
||||
}, [])
|
||||
const goBack = useCallback(() => go(-1), [])
|
||||
const goForward = useCallback(() => go(1), [])
|
||||
const goBack = useCallback(() => go(-1), [go])
|
||||
const goForward = useCallback(() => go(1), [go])
|
||||
|
||||
return {
|
||||
pathname: location.pathname,
|
||||
@ -101,7 +102,7 @@ function createRouteStore(): RouterStore {
|
||||
export const {
|
||||
StoreProvider: RouterProvider,
|
||||
useStore: useRouter
|
||||
} = createStoreContext(createRouteStore)
|
||||
} = createStoreContext(useRouteStore)
|
||||
|
||||
export interface LinkProps {
|
||||
href: string
|
||||
|
@ -1,38 +0,0 @@
|
||||
export default class MemoryStorage implements Storage {
|
||||
private map: Map<string, string>
|
||||
|
||||
get length() {
|
||||
return this.map.size
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.map[Symbol.iterator]()
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.map = new Map()
|
||||
}
|
||||
|
||||
setItem(key: string, value: any) {
|
||||
this.map.set(key, value.toString())
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
if (this.map.has(key)) {
|
||||
return this.map.get(key) as string
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.map = new Map()
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
this.map.delete(key)
|
||||
}
|
||||
|
||||
key(index: number) {
|
||||
return this.map.keys()[index]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user