diff --git a/packages/client/src/helpers/json.ts b/packages/client/src/helpers/json.ts index 665c7b36..3e4c750c 100644 --- a/packages/client/src/helpers/json.ts +++ b/packages/client/src/helpers/json.ts @@ -1,4 +1,6 @@ export const safeJSONParse = (str, defaultValue = {}) => { + if (typeof str === 'object') return str; + try { return JSON.parse(str); } catch (e) { diff --git a/packages/client/src/tiptap/core/extensions/paste.ts b/packages/client/src/tiptap/core/extensions/paste.ts index f381c8de..c4e05ebc 100644 --- a/packages/client/src/tiptap/core/extensions/paste.ts +++ b/packages/client/src/tiptap/core/extensions/paste.ts @@ -195,20 +195,29 @@ export const Paste = Extension.create({ return false; }, clipboardTextSerializer: (slice) => { - const isText = isPureText(slice.content.toJSON()); + const json = slice.content.toJSON(); + const isSelectAll = slice.openStart === slice.openEnd && slice.openEnd === 0; - if (isText) { - return slice.content.textBetween(0, slice.content.size, '\n\n'); - } + if (typeof json === 'object' || isSelectAll) { + return extensionThis.options.prosemirrorToMarkdown({ + content: slice.content, + }); + } else { + const isText = isPureText(json) && !isSelectAll; - const doc = slice.content; - if (!doc) { - return ''; + if (isText) { + return slice.content.textBetween(0, slice.content.size, '\n\n'); + } + + const doc = slice.content; + if (!doc) { + return ''; + } + const content = extensionThis.options.prosemirrorToMarkdown({ + content: doc, + }); + return content; } - const content = extensionThis.options.prosemirrorToMarkdown({ - content: doc, - }); - return content; }, }, }), diff --git a/packages/client/src/tiptap/core/wrappers/table-of-contents/index.tsx b/packages/client/src/tiptap/core/wrappers/table-of-contents/index.tsx index e1d6d800..29aa70f4 100644 --- a/packages/client/src/tiptap/core/wrappers/table-of-contents/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/table-of-contents/index.tsx @@ -51,8 +51,6 @@ export const TableOfContentsWrapper = ({ editor }) => { setItems(headings); }, [editor]); - useEffect(handleUpdate, [handleUpdate]); - useEffect(() => { if (!editor) { return null; diff --git a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/nodes/table-of-contents.ts b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/nodes/table-of-contents.ts new file mode 100644 index 00000000..a2a28441 --- /dev/null +++ b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/nodes/table-of-contents.ts @@ -0,0 +1,9 @@ +import { Node } from './node'; + +export class TableOfContents extends Node { + type = 'tableOfContents'; + + matching() { + return this.DOMNode.nodeName === 'DIV' && this.DOMNode.classList.contains('tableOfContents'); + } +} diff --git a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/renderer.ts b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/renderer.ts index bc93eb59..0941c54d 100644 --- a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/renderer.ts +++ b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/renderer.ts @@ -34,6 +34,7 @@ import { Status } from './nodes/status'; import { Table } from './nodes/table'; import { TableCell } from './nodes/table-cell'; import { TableHeader } from './nodes/table-header'; +import { TableOfContents } from './nodes/table-of-contents'; import { TableRow } from './nodes/table-row'; // 列表 import { TaskList } from './nodes/task-list'; @@ -82,6 +83,7 @@ export class Renderer { TableHeader, TableRow, TableCell, + TableOfContents, // 列表 TaskList, diff --git a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/index.tsx b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/index.tsx index 77748521..986d04df 100644 --- a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/index.tsx +++ b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/index.tsx @@ -32,6 +32,7 @@ export const markdownToProsemirror = ({ schema, content, hasTitle }) => { const parser = new DOMParser(); const { body } = parser.parseFromString(extractImage(html), 'text/html'); + body.append(document.createComment(content)); const node = htmlToPromsemirror(body, !hasTitle); diff --git a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/markdown-to-html/index.ts b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/markdown-to-html/index.ts index 5f2529db..8e6b9150 100644 --- a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/markdown-to-html/index.ts +++ b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/markdown-to-html/index.ts @@ -22,6 +22,7 @@ const markdownIframe = createMarkdownContainer('iframe'); const markdownMention = createMarkdownContainer('mention'); const markdownMind = createMarkdownContainer('mind'); const markdownFlow = createMarkdownContainer('flow'); +const markdownTableOfContents = createMarkdownContainer('tableOfContents'); const markdown = markdownit('commonmark') .enable('strikethrough') @@ -44,7 +45,8 @@ const markdown = markdownit('commonmark') .use(markdownMind) .use(markdownDocumentReference) .use(markdownDocumentChildren) - .use(markdownFlow); + .use(markdownFlow) + .use(markdownTableOfContents); export const markdownToHTML = (rawMarkdown) => { return sanitize(markdown.render(rawMarkdown), {}); diff --git a/packages/client/src/tiptap/markdown/prosemirror-to-markdown/index.ts b/packages/client/src/tiptap/markdown/prosemirror-to-markdown/index.ts index 91cec2cc..d1b9f47c 100644 --- a/packages/client/src/tiptap/markdown/prosemirror-to-markdown/index.ts +++ b/packages/client/src/tiptap/markdown/prosemirror-to-markdown/index.ts @@ -31,6 +31,7 @@ import { Superscript } from 'tiptap/core/extensions/superscript'; import { Table } from 'tiptap/core/extensions/table'; import { TableCell } from 'tiptap/core/extensions/table-cell'; import { TableHeader } from 'tiptap/core/extensions/table-header'; +import { TableOfContents } from 'tiptap/core/extensions/table-of-contents'; import { TableRow } from 'tiptap/core/extensions/table-row'; import { TaskItem } from 'tiptap/core/extensions/task-item'; import { TaskList } from 'tiptap/core/extensions/task-list'; @@ -147,6 +148,7 @@ const SerializerConfig = { [Table.name]: renderTable, [TableCell.name]: renderTableCell, [TableHeader.name]: renderTableCell, + [TableOfContents.name]: renderCustomContainer('tableOfContents'), [TableRow.name]: renderTableRow, [TaskItem.name]: (state, node) => { state.write(`[${node.attrs.checked ? 'x' : ' '}] `); diff --git a/packages/client/src/tiptap/prose-utils/copy-node.ts b/packages/client/src/tiptap/prose-utils/copy-node.ts index 44762618..d76e029c 100644 --- a/packages/client/src/tiptap/prose-utils/copy-node.ts +++ b/packages/client/src/tiptap/prose-utils/copy-node.ts @@ -3,10 +3,10 @@ import { copy } from 'helpers/copy'; import { safeJSONStringify } from 'helpers/json'; import { Fragment, Node } from 'prosemirror-model'; -export function copyNode(nodeOrNodeName: Node); +export function copyNode(nodeOrNodeName: Node | Fragment); export function copyNode(nodeOrNodeName: string, editor: Editor); -export function copyNode(nodeOrNodeName: string | Node, editor?: Editor) { - let targetNode: null | Node = null; +export function copyNode(nodeOrNodeName: string | Node | Fragment, editor?: Editor) { + let targetNode = null; if (typeof nodeOrNodeName === 'string') { const { state } = editor;