From fda147c492f2ed2e4767790babbedc4897ce0226 Mon Sep 17 00:00:00 2001
From: fantasticit
Date: Tue, 22 Mar 2022 13:52:29 +0800
Subject: [PATCH 1/7] fix: fix zIndex
---
.../src/components/document/editor/index.module.scss | 7 ++++++-
.../src/components/template/editor/index.module.scss | 7 ++++++-
.../client/src/components/tiptap/extensions/tableCell.tsx | 2 +-
.../src/components/tiptap/extensions/tableHeader.tsx | 2 +-
.../menus/components/bubbleMenu/bubbleMenuPlugin.tsx | 4 ++--
.../client/src/components/tiptap/views/floatMenuView.tsx | 4 ++--
packages/client/src/styles/prosemirror.scss | 6 +++---
7 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/packages/client/src/components/document/editor/index.module.scss b/packages/client/src/components/document/editor/index.module.scss
index f14a0061..8f5530f5 100644
--- a/packages/client/src/components/document/editor/index.module.scss
+++ b/packages/client/src/components/document/editor/index.module.scss
@@ -5,7 +5,11 @@
flex-direction: column;
> header {
+ position: relative;
+ z-index: 110;
+ background-color: var(--semi-color-nav-bg);
height: 60px;
+
> div {
overflow: auto;
}
@@ -27,12 +31,13 @@
> header {
position: relative;
- z-index: 10001;
+ z-index: 110;
height: 50px;
padding: 0 24px;
display: flex;
align-items: center;
overflow: hidden;
+ background-color: var(--semi-color-nav-bg);
border-bottom: 1px solid var(--semi-color-border);
&.isStandardWidth {
diff --git a/packages/client/src/components/template/editor/index.module.scss b/packages/client/src/components/template/editor/index.module.scss
index e06e7376..afd3800f 100644
--- a/packages/client/src/components/template/editor/index.module.scss
+++ b/packages/client/src/components/template/editor/index.module.scss
@@ -5,7 +5,11 @@
flex-direction: column;
> header {
+ position: relative;
+ z-index: 110;
+ background-color: var(--semi-color-nav-bg);
height: 60px;
+
> div {
overflow: auto;
}
@@ -27,12 +31,13 @@
> header {
position: relative;
- z-index: 10001;
+ z-index: 110;
height: 50px;
padding: 0 24px;
display: flex;
align-items: center;
overflow: hidden;
+ background-color: var(--semi-color-nav-bg);
border-bottom: 1px solid var(--semi-color-border);
&.isStandardWidth {
diff --git a/packages/client/src/components/tiptap/extensions/tableCell.tsx b/packages/client/src/components/tiptap/extensions/tableCell.tsx
index b4a8affc..03ba72f0 100644
--- a/packages/client/src/components/tiptap/extensions/tableCell.tsx
+++ b/packages/client/src/components/tiptap/extensions/tableCell.tsx
@@ -27,7 +27,7 @@ export const TableCell = BuiltInTableCell.extend({
new FloatMenuView({
editor: this.editor,
tippyOptions: {
- zIndex: 10000,
+ zIndex: 100,
offset: [-28, 0],
},
shouldShow: ({ editor }, floatMenuView) => {
diff --git a/packages/client/src/components/tiptap/extensions/tableHeader.tsx b/packages/client/src/components/tiptap/extensions/tableHeader.tsx
index f9b4a356..c891b2be 100644
--- a/packages/client/src/components/tiptap/extensions/tableHeader.tsx
+++ b/packages/client/src/components/tiptap/extensions/tableHeader.tsx
@@ -19,7 +19,7 @@ export const TableHeader = BuiltInTableHeader.extend({
new FloatMenuView({
editor: this.editor,
tippyOptions: {
- zIndex: 10000,
+ zIndex: 100,
},
shouldShow: ({ editor }) => {
if (!editor.isEditable) {
diff --git a/packages/client/src/components/tiptap/menus/components/bubbleMenu/bubbleMenuPlugin.tsx b/packages/client/src/components/tiptap/menus/components/bubbleMenu/bubbleMenuPlugin.tsx
index 914483dd..6007fdc9 100644
--- a/packages/client/src/components/tiptap/menus/components/bubbleMenu/bubbleMenuPlugin.tsx
+++ b/packages/client/src/components/tiptap/menus/components/bubbleMenu/bubbleMenuPlugin.tsx
@@ -84,7 +84,7 @@ export class BubbleMenuView {
this.view.dom.addEventListener('dragstart', this.dragstartHandler);
this.editor.on('focus', this.focusHandler);
this.editor.on('blur', this.blurHandler);
- this.tippyOptions = tippyOptions;
+ this.tippyOptions = tippyOptions || {};
// Detaches menu content from its current parent
this.element.remove();
this.element.style.visibility = 'visible';
@@ -133,7 +133,7 @@ export class BubbleMenuView {
trigger: 'manual',
placement: 'top',
hideOnClick: 'toggle',
- ...this.tippyOptions,
+ ...Object.assign({ zIndex: 99 }, this.tippyOptions),
});
// maybe we have to hide tippy on its own blur event as well
diff --git a/packages/client/src/components/tiptap/views/floatMenuView.tsx b/packages/client/src/components/tiptap/views/floatMenuView.tsx
index 0b17d510..87546494 100644
--- a/packages/client/src/components/tiptap/views/floatMenuView.tsx
+++ b/packages/client/src/components/tiptap/views/floatMenuView.tsx
@@ -47,7 +47,7 @@ export class FloatMenuView {
constructor(props: FloatMenuViewOptions) {
this.editor = props.editor;
this.shouldShow = props.shouldShow;
- this.tippyOptions = props.tippyOptions;
+ this.tippyOptions = props.tippyOptions || {};
if (props.getReferenceClientRect) {
this.getReferenceClientRect = props.getReferenceClientRect;
}
@@ -76,7 +76,7 @@ export class FloatMenuView {
trigger: 'manual',
placement: 'top',
hideOnClick: 'toggle',
- ...(this.tippyOptions ?? {}),
+ ...Object.assign({ zIndex: 99 }, this.tippyOptions),
});
}
diff --git a/packages/client/src/styles/prosemirror.scss b/packages/client/src/styles/prosemirror.scss
index 83e4a11e..2e66a23f 100644
--- a/packages/client/src/styles/prosemirror.scss
+++ b/packages/client/src/styles/prosemirror.scss
@@ -284,7 +284,7 @@
.grip-column {
position: absolute;
- z-index: 10000;
+ z-index: 10;
display: block;
width: 100%;
height: 0.7em;
@@ -302,7 +302,7 @@
.grip-row {
position: absolute;
- z-index: 10000;
+ z-index: 10;
display: block;
height: 100%;
width: 0.7em;
@@ -320,7 +320,7 @@
.grip-table {
position: absolute;
- z-index: 10000;
+ z-index: 10;
display: block;
width: 0.8em;
height: 0.8em;
From d71d725dcf90511e40156199052d0be3ccebb571 Mon Sep 17 00:00:00 2001
From: fantasticit
Date: Tue, 22 Mar 2022 13:57:27 +0800
Subject: [PATCH 2/7] feat: use wrapped tooltip
---
packages/client/src/components/tiptap/menubar.tsx | 3 ++-
packages/client/src/components/tiptap/menus/mediaInsert.tsx | 2 --
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/client/src/components/tiptap/menubar.tsx b/packages/client/src/components/tiptap/menubar.tsx
index 6dad5960..38695471 100644
--- a/packages/client/src/components/tiptap/menubar.tsx
+++ b/packages/client/src/components/tiptap/menubar.tsx
@@ -1,6 +1,7 @@
import React from 'react';
-import { Space, Button, Tooltip } from '@douyinfe/semi-ui';
+import { Space, Button } from '@douyinfe/semi-ui';
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
+import { Tooltip } from 'components/tooltip';
import { IconClear } from 'components/icons';
import { Divider } from './components/divider';
import { MediaInsertMenu } from './menus/mediaInsert';
diff --git a/packages/client/src/components/tiptap/menus/mediaInsert.tsx b/packages/client/src/components/tiptap/menus/mediaInsert.tsx
index 341fd6d8..3db21763 100644
--- a/packages/client/src/components/tiptap/menus/mediaInsert.tsx
+++ b/packages/client/src/components/tiptap/menus/mediaInsert.tsx
@@ -3,7 +3,6 @@ import { Editor } from '@tiptap/core';
import { Button, Dropdown, Popover } from '@douyinfe/semi-ui';
import { IconPlus } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
-import { Upload } from './components/upload';
import {
IconDocument,
IconMind,
@@ -18,7 +17,6 @@ import {
} from 'components/icons';
import { GridSelect } from 'components/grid-select';
import { isTitleActive } from '../services/isActive';
-import { handleFileEvent } from '../services/upload';
export const MediaInsertMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
From 67a5ae54cf137098057d0ad4affbaee494b7eefb Mon Sep 17 00:00:00 2001
From: fantasticit
Date: Tue, 22 Mar 2022 13:58:22 +0800
Subject: [PATCH 3/7] fix: fix wrapped tooltip not trigger mouseleave
---
packages/client/src/styles/globals.scss | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/packages/client/src/styles/globals.scss b/packages/client/src/styles/globals.scss
index c64ce30e..c7749036 100644
--- a/packages/client/src/styles/globals.scss
+++ b/packages/client/src/styles/globals.scss
@@ -204,3 +204,8 @@ a {
flex-direction: column;
align-items: center;
}
+
+// @see https://github.com/react-component/tooltip/issues/18#issuecomment-411476678
+.semi-button-disabled {
+ pointer-events: none;
+}
From 187cdaf17cc8921502c62055d8ca777a21e31506 Mon Sep 17 00:00:00 2001
From: fantasticit
Date: Tue, 22 Mar 2022 17:28:53 +0800
Subject: [PATCH 4/7] feat: html -> prosemirror node
---
.../src/components/tiptap/extensions/paste.ts | 8 +-
.../components/tiptap/extensions/taskItem.ts | 82 ++---
.../components/tiptap/extensions/taskList.ts | 5 -
.../tiptap/services/markdown/index.ts | 12 +-
.../tiptap/services/markdown/markdownTable.ts | 283 ++++++++++++++++++
.../services/markdown/markdownTaskList.ts | 175 +++++++++++
.../tiptap/services/markdown/serializer.ts | 51 +++-
.../services/markdown/src/Marks/Bold.js | 13 +
.../services/markdown/src/Marks/Code.js | 16 +
.../services/markdown/src/Marks/Italic.js | 12 +
.../services/markdown/src/Marks/Link.js | 15 +
.../services/markdown/src/Marks/Mark.js | 14 +
.../services/markdown/src/Nodes/BulletList.js | 15 +
.../services/markdown/src/Nodes/CodeBlock.js | 29 ++
.../markdown/src/Nodes/CodeBlockWrapper.js | 10 +
.../services/markdown/src/Nodes/HardBreak.js | 9 +
.../services/markdown/src/Nodes/Heading.js | 22 ++
.../services/markdown/src/Nodes/Image.js | 21 ++
.../services/markdown/src/Nodes/ListItem.js | 21 ++
.../services/markdown/src/Nodes/Node.ts | 23 ++
.../markdown/src/Nodes/OrderedList.js | 9 +
.../services/markdown/src/Nodes/Paragraph.js | 8 +
.../services/markdown/src/Nodes/Text.js | 19 ++
.../services/markdown/src/Nodes/blockQuote.ts | 15 +
.../services/markdown/src/Nodes/table.ts | 9 +
.../services/markdown/src/Nodes/tableCell.ts | 9 +
.../markdown/src/Nodes/tableHeader.ts | 9 +
.../services/markdown/src/Nodes/tableRow.ts | 9 +
.../services/markdown/src/Nodes/taskList.ts | 9 +
.../markdown/src/Nodes/taskListItem.ts | 9 +
.../tiptap/services/markdown/src/Renderer.js | 182 +++++++++++
.../tiptap/services/markdown/src/utils.ts | 47 +++
32 files changed, 1100 insertions(+), 70 deletions(-)
create mode 100644 packages/client/src/components/tiptap/services/markdown/markdownTable.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/markdownTaskList.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Marks/Bold.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Marks/Code.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Marks/Italic.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Marks/Link.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Marks/Mark.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/BulletList.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlock.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlockWrapper.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/HardBreak.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/Heading.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/Image.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/ListItem.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/Node.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/OrderedList.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/Paragraph.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/Text.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/blockQuote.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/table.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/tableCell.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/tableHeader.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/tableRow.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/taskList.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/taskListItem.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/Renderer.js
create mode 100644 packages/client/src/components/tiptap/services/markdown/src/utils.ts
diff --git a/packages/client/src/components/tiptap/extensions/paste.ts b/packages/client/src/components/tiptap/extensions/paste.ts
index 811e3e75..b6f65658 100644
--- a/packages/client/src/components/tiptap/extensions/paste.ts
+++ b/packages/client/src/components/tiptap/extensions/paste.ts
@@ -5,6 +5,7 @@ import { EXTENSION_PRIORITY_HIGHEST } from '../constants';
import { handleFileEvent } from '../services/upload';
import { isInCode, LANGUAGES } from '../services/code';
import { isMarkdown, normalizePastedMarkdown } from '../services/markdown/helpers';
+import { isTitleNode } from '../services/node';
export const Paste = Extension.create({
name: 'paste',
@@ -63,14 +64,17 @@ export const Paste = Extension.create({
if (isMarkdown(text) || html.length === 0 || pasteCodeLanguage === 'markdown') {
event.preventDefault();
// FIXME: 由于 title schema 的存在导致反序列化必有 title 节点存在
- // const hasTitle = isTitleNode(view.props.state.doc.content.firstChild);
+ const firstNode = view.props.state.doc.content.firstChild;
+ const hasTitle = isTitleNode(firstNode) && firstNode.content.size > 0;
let schema = view.props.state.schema;
const doc = markdownSerializer.deserialize({
schema,
content: normalizePastedMarkdown(text),
+ hasTitle,
});
+
// @ts-ignore
- const transaction = view.state.tr.insert(view.state.selection.head, doc);
+ const transaction = view.state.tr.insert(view.state.selection.head, view.state.schema.nodeFromJSON(doc));
view.dispatch(transaction);
return true;
}
diff --git a/packages/client/src/components/tiptap/extensions/taskItem.ts b/packages/client/src/components/tiptap/extensions/taskItem.ts
index 1cb2373f..63977b73 100644
--- a/packages/client/src/components/tiptap/extensions/taskItem.ts
+++ b/packages/client/src/components/tiptap/extensions/taskItem.ts
@@ -1,34 +1,10 @@
-import { wrappingInputRule } from '@tiptap/core';
+import { wrappingInputRule, mergeAttributes } from '@tiptap/core';
import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item';
import { Plugin } from 'prosemirror-state';
import { findParentNodeClosestToPos } from 'prosemirror-utils';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
const CustomTaskItem = BuiltInTaskItem.extend({
- addOptions() {
- return {
- nested: true,
- HTMLAttributes: {},
- };
- },
-
- addAttributes() {
- return {
- checked: {
- default: false,
- parseHTML: (element) => {
- const checkbox = element.querySelector('input[type=checkbox].task-list-item-checkbox');
- // @ts-ignore
- return checkbox?.checked;
- },
- renderHTML: (attributes) => ({
- 'data-checked': attributes.checked,
- }),
- keepOnSplit: false,
- },
- };
- },
-
parseHTML() {
return [
{
@@ -51,35 +27,35 @@ const CustomTaskItem = BuiltInTaskItem.extend({
];
},
- addProseMirrorPlugins() {
- return [
- new Plugin({
- props: {
- // @ts-ignore
- handleClick: (view, pos, event) => {
- const state = view.state;
- const schema = state.schema;
+ // 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;
- });
- // @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);
- },
- },
- }),
- ];
- },
+ // 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);
+ // },
+ // },
+ // }),
+ // ];
+ // },
});
export const TaskItem = CustomTaskItem.configure({ nested: true });
diff --git a/packages/client/src/components/tiptap/extensions/taskList.ts b/packages/client/src/components/tiptap/extensions/taskList.ts
index 9c56c26a..2233788e 100644
--- a/packages/client/src/components/tiptap/extensions/taskList.ts
+++ b/packages/client/src/components/tiptap/extensions/taskList.ts
@@ -1,4 +1,3 @@
-import { mergeAttributes } from '@tiptap/core';
import { TaskList as BuiltInTaskList } from '@tiptap/extension-task-list';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
@@ -11,8 +10,4 @@ export const TaskList = BuiltInTaskList.extend({
},
];
},
-
- renderHTML({ HTMLAttributes: { numeric, ...HTMLAttributes } }) {
- return [numeric ? 'ol' : 'ul', mergeAttributes(HTMLAttributes, { 'data-type': 'taskList' }), 0];
- },
});
diff --git a/packages/client/src/components/tiptap/services/markdown/index.ts b/packages/client/src/components/tiptap/services/markdown/index.ts
index 1aae44d9..a9130762 100644
--- a/packages/client/src/components/tiptap/services/markdown/index.ts
+++ b/packages/client/src/components/tiptap/services/markdown/index.ts
@@ -3,27 +3,25 @@ import sub from 'markdown-it-sub';
import sup from 'markdown-it-sup';
import footnote from 'markdown-it-footnote';
import anchor from 'markdown-it-anchor';
-import tasklist from 'markdown-it-task-lists';
import emoji from 'markdown-it-emoji';
import katex from '@traptitech/markdown-it-katex';
+import tasklist from './markdownTaskList';
import splitMixedLists from './markedownSplitMixedList';
import markdownUnderline from './markdownUnderline';
import markdownBanner from './markdownBanner';
+import { markdownItTable } from './markdownTable';
-export const markdown = markdownit({
- html: true,
- linkify: true,
- typographer: true,
-})
+export const markdown = markdownit('commonmark')
.enable('strikethrough')
.use(sub)
.use(sup)
.use(footnote)
.use(anchor)
- .use(tasklist, { enable: true })
+ .use(tasklist)
.use(splitMixedLists)
.use(markdownUnderline)
.use(markdownBanner)
+ .use(markdownItTable)
.use(emoji)
.use(katex);
diff --git a/packages/client/src/components/tiptap/services/markdown/markdownTable.ts b/packages/client/src/components/tiptap/services/markdown/markdownTable.ts
new file mode 100644
index 00000000..e4aefb52
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/markdownTable.ts
@@ -0,0 +1,283 @@
+// Copied from https://github.com/markdown-it/markdown-it/blob/master/lib/rules_block/table.js
+
+function isSpace(code) {
+ switch (code) {
+ case 0x09:
+ case 0x20:
+ return true;
+ }
+ return false;
+}
+
+function getLine(state, line) {
+ var pos = state.bMarks[line] + state.tShift[line],
+ max = state.eMarks[line];
+
+ return state.src.substr(pos, max - pos);
+}
+
+function escapedSplit(str) {
+ var result = [],
+ pos = 0,
+ max = str.length,
+ ch,
+ isEscaped = false,
+ lastPos = 0,
+ current = '';
+
+ ch = str.charCodeAt(pos);
+
+ while (pos < max) {
+ if (ch === 0x7c /* | */) {
+ if (!isEscaped) {
+ // pipe separating cells, '|'
+ result.push(current + str.substring(lastPos, pos));
+ current = '';
+ lastPos = pos + 1;
+ } else {
+ // escaped pipe, '\|'
+ current += str.substring(lastPos, pos - 1);
+ lastPos = pos;
+ }
+ }
+
+ isEscaped = ch === 0x5c /* \ */;
+ pos++;
+
+ ch = str.charCodeAt(pos);
+ }
+
+ result.push(current + str.substring(lastPos));
+
+ return result;
+}
+
+function table(state, startLine, endLine, silent) {
+ var ch,
+ lineText,
+ pos,
+ i,
+ l,
+ nextLine,
+ columns,
+ columnCount,
+ token,
+ aligns,
+ t,
+ tableLines,
+ tbodyLines,
+ oldParentType,
+ terminate,
+ terminatorRules,
+ firstCh,
+ secondCh;
+
+ // should have at least two lines
+ if (startLine + 2 > endLine) {
+ return false;
+ }
+
+ nextLine = startLine + 1;
+
+ if (state.sCount[nextLine] < state.blkIndent) {
+ return false;
+ }
+
+ // if it's indented more than 3 spaces, it should be a code block
+ if (state.sCount[nextLine] - state.blkIndent >= 4) {
+ return false;
+ }
+
+ // first character of the second line should be '|', '-', ':',
+ // and no other characters are allowed but spaces;
+ // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
+
+ pos = state.bMarks[nextLine] + state.tShift[nextLine];
+ if (pos >= state.eMarks[nextLine]) {
+ return false;
+ }
+
+ firstCh = state.src.charCodeAt(pos++);
+ if (firstCh !== 0x7c /* | */ && firstCh !== 0x2d /* - */ && firstCh !== 0x3a /* : */) {
+ return false;
+ }
+
+ if (pos >= state.eMarks[nextLine]) {
+ return false;
+ }
+
+ secondCh = state.src.charCodeAt(pos++);
+ if (secondCh !== 0x7c /* | */ && secondCh !== 0x2d /* - */ && secondCh !== 0x3a /* : */ && !isSpace(secondCh)) {
+ return false;
+ }
+
+ // if first character is '-', then second character must not be a space
+ // (due to parsing ambiguity with list)
+ if (firstCh === 0x2d /* - */ && isSpace(secondCh)) {
+ return false;
+ }
+
+ while (pos < state.eMarks[nextLine]) {
+ ch = state.src.charCodeAt(pos);
+
+ if (ch !== 0x7c /* | */ && ch !== 0x2d /* - */ && ch !== 0x3a /* : */ && !isSpace(ch)) {
+ return false;
+ }
+
+ pos++;
+ }
+
+ lineText = getLine(state, startLine + 1);
+
+ columns = lineText.split('|');
+ aligns = [];
+ for (i = 0; i < columns.length; i++) {
+ t = columns[i].trim();
+ if (!t) {
+ // allow empty columns before and after table, but not in between columns;
+ // e.g. allow ` |---| `, disallow ` ---||--- `
+ if (i === 0 || i === columns.length - 1) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+
+ if (!/^:?-+:?$/.test(t)) {
+ return false;
+ }
+ if (t.charCodeAt(t.length - 1) === 0x3a /* : */) {
+ aligns.push(t.charCodeAt(0) === 0x3a /* : */ ? 'center' : 'right');
+ } else if (t.charCodeAt(0) === 0x3a /* : */) {
+ aligns.push('left');
+ } else {
+ aligns.push('');
+ }
+ }
+
+ lineText = getLine(state, startLine).trim();
+ if (lineText.indexOf('|') === -1) {
+ return false;
+ }
+ if (state.sCount[startLine] - state.blkIndent >= 4) {
+ return false;
+ }
+ columns = escapedSplit(lineText);
+ if (columns.length && columns[0] === '') columns.shift();
+ if (columns.length && columns[columns.length - 1] === '') columns.pop();
+
+ // header row will define an amount of columns in the entire table,
+ // and align row should be exactly the same (the rest of the rows can differ)
+ columnCount = columns.length;
+ if (columnCount === 0 || columnCount !== aligns.length) {
+ return false;
+ }
+
+ if (silent) {
+ return true;
+ }
+
+ oldParentType = state.parentType;
+ state.parentType = 'table';
+
+ // use 'blockquote' lists for termination because it's
+ // the most similar to tables
+ terminatorRules = state.md.block.ruler.getRules('blockquote');
+
+ token = state.push('table_open', 'table', 1);
+ token.map = tableLines = [startLine, 0];
+
+ token = state.push('thead_open', 'thead', 1);
+ token.map = [startLine, startLine + 1];
+
+ token = state.push('tr_open', 'tr', 1);
+ token.map = [startLine, startLine + 1];
+
+ for (i = 0; i < columns.length; i++) {
+ token = state.push('th_open', 'th', 1);
+ if (aligns[i]) {
+ token.attrs = [['style', 'text-align:' + aligns[i]]];
+ }
+
+ token = state.push('paragraph_open', 'p', 1);
+ token = state.push('inline', '', 0);
+ token.content = columns[i].trim();
+ token.children = [];
+ token = state.push('paragraph_close', 'p', -1);
+
+ token = state.push('th_close', 'th', -1);
+ }
+
+ token = state.push('tr_close', 'tr', -1);
+ token = state.push('thead_close', 'thead', -1);
+
+ for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
+ if (state.sCount[nextLine] < state.blkIndent) {
+ break;
+ }
+
+ terminate = false;
+ for (i = 0, l = terminatorRules.length; i < l; i++) {
+ if (terminatorRules[i](state, nextLine, endLine, true)) {
+ terminate = true;
+ break;
+ }
+ }
+
+ if (terminate) {
+ break;
+ }
+ lineText = getLine(state, nextLine).trim();
+ if (!lineText) {
+ break;
+ }
+ if (state.sCount[nextLine] - state.blkIndent >= 4) {
+ break;
+ }
+ columns = escapedSplit(lineText);
+ if (columns.length && columns[0] === '') columns.shift();
+ if (columns.length && columns[columns.length - 1] === '') columns.pop();
+
+ if (nextLine === startLine + 2) {
+ token = state.push('tbody_open', 'tbody', 1);
+ token.map = tbodyLines = [startLine + 2, 0];
+ }
+
+ token = state.push('tr_open', 'tr', 1);
+ token.map = [nextLine, nextLine + 1];
+
+ for (i = 0; i < columnCount; i++) {
+ token = state.push('td_open', 'td', 1);
+ if (aligns[i]) {
+ token.attrs = [['style', 'text-align:' + aligns[i]]];
+ }
+
+ token = state.push('paragraph_open', 'p', 1);
+ token = state.push('inline', '', 0);
+ token.content = columns[i].trim();
+ token.children = [];
+ token = state.push('paragraph_close', 'p', -1);
+
+ token = state.push('td_close', 'td', -1);
+ }
+ token = state.push('tr_close', 'tr', -1);
+ }
+
+ if (tbodyLines) {
+ token = state.push('tbody_close', 'tbody', -1);
+ tbodyLines[1] = nextLine;
+ }
+
+ token = state.push('table_close', 'table', -1);
+ tableLines[1] = nextLine;
+
+ state.parentType = oldParentType;
+ state.line = nextLine;
+ return true;
+}
+
+export const markdownItTable = (md, options) => {
+ md.block.ruler.before('paragraph', 'table', table, {
+ alt: ['paragraph', 'reference'],
+ });
+};
diff --git a/packages/client/src/components/tiptap/services/markdown/markdownTaskList.ts b/packages/client/src/components/tiptap/services/markdown/markdownTaskList.ts
new file mode 100644
index 00000000..0c1e0f0c
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/markdownTaskList.ts
@@ -0,0 +1,175 @@
+/*
+ * SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: ISC
+ */
+
+// Markdown-it plugin to render GitHub-style task lists; see
+//
+// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments
+// https://github.com/blog/1825-task-lists-in-all-markdown-documents
+
+import MarkdownIt from 'markdown-it/lib';
+import StateCore from 'markdown-it/lib/rules_core/state_core';
+import Token from 'markdown-it/lib/token';
+
+interface TaskListsOptions {
+ enabled: boolean;
+ label: boolean;
+ lineNumber: boolean;
+}
+
+const checkboxRegex = /^ *\[([ x])\] /i;
+
+export default function markdownItTaskLists(
+ md: MarkdownIt,
+ options: TaskListsOptions = { enabled: true, label: true, lineNumber: false }
+): void {
+ md.core.ruler.after('inline', 'github-task-lists', (state) => processToken(state, options));
+ md.renderer.rules.taskListItemCheckbox = (tokens) => {
+ const token = tokens[0];
+ const checkedAttribute = token.attrGet('checked') ? 'checked=""' : '';
+ const disabledAttribute = token.attrGet('disabled') ? 'disabled=""' : '';
+ const id = token.attrGet('id');
+ const line = token.attrGet('line');
+ const idAttribute = `id="${id}"`;
+ const dataLineAttribute = line && options.lineNumber ? `data-line="${line}"` : '';
+
+ return ``;
+ };
+
+ md.renderer.rules.taskListItemLabel_close = () => {
+ return '
';
+ };
+
+ md.renderer.rules.taskListItemLabel_open = (tokens) => {
+ const token = tokens[0];
+ const id = token.attrGet('id');
+ return ``;
+ };
+}
+
+function attrSet(token, name, value) {
+ var index = token.attrIndex(name);
+ var attr = [name, value];
+
+ if (index < 0) {
+ token.attrPush(attr);
+ } else {
+ token.attrs[index] = attr;
+ }
+}
+
+function processToken(state: StateCore, options: TaskListsOptions): boolean {
+ const allTokens = state.tokens;
+
+ attrSet(allTokens[0], 'class', 'contains-task-list');
+
+ for (let i = 2; i < allTokens.length; i++) {
+ if (!isTodoItem(allTokens, i)) {
+ continue;
+ }
+
+ const { isChecked } = todoify(allTokens[i], options);
+ allTokens[i - 2].attrJoin('class', `task-list-item`);
+ allTokens[i - 2].attrJoin('data-checked', isChecked ? `true` : `false`);
+
+ const parentToken = findParentToken(allTokens, i - 2);
+ if (parentToken) {
+ parentToken.attrJoin('class', 'task-list');
+ }
+ }
+ return false;
+}
+
+function findParentToken(tokens: Token[], index: number): Token | undefined {
+ const targetLevel = tokens[index].level - 1;
+ for (let currentTokenIndex = index - 1; currentTokenIndex >= 0; currentTokenIndex--) {
+ if (tokens[currentTokenIndex].level === targetLevel) {
+ return tokens[currentTokenIndex];
+ }
+ }
+ return undefined;
+}
+
+function isTodoItem(tokens: Token[], index: number): boolean {
+ return (
+ isInline(tokens[index]) &&
+ isParagraph(tokens[index - 1]) &&
+ isListItem(tokens[index - 2]) &&
+ startsWithTodoMarkdown(tokens[index])
+ );
+}
+
+function todoify(token: Token, options: TaskListsOptions) {
+ if (token.children == null) {
+ return;
+ }
+
+ const id = generateIdForToken(token);
+
+ const { checkbox, isChecked } = createCheckboxToken(token, options.enabled, id);
+ token.children.splice(0, 0, checkbox);
+ token.children[1].content = token.children[1].content.replace(checkboxRegex, '');
+
+ if (options.label) {
+ token.children.splice(1, 0, createLabelBeginToken(id));
+ token.children.push(createLabelEndToken());
+ }
+
+ return { isChecked };
+}
+
+function generateIdForToken(token: Token): string {
+ if (token.map) {
+ return `task-item-${token.map[0]}`;
+ } else {
+ return `task-item-${Math.ceil(Math.random() * (10000 * 1000) - 1000)}`;
+ }
+}
+
+function createCheckboxToken(token: Token, enabled: boolean, id: string): Token {
+ const checkbox = new Token('taskListItemCheckbox', '', 0);
+ if (!enabled) {
+ checkbox.attrSet('disabled', 'true');
+ }
+ if (token.map) {
+ checkbox.attrSet('line', token.map[0].toString());
+ }
+
+ checkbox.attrSet('id', id);
+
+ const checkboxRegexResult = checkboxRegex.exec(token.content);
+ const isChecked = !!checkboxRegexResult && checkboxRegexResult[1].toLowerCase() === 'x';
+ if (isChecked) {
+ checkbox.attrSet('checked', 'true');
+ }
+
+ return { checkbox, isChecked };
+}
+
+function createLabelBeginToken(id: string): Token {
+ const labelBeginToken = new Token('taskListItemLabel_open', '', 1);
+ labelBeginToken.attrSet('id', id);
+ return labelBeginToken;
+}
+
+function createLabelEndToken(): Token {
+ return new Token('taskListItemLabel_close', '', -1);
+}
+
+function isInline(token: Token): boolean {
+ return token.type === 'inline';
+}
+
+function isParagraph(token: Token): boolean {
+ return token.type === 'paragraph_open';
+}
+
+function isListItem(token: Token): boolean {
+ return token.type === 'list_item_open';
+}
+
+function startsWithTodoMarkdown(token: Token): boolean {
+ return checkboxRegex.test(token.content);
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/serializer.ts b/packages/client/src/components/tiptap/services/markdown/serializer.ts
index 6c13fc42..07b4eb64 100644
--- a/packages/client/src/components/tiptap/services/markdown/serializer.ts
+++ b/packages/client/src/components/tiptap/services/markdown/serializer.ts
@@ -49,6 +49,12 @@ import {
renderHTMLNode,
} from './serializerHelpers';
+// import * as HTML/ from 'html-to-prosemirror'
+
+import { Renderer } from './src/Renderer';
+
+const renderer = new Renderer();
+
const defaultSerializerConfig = {
marks: {
[Bold.name]: defaultMarkdownSerializer.marks.strong,
@@ -188,14 +194,53 @@ const renderMarkdown = (rawMarkdown) => {
const createMarkdownSerializer = () => ({
// 将 markdown 字符串转换为 ProseMirror JSONDocument
- deserialize: ({ schema, content }) => {
+ deserialize: ({ schema, content, hasTitle }) => {
const html = renderMarkdown(content);
if (!html) return null;
const parser = new DOMParser();
const { body } = parser.parseFromString(html, 'text/html');
body.append(document.createComment(content));
- const state = ProseMirrorDOMParser.fromSchema(schema).parse(body);
- return state;
+ const json = renderer.render(body);
+
+ console.log({ hasTitle, json, body });
+
+ if (!hasTitle) {
+ const firstNode = json.content[0];
+
+ if (firstNode) {
+ if (firstNode.type === 'heading') {
+ firstNode.type = 'title';
+ }
+ }
+ }
+
+ const nodes = json.content;
+
+ const result = { type: 'doc', content: [] };
+
+ for (let i = 0; i < nodes.length; ) {
+ const node = nodes[i];
+
+ if (node.type === 'tableRow') {
+ const nextNode = nodes[i + 1];
+
+ if (nextNode && nextNode.type === 'table') {
+ nextNode.content.unshift(node);
+ result.content.push(nextNode);
+ i += 2;
+ } else {
+ // 出错了!!
+ }
+ } else {
+ result.content.push(node);
+ i += 1;
+ }
+ }
+
+ return result;
+
+ // const state = ProseMirrorDOMParser.fromSchema(schema).parse(body);
+ // return state.toJSON();
},
// 将 ProseMirror JSONDocument 转换为 markdown 字符串
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Marks/Bold.js b/packages/client/src/components/tiptap/services/markdown/src/Marks/Bold.js
new file mode 100644
index 00000000..cdf6017f
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Marks/Bold.js
@@ -0,0 +1,13 @@
+import { Mark } from './Mark';
+
+export class Bold extends Mark {
+ matching() {
+ return this.DOMNode.nodeName === 'STRONG';
+ }
+
+ data() {
+ return {
+ type: 'bold',
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Marks/Code.js b/packages/client/src/components/tiptap/services/markdown/src/Marks/Code.js
new file mode 100644
index 00000000..d79e71a1
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Marks/Code.js
@@ -0,0 +1,16 @@
+import { Mark } from './Mark';
+export class Code extends Mark {
+ matching() {
+ if (this.DOMNode.parentNode.nodeName === 'PRE') {
+ return false;
+ }
+
+ return this.DOMNode.nodeName === 'CODE';
+ }
+
+ data() {
+ return {
+ type: 'code',
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Marks/Italic.js b/packages/client/src/components/tiptap/services/markdown/src/Marks/Italic.js
new file mode 100644
index 00000000..5c48c774
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Marks/Italic.js
@@ -0,0 +1,12 @@
+import { Mark } from './Mark';
+export class Italic extends Mark {
+ matching() {
+ return this.DOMNode.nodeName === 'EM';
+ }
+
+ data() {
+ return {
+ type: 'italic',
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Marks/Link.js b/packages/client/src/components/tiptap/services/markdown/src/Marks/Link.js
new file mode 100644
index 00000000..50438b94
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Marks/Link.js
@@ -0,0 +1,15 @@
+import { Mark } from './Mark';
+export class Link extends Mark {
+ matching() {
+ return this.DOMNode.nodeName === 'A';
+ }
+
+ data() {
+ return {
+ type: 'link',
+ attrs: {
+ href: this.DOMNode.getAttribute('href'),
+ },
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Marks/Mark.js b/packages/client/src/components/tiptap/services/markdown/src/Marks/Mark.js
new file mode 100644
index 00000000..80053a06
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Marks/Mark.js
@@ -0,0 +1,14 @@
+export class Mark {
+ constructor(DomNode) {
+ this.type = 'mark';
+ this.DOMNode = DomNode;
+ }
+
+ matching() {
+ return false;
+ }
+
+ data() {
+ return [];
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/BulletList.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/BulletList.js
new file mode 100644
index 00000000..61355ab8
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/BulletList.js
@@ -0,0 +1,15 @@
+import { Node } from './Node';
+
+export class BulletList extends Node {
+ type = 'bulletList';
+
+ matching() {
+ return this.DOMNode.nodeName === 'UL';
+ }
+
+ // data() {
+ // return {
+ // type: 'bulletList',
+ // };
+ // }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlock.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlock.js
new file mode 100644
index 00000000..0b561060
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlock.js
@@ -0,0 +1,29 @@
+import { Node } from './Node';
+export class CodeBlock extends Node {
+ type = 'codeBlock';
+ matching() {
+ return this.DOMNode.nodeName === 'CODE' && this.DOMNode.parentNode.nodeName === 'PRE';
+ }
+
+ // getLanguage() {
+ // const language = this.DOMNode.getAttribute('class');
+ // return language ? language.replace(/^language-/, '') : language;
+ // }
+
+ // data() {
+ // const language = this.getLanguage();
+
+ // if (language) {
+ // return {
+ // type: 'codeBlock',
+ // attrs: {
+ // language,
+ // },
+ // };
+ // }
+
+ // return {
+ // type: 'codeBlock',
+ // };
+ // }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlockWrapper.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlockWrapper.js
new file mode 100644
index 00000000..d45f221d
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlockWrapper.js
@@ -0,0 +1,10 @@
+import { Node } from './Node';
+export class CodeBlockWrapper extends Node {
+ matching() {
+ return this.DOMNode.nodeName === 'PRE';
+ }
+
+ data() {
+ return null;
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/HardBreak.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/HardBreak.js
new file mode 100644
index 00000000..c2119b50
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/HardBreak.js
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class HardBreak extends Node {
+ type = 'hardBreak';
+
+ matching() {
+ return this.DOMNode.nodeName === 'BR';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/Heading.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Heading.js
new file mode 100644
index 00000000..c50027c3
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Heading.js
@@ -0,0 +1,22 @@
+import { Node } from './Node';
+export class Heading extends Node {
+ type = 'heading';
+
+ getLevel() {
+ const matches = this.DOMNode.nodeName.match(/^H([1-6])/);
+ return matches ? matches[1] : null;
+ }
+
+ matching() {
+ return Boolean(this.getLevel());
+ }
+
+ // data() {
+ // return {
+ // type: 'heading',
+ // attrs: {
+ // level: this.getLevel(),
+ // },
+ // };
+ // }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/Image.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Image.js
new file mode 100644
index 00000000..6ea180d8
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Image.js
@@ -0,0 +1,21 @@
+import { Node } from './Node';
+
+export class Image extends Node {
+ type = 'image';
+
+ matching() {
+ return this.DOMNode.nodeName === 'IMG';
+ }
+
+ data() {
+ return {
+ type: 'image',
+ attrs: {
+ src: this.DOMNode.getAttribute('src'),
+ class: this.DOMNode.getAttribute('class') || undefined,
+ alt: this.DOMNode.getAttribute('alt') || undefined,
+ title: this.DOMNode.getAttribute('title') || undefined,
+ },
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/ListItem.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/ListItem.js
new file mode 100644
index 00000000..609cb3fc
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/ListItem.js
@@ -0,0 +1,21 @@
+import { Node } from './Node';
+export class ListItem extends Node {
+ constructor(...args) {
+ super(...args);
+ this.wrapper = {
+ type: 'paragraph',
+ };
+ }
+
+ type = 'listItem';
+
+ matching() {
+ return this.DOMNode.nodeName === 'LI';
+ }
+
+ // data() {
+ // if (this.DOMNode.childNodes.length === 1 && this.DOMNode.childNodes[0].nodeName === 'P') {
+ // this.wrapper = null;
+ // }
+ // }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/Node.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Node.ts
new file mode 100644
index 00000000..97362a58
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Node.ts
@@ -0,0 +1,23 @@
+import { getAttributes } from '../utils';
+
+export class Node {
+ wrapper: null;
+ type = 'node';
+ DOMNode: HTMLElement;
+
+ constructor(DomNode: HTMLElement) {
+ this.wrapper = null;
+ this.DOMNode = DomNode;
+ }
+
+ matching() {
+ return false;
+ }
+
+ data() {
+ return {
+ type: this.type,
+ attrs: getAttributes(this.type, this.DOMNode),
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/OrderedList.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/OrderedList.js
new file mode 100644
index 00000000..0fa69da5
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/OrderedList.js
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class OrderedList extends Node {
+ type = 'orderedList';
+
+ matching() {
+ return this.DOMNode.nodeName === 'OL';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/Paragraph.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Paragraph.js
new file mode 100644
index 00000000..0ddc339c
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Paragraph.js
@@ -0,0 +1,8 @@
+import { Node } from './Node';
+export class Paragraph extends Node {
+ type = 'paragraph';
+
+ matching() {
+ return this.DOMNode.nodeName === 'P';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/Text.js b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Text.js
new file mode 100644
index 00000000..e761c616
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/Text.js
@@ -0,0 +1,19 @@
+import { Node } from './Node';
+export class Text extends Node {
+ matching() {
+ return this.DOMNode.nodeName === '#text';
+ }
+
+ data() {
+ const text = this.DOMNode.nodeValue.replace(/^[\n]+/g, '');
+
+ if (!text) {
+ return null;
+ }
+
+ return {
+ type: 'text',
+ text,
+ };
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/blockQuote.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/blockQuote.ts
new file mode 100644
index 00000000..f20163ed
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/blockQuote.ts
@@ -0,0 +1,15 @@
+import { Node } from './Node';
+
+export class Blockquote extends Node {
+ type = 'blockquote';
+
+ matching() {
+ return this.DOMNode.nodeName === 'BLOCKQUOTE';
+ }
+
+ // data() {
+ // return {
+ // type: 'blockquote',
+ // };
+ // }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/table.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/table.ts
new file mode 100644
index 00000000..a9cc31f6
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/table.ts
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class Table extends Node {
+ type = 'table';
+
+ matching() {
+ return this.DOMNode.nodeName === 'TBODY' && this.DOMNode.parentNode.nodeName === 'TABLE';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableCell.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableCell.ts
new file mode 100644
index 00000000..b90151f8
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableCell.ts
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class TableCell extends Node {
+ type = 'tableCell';
+
+ matching() {
+ return this.DOMNode.nodeName === 'TD';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableHeader.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableHeader.ts
new file mode 100644
index 00000000..b7625a4a
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableHeader.ts
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class TableHeader extends Node {
+ type = 'tableHeader';
+
+ matching() {
+ return this.DOMNode.nodeName === 'TH';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableRow.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableRow.ts
new file mode 100644
index 00000000..75d14d0b
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/tableRow.ts
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class TableRow extends Node {
+ type = 'tableRow';
+
+ matching() {
+ return this.DOMNode.nodeName === 'TR';
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/taskList.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/taskList.ts
new file mode 100644
index 00000000..ea943a26
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/taskList.ts
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class TaskList extends Node {
+ type = 'taskList';
+
+ matching() {
+ return this.DOMNode.nodeName === 'UL' && this.DOMNode.classList.contains('task-list');
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Nodes/taskListItem.ts b/packages/client/src/components/tiptap/services/markdown/src/Nodes/taskListItem.ts
new file mode 100644
index 00000000..bbf4795e
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Nodes/taskListItem.ts
@@ -0,0 +1,9 @@
+import { Node } from './Node';
+
+export class TaskListItem extends Node {
+ type = 'taskItem';
+
+ matching() {
+ return this.DOMNode.nodeName === 'LI' && this.DOMNode.classList.contains('task-list-item');
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/Renderer.js b/packages/client/src/components/tiptap/services/markdown/src/Renderer.js
new file mode 100644
index 00000000..9bbaf499
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/Renderer.js
@@ -0,0 +1,182 @@
+import { BulletList } from './Nodes/BulletList';
+import { CodeBlock } from './Nodes/CodeBlock';
+import { CodeBlockWrapper } from './Nodes/CodeBlockWrapper';
+import { HardBreak } from './Nodes/HardBreak';
+import { Heading } from './Nodes/Heading';
+import { Image } from './Nodes/Image';
+import { ListItem } from './Nodes/ListItem';
+import { OrderedList } from './Nodes/OrderedList';
+import { Paragraph } from './Nodes/Paragraph';
+import { Text } from './Nodes/Text';
+import { Blockquote } from './Nodes/blockQuote';
+
+import { Table } from './Nodes/table';
+import { TableHeader } from './Nodes/tableHeader';
+import { TableRow } from './Nodes/tableRow';
+import { TableCell } from './Nodes/tableCell';
+
+import { TaskList } from './Nodes/taskList';
+import { TaskListItem } from './Nodes/taskListItem';
+
+import { Bold } from './Marks/Bold';
+import { Code } from './Marks/Code';
+import { Italic } from './Marks/Italic';
+import { Link } from './Marks/Link';
+
+export class Renderer {
+ constructor() {
+ this.document = undefined;
+ this.storedMarks = [];
+
+ this.nodes = [
+ CodeBlock,
+ CodeBlockWrapper,
+ HardBreak,
+ Heading,
+ Image,
+ Paragraph,
+ Text,
+ Blockquote,
+
+ Table,
+ TableHeader,
+ TableRow,
+ TableCell,
+
+ // 列表
+ TaskList,
+ TaskListItem,
+ OrderedList,
+ ListItem,
+ BulletList,
+ ];
+
+ this.marks = [Bold, Code, Italic, Link];
+ }
+
+ setDocument(document) {
+ this.document = document;
+ }
+
+ stripWhitespace(value) {
+ // return minify(value, {
+ // collapseWhitespace: true,
+ // });
+ return value;
+ }
+
+ getDocumentBody() {
+ return this.document;
+ // return this.document.window.document.querySelector('body');
+ }
+
+ render(value) {
+ this.setDocument(value);
+
+ console.log(value);
+
+ const content = this.renderChildren(this.getDocumentBody());
+
+ return {
+ type: 'doc',
+ content,
+ };
+ }
+
+ renderChildren(node) {
+ let nodes = [];
+
+ node.childNodes.forEach((child) => {
+ const NodeClass = this.getMatchingNode(child);
+ let MarkClass;
+
+ if (NodeClass) {
+ let item = NodeClass.data();
+
+ if (!item) {
+ if (child.hasChildNodes()) {
+ nodes.push(...this.renderChildren(child));
+ }
+ return;
+ }
+
+ if (child.hasChildNodes()) {
+ item = {
+ ...item,
+ content: this.renderChildren(child),
+ };
+ }
+
+ if (this.storedMarks.length) {
+ item = {
+ ...item,
+ marks: this.storedMarks,
+ };
+ this.storedMarks = [];
+ }
+
+ if (NodeClass.wrapper) {
+ item.content = [
+ {
+ ...NodeClass.wrapper,
+ content: item.content || [],
+ },
+ ];
+ }
+
+ nodes.push(item);
+ } else if ((MarkClass = this.getMatchingMark(child))) {
+ this.storedMarks.push(MarkClass.data());
+
+ if (child.hasChildNodes()) {
+ nodes.push(...this.renderChildren(child));
+ }
+ } else if (child.hasChildNodes()) {
+ nodes.push(...this.renderChildren(child));
+ }
+ });
+
+ return nodes;
+ }
+
+ getMatchingNode(item) {
+ return this.getMatchingClass(item, this.nodes);
+ }
+
+ getMatchingMark(item) {
+ return this.getMatchingClass(item, this.marks);
+ }
+
+ getMatchingClass(node, classes) {
+ for (let i in classes) {
+ const Class = classes[i];
+ const instance = new Class(node);
+ // console.log(node);
+ if (instance.matching()) {
+ return instance;
+ }
+ }
+
+ return false;
+ }
+
+ addNode(node) {
+ this.nodes.push(node);
+ }
+
+ addNodes(nodes) {
+ for (const i in nodes) {
+ this.addNode(nodes[i]);
+ }
+ }
+
+ addMark(mark) {
+ this.marks.push(mark);
+ }
+
+ addMarks(marks) {
+ for (const i in marks) {
+ this.addMark(marks[i]);
+ }
+ }
+}
diff --git a/packages/client/src/components/tiptap/services/markdown/src/utils.ts b/packages/client/src/components/tiptap/services/markdown/src/utils.ts
new file mode 100644
index 00000000..18ff85a4
--- /dev/null
+++ b/packages/client/src/components/tiptap/services/markdown/src/utils.ts
@@ -0,0 +1,47 @@
+import { BaseKit } from '../../../basekit';
+
+export const getAttributes = (name: string, element: HTMLElement): Record => {
+ const ext = BaseKit.find((ext) => ext.name === name);
+
+ const run = (
+ ret = {},
+ config: Record Record }>
+ ) => {
+ return Object.keys(config).reduce((accu, key) => {
+ const conf = config[key];
+ accu[key] = conf.default;
+
+ if (conf.parseHTML) {
+ try {
+ accu[key] = conf.parseHTML(element);
+ } catch (e) {
+ //
+ }
+ }
+
+ return accu;
+ }, ret);
+ };
+
+ let parent = ext && ext.parent;
+
+ if (!parent) return {};
+
+ while (parent.parent) {
+ parent = parent.parent;
+ }
+
+ const { config } = parent;
+ const { addGlobalAttributes, addAttributes } = config;
+ const attrs = {};
+
+ if (addGlobalAttributes) {
+ run(attrs, addGlobalAttributes.call(ext));
+ }
+
+ if (addAttributes) {
+ run(attrs, addAttributes.call(ext));
+ }
+
+ return attrs;
+};
From 9ddab5a1348d8016c81baa5f7c558726d734a33e Mon Sep 17 00:00:00 2001
From: fantasticit
Date: Tue, 22 Mar 2022 20:31:21 +0800
Subject: [PATCH 5/7] feat: improve copy paste
---
.../tiptap/components/katex/index.module.scss | 2 +-
.../tiptap/components/katex/index.tsx | 11 +-
.../components/tiptap/extensions/banner.ts | 2 +-
.../tiptap/extensions/horizontalRule.ts | 8 +-
.../src/components/tiptap/extensions/katex.ts | 20 +-
.../src/components/tiptap/extensions/paste.ts | 10 +-
.../tiptap/menus/components/paragraph.tsx | 2 +
.../markdown/htmlToProsemirror/README.md | 1 +
.../markdown/htmlToProsemirror/index.ts | 7 +
.../marks/bold.ts} | 2 +-
.../marks/code.ts} | 3 +-
.../marks/italic.ts} | 2 +-
.../marks/link.ts} | 2 +-
.../marks/mark.ts} | 3 +
.../nodes}/blockQuote.ts | 2 +-
.../nodes/bulletList.ts} | 8 +-
.../htmlToProsemirror/nodes/codeBlock.ts | 9 +
.../nodes/codeBlockWrapper.ts} | 3 +-
.../nodes/hardBreak.ts} | 2 +-
.../nodes/heading.ts} | 18 +-
.../htmlToProsemirror/nodes/horizontalRule.ts | 9 +
.../nodes/image.ts} | 2 +-
.../markdown/htmlToProsemirror/nodes/katex.ts | 9 +
.../htmlToProsemirror/nodes/listItem.ts | 16 ++
.../nodes/node.ts} | 4 +-
.../nodes/orderedList.ts} | 2 +-
.../nodes/paragraph.ts} | 2 +-
.../nodes}/table.ts | 2 +-
.../nodes}/tableCell.ts | 2 +-
.../nodes}/tableHeader.ts | 2 +-
.../nodes}/tableRow.ts | 2 +-
.../nodes}/taskList.ts | 2 +-
.../nodes}/taskListItem.ts | 2 +-
.../nodes/text.ts} | 3 +-
.../renderer.ts} | 67 +++---
.../markdown/htmlToProsemirror/utils.ts | 60 +++++
.../markdown/{ => markdownToHTML}/index.ts | 11 +-
.../{ => markdownToHTML}/markdownBanner.ts | 0
.../markdown/markdownToHTML/markdownKatex.ts | 225 ++++++++++++++++++
.../{ => markdownToHTML}/markdownTable.ts | 0
.../{ => markdownToHTML}/markdownTaskList.ts | 0
.../{ => markdownToHTML}/markdownUnderline.ts | 0
.../markedownSplitMixedList.ts | 2 +-
.../tiptap/services/markdown/serializer.ts | 37 +--
.../services/markdown/src/Nodes/CodeBlock.js | 29 ---
.../services/markdown/src/Nodes/ListItem.js | 21 --
.../tiptap/services/markdown/src/utils.ts | 47 ----
packages/client/src/hooks/useTheme.tsx | 19 +-
packages/client/src/styles/prosemirror.scss | 4 +-
.../server/src/entities/document.entity.ts | 2 +-
50 files changed, 473 insertions(+), 227 deletions(-)
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/README.md
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/index.ts
rename packages/client/src/components/tiptap/services/markdown/{src/Marks/Bold.js => htmlToProsemirror/marks/bold.ts} (83%)
rename packages/client/src/components/tiptap/services/markdown/{src/Marks/Code.js => htmlToProsemirror/marks/code.ts} (88%)
rename packages/client/src/components/tiptap/services/markdown/{src/Marks/Italic.js => htmlToProsemirror/marks/italic.ts} (83%)
rename packages/client/src/components/tiptap/services/markdown/{src/Marks/Link.js => htmlToProsemirror/marks/link.ts} (87%)
rename packages/client/src/components/tiptap/services/markdown/{src/Marks/Mark.js => htmlToProsemirror/marks/mark.ts} (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/blockQuote.ts (87%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/BulletList.js => htmlToProsemirror/nodes/bulletList.ts} (52%)
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/nodes/codeBlock.ts
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/CodeBlockWrapper.js => htmlToProsemirror/nodes/codeBlockWrapper.ts} (81%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/HardBreak.js => htmlToProsemirror/nodes/hardBreak.ts} (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/Heading.js => htmlToProsemirror/nodes/heading.ts} (56%)
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/nodes/horizontalRule.ts
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/Image.js => htmlToProsemirror/nodes/image.ts} (93%)
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/nodes/katex.ts
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/nodes/listItem.ts
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/Node.ts => htmlToProsemirror/nodes/node.ts} (85%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/OrderedList.js => htmlToProsemirror/nodes/orderedList.ts} (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/Paragraph.js => htmlToProsemirror/nodes/paragraph.ts} (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/table.ts (84%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/tableCell.ts (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/tableHeader.ts (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/tableRow.ts (80%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/taskList.ts (84%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes => htmlToProsemirror/nodes}/taskListItem.ts (85%)
rename packages/client/src/components/tiptap/services/markdown/{src/Nodes/Text.js => htmlToProsemirror/nodes/text.ts} (89%)
rename packages/client/src/components/tiptap/services/markdown/{src/Renderer.js => htmlToProsemirror/renderer.ts} (69%)
create mode 100644 packages/client/src/components/tiptap/services/markdown/htmlToProsemirror/utils.ts
rename packages/client/src/components/tiptap/services/markdown/{ => markdownToHTML}/index.ts (74%)
rename packages/client/src/components/tiptap/services/markdown/{ => markdownToHTML}/markdownBanner.ts (100%)
create mode 100644 packages/client/src/components/tiptap/services/markdown/markdownToHTML/markdownKatex.ts
rename packages/client/src/components/tiptap/services/markdown/{ => markdownToHTML}/markdownTable.ts (100%)
rename packages/client/src/components/tiptap/services/markdown/{ => markdownToHTML}/markdownTaskList.ts (100%)
rename packages/client/src/components/tiptap/services/markdown/{ => markdownToHTML}/markdownUnderline.ts (100%)
rename packages/client/src/components/tiptap/services/markdown/{ => markdownToHTML}/markedownSplitMixedList.ts (97%)
delete mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/CodeBlock.js
delete mode 100644 packages/client/src/components/tiptap/services/markdown/src/Nodes/ListItem.js
delete mode 100644 packages/client/src/components/tiptap/services/markdown/src/utils.ts
diff --git a/packages/client/src/components/tiptap/components/katex/index.module.scss b/packages/client/src/components/tiptap/components/katex/index.module.scss
index 9796867f..a77831f1 100644
--- a/packages/client/src/components/tiptap/components/katex/index.module.scss
+++ b/packages/client/src/components/tiptap/components/katex/index.module.scss
@@ -1,5 +1,5 @@
.wrap {
margin: 8px 0;
- display: flex;
+ display: inline-flex;
justify-content: center;
}
diff --git a/packages/client/src/components/tiptap/components/katex/index.tsx b/packages/client/src/components/tiptap/components/katex/index.tsx
index bf7371b3..47d74585 100644
--- a/packages/client/src/components/tiptap/components/katex/index.tsx
+++ b/packages/client/src/components/tiptap/components/katex/index.tsx
@@ -1,5 +1,5 @@
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
-import { useMemo } from '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';
@@ -10,6 +10,9 @@ const { Text } = Typography;
export const KatexWrapper = ({ editor, node, updateAttributes }) => {
const isEditable = editor.isEditable;
const { text } = node.attrs;
+
+ console.log(node.attrs);
+
const formatText = useMemo(() => {
try {
return katex.renderToString(`${text}`);
@@ -24,8 +27,12 @@ export const KatexWrapper = ({ editor, node, updateAttributes }) => {
点击输入公式
);
+ // useEffect(() => {
+ // updateAttributes(node.attrs);
+ // }, []);
+
return (
-
+
{isEditable ? (
{
diff --git a/packages/client/src/components/tiptap/extensions/horizontalRule.ts b/packages/client/src/components/tiptap/extensions/horizontalRule.ts
index a39e38a2..f9c37438 100644
--- a/packages/client/src/components/tiptap/extensions/horizontalRule.ts
+++ b/packages/client/src/components/tiptap/extensions/horizontalRule.ts
@@ -19,18 +19,16 @@ export const HorizontalRule = Node.create({
addOptions() {
return {
- HTMLAttributes: {
- class: 'hr-line',
- },
+ HTMLAttributes: {},
};
},
parseHTML() {
- return [{ tag: 'div[class=hr-line]' }];
+ return [{ tag: 'hr' }];
},
renderHTML({ HTMLAttributes }) {
- return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
+ return ['hr', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
},
addCommands() {
diff --git a/packages/client/src/components/tiptap/extensions/katex.ts b/packages/client/src/components/tiptap/extensions/katex.ts
index 0df0480f..91b992ce 100644
--- a/packages/client/src/components/tiptap/extensions/katex.ts
+++ b/packages/client/src/components/tiptap/extensions/katex.ts
@@ -14,26 +14,36 @@ export const KatexInputRegex = /^\$\$(.+)?\$\$$/;
export const Katex = Node.create({
name: 'katex',
- group: 'block',
- defining: true,
- draggable: true,
+ group: 'inline',
+ inline: true,
selectable: true,
atom: true,
+ addOptions() {
+ return {
+ HTMLAttributes: {
+ class: 'katex',
+ },
+ };
+ },
+
addAttributes() {
return {
text: {
default: '',
+ parseHTML: (element) => {
+ return element.getAttribute('data-text');
+ },
},
};
},
parseHTML() {
- return [{ tag: 'div[data-type=katex]' }];
+ return [{ tag: 'span.katex' }];
},
renderHTML({ HTMLAttributes }) {
- return ['div', mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes)];
+ return ['span', mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes)];
},
// @ts-ignore
diff --git a/packages/client/src/components/tiptap/extensions/paste.ts b/packages/client/src/components/tiptap/extensions/paste.ts
index b6f65658..e10401ac 100644
--- a/packages/client/src/components/tiptap/extensions/paste.ts
+++ b/packages/client/src/components/tiptap/extensions/paste.ts
@@ -1,6 +1,6 @@
import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state';
-import { markdownSerializer } from '../services/markdown';
+import { markdownSerializer } from '../services/markdown/serializer';
import { EXTENSION_PRIORITY_HIGHEST } from '../constants';
import { handleFileEvent } from '../services/upload';
import { isInCode, LANGUAGES } from '../services/code';
@@ -63,16 +63,14 @@ export const Paste = Extension.create({
// 处理 markdown
if (isMarkdown(text) || html.length === 0 || pasteCodeLanguage === 'markdown') {
event.preventDefault();
- // FIXME: 由于 title schema 的存在导致反序列化必有 title 节点存在
const firstNode = view.props.state.doc.content.firstChild;
const hasTitle = isTitleNode(firstNode) && firstNode.content.size > 0;
- let schema = view.props.state.schema;
- const doc = markdownSerializer.deserialize({
+ const schema = view.props.state.schema;
+ const doc = markdownSerializer.markdownToProsemirror({
schema,
content: normalizePastedMarkdown(text),
hasTitle,
});
-
// @ts-ignore
const transaction = view.state.tr.insert(view.state.selection.head, view.state.schema.nodeFromJSON(doc));
view.dispatch(transaction);
@@ -113,7 +111,7 @@ export const Paste = Extension.create({
if (!doc) {
return '';
}
- const content = markdownSerializer.serialize({
+ const content = markdownSerializer.proseMirrorToMarkdown({
schema: this.editor.schema,
content: doc,
});
diff --git a/packages/client/src/components/tiptap/menus/components/paragraph.tsx b/packages/client/src/components/tiptap/menus/components/paragraph.tsx
index e45e9600..c42f5564 100644
--- a/packages/client/src/components/tiptap/menus/components/paragraph.tsx
+++ b/packages/client/src/components/tiptap/menus/components/paragraph.tsx
@@ -21,6 +21,8 @@ export const Paragraph = ({ editor }) => {
}
}, []);
+ console.log(getCurrentCaretTitle(editor));
+
return (