feat: add eslint to frontend

This commit is contained in:
steven 2021-12-13 17:23:35 +08:00
parent 493391bb03
commit 73812cd58d
21 changed files with 1444 additions and 163 deletions

View file

@ -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"
}
} }

8
web/.vscode/setting.json vendored Normal file
View file

@ -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"
}

View file

@ -4,7 +4,8 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"serve": "vite preview" "serve": "vite preview",
"lint": "eslint --ext .js,.ts,.tsx, src"
}, },
"dependencies": { "dependencies": {
"react": "^17.0.2", "react": "^17.0.2",
@ -14,8 +15,15 @@
"devDependencies": { "devDependencies": {
"@types/react": "^17.0.2", "@types/react": "^17.0.2",
"@types/react-dom": "^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", "@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", "less": "^4.1.1",
"prettier": "2.5.1",
"typescript": "^4.3.2", "typescript": "^4.3.2",
"vite": "^2.6.14" "vite": "^2.6.14"
}, },

View file

@ -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 { memoService, queryService } from "../services";
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter"; import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
@ -143,7 +143,7 @@ interface MemoFilterInputerProps {
handleFilterRemove: (index: number) => void; handleFilterRemove: (index: number) => void;
} }
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo((props: MemoFilterInputerProps) => { const FilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
const { index, filter, handleFilterChange, handleFilterRemove } = props; const { index, filter, handleFilterChange, handleFilterRemove } = props;
const { type } = filter; const { type } = filter;
const [inputElements, setInputElements] = useState<JSX.Element>(<></>); const [inputElements, setInputElements] = useState<JSX.Element>(<></>);
@ -294,7 +294,9 @@ const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo((props: MemoFil
<img className="remove-btn" src="/icons/close.svg" onClick={handleRemoveBtnClick} /> <img className="remove-btn" src="/icons/close.svg" onClick={handleRemoveBtnClick} />
</div> </div>
); );
}); };
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo(FilterInputer);
export default function showCreateQueryDialog(queryId?: string): void { export default function showCreateQueryDialog(queryId?: string): void {
showDialog( showDialog(

View file

@ -25,6 +25,7 @@ interface Props {
onContentChange: (content: string) => void; onContentChange: (content: string) => void;
} }
// eslint-disable-next-line react/display-name
const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefActions>) => { const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefActions>) => {
const { const {
globalState: { useTinyUndoHistoryCache }, globalState: { useTinyUndoHistoryCache },
@ -45,16 +46,21 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
const refresh = useRefresh(); const refresh = useRefresh();
useEffect(() => { useEffect(() => {
if (initialContent) { if (initialContent && editorRef.current) {
editorRef.current!.value = initialContent; editorRef.current.value = initialContent;
refresh(); refresh();
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
if (useTinyUndoHistoryCache) { if (useTinyUndoHistoryCache) {
if (!editorRef.current) {
return;
}
const { tinyUndoActionsCache, tinyUndoIndexCache } = storage.get(["tinyUndoActionsCache", "tinyUndoIndexCache"]); const { tinyUndoActionsCache, tinyUndoIndexCache } = storage.get(["tinyUndoActionsCache", "tinyUndoIndexCache"]);
tinyUndoRef.current = new TinyUndo(editorRef.current!, {
tinyUndoRef.current = new TinyUndo(editorRef.current, {
interval: 5000, interval: 5000,
initialActions: tinyUndoActionsCache, initialActions: tinyUndoActionsCache,
initialIndex: tinyUndoIndexCache, initialIndex: tinyUndoIndexCache,
@ -88,17 +94,23 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
ref, ref,
() => ({ () => ({
focus: () => { focus: () => {
editorRef.current!.focus(); editorRef.current?.focus();
}, },
insertText: (rawText: string) => { insertText: (rawText: string) => {
const prevValue = editorRef.current!.value; if (!editorRef.current) {
editorRef.current!.value = prevValue + rawText; return;
handleContentChangeCallback(editorRef.current!.value); }
const prevValue = editorRef.current.value;
editorRef.current.value = prevValue + rawText;
handleContentChangeCallback(editorRef.current.value);
refresh(); refresh();
}, },
setContent: (text: string) => { setContent: (text: string) => {
editorRef.current!.value = text; if (editorRef.current) {
editorRef.current.value = text;
refresh(); refresh();
}
}, },
getContent: (): string => { getContent: (): string => {
return editorRef.current?.value ?? ""; return editorRef.current?.value ?? "";
@ -108,7 +120,7 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
); );
const handleEditorInput = useCallback(() => { const handleEditorInput = useCallback(() => {
handleContentChangeCallback(editorRef.current!.value); handleContentChangeCallback(editorRef.current?.value ?? "");
refresh(); refresh();
}, []); }, []);
@ -124,8 +136,12 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
}, []); }, []);
const handleCommonConfirmBtnClick = useCallback(() => { const handleCommonConfirmBtnClick = useCallback(() => {
handleConfirmBtnClickCallback(editorRef.current!.value); if (!editorRef.current) {
editorRef.current!.value = ""; return;
}
handleConfirmBtnClickCallback(editorRef.current.value);
editorRef.current.value = "";
refresh(); refresh();
// After confirm btn clicked, tiny-undo should reset state(clear actions and index) // After confirm btn clicked, tiny-undo should reset state(clear actions and index)
tinyUndoRef.current?.resetState(); tinyUndoRef.current?.resetState();

View file

@ -14,7 +14,6 @@ const MemoList: React.FC<Props> = () => {
const { const {
locationState: { query }, locationState: { query },
memoState: { memos }, memoState: { memos },
globalState,
} = useContext(appContext); } = useContext(appContext);
const [isFetching, setFetchStatus] = useState(true); const [isFetching, setFetchStatus] = useState(true);
const wrapperElement = useRef<HTMLDivElement>(null); const wrapperElement = useRef<HTMLDivElement>(null);

View file

@ -23,7 +23,6 @@ const MyAccountSection: React.FC<Props> = () => {
const [username, setUsername] = useState<string>(user.username); const [username, setUsername] = useState<string>(user.username);
const [showEditUsernameInputs, setShowEditUsernameInputs] = useState(false); const [showEditUsernameInputs, setShowEditUsernameInputs] = useState(false);
const [showConfirmUnbindGithubBtn, setShowConfirmUnbindGithubBtn] = useState(false); const [showConfirmUnbindGithubBtn, setShowConfirmUnbindGithubBtn] = useState(false);
const [showConfirmUnbindWxBtn, setShowConfirmUnbindWxBtn] = useState(false);
const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => { const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const nextUsername = e.target.value as string; const nextUsername = e.target.value as string;

View file

@ -11,7 +11,7 @@ const PreferencesSection: React.FC<Props> = () => {
const { globalState } = useContext(appContext); const { globalState } = useContext(appContext);
const { useTinyUndoHistoryCache, shouldHideImageUrl, shouldSplitMemoWord, shouldUseMarkdownParser } = globalState; const { useTinyUndoHistoryCache, shouldHideImageUrl, shouldSplitMemoWord, shouldUseMarkdownParser } = globalState;
const demoMemoContent = `👋 你好呀~\n我是一个demo\n* 👏 欢迎使用memos`; const demoMemoContent = "👋 你好呀~\n我是一个demo\n* 👏 欢迎使用memos";
const handleOpenTinyUndoChanged = () => { const handleOpenTinyUndoChanged = () => {
globalStateService.setAppSetting({ globalStateService.setAppSetting({
@ -87,7 +87,7 @@ const PreferencesSection: React.FC<Props> = () => {
<label className="form-label checkbox-form-label" onClick={handleOpenTinyUndoChanged}> <label className="form-label checkbox-form-label" onClick={handleOpenTinyUndoChanged}>
<span className="normal-text"> <span className="normal-text">
{" "} {" "}
<a target="_blank" href="https://github.com/boojack/tiny-undo" onClick={(e) => e.stopPropagation()}> <a target="_blank" href="https://github.com/boojack/tiny-undo" onClick={(e) => e.stopPropagation()} rel="noreferrer">
tiny-undo tiny-undo
</a> </a>
</span> </span>

View file

@ -1,4 +1,4 @@
import React, { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
import appContext from "../stores/appContext"; import appContext from "../stores/appContext";
import useToggle from "../hooks/useToggle"; import useToggle from "../hooks/useToggle";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";

View file

@ -1,4 +1,4 @@
import React, { memo, useEffect, useRef } from "react"; import { memo, useEffect, useRef } from "react";
import useToggle from "../../hooks/useToggle"; import useToggle from "../../hooks/useToggle";
import "../../less/common/selector.less"; import "../../less/common/selector.less";

View file

@ -16,9 +16,9 @@ async function request<T>(method: string, url: string, data?: any): Promise<Resp
"Content-Type": "application/json", "Content-Type": "application/json",
}; };
if (data !== null) { if (data !== null) {
}
requestConfig.body = JSON.stringify(data); requestConfig.body = JSON.stringify(data);
} }
}
const response = await fetch(url, requestConfig); const response = await fetch(url, requestConfig);
const responseData = (await response.json()) as ResponseType<T>; const responseData = (await response.json()) as ResponseType<T>;

View file

@ -169,7 +169,7 @@ namespace utils {
finalObject[key] = temp; finalObject[key] = temp;
} }
} else { } else {
if (Boolean(val)) { if (val) {
finalObject[key] = val; finalObject[key] = val;
} }
} }

View file

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
function useLoading(initialState: boolean = true) { function useLoading(initialState = true) {
const [state, setState] = useState({ isLoading: initialState, isFailed: false, isSucceed: false }); const [state, setState] = useState({ isLoading: initialState, isFailed: false, isSucceed: false });
return { return {

View file

@ -1,7 +1,7 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
function useRefresh() { function useRefresh() {
const [_, setBoolean] = useState<Boolean>(false); const [, setBoolean] = useState<boolean>(false);
const refresh = useCallback(() => { const refresh = useCallback(() => {
setBoolean((ps) => { setBoolean((ps) => {

View file

@ -1,4 +1,4 @@
export type State = Readonly<Object>; export type State = Readonly<Record<string, any>>;
export type Action = { export type Action = {
type: string; type: string;
payload: any; payload: any;

View file

@ -1,14 +1,15 @@
const cachedResourceMap = new Map<string, string>(); const cachedResourceMap = new Map<string, string>();
const convertResourceToDataURL = (url: string, useCache = true): Promise<string> => { const convertResourceToDataURL = async (url: string, useCache = true): Promise<string> => {
if (useCache && cachedResourceMap.has(url)) { if (useCache && cachedResourceMap.has(url)) {
return Promise.resolve(cachedResourceMap.get(url) as string); return Promise.resolve(cachedResourceMap.get(url) as string);
} }
return new Promise(async (resolve) => {
const res = await fetch(url); const res = await fetch(url);
const blob = await res.blob(); const blob = await res.blob();
var reader = new FileReader();
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = () => {
const base64Url = reader.result as string; const base64Url = reader.result as string;
cachedResourceMap.set(url, base64Url); cachedResourceMap.set(url, base64Url);

View file

@ -21,7 +21,7 @@ const getFontsStyleElement = async (element: HTMLElement) => {
const resourceUrls = src.split(",").map((s) => { const resourceUrls = src.split(",").map((s) => {
return s.replace(/url\("?(.+?)"?\)/, "$1"); return s.replace(/url\("?(.+?)"?\)/, "$1");
}); });
let base64Urls = []; const base64Urls: string[] = [];
for (const url of resourceUrls) { for (const url of resourceUrls) {
try { try {

View file

@ -73,11 +73,16 @@ export const toCanvas = async (element: HTMLElement, options?: Options): Promise
const imageEl = new Image(); const imageEl = new Image();
imageEl.src = url; imageEl.src = url;
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
const ratio = options?.pixelRatio || 1; const ratio = options?.pixelRatio || 1;
const { width, height } = getElementSize(element); const { width, height } = getElementSize(element);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context) {
return Promise.reject("Canvas error");
}
canvas.width = width * ratio; canvas.width = width * ratio;
canvas.height = height * ratio; canvas.height = height * ratio;

View file

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
type State = Readonly<Object>; type State = Readonly<Record<string, any>>;
interface Action { interface Action {
type: string; type: string;
} }

View file

@ -8,8 +8,8 @@ export default defineConfig({
cors: true, cors: true,
proxy: { proxy: {
"/api": { "/api": {
target: "http://localhost:8080/", // target: "http://localhost:8080/",
// target: "https://memos.justsven.top/", target: "https://memos.justsven.top/",
changeOrigin: true, changeOrigin: true,
}, },
}, },

File diff suppressed because it is too large Load diff