From 187cdaf17cc8921502c62055d8ca777a21e31506 Mon Sep 17 00:00:00 2001
From: fantasticit
`;
+ };
+}
+
+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