mirror of https://github.com/fantasticit/think.git
tiptap: render object data to attributes, so can copy and paste it
This commit is contained in:
parent
79f40ecb7a
commit
3ca68989f0
|
@ -2,7 +2,7 @@ import { IUser } from '@think/domains';
|
||||||
import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core';
|
import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { ExcalidrawWrapper } from 'tiptap/core/wrappers/excalidraw';
|
import { ExcalidrawWrapper } from 'tiptap/core/wrappers/excalidraw';
|
||||||
import { getDatasetAttribute } from 'tiptap/prose-utils';
|
import { getDatasetAttribute, nodeAttrsToDataset } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
const DEFAULT_MIND_DATA = { elements: [] };
|
const DEFAULT_MIND_DATA = { elements: [] };
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ export const Excalidraw = Node.create({
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'mind',
|
class: 'excalidraw',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -64,13 +64,13 @@ export const Excalidraw = Node.create({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'div[class=mind]',
|
tag: 'div[class=excalidraw]',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes, node }) {
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, nodeAttrsToDataset(node))];
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { IUser } from '@think/domains';
|
||||||
import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core';
|
import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { MindWrapper } from 'tiptap/core/wrappers/mind';
|
import { MindWrapper } from 'tiptap/core/wrappers/mind';
|
||||||
import { getDatasetAttribute } from 'tiptap/prose-utils';
|
import { getDatasetAttribute, nodeAttrsToDataset } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
const DEFAULT_MIND_DATA = {
|
const DEFAULT_MIND_DATA = {
|
||||||
root: { data: { text: '中心节点' }, children: [] },
|
root: { data: { text: '中心节点' }, children: [] },
|
||||||
|
@ -77,8 +77,8 @@ export const Mind = Node.create({
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes, node }) {
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, nodeAttrsToDataset(node))];
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
|
|
|
@ -23,6 +23,11 @@ interface IPasteOptions {
|
||||||
*/
|
*/
|
||||||
htmlToProsemirror: (arg: { schema: Schema; html: string; needTitle: boolean; defaultTitle?: string }) => Node;
|
htmlToProsemirror: (arg: { schema: Schema; html: string; needTitle: boolean; defaultTitle?: string }) => Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 markdown 转换为 html
|
||||||
|
*/
|
||||||
|
markdownToHTML: (arg: string) => string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 markdown 转换为 prosemirror 节点
|
* 将 markdown 转换为 prosemirror 节点
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +46,7 @@ export const Paste = Extension.create<IPasteOptions>({
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
htmlToProsemirror: (arg) => '',
|
htmlToProsemirror: (arg) => '',
|
||||||
|
markdownToHTML: (arg) => arg,
|
||||||
markdownToProsemirror: (arg) => arg.content,
|
markdownToProsemirror: (arg) => arg.content,
|
||||||
prosemirrorToMarkdown: (arg) => String(arg.content),
|
prosemirrorToMarkdown: (arg) => String(arg.content),
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,7 +66,7 @@ import { TrailingNode } from 'tiptap/core/extensions/trailing-node';
|
||||||
import { Underline } from 'tiptap/core/extensions/underline';
|
import { Underline } from 'tiptap/core/extensions/underline';
|
||||||
// markdown 支持
|
// markdown 支持
|
||||||
import { htmlToProsemirror } from 'tiptap/markdown/html-to-prosemirror';
|
import { htmlToProsemirror } from 'tiptap/markdown/html-to-prosemirror';
|
||||||
import { markdownToProsemirror } from 'tiptap/markdown/markdown-to-prosemirror';
|
import { markdownToHTML, markdownToProsemirror } from 'tiptap/markdown/markdown-to-prosemirror';
|
||||||
import { prosemirrorToMarkdown } from 'tiptap/markdown/prosemirror-to-markdown';
|
import { prosemirrorToMarkdown } from 'tiptap/markdown/prosemirror-to-markdown';
|
||||||
|
|
||||||
const DocumentWithTitle = Document.extend({
|
const DocumentWithTitle = Document.extend({
|
||||||
|
@ -137,6 +137,7 @@ export const CollaborationKit = [
|
||||||
Underline,
|
Underline,
|
||||||
Paste.configure({
|
Paste.configure({
|
||||||
htmlToProsemirror,
|
htmlToProsemirror,
|
||||||
|
markdownToHTML,
|
||||||
markdownToProsemirror,
|
markdownToProsemirror,
|
||||||
prosemirrorToMarkdown,
|
prosemirrorToMarkdown,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Node } from './node';
|
||||||
|
|
||||||
|
export class Excalidraw extends Node {
|
||||||
|
type = 'excalidraw';
|
||||||
|
|
||||||
|
matching() {
|
||||||
|
return this.DOMNode.nodeName === 'DIV' && this.DOMNode.classList.contains('excalidraw');
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import { CodeBlockWrapper } from './nodes/code-block-wrapper';
|
||||||
import { Countdown } from './nodes/countdown';
|
import { Countdown } from './nodes/countdown';
|
||||||
import { DocumentChildren } from './nodes/document-children';
|
import { DocumentChildren } from './nodes/document-children';
|
||||||
import { DocumentReference } from './nodes/document-reference';
|
import { DocumentReference } from './nodes/document-reference';
|
||||||
|
import { Excalidraw } from './nodes/excalidraw';
|
||||||
import { Flow } from './nodes/flow';
|
import { Flow } from './nodes/flow';
|
||||||
import { HardBreak } from './nodes/hard-break';
|
import { HardBreak } from './nodes/hard-break';
|
||||||
import { Heading } from './nodes/heading';
|
import { Heading } from './nodes/heading';
|
||||||
|
@ -57,6 +58,7 @@ export class Renderer {
|
||||||
Attachment,
|
Attachment,
|
||||||
Countdown,
|
Countdown,
|
||||||
Callout,
|
Callout,
|
||||||
|
Excalidraw,
|
||||||
Iframe,
|
Iframe,
|
||||||
Status,
|
Status,
|
||||||
Mention,
|
Mention,
|
||||||
|
|
|
@ -37,3 +37,5 @@ export const markdownToProsemirror = ({ schema, content, needTitle, defaultTitle
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { markdownToHTML };
|
||||||
|
|
|
@ -21,6 +21,7 @@ const markdownDocumentChildren = createMarkdownContainer('documentChildren');
|
||||||
const markdownIframe = createMarkdownContainer('iframe');
|
const markdownIframe = createMarkdownContainer('iframe');
|
||||||
const markdownMention = createMarkdownContainer('mention');
|
const markdownMention = createMarkdownContainer('mention');
|
||||||
const markdownMind = createMarkdownContainer('mind');
|
const markdownMind = createMarkdownContainer('mind');
|
||||||
|
const markdownExcalidraw = createMarkdownContainer('excalidraw');
|
||||||
const markdownFlow = createMarkdownContainer('flow');
|
const markdownFlow = createMarkdownContainer('flow');
|
||||||
const markdownTableOfContents = createMarkdownContainer('tableOfContents');
|
const markdownTableOfContents = createMarkdownContainer('tableOfContents');
|
||||||
const markdownTitle = createMarkdownContainer('title');
|
const markdownTitle = createMarkdownContainer('title');
|
||||||
|
@ -44,6 +45,7 @@ const markdown = markdownit('commonmark')
|
||||||
.use(markdownStatus)
|
.use(markdownStatus)
|
||||||
.use(markdownMention)
|
.use(markdownMention)
|
||||||
.use(markdownMind)
|
.use(markdownMind)
|
||||||
|
.use(markdownExcalidraw)
|
||||||
.use(markdownDocumentReference)
|
.use(markdownDocumentReference)
|
||||||
.use(markdownDocumentChildren)
|
.use(markdownDocumentChildren)
|
||||||
.use(markdownFlow)
|
.use(markdownFlow)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { CodeBlock } from 'tiptap/core/extensions/code-block';
|
||||||
import { Countdown } from 'tiptap/core/extensions/countdown';
|
import { Countdown } from 'tiptap/core/extensions/countdown';
|
||||||
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
||||||
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
||||||
|
import { Excalidraw } from 'tiptap/core/extensions/excalidraw';
|
||||||
import { Flow } from 'tiptap/core/extensions/flow';
|
import { Flow } from 'tiptap/core/extensions/flow';
|
||||||
import { HardBreak } from 'tiptap/core/extensions/hard-break';
|
import { HardBreak } from 'tiptap/core/extensions/hard-break';
|
||||||
import { Heading } from 'tiptap/core/extensions/heading';
|
import { Heading } from 'tiptap/core/extensions/heading';
|
||||||
|
@ -127,6 +128,7 @@ const SerializerConfig = {
|
||||||
[Countdown.name]: renderCustomContainer('countdown'),
|
[Countdown.name]: renderCustomContainer('countdown'),
|
||||||
[DocumentChildren.name]: renderCustomContainer('documentChildren'),
|
[DocumentChildren.name]: renderCustomContainer('documentChildren'),
|
||||||
[DocumentReference.name]: renderCustomContainer('documentReference'),
|
[DocumentReference.name]: renderCustomContainer('documentReference'),
|
||||||
|
[Excalidraw.name]: renderCustomContainer('excalidraw'),
|
||||||
[Flow.name]: renderCustomContainer('flow'),
|
[Flow.name]: renderCustomContainer('flow'),
|
||||||
[HardBreak.name]: renderHardBreak,
|
[HardBreak.name]: renderHardBreak,
|
||||||
[Heading.name]: defaultMarkdownSerializer.nodes.heading,
|
[Heading.name]: defaultMarkdownSerializer.nodes.heading,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { safeJSONParse } from 'helpers/json';
|
import { safeJSONParse } from 'helpers/json';
|
||||||
|
import { Node } from 'prosemirror-state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 JSON 转为字符串
|
* 将 JSON 转为字符串
|
||||||
|
@ -89,3 +90,32 @@ export const getDatasetAttribute =
|
||||||
const toNumber = parseInt(value);
|
const toNumber = parseInt(value);
|
||||||
return toNumber !== toNumber ? value : toNumber; // 避免 NaN
|
return toNumber !== toNumber ? value : toNumber; // 避免 NaN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将节点属性转换为 dataset
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const nodeAttrsToDataset = (node: Node) => {
|
||||||
|
const { attrs } = node;
|
||||||
|
|
||||||
|
return Object.keys(attrs).reduce((accu, key) => {
|
||||||
|
const value = attrs[key];
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return accu;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encodeValue = '';
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
encodeValue = jsonToStr(value);
|
||||||
|
} else {
|
||||||
|
encodeValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
accu[key] = encodeValue;
|
||||||
|
|
||||||
|
return accu;
|
||||||
|
}, Object.create(null));
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue