refactor: add eslint

pull/31/head
fantasticit 2022-05-02 00:15:13 +08:00
parent 881b1c6846
commit 8a1da277f5
33 changed files with 116 additions and 145 deletions

View File

@ -2,5 +2,5 @@ node_modules
**/.next/**
**/_next/**
**/dist/**
./packages/client/src/tiptap/next.config.js
.eslintrc.js
./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js

View File

@ -1,17 +1,15 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
experimentalObjectRestSpread: true,
},
allowImportExportEverywhere: true,
project: ['./packages/client/tsconfig.json'],
},
plugins: ['@typescript-eslint', 'react-hooks'],
extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
overrides: [
{
files: ['*.ts', '*.tsx', '.js', '.jsx'],
parserOptions: {
project: ['./packages/client/tsconfig.json'],
},
},
],
settings: {
react: {
version: 'detect',

View File

@ -4,7 +4,7 @@
"author": "fantasticit",
"scripts": {
"clean": "npx rimraf ./node_modules ./packages/**/node_modules",
"dev": "concurrently \"pnpm:dev:*\"",
"dev": "concurrently 'pnpm:dev:*'",
"dev:server": "pnpm run --dir packages/server dev",
"dev:client": "pnpm run --dir packages/client dev",
"build": "pnpm build:server && pnpm build:client",
@ -14,17 +14,18 @@
"build:config": "pnpm run --dir packages/config build",
"build:server": "pnpm run --dir packages/server build",
"build:client": "pnpm run --dir packages/client build",
"start": "concurrently \"pnpm:start:*\"",
"start": "concurrently 'pnpm:start:*'",
"start:server": "pnpm run --dir packages/server start",
"start:client": "pnpm run --dir packages/client start",
"pm2": "pnpm run pm2:server && pnpm run pm2:client",
"pm2:server": "pnpm run --dir packages/server pm2",
"pm2:client": "pnpm run --dir packages/client pm2",
"lint": "concurrently 'pnpm:lint:*'",
"lint:client": "eslint --fix './packages/client/**/*.{ts,tsx,js,jsx}' -c '.eslintrc.client.js'",
"lint:server": "eslint --fix './packages/server/src/*.{ts,js}' -c '.eslintrc.server.js'",
"format": "concurrently \"pnpm:format:*\"",
"format:ts": "prettier --write --parser typescript \"packages/**/*.{ts,tsx,js,jsx}\"",
"format:css": "stylelint --fix --formatter verbose --allow-empty-input \"packages/**/*.{css,scss,sass}\"",
"format": "concurrently 'pnpm:format:*'",
"format:ts": "prettier --write --parser typescript 'packages/**/*.{ts,tsx,js,jsx}'",
"format:css": "stylelint --fix --formatter verbose --allow-empty-input 'packages/**/*.{css,scss,sass}'",
"prepare": "husky install",
"precommit": "lint-staged"
},
@ -62,6 +63,12 @@
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "prettier --write",
"./packages/client/**/*.{ts,tsx,js,jsx}": [
"eslint --fix -c '.eslintrc.client.js'"
],
"./packages/server/src/*.{ts,js}": [
"eslint --fix -c '.eslintrc.server.js'"
],
"*.{css,scss,sass}": " stylelint --fix --formatter verbose --allow-empty-input"
}
}

View File

