client: tableOfContent markdown support

pull/60/head
fantasticit 2022-05-27 23:13:52 +08:00
parent 42e05bec0f
commit c37182f0a8
9 changed files with 42 additions and 17 deletions

View File

@ -1,4 +1,6 @@
export const safeJSONParse = (str, defaultValue = {}) => { export const safeJSONParse = (str, defaultValue = {}) => {
if (typeof str === 'object') return str;
try { try {
return JSON.parse(str); return JSON.parse(str);
} catch (e) { } catch (e) {

View File

@ -195,20 +195,29 @@ export const Paste = Extension.create<IPasteOptions>({
return false; return false;
}, },
clipboardTextSerializer: (slice) => { clipboardTextSerializer: (slice) => {
const isText = isPureText(slice.content.toJSON()); const json = slice.content.toJSON();
const isSelectAll = slice.openStart === slice.openEnd && slice.openEnd === 0;
if (isText) { if (typeof json === 'object' || isSelectAll) {
return slice.content.textBetween(0, slice.content.size, '\n\n'); return extensionThis.options.prosemirrorToMarkdown({
} content: slice.content,
});
} else {
const isText = isPureText(json) && !isSelectAll;
const doc = slice.content; if (isText) {
if (!doc) { return slice.content.textBetween(0, slice.content.size, '\n\n');
return ''; }
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;
}, },
}, },
}), }),

View File

@ -51,8 +51,6 @@ export const TableOfContentsWrapper = ({ editor }) => {
setItems(headings); setItems(headings);
}, [editor]); }, [editor]);
useEffect(handleUpdate, [handleUpdate]);
useEffect(() => { useEffect(() => {
if (!editor) { if (!editor) {
return null; return null;

View File

@ -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');
}
}

View File

@ -34,6 +34,7 @@ import { Status } from './nodes/status';
import { Table } from './nodes/table'; import { Table } from './nodes/table';
import { TableCell } from './nodes/table-cell'; import { TableCell } from './nodes/table-cell';
import { TableHeader } from './nodes/table-header'; import { TableHeader } from './nodes/table-header';
import { TableOfContents } from './nodes/table-of-contents';
import { TableRow } from './nodes/table-row'; import { TableRow } from './nodes/table-row';
// 列表 // 列表
import { TaskList } from './nodes/task-list'; import { TaskList } from './nodes/task-list';
@ -82,6 +83,7 @@ export class Renderer {
TableHeader, TableHeader,
TableRow, TableRow,
TableCell, TableCell,
TableOfContents,
// 列表 // 列表
TaskList, TaskList,

View File

@ -32,6 +32,7 @@ export const markdownToProsemirror = ({ schema, content, hasTitle }) => {
const parser = new DOMParser(); const parser = new DOMParser();
const { body } = parser.parseFromString(extractImage(html), 'text/html'); const { body } = parser.parseFromString(extractImage(html), 'text/html');
body.append(document.createComment(content)); body.append(document.createComment(content));
const node = htmlToPromsemirror(body, !hasTitle); const node = htmlToPromsemirror(body, !hasTitle);

View File

@ -22,6 +22,7 @@ const markdownIframe = createMarkdownContainer('iframe');
const markdownMention = createMarkdownContainer('mention'); const markdownMention = createMarkdownContainer('mention');
const markdownMind = createMarkdownContainer('mind'); const markdownMind = createMarkdownContainer('mind');
const markdownFlow = createMarkdownContainer('flow'); const markdownFlow = createMarkdownContainer('flow');
const markdownTableOfContents = createMarkdownContainer('tableOfContents');
const markdown = markdownit('commonmark') const markdown = markdownit('commonmark')
.enable('strikethrough') .enable('strikethrough')
@ -44,7 +45,8 @@ const markdown = markdownit('commonmark')
.use(markdownMind) .use(markdownMind)
.use(markdownDocumentReference) .use(markdownDocumentReference)
.use(markdownDocumentChildren) .use(markdownDocumentChildren)
.use(markdownFlow); .use(markdownFlow)
.use(markdownTableOfContents);
export const markdownToHTML = (rawMarkdown) => { export const markdownToHTML = (rawMarkdown) => {
return sanitize(markdown.render(rawMarkdown), {}); return sanitize(markdown.render(rawMarkdown), {});

View File

@ -31,6 +31,7 @@ import { Superscript } from 'tiptap/core/extensions/superscript';
import { Table } from 'tiptap/core/extensions/table'; import { Table } from 'tiptap/core/extensions/table';
import { TableCell } from 'tiptap/core/extensions/table-cell'; import { TableCell } from 'tiptap/core/extensions/table-cell';
import { TableHeader } from 'tiptap/core/extensions/table-header'; 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 { TableRow } from 'tiptap/core/extensions/table-row';
import { TaskItem } from 'tiptap/core/extensions/task-item'; import { TaskItem } from 'tiptap/core/extensions/task-item';
import { TaskList } from 'tiptap/core/extensions/task-list'; import { TaskList } from 'tiptap/core/extensions/task-list';
@ -147,6 +148,7 @@ const SerializerConfig = {
[Table.name]: renderTable, [Table.name]: renderTable,
[TableCell.name]: renderTableCell, [TableCell.name]: renderTableCell,
[TableHeader.name]: renderTableCell, [TableHeader.name]: renderTableCell,
[TableOfContents.name]: renderCustomContainer('tableOfContents'),
[TableRow.name]: renderTableRow, [TableRow.name]: renderTableRow,
[TaskItem.name]: (state, node) => { [TaskItem.name]: (state, node) => {
state.write(`[${node.attrs.checked ? 'x' : ' '}] `); state.write(`[${node.attrs.checked ? 'x' : ' '}] `);

View File

@ -3,10 +3,10 @@ import { copy } from 'helpers/copy';
import { safeJSONStringify } from 'helpers/json'; import { safeJSONStringify } from 'helpers/json';
import { Fragment, Node } from 'prosemirror-model'; import { Fragment, Node } from 'prosemirror-model';
export function copyNode(nodeOrNodeName: Node); export function copyNode(nodeOrNodeName: Node | Fragment<any>);
export function copyNode(nodeOrNodeName: string, editor: Editor); export function copyNode(nodeOrNodeName: string, editor: Editor);
export function copyNode(nodeOrNodeName: string | Node, editor?: Editor) { export function copyNode(nodeOrNodeName: string | Node | Fragment<any>, editor?: Editor) {
let targetNode: null | Node = null; let targetNode = null;
if (typeof nodeOrNodeName === 'string') { if (typeof nodeOrNodeName === 'string') {
const { state } = editor; const { state } = editor;