diff --git a/web/.eslintrc.json b/web/.eslintrc.json index 7b74b597..b144ee76 100644 --- a/web/.eslintrc.json +++ b/web/.eslintrc.json @@ -1,3 +1,24 @@ { - "extends": ["prettier"] + "env": { + "browser": true, + "es2021": true + }, + "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["react", "@typescript-eslint", "prettier"], + "ignorePatterns": ["node_modules", "dist", "public"], + "rules": { + "prettier/prettier": "error", + "@typescript-eslint/no-empty-interface": ["off"], + "@typescript-eslint/no-explicit-any": ["off"], + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-namespace": "off" + } } diff --git a/web/.vscode/setting.json b/web/.vscode/setting.json new file mode 100644 index 00000000..6c71edb3 --- /dev/null +++ b/web/.vscode/setting.json @@ -0,0 +1,8 @@ +{ + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} diff --git a/web/package.json b/web/package.json index b500838e..6208c664 100644 --- a/web/package.json +++ b/web/package.json @@ -4,7 +4,8 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "eslint --ext .js,.ts,.tsx, src" }, "dependencies": { "react": "^17.0.2", @@ -14,8 +15,15 @@ "devDependencies": { "@types/react": "^17.0.2", "@types/react-dom": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^5.6.0", + "@typescript-eslint/parser": "^5.6.0", "@vitejs/plugin-react": "^1.0.0", + "eslint": "^8.4.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.27.1", "less": "^4.1.1", + "prettier": "2.5.1", "typescript": "^4.3.2", "vite": "^2.6.14" }, diff --git a/web/src/components/CreateQueryDialog.tsx b/web/src/components/CreateQueryDialog.tsx index 6c4e2909..65a48922 100644 --- a/web/src/components/CreateQueryDialog.tsx +++ b/web/src/components/CreateQueryDialog.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useEffect, useState } from "react"; +import { memo, useCallback, useEffect, useState } from "react"; import { memoService, queryService } from "../services"; import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter"; import useLoading from "../hooks/useLoading"; @@ -143,7 +143,7 @@ interface MemoFilterInputerProps { handleFilterRemove: (index: number) => void; } -const MemoFilterInputer: React.FC = memo((props: MemoFilterInputerProps) => { +const FilterInputer: React.FC = (props: MemoFilterInputerProps) => { const { index, filter, handleFilterChange, handleFilterRemove } = props; const { type } = filter; const [inputElements, setInputElements] = useState(<>); @@ -294,7 +294,9 @@ const MemoFilterInputer: React.FC = memo((props: MemoFil ); -}); +}; + +const MemoFilterInputer: React.FC = memo(FilterInputer); export default function showCreateQueryDialog(queryId?: string): void { showDialog( diff --git a/web/src/components/Editor/Editor.tsx b/web/src/components/Editor/Editor.tsx index a79fb632..79848a6d 100644 --- a/web/src/components/Editor/Editor.tsx +++ b/web/src/components/Editor/Editor.tsx @@ -25,6 +25,7 @@ interface Props { onContentChange: (content: string) => void; } +// eslint-disable-next-line react/display-name const Editor = forwardRef((props: Props, ref: React.ForwardedRef) => { const { globalState: { useTinyUndoHistoryCache }, @@ -45,16 +46,21 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef { - if (initialContent) { - editorRef.current!.value = initialContent; + if (initialContent && editorRef.current) { + editorRef.current.value = initialContent; refresh(); } }, []); useEffect(() => { if (useTinyUndoHistoryCache) { + if (!editorRef.current) { + return; + } + const { tinyUndoActionsCache, tinyUndoIndexCache } = storage.get(["tinyUndoActionsCache", "tinyUndoIndexCache"]); - tinyUndoRef.current = new TinyUndo(editorRef.current!, { + + tinyUndoRef.current = new TinyUndo(editorRef.current, { interval: 5000, initialActions: tinyUndoActionsCache, initialIndex: tinyUndoIndexCache, @@ -88,17 +94,23 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef ({ focus: () => { - editorRef.current!.focus(); + editorRef.current?.focus(); }, insertText: (rawText: string) => { - const prevValue = editorRef.current!.value; - editorRef.current!.value = prevValue + rawText; - handleContentChangeCallback(editorRef.current!.value); + if (!editorRef.current) { + return; + } + + const prevValue = editorRef.current.value; + editorRef.current.value = prevValue + rawText; + handleContentChangeCallback(editorRef.current.value); refresh(); }, setContent: (text: string) => { - editorRef.current!.value = text; - refresh(); + if (editorRef.current) { + editorRef.current.value = text; + refresh(); + } }, getContent: (): string => { return editorRef.current?.value ?? ""; @@ -108,7 +120,7 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef { - handleContentChangeCallback(editorRef.current!.value); + handleContentChangeCallback(editorRef.current?.value ?? ""); refresh(); }, []); @@ -124,8 +136,12 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef { - handleConfirmBtnClickCallback(editorRef.current!.value); - editorRef.current!.value = ""; + if (!editorRef.current) { + return; + } + + handleConfirmBtnClickCallback(editorRef.current.value); + editorRef.current.value = ""; refresh(); // After confirm btn clicked, tiny-undo should reset state(clear actions and index) tinyUndoRef.current?.resetState(); diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index d3bc0051..4e2d636e 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -14,7 +14,6 @@ const MemoList: React.FC = () => { const { locationState: { query }, memoState: { memos }, - globalState, } = useContext(appContext); const [isFetching, setFetchStatus] = useState(true); const wrapperElement = useRef(null); diff --git a/web/src/components/MyAccountSection.tsx b/web/src/components/MyAccountSection.tsx index e9ef13b1..05e22ef4 100644 --- a/web/src/components/MyAccountSection.tsx +++ b/web/src/components/MyAccountSection.tsx @@ -23,7 +23,6 @@ const MyAccountSection: React.FC = () => { const [username, setUsername] = useState(user.username); const [showEditUsernameInputs, setShowEditUsernameInputs] = useState(false); const [showConfirmUnbindGithubBtn, setShowConfirmUnbindGithubBtn] = useState(false); - const [showConfirmUnbindWxBtn, setShowConfirmUnbindWxBtn] = useState(false); const handleUsernameChanged = (e: React.ChangeEvent) => { const nextUsername = e.target.value as string; diff --git a/web/src/components/PreferencesSection.tsx b/web/src/components/PreferencesSection.tsx index 82d8d601..09b54ed7 100644 --- a/web/src/components/PreferencesSection.tsx +++ b/web/src/components/PreferencesSection.tsx @@ -11,7 +11,7 @@ const PreferencesSection: React.FC = () => { const { globalState } = useContext(appContext); const { useTinyUndoHistoryCache, shouldHideImageUrl, shouldSplitMemoWord, shouldUseMarkdownParser } = globalState; - const demoMemoContent = `👋 你好呀~\n我是一个demo:\n* 👏 欢迎使用memos;`; + const demoMemoContent = "👋 你好呀~\n我是一个demo:\n* 👏 欢迎使用memos;"; const handleOpenTinyUndoChanged = () => { globalStateService.setAppSetting({ @@ -87,7 +87,7 @@ const PreferencesSection: React.FC = () => {