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": {
"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"
},

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 { 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<MemoFilterInputerProps> = memo((props: MemoFilterInputerProps) => {
const FilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
const { index, filter, handleFilterChange, handleFilterRemove } = props;
const { type } = filter;
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} />
</div>
);
});
};
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo(FilterInputer);
export default function showCreateQueryDialog(queryId?: string): void {
showDialog(

View file

@ -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<EditorRefActions>) => {
const {
globalState: { useTinyUndoHistoryCache },
@ -45,16 +46,21 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
const refresh = useRefresh();
useEffect(() => {
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<EditorRefAction
ref,
() => ({
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<EditorRefAction
);
const handleEditorInput = useCallback(() => {
handleContentChangeCallback(editorRef.current!.value);
handleContentChangeCallback(editorRef.current?.value ?? "");
refresh();
}, []);
@ -124,8 +136,12 @@ const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefAction
}, []);
const handleCommonConfirmBtnClick = useCallback(() => {
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();

View file

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

View file

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

View file

@ -11,7 +11,7 @@ const PreferencesSection: React.FC<Props> = () => {
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<Props> = () => {
<label className="form-label checkbox-form-label" onClick={handleOpenTinyUndoChanged}>
<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
</a>
</span>

View file

@ -1,4 +1,4 @@
import React, { useContext, useEffect } from "react";
import { useContext, useEffect } from "react";
import appContext from "../stores/appContext";
import useToggle from "../hooks/useToggle";
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 "../../less/common/selector.less";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,15 @@
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)) {
return Promise.resolve(cachedResourceMap.get(url) as string);
}
return new Promise(async (resolve) => {
const res = await fetch(url);
const blob = await res.blob();
var reader = new FileReader();
const res = await fetch(url);
const blob = await res.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64Url = reader.result as string;
cachedResourceMap.set(url, base64Url);

View file

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

View file

@ -73,11 +73,16 @@ export const toCanvas = async (element: HTMLElement, options?: Options): Promise
const imageEl = new Image();
imageEl.src = url;
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
const ratio = options?.pixelRatio || 1;
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.height = height * ratio;

View file

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

View file

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

File diff suppressed because it is too large Load diff