mirror of https://github.com/fantasticit/think.git
refactor: add eslint
This commit is contained in:
parent
881b1c6846
commit
8a1da277f5
|
@ -2,5 +2,5 @@ node_modules
|
||||||
**/.next/**
|
**/.next/**
|
||||||
**/_next/**
|
**/_next/**
|
||||||
**/dist/**
|
**/dist/**
|
||||||
./packages/client/src/tiptap/next.config.js
|
.eslintrc.js
|
||||||
./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js
|
./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
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'],
|
plugins: ['@typescript-eslint', 'react-hooks'],
|
||||||
extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx', '.js', '.jsx'],
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./packages/client/tsconfig.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
version: 'detect',
|
version: 'detect',
|
||||||
|
|
17
package.json
17
package.json
|
@ -4,7 +4,7 @@
|
||||||
"author": "fantasticit",
|
"author": "fantasticit",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "npx rimraf ./node_modules ./packages/**/node_modules",
|
"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:server": "pnpm run --dir packages/server dev",
|
||||||
"dev:client": "pnpm run --dir packages/client dev",
|
"dev:client": "pnpm run --dir packages/client dev",
|
||||||
"build": "pnpm build:server && pnpm build:client",
|
"build": "pnpm build:server && pnpm build:client",
|
||||||
|
@ -14,17 +14,18 @@
|
||||||
"build:config": "pnpm run --dir packages/config build",
|
"build:config": "pnpm run --dir packages/config build",
|
||||||
"build:server": "pnpm run --dir packages/server build",
|
"build:server": "pnpm run --dir packages/server build",
|
||||||
"build:client": "pnpm run --dir packages/client 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:server": "pnpm run --dir packages/server start",
|
||||||
"start:client": "pnpm run --dir packages/client start",
|
"start:client": "pnpm run --dir packages/client start",
|
||||||
"pm2": "pnpm run pm2:server && pnpm run pm2:client",
|
"pm2": "pnpm run pm2:server && pnpm run pm2:client",
|
||||||
"pm2:server": "pnpm run --dir packages/server pm2",
|
"pm2:server": "pnpm run --dir packages/server pm2",
|
||||||
"pm2:client": "pnpm run --dir packages/client 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: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'",
|
"lint:server": "eslint --fix './packages/server/src/*.{ts,js}' -c '.eslintrc.server.js'",
|
||||||
"format": "concurrently \"pnpm:format:*\"",
|
"format": "concurrently 'pnpm:format:*'",
|
||||||
"format:ts": "prettier --write --parser typescript \"packages/**/*.{ts,tsx,js,jsx}\"",
|
"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:css": "stylelint --fix --formatter verbose --allow-empty-input 'packages/**/*.{css,scss,sass}'",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"precommit": "lint-staged"
|
"precommit": "lint-staged"
|
||||||
},
|
},
|
||||||
|
@ -62,6 +63,12 @@
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx,js,jsx}": "prettier --write",
|
"*.{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"
|
"*.{css,scss,sass}": " stylelint --fix --formatter verbose --allow-empty-input"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,16 @@
|
||||||
/* eslint-disable */
|
/* eslint-env es6 */
|
||||||
|
const semi = require('@douyinfe/semi-next').default({});
|
||||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||||
const { getConfig } = require('@think/config');
|
const { getConfig } = require('@think/config');
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
|
||||||
const nextConfig = require('@douyinfe/semi-next').default({})({
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = semi({
|
||||||
assetPrefix: config.assetPrefix,
|
assetPrefix: config.assetPrefix,
|
||||||
env: {
|
env: {
|
||||||
SERVER_API_URL: config?.client?.apiUrl,
|
SERVER_API_URL: config.client.apiUrl,
|
||||||
COLLABORATION_API_URL: config?.client?.collaborationUrl,
|
COLLABORATION_API_URL: config.client.collaborationUrl,
|
||||||
ENABLE_ALIYUN_OSS: !!config?.oss?.aliyun?.accessKeyId,
|
ENABLE_ALIYUN_OSS: !!config.oss.aliyun.accessKeyId,
|
||||||
},
|
},
|
||||||
webpack: (config, { dev, isServer }) => {
|
webpack: (config, { dev, isServer }) => {
|
||||||
config.resolve.plugins.push(new TsconfigPathsPlugin());
|
config.resolve.plugins.push(new TsconfigPathsPlugin());
|
||||||
|
|
|
@ -37,7 +37,6 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority, className, style }) => {
|
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority, className, style }) => {
|
||||||
if (!currentUser) return null;
|
|
||||||
const $hasShowUserSettingModal = useRef(false);
|
const $hasShowUserSettingModal = useRef(false);
|
||||||
const { users, addUser, updateUser } = useCollaborationDocument(documentId);
|
const { users, addUser, updateUser } = useCollaborationDocument(documentId);
|
||||||
const [status, setStatus] = useState<ProviderStatus>('connecting');
|
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({
|
const editor = useEditor({
|
||||||
editable: authority && authority.editable,
|
editable: authority && authority.editable,
|
||||||
extensions: [
|
extensions: [
|
||||||
|
@ -77,7 +76,9 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
|
||||||
try {
|
try {
|
||||||
const title = transaction.doc.content.firstChild.content.firstChild.textContent;
|
const title = transaction.doc.content.firstChild.content.firstChild.textContent;
|
||||||
triggerChangeDocumentTitle(title);
|
triggerChangeDocumentTitle(title);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
}, 50),
|
}, 50),
|
||||||
});
|
});
|
||||||
const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false);
|
const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false);
|
||||||
|
@ -98,7 +99,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
|
||||||
destoryProvider(provider, 'EDITOR');
|
destoryProvider(provider, 'EDITOR');
|
||||||
destoryIndexdbProvider(documentId);
|
destoryIndexdbProvider(documentId);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [documentId, provider]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
@ -157,7 +158,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
|
||||||
Router.events.off('routeChangeStart', handler);
|
Router.events.off('routeChangeStart', handler);
|
||||||
window.removeEventListener('unload', handler);
|
window.removeEventListener('unload', handler);
|
||||||
};
|
};
|
||||||
}, [editor, users, currentUser]);
|
}, [editor, users, currentUser, toggleMentionUsersSettingVisible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (event: KeyboardEvent) => {
|
const listener = (event: KeyboardEvent) => {
|
||||||
|
|
|
@ -27,7 +27,6 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
if (!documentId) return null;
|
|
||||||
const { width: windowWith } = useWindowSize();
|
const { width: windowWith } = useWindowSize();
|
||||||
const { width, fontSize } = useDocumentStyle();
|
const { width, fontSize } = useDocumentStyle();
|
||||||
const editorWrapClassNames = useMemo(() => {
|
const editorWrapClassNames = useMemo(() => {
|
||||||
|
@ -42,7 +41,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
Router.push({
|
Router.push({
|
||||||
pathname: `/wiki/${document.wikiId}/document/${documentId}`,
|
pathname: `/wiki/${document.wikiId}/document/${documentId}`,
|
||||||
});
|
});
|
||||||
}, [document]);
|
}, [document, documentId]);
|
||||||
|
|
||||||
const DocumentTitle = (
|
const DocumentTitle = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -49,7 +49,7 @@ export const Editor: React.FC<IProps> = ({ user, documentId, document, children
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [documentId, user.token]);
|
}, [documentId, user, toggleLoading]);
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
editable: false,
|
editable: false,
|
||||||
extensions: [
|
extensions: [
|
||||||
|
@ -68,7 +68,7 @@ export const Editor: React.FC<IProps> = ({ user, documentId, document, children
|
||||||
return () => {
|
return () => {
|
||||||
destoryProvider(provider, 'READER');
|
destoryProvider(provider, 'READER');
|
||||||
};
|
};
|
||||||
}, []);
|
}, [provider]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataRender
|
<DataRender
|
||||||
|
|
|
@ -64,7 +64,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
|
||||||
hasCancel: false,
|
hasCancel: false,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
onOk() {
|
onOk() {
|
||||||
const $input = document.querySelector('#js-share-document-password') as HTMLInputElement;
|
const $input = document.querySelector('#js-share-document-password');
|
||||||
query($input.value);
|
query($input.value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const ImageViewer: React.FC<IProps> = ({ container, containerSelector })
|
||||||
if (!el) {
|
if (!el) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const viewer = new Viewer(el as HTMLElement, { inline: false });
|
const viewer = new Viewer(el, { inline: false });
|
||||||
const io = new MutationObserver(() => {
|
const io = new MutationObserver(() => {
|
||||||
viewer.update();
|
viewer.update();
|
||||||
});
|
});
|
||||||
|
|
|
@ -117,25 +117,32 @@ const MessageBox = () => {
|
||||||
Notification.info({
|
Notification.info({
|
||||||
title: '消息通知',
|
title: '消息通知',
|
||||||
content: (
|
content: (
|
||||||
<Link href={msg.url}>
|
<>
|
||||||
<a className={styles.item}>
|
<div>
|
||||||
<div className={styles.leftWrap}>
|
<Text
|
||||||
<Text
|
ellipsis={{
|
||||||
ellipsis={{
|
showTooltip: {
|
||||||
showTooltip: {
|
opts: { content: msg.message },
|
||||||
opts: { content: msg.message },
|
},
|
||||||
},
|
}}
|
||||||
}}
|
style={{ width: 240 }}
|
||||||
style={{ width: 240 }}
|
>
|
||||||
>
|
{msg.title}
|
||||||
{msg.title}
|
</Text>
|
||||||
</Text>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
</a>
|
<Text link>
|
||||||
</Link>
|
<Link href={msg.url}>
|
||||||
|
<a className={styles.item} target="_blank">
|
||||||
|
查看详情
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
|
|
||||||
duration: 3,
|
duration: 3,
|
||||||
|
showClose: true,
|
||||||
onHookClose() {
|
onHookClose() {
|
||||||
readMessage(msg.id);
|
readMessage(msg.id);
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,11 +43,7 @@ import { Iframe } from './menus/iframe';
|
||||||
import { Table } from './menus/table';
|
import { Table } from './menus/table';
|
||||||
import { Mind } from './menus/mind';
|
import { Mind } from './menus/mind';
|
||||||
|
|
||||||
export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
|
const _MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Space spacing={2}>
|
<Space spacing={2}>
|
||||||
|
@ -87,8 +83,8 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Emoji editor={editor} />
|
<Emoji editor={editor} />
|
||||||
<Link editor={editor} />
|
|
||||||
<Blockquote editor={editor} />
|
<Blockquote editor={editor} />
|
||||||
|
<Link editor={editor} />
|
||||||
<HorizontalRule editor={editor} />
|
<HorizontalRule editor={editor} />
|
||||||
<Search 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 }) => {
|
export const MenuBar = React.memo(_MenuBar, (prevProps, nextProps) => {
|
||||||
if (!editor) {
|
return prevProps.editor === nextProps.editor;
|
||||||
return null;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
const _CommentMenuBar: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Space spacing={2}>
|
<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;
|
||||||
|
});
|
||||||
|
|
|
@ -9,10 +9,6 @@ import { ColorPicker } from '../_components/color-picker';
|
||||||
export const BackgroundColor: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const BackgroundColor: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
const { backgroundColor } = editor.getAttributes('textStyle');
|
const { backgroundColor } = editor.getAttributes('textStyle');
|
||||||
|
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onSetColor={(color) => {
|
onSetColor={(color) => {
|
||||||
|
|
|
@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Bold: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Bold: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="粗体">
|
<Tooltip content="粗体">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,10 +5,6 @@ import { IconClear } from 'components/icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
|
|
||||||
export const CleadrNodeAndMarks: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const CleadrNodeAndMarks: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="清除格式">
|
<Tooltip content="清除格式">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Code: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Code: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="行内代码">
|
<Tooltip content="行内代码">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -163,10 +163,6 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
setRecentUsed(transformToCommands(insertMenuLRUCache.get() as string[]));
|
setRecentUsed(transformToCommands(insertMenuLRUCache.get() as string[]));
|
||||||
}, [visible, transformToCommands]);
|
}, [visible, transformToCommands]);
|
||||||
|
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
zIndex={10000}
|
zIndex={10000}
|
||||||
|
@ -181,13 +177,13 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
}}
|
}}
|
||||||
render={
|
render={
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
{renderedCommands.map((command) => {
|
{renderedCommands.map((command, index) => {
|
||||||
return command.title ? (
|
return command.title ? (
|
||||||
<Dropdown.Title>{command.title}</Dropdown.Title>
|
<Dropdown.Title key={'title' + index}>{command.title}</Dropdown.Title>
|
||||||
) : command.custom ? (
|
) : command.custom ? (
|
||||||
command.custom(editor, runCommand)
|
command.custom(editor, runCommand)
|
||||||
) : (
|
) : (
|
||||||
<Dropdown.Item onClick={runCommand(command)}>
|
<Dropdown.Item key={command.label} onClick={runCommand(command)}>
|
||||||
{command.icon}
|
{command.icon}
|
||||||
{command.label}
|
{command.label}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
|
@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Italic: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Italic: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="斜体">
|
<Tooltip content="斜体">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||||
import { Space, Button } from '@douyinfe/semi-ui';
|
import { Space, Button } from '@douyinfe/semi-ui';
|
||||||
import { IconExternalOpen, IconUnlink, IconEdit } from '@douyinfe/semi-icons';
|
import { IconExternalOpen, IconUnlink, IconEdit } from '@douyinfe/semi-icons';
|
||||||
|
@ -12,7 +11,6 @@ import { triggerOpenLinkSettingModal } from '../_event';
|
||||||
export const LinkBubbleMenu = ({ editor }) => {
|
export const LinkBubbleMenu = ({ editor }) => {
|
||||||
const attrs = editor.getAttributes(Link.name);
|
const attrs = editor.getAttributes(Link.name);
|
||||||
const { href, target } = attrs;
|
const { href, target } = attrs;
|
||||||
const isLinkActive = editor.isActive(Link.name);
|
|
||||||
const [text, setText] = useState();
|
const [text, setText] = useState();
|
||||||
const [from, setFrom] = useState(-1);
|
const [from, setFrom] = useState(-1);
|
||||||
const [to, setTo] = 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]);
|
const unsetLink = useCallback(() => editor.chain().extendMarkRange(Link.name).unsetLink().run(), [editor]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLinkActive) return;
|
const listener = () => {
|
||||||
|
const isLinkActive = editor.isActive(Link.name);
|
||||||
|
|
||||||
const { state } = editor;
|
if (!isLinkActive) return;
|
||||||
const isInLink = isMarkActive(state.schema.marks.link)(state);
|
|
||||||
|
|
||||||
if (!isInLink) return;
|
const { state } = editor;
|
||||||
|
const isInLink = isMarkActive(state.schema.marks.link)(state);
|
||||||
|
|
||||||
const { $head } = editor.state.selection;
|
if (!isInLink) return;
|
||||||
const marks = $head.marks();
|
|
||||||
if (!marks.length) return;
|
|
||||||
|
|
||||||
const mark = marks[0];
|
const { $head } = editor.state.selection;
|
||||||
const node = $head.node($head.depth);
|
const marks = $head.marks();
|
||||||
const startPosOfThisLine = $head.pos - (($head.nodeBefore && $head.nodeBefore.nodeSize) || 0);
|
if (!marks.length) return;
|
||||||
const endPosOfThisLine = $head.nodeAfter
|
|
||||||
? startPosOfThisLine + $head.nodeAfter.nodeSize
|
|
||||||
: $head.pos - $head.parentOffset + node.content.size;
|
|
||||||
|
|
||||||
const { start, end } = findMarkPosition(state, mark, startPosOfThisLine, endPosOfThisLine);
|
const mark = marks[0];
|
||||||
const text = state.doc.textBetween(start, end);
|
const node = $head.node($head.depth);
|
||||||
setText(text);
|
const startPosOfThisLine = $head.pos - (($head.nodeBefore && $head.nodeBefore.nodeSize) || 0);
|
||||||
setFrom(start);
|
const endPosOfThisLine = $head.nodeAfter
|
||||||
setTo(end);
|
? 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 (
|
return (
|
||||||
<BubbleMenu
|
<BubbleMenu
|
||||||
|
|
|
@ -9,10 +9,6 @@ import { LinkBubbleMenu } from './bubble';
|
||||||
import { LinkSettingModal } from './modal';
|
import { LinkSettingModal } from './modal';
|
||||||
|
|
||||||
export const Link: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Link: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip content="插入链接">
|
<Tooltip content="插入链接">
|
||||||
|
|
|
@ -21,6 +21,9 @@ export const LinkSettingModal: React.FC<IProps> = ({ editor }) => {
|
||||||
|
|
||||||
const { from, to } = initialState;
|
const { from, to } = initialState;
|
||||||
const { view } = editor;
|
const { view } = editor;
|
||||||
|
|
||||||
|
console.log(from, to);
|
||||||
|
|
||||||
const schema = view.state.schema;
|
const schema = view.state.schema;
|
||||||
const node = schema.text(values.text, [schema.marks.link.create({ href: values.href })]);
|
const node = schema.text(values.text, [schema.marks.link.create({ href: values.href })]);
|
||||||
view.dispatch(view.state.tr.replaceRangeWith(from, to, node));
|
view.dispatch(view.state.tr.replaceRangeWith(from, to, node));
|
||||||
|
|
|
@ -5,10 +5,6 @@ import { IconRedo } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
|
|
||||||
export const Redo: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Redo: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="撤销">
|
<Tooltip content="撤销">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Strike: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Strike: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="删除线">
|
<Tooltip content="删除线">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,10 +5,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Subscript: React.FC<{ editor: any }> = ({ editor }) => {
|
export const Subscript: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="下标">
|
<Tooltip content="下标">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,10 +5,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Superscript: React.FC<{ editor: any }> = ({ editor }) => {
|
export const Superscript: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="上标">
|
<Tooltip content="上标">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -9,10 +9,6 @@ import { ColorPicker } from '../_components/color-picker';
|
||||||
export const TextColor: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const TextColor: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
const { color } = editor.getAttributes('textStyle');
|
const { color } = editor.getAttributes('textStyle');
|
||||||
|
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onSetColor={(color) => {
|
onSetColor={(color) => {
|
||||||
|
|
|
@ -6,10 +6,6 @@ import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from 'tiptap/prose-utils';
|
import { isTitleActive } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
export const Underline: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Underline: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="下划线">
|
<Tooltip content="下划线">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,10 +5,6 @@ import { IconUndo } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
|
|
||||||
export const Undo: React.FC<{ editor: Editor }> = ({ editor }) => {
|
export const Undo: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
if (!editor) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content="撤销">
|
<Tooltip content="撤销">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -19,6 +19,7 @@ export const extractMarkAttributesFromMatch = ([, , , attrsString]) => {
|
||||||
|
|
||||||
export function findMarkPosition(state: EditorState, mark, from, to) {
|
export function findMarkPosition(state: EditorState, mark, from, to) {
|
||||||
let markPos = { start: -1, end: -1 };
|
let markPos = { start: -1, end: -1 };
|
||||||
|
|
||||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||||
if (markPos.start > -1) {
|
if (markPos.start > -1) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
margin: 0.75em 0 0;
|
margin: 0.75em 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
|
@ -40,8 +40,8 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
|
||||||
|
|
||||||
editor.registerPlugin(plugin);
|
editor.registerPlugin(plugin);
|
||||||
return () => editor.unregisterPlugin(pluginKey);
|
return () => editor.unregisterPlugin(pluginKey);
|
||||||
// TODO: 检验是否应该是 props.editor
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [props, element]);
|
}, [props.editor, element]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
||||||
|
|
|
@ -196,7 +196,7 @@ function MindElixir(
|
||||||
mobileMenu,
|
mobileMenu,
|
||||||
}: Options
|
}: Options
|
||||||
) {
|
) {
|
||||||
const box = document.querySelector(el) as HTMLElement;
|
const box = document.querySelector(el);
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
this.mindElixirBox = box;
|
this.mindElixirBox = box;
|
||||||
this.before = before || {};
|
this.before = before || {};
|
||||||
|
|
|
@ -103,7 +103,7 @@ export function createInputDiv(tpc: Topic) {
|
||||||
console.time('createInputDiv');
|
console.time('createInputDiv');
|
||||||
if (!tpc) return;
|
if (!tpc) return;
|
||||||
let div = $d.createElement('div');
|
let div = $d.createElement('div');
|
||||||
const origin = tpc.childNodes[0].textContent as string;
|
const origin = tpc.childNodes[0].textContent;
|
||||||
tpc.appendChild(div);
|
tpc.appendChild(div);
|
||||||
div.id = 'input-box';
|
div.id = 'input-box';
|
||||||
div.innerText = origin;
|
div.innerText = origin;
|
||||||
|
@ -138,7 +138,7 @@ export function createInputDiv(tpc: Topic) {
|
||||||
if (!div) return; // 防止重复blur
|
if (!div) return; // 防止重复blur
|
||||||
const node = tpc.nodeObj;
|
const node = tpc.nodeObj;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// 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;
|
if (topic === '') node.topic = origin;
|
||||||
else node.topic = topic;
|
else node.topic = topic;
|
||||||
div.remove();
|
div.remove();
|
||||||
|
|
|
@ -31,5 +31,5 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "next.config.js"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue