mirror of https://github.com/fantasticit/think.git
feat: improve editor
This commit is contained in:
parent
3f981d44b0
commit
612754fe4b
|
@ -53,7 +53,6 @@
|
||||||
"@tiptap/extension-underline": "^2.0.0-beta.23",
|
"@tiptap/extension-underline": "^2.0.0-beta.23",
|
||||||
"@tiptap/react": "^2.0.0-beta.107",
|
"@tiptap/react": "^2.0.0-beta.107",
|
||||||
"@tiptap/suggestion": "^2.0.0-beta.90",
|
"@tiptap/suggestion": "^2.0.0-beta.90",
|
||||||
"@traptitech/markdown-it-katex": "^3.5.0",
|
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
|
@ -66,11 +65,8 @@
|
||||||
"markdown-it-anchor": "^8.4.1",
|
"markdown-it-anchor": "^8.4.1",
|
||||||
"markdown-it-container": "^3.0.0",
|
"markdown-it-container": "^3.0.0",
|
||||||
"markdown-it-emoji": "^2.0.0",
|
"markdown-it-emoji": "^2.0.0",
|
||||||
"markdown-it-footnote": "^3.0.3",
|
|
||||||
"markdown-it-sub": "^1.0.0",
|
"markdown-it-sub": "^1.0.0",
|
||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"markdown-it-task-lists": "^2.1.1",
|
|
||||||
"marked": "^4.0.12",
|
|
||||||
"next": "12.0.10",
|
"next": "12.0.10",
|
||||||
"prosemirror-markdown": "^1.7.0",
|
"prosemirror-markdown": "^1.7.0",
|
||||||
"prosemirror-tables": "^1.1.1",
|
"prosemirror-tables": "^1.1.1",
|
||||||
|
|
|
@ -78,8 +78,8 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId })
|
||||||
CollaborationEventEmitter.on(KEY, ({ states: users }) => {
|
CollaborationEventEmitter.on(KEY, ({ states: users }) => {
|
||||||
const newCollaborationUsers = users
|
const newCollaborationUsers = users
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((state) => ({ ...state.user, clientId: state.clientId }))
|
.filter((state) => state.user)
|
||||||
.filter(Boolean);
|
.map((state) => ({ ...state.user, clientId: state.clientId }));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
collaborationUsers.length === newCollaborationUsers.length &&
|
collaborationUsers.length === newCollaborationUsers.length &&
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { ILoginUser, IAuthority } from '@think/domains';
|
||||||
import { useToggle } from 'hooks/useToggle';
|
import { useToggle } from 'hooks/useToggle';
|
||||||
import {
|
import {
|
||||||
DEFAULT_EXTENSION,
|
DEFAULT_EXTENSION,
|
||||||
Document,
|
|
||||||
DocumentWithTitle,
|
DocumentWithTitle,
|
||||||
getCollaborationExtension,
|
getCollaborationExtension,
|
||||||
getCollaborationCursorExtension,
|
getCollaborationCursorExtension,
|
||||||
|
@ -16,6 +15,8 @@ import {
|
||||||
} from 'components/tiptap';
|
} from 'components/tiptap';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { joinUser } from 'components/document/collaboration';
|
import { joinUser } from 'components/document/collaboration';
|
||||||
|
import { debounce } from 'helpers/debounce';
|
||||||
|
import { changeTitle } from './index';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -44,10 +45,6 @@ export const Editor: React.FC<IProps> = ({ user, documentId, authority, classNam
|
||||||
});
|
});
|
||||||
}, [documentId, user.token]);
|
}, [documentId, user.token]);
|
||||||
|
|
||||||
const noTitleEditor = useEditor({
|
|
||||||
extensions: [...DEFAULT_EXTENSION, Document],
|
|
||||||
});
|
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
editable: authority && authority.editable,
|
editable: authority && authority.editable,
|
||||||
extensions: [
|
extensions: [
|
||||||
|
@ -56,10 +53,12 @@ export const Editor: React.FC<IProps> = ({ user, documentId, authority, classNam
|
||||||
getCollaborationExtension(provider),
|
getCollaborationExtension(provider),
|
||||||
getCollaborationCursorExtension(provider, user),
|
getCollaborationCursorExtension(provider, user),
|
||||||
],
|
],
|
||||||
editorProps: {
|
onTransaction: debounce(({ transaction }) => {
|
||||||
// @ts-ignore
|
try {
|
||||||
noTitleEditor,
|
const title = transaction.doc.content.firstChild.content.firstChild.textContent;
|
||||||
},
|
changeTitle(title);
|
||||||
|
} catch (e) {}
|
||||||
|
}, 200),
|
||||||
});
|
});
|
||||||
const [loading, toggleLoading] = useToggle(true);
|
const [loading, toggleLoading] = useToggle(true);
|
||||||
|
|
||||||
|
@ -68,8 +67,6 @@ export const Editor: React.FC<IProps> = ({ user, documentId, authority, classNam
|
||||||
toggleLoading(false);
|
toggleLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// provid
|
|
||||||
|
|
||||||
provider.on('status', async ({ status }) => {
|
provider.on('status', async ({ status }) => {
|
||||||
console.log('status', status);
|
console.log('status', status);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
z-index: 110;
|
z-index: 110;
|
||||||
background-color: var(--semi-color-nav-bg);
|
background-color: var(--semi-color-nav-bg);
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -39,6 +40,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--semi-color-nav-bg);
|
background-color: var(--semi-color-nav-bg);
|
||||||
border-bottom: 1px solid var(--semi-color-border);
|
border-bottom: 1px solid var(--semi-color-border);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
&.isStandardWidth {
|
&.isStandardWidth {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Layout, Nav, Skeleton, Typography, Space, Button, Tooltip, Spin, Popover } from '@douyinfe/semi-ui';
|
import { Nav, Skeleton, Typography, Space, Button, Tooltip, Spin, Popover } from '@douyinfe/semi-ui';
|
||||||
import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
|
import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
|
||||||
import { useUser } from 'data/user';
|
import { useUser } from 'data/user';
|
||||||
import { useDocumentDetail } from 'data/document';
|
import { useDocumentDetail } from 'data/document';
|
||||||
|
@ -13,12 +13,19 @@ import { DocumentStar } from 'components/document/star';
|
||||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||||
import { DocumentStyle } from 'components/document/style';
|
import { DocumentStyle } from 'components/document/style';
|
||||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||||
|
import { EventEmitter } from 'helpers/event-emitter';
|
||||||
import { Editor } from './editor';
|
import { Editor } from './editor';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Header, Content } = Layout;
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const em = new EventEmitter();
|
||||||
|
const TITLE_CHANGE_EVENT = 'TITLE_CHANGE_EVENT';
|
||||||
|
|
||||||
|
export const changeTitle = (title) => {
|
||||||
|
em.emit(TITLE_CHANGE_EVENT, title);
|
||||||
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +37,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
const editorWrapClassNames = useMemo(() => {
|
const editorWrapClassNames = useMemo(() => {
|
||||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||||
}, [width]);
|
}, [width]);
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
|
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
|
||||||
const { document, authority } = documentAndAuth || {};
|
const { document, authority } = documentAndAuth || {};
|
||||||
|
@ -54,13 +61,21 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
}
|
}
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<Text ellipsis={{ showTooltip: true }} style={{ width: ~~(windowWith / 4) }}>
|
<Text ellipsis={{ showTooltip: true }} style={{ width: ~~(windowWith / 4) }}>
|
||||||
{document.title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
em.on(TITLE_CHANGE_EVENT, setTitle);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
em.destroy();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<header>
|
<header>
|
||||||
|
@ -91,10 +106,9 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
<Spin></Spin>
|
<Spin></Spin>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
error={null}
|
error={docAuthError}
|
||||||
normalContent={() => {
|
normalContent={() => {
|
||||||
return (
|
return (
|
||||||
// <div style={{ fontSize }}>
|
|
||||||
<>
|
<>
|
||||||
<Seo title={document.title} />
|
<Seo title={document.title} />
|
||||||
<Editor
|
<Editor
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
z-index: 110;
|
z-index: 110;
|
||||||
background-color: var(--semi-color-nav-bg);
|
background-color: var(--semi-color-nav-bg);
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -39,6 +40,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--semi-color-nav-bg);
|
background-color: var(--semi-color-nav-bg);
|
||||||
border-bottom: 1px solid var(--semi-color-border);
|
border-bottom: 1px solid var(--semi-color-border);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
&.isStandardWidth {
|
&.isStandardWidth {
|
||||||
> div {
|
> div {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.items {
|
.items {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
max-height: 50vh;
|
max-height: 40vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.wrap {
|
||||||
|
margin: 8px 0;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { Popover, TextArea, Typography, Space } from '@douyinfe/semi-ui';
|
||||||
|
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
||||||
|
import katex from 'katex';
|
||||||
|
import { Checkbox } from '@douyinfe/semi-ui';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
export const TaskItemWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
|
const isEditable = editor.isEditable;
|
||||||
|
const { checked } = node.attrs;
|
||||||
|
|
||||||
|
console.log(node.attrs);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeViewWrapper as="span" className={styles.wrap} contentEditable={false}>
|
||||||
|
<Checkbox checked={checked} onChange={(e) => updateAttributes({ checked: e.target.checked })} />
|
||||||
|
<NodeViewContent></NodeViewContent>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
);
|
||||||
|
};
|
|
@ -11,7 +11,7 @@ const attrs = {
|
||||||
bdo: ['dir'],
|
bdo: ['dir'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HTMLMarks = marks.map(({ name, tag }) =>
|
export const HTMLMarks = marks.slice(1).map(({ name, tag }) =>
|
||||||
Mark.create({
|
Mark.create({
|
||||||
name,
|
name,
|
||||||
tag,
|
tag,
|
||||||
|
|
|
@ -116,7 +116,8 @@ export const Paste = Extension.create({
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
clipboardTextSerializer: (slice) => {
|
clipboardTextSerializer: (slice) => {
|
||||||
const doc = this.editor.schema.topNodeType.createAndFill(undefined, slice.content);
|
const doc = slice.content;
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export const Table = BuiltInTable.extend({
|
||||||
if (fixedWidth && totalWidth > 0) {
|
if (fixedWidth && totalWidth > 0) {
|
||||||
HTMLAttributes.style = `width: ${totalWidth}px;`;
|
HTMLAttributes.style = `width: ${totalWidth}px;`;
|
||||||
} else if (totalWidth && totalWidth > 0) {
|
} else if (totalWidth && totalWidth > 0) {
|
||||||
HTMLAttributes.style = `min-width: ${totalWidth}px`;
|
HTMLAttributes.style = `min-width: 100%`;
|
||||||
} else {
|
} else {
|
||||||
HTMLAttributes.style = null;
|
HTMLAttributes.style = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { wrappingInputRule, mergeAttributes } from '@tiptap/core';
|
import { wrappingInputRule } from '@tiptap/core';
|
||||||
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item';
|
import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item';
|
||||||
import { Plugin } from 'prosemirror-state';
|
import { Plugin } from 'prosemirror-state';
|
||||||
import { findParentNodeClosestToPos } from 'prosemirror-utils';
|
import { findParentNodeClosestToPos } from 'prosemirror-utils';
|
||||||
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
|
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
|
||||||
|
import { TaskItemWrapper } from '../components/taskItem';
|
||||||
|
|
||||||
const CustomTaskItem = BuiltInTaskItem.extend({
|
const CustomTaskItem = BuiltInTaskItem.extend({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
|
@ -27,35 +29,79 @@ const CustomTaskItem = BuiltInTaskItem.extend({
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
// addProseMirrorPlugins() {
|
// addNodeView() {
|
||||||
// return [
|
// return ReactNodeViewRenderer(TaskItemWrapper);
|
||||||
// new Plugin({
|
|
||||||
// props: {
|
|
||||||
// // @ts-ignore
|
|
||||||
// handleClick: (view, pos, event) => {
|
|
||||||
// const state = view.state;
|
|
||||||
// const schema = state.schema;
|
|
||||||
|
|
||||||
// const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
|
||||||
// const position = state.doc.resolve(coordinates.pos);
|
|
||||||
// const parentList = findParentNodeClosestToPos(position, function (node) {
|
|
||||||
// return node.type === schema.nodes.taskItem || node.type === schema.nodes.listItem;
|
|
||||||
// });
|
|
||||||
// // @ts-ignore
|
|
||||||
// const isListClicked = event.target.tagName.toLowerCase() === 'li';
|
|
||||||
// if (!isListClicked || !parentList || parentList.node.type !== schema.nodes.taskItem) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const tr = state.tr;
|
|
||||||
// tr.setNodeMarkup(parentList.pos, schema.nodes.taskItem, {
|
|
||||||
// checked: !parentList.node.attrs.checked,
|
|
||||||
// });
|
|
||||||
// view.dispatch(tr);
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// ];
|
|
||||||
// },
|
// },
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
return ({ node, HTMLAttributes, getPos, editor }) => {
|
||||||
|
const listItem = document.createElement('li');
|
||||||
|
const checkboxWrapper = document.createElement('span');
|
||||||
|
const content = document.createElement('div');
|
||||||
|
|
||||||
|
checkboxWrapper.contentEditable = 'false';
|
||||||
|
|
||||||
|
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
|
||||||
|
listItem.setAttribute(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
listItem.dataset.checked = node.attrs.checked;
|
||||||
|
listItem.append(checkboxWrapper, content);
|
||||||
|
|
||||||
|
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
||||||
|
listItem.setAttribute(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dom: listItem,
|
||||||
|
contentDOM: content,
|
||||||
|
update: (updatedNode) => {
|
||||||
|
if (updatedNode.type !== this.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
listItem.dataset.checked = updatedNode.attrs.checked;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
props: {
|
||||||
|
// @ts-ignore
|
||||||
|
handleClick: (view, pos, event) => {
|
||||||
|
const state = view.state;
|
||||||
|
const schema = state.schema;
|
||||||
|
|
||||||
|
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
||||||
|
const position = state.doc.resolve(coordinates.pos);
|
||||||
|
const parentList = findParentNodeClosestToPos(position, function (node) {
|
||||||
|
return node.type === schema.nodes.taskItem || node.type === schema.nodes.listItem;
|
||||||
|
});
|
||||||
|
if (!parentList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = view.nodeDOM(parentList.pos) as HTMLLIElement;
|
||||||
|
if (element.tagName.toLowerCase() !== 'li') return;
|
||||||
|
|
||||||
|
const parentElement = element.parentElement;
|
||||||
|
const type = parentElement && parentElement.getAttribute('data-type');
|
||||||
|
if (!type || type.toLowerCase() !== 'tasklist') return;
|
||||||
|
|
||||||
|
const tr = state.tr;
|
||||||
|
const nextValue = !(element.getAttribute('data-checked') === 'true');
|
||||||
|
tr.setNodeMarkup(parentList.pos, schema.nodes.taskItem, {
|
||||||
|
checked: nextValue,
|
||||||
|
});
|
||||||
|
view.dispatch(tr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TaskItem = CustomTaskItem.configure({ nested: true });
|
export const TaskItem = CustomTaskItem.configure({ nested: true });
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { HorizontalRule } from '../extensions/horizontalRule';
|
||||||
import { Iframe } from '../extensions/iframe';
|
import { Iframe } from '../extensions/iframe';
|
||||||
import { Mind } from '../extensions/mind';
|
import { Mind } from '../extensions/mind';
|
||||||
import { Table } from '../extensions/table';
|
import { Table } from '../extensions/table';
|
||||||
|
import { TaskList } from '../extensions/taskList';
|
||||||
|
import { TaskItem } from '../extensions/taskItem';
|
||||||
import { Katex } from '../extensions/katex';
|
import { Katex } from '../extensions/katex';
|
||||||
import { DocumentReference } from '../extensions/documentReference';
|
import { DocumentReference } from '../extensions/documentReference';
|
||||||
import { DocumentChildren } from '../extensions/documentChildren';
|
import { DocumentChildren } from '../extensions/documentChildren';
|
||||||
|
@ -26,6 +28,8 @@ const OTHER_BUBBLE_MENU_TYPES = [
|
||||||
Iframe.name,
|
Iframe.name,
|
||||||
Mind.name,
|
Mind.name,
|
||||||
Table.name,
|
Table.name,
|
||||||
|
TaskList.name,
|
||||||
|
TaskItem.name,
|
||||||
DocumentReference.name,
|
DocumentReference.name,
|
||||||
DocumentChildren.name,
|
DocumentChildren.name,
|
||||||
Katex.name,
|
Katex.name,
|
||||||
|
|
|
@ -10,8 +10,6 @@ export const markdownToProsemirror = ({ schema, content, hasTitle }) => {
|
||||||
|
|
||||||
if (!html) return null;
|
if (!html) return null;
|
||||||
|
|
||||||
console.log(html);
|
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const { body } = parser.parseFromString(html, 'text/html');
|
const { body } = parser.parseFromString(html, 'text/html');
|
||||||
body.append(document.createComment(content));
|
body.append(document.createComment(content));
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { DocumentReference } from '../../../extensions/documentReference';
|
||||||
import { HardBreak } from '../../../extensions/hardBreak';
|
import { HardBreak } from '../../../extensions/hardBreak';
|
||||||
import { Heading } from '../../../extensions/heading';
|
import { Heading } from '../../../extensions/heading';
|
||||||
import { HorizontalRule } from '../../../extensions/horizontalRule';
|
import { HorizontalRule } from '../../../extensions/horizontalRule';
|
||||||
import { marks, HTMLMarks } from '../../../extensions/htmlMarks';
|
import { marks } from '../../../extensions/htmlMarks';
|
||||||
import { Iframe } from '../../../extensions/iframe';
|
import { Iframe } from '../../../extensions/iframe';
|
||||||
import { Image } from '../../../extensions/image';
|
import { Image } from '../../../extensions/image';
|
||||||
import { Italic } from '../../../extensions/italic';
|
import { Italic } from '../../../extensions/italic';
|
||||||
|
@ -29,6 +29,7 @@ import { TableRow } from '../../../extensions/tableRow';
|
||||||
import { Text } from '../../../extensions/text';
|
import { Text } from '../../../extensions/text';
|
||||||
import { TaskItem } from '../../../extensions/taskItem';
|
import { TaskItem } from '../../../extensions/taskItem';
|
||||||
import { TaskList } from '../../../extensions/taskList';
|
import { TaskList } from '../../../extensions/taskList';
|
||||||
|
import { TextStyle } from '../../../extensions/textStyle';
|
||||||
import { Title } from '../../../extensions/title';
|
import { Title } from '../../../extensions/title';
|
||||||
import {
|
import {
|
||||||
isPlainURL,
|
isPlainURL,
|
||||||
|
@ -66,6 +67,8 @@ const SerializerConfig = {
|
||||||
mixable: true,
|
mixable: true,
|
||||||
expelEnclosingWhitespace: true,
|
expelEnclosingWhitespace: true,
|
||||||
},
|
},
|
||||||
|
// FIXME: 如何导出 style?
|
||||||
|
[TextStyle.name]: { open: '', close: '', mixable: true, expelEnclosingWhitespace: true },
|
||||||
...marks.reduce(
|
...marks.reduce(
|
||||||
(acc, { name, tag }) => ({
|
(acc, { name, tag }) => ({
|
||||||
...acc,
|
...acc,
|
||||||
|
@ -151,10 +154,9 @@ const SerializerConfig = {
|
||||||
*/
|
*/
|
||||||
export const prosemirrorToMarkdown = ({ content }) => {
|
export const prosemirrorToMarkdown = ({ content }) => {
|
||||||
const serializer = new ProseMirrorMarkdownSerializer(SerializerConfig.nodes, SerializerConfig.marks);
|
const serializer = new ProseMirrorMarkdownSerializer(SerializerConfig.nodes, SerializerConfig.marks);
|
||||||
|
const markdown = serializer.serialize(content, {
|
||||||
console.log(content);
|
|
||||||
|
|
||||||
return serializer.serialize(content, {
|
|
||||||
tightLists: true,
|
tightLists: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return markdown;
|
||||||
};
|
};
|
||||||
|
|
|
@ -178,11 +178,31 @@
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
> label {
|
> span {
|
||||||
flex: 0 0 auto;
|
position: relative;
|
||||||
user-select: none;
|
display: block;
|
||||||
transform: translateY(2px);
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid var(--semi-color-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
left: 4.071429px;
|
||||||
|
top: -0.357143px;
|
||||||
|
width: 6.714286px;
|
||||||
|
height: 12.142857px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 0;
|
||||||
|
transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s;
|
||||||
|
transform: rotate(45deg) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
@ -193,6 +213,15 @@
|
||||||
&[data-checked='true'] {
|
&[data-checked='true'] {
|
||||||
color: var(--semi-color-text-2);
|
color: var(--semi-color-text-2);
|
||||||
|
|
||||||
|
> span {
|
||||||
|
background-color: var(--semi-color-primary);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(45deg) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ export class CreateDocumentDto {
|
||||||
@IsString({ message: '文档名称类型错误(正确类型为:String)' })
|
@IsString({ message: '文档名称类型错误(正确类型为:String)' })
|
||||||
@IsNotEmpty({ message: '文档名称不能为空' })
|
@IsNotEmpty({ message: '文档名称不能为空' })
|
||||||
@MinLength(1, { message: '文档名称至少1个字符' })
|
@MinLength(1, { message: '文档名称至少1个字符' })
|
||||||
@MaxLength(50, { message: '文档名称最多50个字符' })
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
readonly title?: string;
|
readonly title?: string;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ export class DocumentEntity {
|
||||||
@Column({ type: 'varchar', comment: '父文档 Id', default: null })
|
@Column({ type: 'varchar', comment: '父文档 Id', default: null })
|
||||||
public parentDocumentId: string;
|
public parentDocumentId: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 50, comment: '文档标题', default: '' })
|
@Column({ type: 'varchar', default: '未命名文档', comment: '文档标题' })
|
||||||
public title: string;
|
public title: string;
|
||||||
|
|
||||||
@Column({ type: 'text', comment: '文档内容' })
|
@Column({ type: 'text', comment: '文档内容' })
|
||||||
|
|
|
@ -87,7 +87,6 @@ importers:
|
||||||
'@tiptap/extension-underline': ^2.0.0-beta.23
|
'@tiptap/extension-underline': ^2.0.0-beta.23
|
||||||
'@tiptap/react': ^2.0.0-beta.107
|
'@tiptap/react': ^2.0.0-beta.107
|
||||||
'@tiptap/suggestion': ^2.0.0-beta.90
|
'@tiptap/suggestion': ^2.0.0-beta.90
|
||||||
'@traptitech/markdown-it-katex': ^3.5.0
|
|
||||||
'@types/node': 17.0.13
|
'@types/node': 17.0.13
|
||||||
'@types/react': 17.0.38
|
'@types/react': 17.0.38
|
||||||
axios: ^0.25.0
|
axios: ^0.25.0
|
||||||
|
@ -102,11 +101,8 @@ importers:
|
||||||
markdown-it-anchor: ^8.4.1
|
markdown-it-anchor: ^8.4.1
|
||||||
markdown-it-container: ^3.0.0
|
markdown-it-container: ^3.0.0
|
||||||
markdown-it-emoji: ^2.0.0
|
markdown-it-emoji: ^2.0.0
|
||||||
markdown-it-footnote: ^3.0.3
|
|
||||||
markdown-it-sub: ^1.0.0
|
markdown-it-sub: ^1.0.0
|
||||||
markdown-it-sup: ^1.0.0
|
markdown-it-sup: ^1.0.0
|
||||||
markdown-it-task-lists: ^2.1.1
|
|
||||||
marked: ^4.0.12
|
|
||||||
next: 12.0.10
|
next: 12.0.10
|
||||||
prosemirror-markdown: ^1.7.0
|
prosemirror-markdown: ^1.7.0
|
||||||
prosemirror-tables: ^1.1.1
|
prosemirror-tables: ^1.1.1
|
||||||
|
@ -166,7 +162,6 @@ importers:
|
||||||
'@tiptap/extension-underline': 2.0.0-beta.23_@tiptap+core@2.0.0-beta.171
|
'@tiptap/extension-underline': 2.0.0-beta.23_@tiptap+core@2.0.0-beta.171
|
||||||
'@tiptap/react': 2.0.0-beta.107_a3fcdb91535fe17b69dfabaa94f3bb3d
|
'@tiptap/react': 2.0.0-beta.107_a3fcdb91535fe17b69dfabaa94f3bb3d
|
||||||
'@tiptap/suggestion': 2.0.0-beta.90_@tiptap+core@2.0.0-beta.171
|
'@tiptap/suggestion': 2.0.0-beta.90_@tiptap+core@2.0.0-beta.171
|
||||||
'@traptitech/markdown-it-katex': 3.5.0
|
|
||||||
axios: 0.25.0
|
axios: 0.25.0
|
||||||
classnames: 2.3.1
|
classnames: 2.3.1
|
||||||
copy-to-clipboard: 3.3.1
|
copy-to-clipboard: 3.3.1
|
||||||
|
@ -179,11 +174,8 @@ importers:
|
||||||
markdown-it-anchor: 8.4.1_markdown-it@12.3.2
|
markdown-it-anchor: 8.4.1_markdown-it@12.3.2
|
||||||
markdown-it-container: 3.0.0
|
markdown-it-container: 3.0.0
|
||||||
markdown-it-emoji: 2.0.0
|
markdown-it-emoji: 2.0.0
|
||||||
markdown-it-footnote: 3.0.3
|
|
||||||
markdown-it-sub: 1.0.0
|
markdown-it-sub: 1.0.0
|
||||||
markdown-it-sup: 1.0.0
|
markdown-it-sup: 1.0.0
|
||||||
markdown-it-task-lists: 2.1.1
|
|
||||||
marked: 4.0.12
|
|
||||||
next: 12.0.10_react-dom@17.0.2+react@17.0.2
|
next: 12.0.10_react-dom@17.0.2+react@17.0.2
|
||||||
prosemirror-markdown: 1.7.0
|
prosemirror-markdown: 1.7.0
|
||||||
prosemirror-tables: 1.1.1
|
prosemirror-tables: 1.1.1
|
||||||
|
@ -1953,12 +1945,6 @@ packages:
|
||||||
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
|
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
/@traptitech/markdown-it-katex/3.5.0:
|
|
||||||
resolution: {integrity: sha512-7/GI3ETKJjrZD9+azn7WraDWo0ZQ6grtzR4I36qu7U0vOJMBtC+znX7UghdOScrgGnxqGvgWm07SYnlcCtdCvw==}
|
|
||||||
dependencies:
|
|
||||||
katex: 0.15.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tsconfig/node10/1.0.8:
|
/@tsconfig/node10/1.0.8:
|
||||||
resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==}
|
resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -6008,10 +5994,6 @@ packages:
|
||||||
resolution: {integrity: sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ==}
|
resolution: {integrity: sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/markdown-it-footnote/3.0.3:
|
|
||||||
resolution: {integrity: sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/markdown-it-sub/1.0.0:
|
/markdown-it-sub/1.0.0:
|
||||||
resolution: {integrity: sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g=}
|
resolution: {integrity: sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g=}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -6020,10 +6002,6 @@ packages:
|
||||||
resolution: {integrity: sha1-y5yf+RpSVawI8/09YyhuFd8KH8M=}
|
resolution: {integrity: sha1-y5yf+RpSVawI8/09YyhuFd8KH8M=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/markdown-it-task-lists/2.1.1:
|
|
||||||
resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/markdown-it/12.3.2:
|
/markdown-it/12.3.2:
|
||||||
resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==}
|
resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -6035,12 +6013,6 @@ packages:
|
||||||
uc.micro: 1.0.6
|
uc.micro: 1.0.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/marked/4.0.12:
|
|
||||||
resolution: {integrity: sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==}
|
|
||||||
engines: {node: '>= 12'}
|
|
||||||
hasBin: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdurl/1.0.1:
|
/mdurl/1.0.1:
|
||||||
resolution: {integrity: sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=}
|
resolution: {integrity: sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
Loading…
Reference in New Issue