@ -1,14 +1,16 @@
/* eslint-disable */
/* eslint-env es6 */
const semi = require('@douyinfe/semi-next').default({});
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const { getConfig } = require('@think/config');
const config = getConfig();
const nextConfig = require('@douyinfe/semi-next').default({})({
/** @type {import('next').NextConfig} */
const nextConfig = semi({
assetPrefix: config.assetPrefix,
env: {
SERVER_API_URL: config?.client?.apiUrl,
COLLABORATION_API_URL: config?.client?.collaborationUrl,
ENABLE_ALIYUN_OSS: !!config?.oss?.aliyun?.accessKeyId,
SERVER_API_URL: config.client.apiUrl,
COLLABORATION_API_URL: config.client.collaborationUrl,
ENABLE_ALIYUN_OSS: !!config.oss.aliyun.accessKeyId,
},
webpack: (config, { dev, isServer }) => {
config.resolve.plugins.push(new TsconfigPathsPlugin());

View File

@ -37,7 +37,6 @@ interface IProps {
}
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority, className, style }) => {
if (!currentUser) return null;
const $hasShowUserSettingModal = useRef(false);
const { users, addUser, updateUser } = useCollaborationDocument(documentId);
const [status, setStatus] = useState<ProviderStatus>('connecting');
@ -64,7 +63,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
},
},
});
}, [documentId, currentUser.token]);
}, [documentId, currentUser, toggleLoading]);
const editor = useEditor({
editable: authority && authority.editable,
extensions: [
@ -77,7 +76,9 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
try {
const title = transaction.doc.content.firstChild.content.firstChild.textContent;
triggerChangeDocumentTitle(title);
} catch (e) {}
} catch (e) {
//
}
}, 50),
});
const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false);
@ -98,7 +99,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
destoryProvider(provider, 'EDITOR');
destoryIndexdbProvider(documentId);
};
}, []);
}, [documentId, provider]);
useEffect(() => {
if (!editor) return;
@ -157,7 +158,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
Router.events.off('routeChangeStart', handler);
window.removeEventListener('unload', handler);
};
}, [editor, users, currentUser]);
}, [editor, users, currentUser, toggleMentionUsersSettingVisible]);
useEffect(() => {
const listener = (event: KeyboardEvent) => {

View File

@ -27,7 +27,6 @@ interface IProps {
}
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
if (!documentId) return null;
const { width: windowWith } = useWindowSize();
const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => {
@ -42,7 +41,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
Router.push({
pathname: `/wiki/${document.wikiId}/document/${documentId}`,
});
}, [document]);
}, [document, documentId]);
const DocumentTitle = (
<>

View File

@ -49,7 +49,7 @@ export const Editor: React.FC<IProps> = ({ user, documentId, document, children
},
},
});
}, [documentId, user.token]);
}, [documentId, user, toggleLoading]);
const editor = useEditor({
editable: false,
extensions: [
@ -68,7 +68,7 @@ export const Editor: React.FC<IProps> = ({ user, documentId, document, children
return () => {
destoryProvider(provider, 'READER');
};
}, []);
}, [provider]);
return (
<DataRender

View File

@ -64,7 +64,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
hasCancel: false,
maskClosable: false,
onOk() {
const $input = document.querySelector('#js-share-document-password') as HTMLInputElement;
const $input = document.querySelector('#js-share-document-password');
query($input.value);
},
});

View File

@ -12,7 +12,7 @@ export const ImageViewer: React.FC<IProps> = ({ container, containerSelector })
if (!el) {
return null;
}
const viewer = new Viewer(el as HTMLElement, { inline: false });
const viewer = new Viewer(el, { inline: false });
const io = new MutationObserver(() => {
viewer.update();
});

View File

@ -117,25 +117,32 @@ const MessageBox = () => {
Notification.info({
title: '消息通知',
content: (
<Link href={msg.url}>
<a className={styles.item}>
<div className={styles.leftWrap}>
<Text
ellipsis={{
showTooltip: {
opts: { content: msg.message },
},
}}
style={{ width: 240 }}
>
{msg.title}
</Text>
</div>
</a>
</Link>
<>
<div>
<Text
ellipsis={{
showTooltip: {
opts: { content: msg.message },
},
}}
style={{ width: 240 }}
>
{msg.title}
</Text>
</div>
<div>
<Text link>
<Link href={msg.url}>
<a className={styles.item} target="_blank">
</a>
</Link>
</Text>
</div>
</>
),
duration: 3,
showClose: true,
onHookClose() {
readMessage(msg.id);
},

View File

@ -43,11 +43,7 @@ import { Iframe } from './menus/iframe';
import { Table } from './menus/table';
import { Mind } from './menus/mind';
export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
if (!editor) {
return null;
}
const _MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
return (
<div>
<Space spacing={2}>
@ -87,8 +83,8 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
<Divider />
<Emoji editor={editor} />
<Link editor={editor} />
<Blockquote editor={editor} />
<Link editor={editor} />
<HorizontalRule editor={editor} />
<Search editor={editor} />
@ -107,11 +103,11 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
);
};
export const CommentMenuBar: React.FC<{ editor: any }> = ({ editor }) => {
if (!editor) {
return null;
}
export const MenuBar = React.memo(_MenuBar, (prevProps, nextProps) => {
return prevProps.editor === nextProps.editor;
});
const _CommentMenuBar: React.FC<{ editor: any }> = ({ editor }) => {
return (
<>
<Space spacing={2}>
@ -135,3 +131,7 @@ export const CommentMenuBar: React.FC<{ editor: any }> = ({ editor }) => {
</>
);
};
export const CommentMenuBar = React.memo(_CommentMenuBar, (prevProps, nextProps) => {
return prevProps.editor === nextProps.editor;
});

View File

@ -9,10 +9,6 @@ import { ColorPicker } from '../_components/color-picker';
export const BackgroundColor: React.FC<{ editor: Editor }> = ({ editor }) => {
const { backgroundColor } = editor.getAttributes('textStyle');
if (!editor) {
return null;
}
return (
<ColorPicker
onSetColor={(color) => {

View File

@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Bold: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="粗体">
<Button

View File

@ -5,10 +5,6 @@ import { IconClear } from 'components/icons';
import { Tooltip } from 'components/tooltip';
export const CleadrNodeAndMarks: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="清除格式">
<Button

View File

@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Code: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="行内代码">
<Button

View File

@ -163,10 +163,6 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
setRecentUsed(transformToCommands(insertMenuLRUCache.get() as string[]));
}, [visible, transformToCommands]);
if (!editor) {
return null;
}
return (
<Dropdown
zIndex={10000}
@ -181,13 +177,13 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
}}
render={
<Dropdown.Menu>
{renderedCommands.map((command) => {
{renderedCommands.map((command, index) => {
return command.title ? (
<Dropdown.Title>{command.title}</Dropdown.Title>
<Dropdown.Title key={'title' + index}>{command.title}</Dropdown.Title>
) : command.custom ? (
command.custom(editor, runCommand)
) : (
<Dropdown.Item onClick={runCommand(command)}>
<Dropdown.Item key={command.label} onClick={runCommand(command)}>
{command.icon}
{command.label}
</Dropdown.Item>

View File

@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Italic: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="斜体">
<Button

View File

@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState, useRef, useCallback } from 'react';
import { Space, Button } from '@douyinfe/semi-ui';
import { IconExternalOpen, IconUnlink, IconEdit } from '@douyinfe/semi-icons';
@ -12,7 +11,6 @@ import { triggerOpenLinkSettingModal } from '../_event';
export const LinkBubbleMenu = ({ editor }) => {
const attrs = editor.getAttributes(Link.name);
const { href, target } = attrs;
const isLinkActive = editor.isActive(Link.name);
const [text, setText] = useState();
const [from, setFrom] = useState(-1);
const [to, setTo] = useState(-1);
@ -28,30 +26,40 @@ export const LinkBubbleMenu = ({ editor }) => {
const unsetLink = useCallback(() => editor.chain().extendMarkRange(Link.name).unsetLink().run(), [editor]);
useEffect(() => {
if (!isLinkActive) return;
const listener = () => {
const isLinkActive = editor.isActive(Link.name);
const { state } = editor;
const isInLink = isMarkActive(state.schema.marks.link)(state);
if (!isLinkActive) return;
if (!isInLink) return;
const { state } = editor;
const isInLink = isMarkActive(state.schema.marks.link)(state);
const { $head } = editor.state.selection;
const marks = $head.marks();
if (!marks.length) return;
if (!isInLink) return;
const mark = marks[0];
const node = $head.node($head.depth);
const startPosOfThisLine = $head.pos - (($head.nodeBefore && $head.nodeBefore.nodeSize) || 0);
const endPosOfThisLine = $head.nodeAfter
? startPosOfThisLine + $head.nodeAfter.nodeSize
: $head.pos - $head.parentOffset + node.content.size;
const { $head } = editor.state.selection;
const marks = $head.marks();
if (!marks.length) return;
const { start, end } = findMarkPosition(state, mark, startPosOfThisLine, endPosOfThisLine);
const text = state.doc.textBetween(start, end);
setText(text);
setFrom(start);
setTo(end);
});
const mark = marks[0];
const node = $head.node($head.depth);
const startPosOfThisLine = $head.pos - (($head.nodeBefore && $head.nodeBefore.nodeSize) || 0);
const endPosOfThisLine = $head.nodeAfter
? startPosOfThisLine + $head.nodeAfter.nodeSize
: $head.pos - $head.parentOffset + node.content.size;
const { start, end } = findMarkPosition(state, mark, startPosOfThisLine, endPosOfThisLine);
const text = state.doc.textBetween(start, end);
setText(text);
setFrom(start);
setTo(end);
};
editor.on('selectionUpdate', listener);
return () => {
editor.off('selectionUpdate', listener);
};
}, [editor]);
return (
<BubbleMenu

View File

@ -9,10 +9,6 @@ import { LinkBubbleMenu } from './bubble';
import { LinkSettingModal } from './modal';
export const Link: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<>
<Tooltip content="插入链接">

View File

@ -21,6 +21,9 @@ export const LinkSettingModal: React.FC<IProps> = ({ editor }) => {
const { from, to } = initialState;
const { view } = editor;
console.log(from, to);
const schema = view.state.schema;
const node = schema.text(values.text, [schema.marks.link.create({ href: values.href })]);
view.dispatch(view.state.tr.replaceRangeWith(from, to, node));

View File

@ -5,10 +5,6 @@ import { IconRedo } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
export const Redo: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="撤销">
<Button

View File

@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Strike: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="删除线">
<Button

View File

@ -5,10 +5,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Subscript: React.FC<{ editor: any }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="下标">
<Button

View File

@ -5,10 +5,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Superscript: React.FC<{ editor: any }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="上标">
<Button

View File

@ -9,10 +9,6 @@ import { ColorPicker } from '../_components/color-picker';
export const TextColor: React.FC<{ editor: Editor }> = ({ editor }) => {
const { color } = editor.getAttributes('textStyle');
if (!editor) {
return null;
}
return (
<ColorPicker
onSetColor={(color) => {

View File

@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
import { isTitleActive } from 'tiptap/prose-utils';
export const Underline: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="下划线">
<Button

View File

@ -5,10 +5,6 @@ import { IconUndo } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
export const Undo: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<Tooltip content="撤销">
<Button

View File

@ -19,6 +19,7 @@ export const extractMarkAttributesFromMatch = ([, , , attrsString]) => {
export function findMarkPosition(state: EditorState, mark, from, to) {
let markPos = { start: -1, end: -1 };
state.doc.nodesBetween(from, to, (node, pos) => {
if (markPos.start > -1) {
return false;

View File

@ -6,6 +6,7 @@
table {
width: 100%;
max-width: 100%;
margin: 0.75em 0 0;
overflow: hidden;
border-collapse: collapse;

View File

@ -40,8 +40,8 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
editor.registerPlugin(plugin);
return () => editor.unregisterPlugin(pluginKey);
// TODO: 检验是否应该是 props.editor
}, [props, element]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.editor, element]);
return (
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>

View File

@ -196,7 +196,7 @@ function MindElixir(
mobileMenu,
}: Options
) {
const box = document.querySelector(el) as HTMLElement;
const box = document.querySelector(el);
if (!box) return;
this.mindElixirBox = box;
this.before = before || {};

View File

@ -103,7 +103,7 @@ export function createInputDiv(tpc: Topic) {
console.time('createInputDiv');
if (!tpc) return;
let div = $d.createElement('div');
const origin = tpc.childNodes[0].textContent as string;
const origin = tpc.childNodes[0].textContent;
tpc.appendChild(div);
div.id = 'input-box';
div.innerText = origin;
@ -138,7 +138,7 @@ export function createInputDiv(tpc: Topic) {
if (!div) return; // 防止重复blur
const node = tpc.nodeObj;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const topic = div.textContent!.trim();
const topic = div.textContent.trim();
if (topic === '') node.topic = origin;
else node.topic = topic;
div.remove();

View File

@ -31,5 +31,5 @@
}
},
"include": ["next-env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "next.config.js"]
}