mirror of https://github.com/fantasticit/think.git
feat: improve copy paste
This commit is contained in:
parent
187cdaf17c
commit
9ddab5a134
|
@ -1,5 +1,5 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
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 { Popover, TextArea, Typography, Space } from '@douyinfe/semi-ui';
|
||||||
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
||||||
import katex from 'katex';
|
import katex from 'katex';
|
||||||
|
@ -10,6 +10,9 @@ const { Text } = Typography;
|
||||||
export const KatexWrapper = ({ editor, node, updateAttributes }) => {
|
export const KatexWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const isEditable = editor.isEditable;
|
const isEditable = editor.isEditable;
|
||||||
const { text } = node.attrs;
|
const { text } = node.attrs;
|
||||||
|
|
||||||
|
console.log(node.attrs);
|
||||||
|
|
||||||
const formatText = useMemo(() => {
|
const formatText = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
return katex.renderToString(`${text}`);
|
return katex.renderToString(`${text}`);
|
||||||
|
@ -24,8 +27,12 @@ export const KatexWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
<span contentEditable={false}>点击输入公式</span>
|
<span contentEditable={false}>点击输入公式</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// updateAttributes(node.attrs);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper as="div" className={styles.wrap} contentEditable={false}>
|
<NodeViewWrapper as="span" className={styles.wrap} contentEditable={false}>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Popover
|
<Popover
|
||||||
showArrow
|
showArrow
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { BannerWrapper } from '../components/banner';
|
import { BannerWrapper } from '../components/banner';
|
||||||
import { typesAvailable } from '../services/markdown/markdownBanner';
|
import { typesAvailable } from '../services/markdown/markdownToHTML/markdownBanner';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
|
|
|
@ -19,18 +19,16 @@ export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {},
|
||||||
class: 'hr-line',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [{ tag: 'div[class=hr-line]' }];
|
return [{ tag: 'hr' }];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['hr', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
|
|
|
@ -14,26 +14,36 @@ export const KatexInputRegex = /^\$\$(.+)?\$\$$/;
|
||||||
|
|
||||||
export const Katex = Node.create({
|
export const Katex = Node.create({
|
||||||
name: 'katex',
|
name: 'katex',
|
||||||
group: 'block',
|
group: 'inline',
|
||||||
defining: true,
|
inline: true,
|
||||||
draggable: true,
|
|
||||||
selectable: true,
|
selectable: true,
|
||||||
atom: true,
|
atom: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'katex',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
text: {
|
text: {
|
||||||
default: '',
|
default: '',
|
||||||
|
parseHTML: (element) => {
|
||||||
|
return element.getAttribute('data-text');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [{ tag: 'div[data-type=katex]' }];
|
return [{ tag: 'span.katex' }];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ['div', mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes)];
|
return ['span', mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Extension } from '@tiptap/core';
|
import { Extension } from '@tiptap/core';
|
||||||
import { Plugin, PluginKey } from 'prosemirror-state';
|
import { Plugin, PluginKey } from 'prosemirror-state';
|
||||||
import { markdownSerializer } from '../services/markdown';
|
import { markdownSerializer } from '../services/markdown/serializer';
|
||||||
import { EXTENSION_PRIORITY_HIGHEST } from '../constants';
|
import { EXTENSION_PRIORITY_HIGHEST } from '../constants';
|
||||||
import { handleFileEvent } from '../services/upload';
|
import { handleFileEvent } from '../services/upload';
|
||||||
import { isInCode, LANGUAGES } from '../services/code';
|
import { isInCode, LANGUAGES } from '../services/code';
|
||||||
|
@ -63,16 +63,14 @@ export const Paste = Extension.create({
|
||||||
// 处理 markdown
|
// 处理 markdown
|
||||||
if (isMarkdown(text) || html.length === 0 || pasteCodeLanguage === 'markdown') {
|
if (isMarkdown(text) || html.length === 0 || pasteCodeLanguage === 'markdown') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// FIXME: 由于 title schema 的存在导致反序列化必有 title 节点存在
|
|
||||||
const firstNode = view.props.state.doc.content.firstChild;
|
const firstNode = view.props.state.doc.content.firstChild;
|
||||||
const hasTitle = isTitleNode(firstNode) && firstNode.content.size > 0;
|
const hasTitle = isTitleNode(firstNode) && firstNode.content.size > 0;
|
||||||
let schema = view.props.state.schema;
|
const schema = view.props.state.schema;
|
||||||
const doc = markdownSerializer.deserialize({
|
const doc = markdownSerializer.markdownToProsemirror({
|
||||||
schema,
|
schema,
|
||||||
content: normalizePastedMarkdown(text),
|
content: normalizePastedMarkdown(text),
|
||||||
hasTitle,
|
hasTitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const transaction = view.state.tr.insert(view.state.selection.head, view.state.schema.nodeFromJSON(doc));
|
const transaction = view.state.tr.insert(view.state.selection.head, view.state.schema.nodeFromJSON(doc));
|
||||||
view.dispatch(transaction);
|
view.dispatch(transaction);
|
||||||
|
@ -113,7 +111,7 @@ export const Paste = Extension.create({
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const content = markdownSerializer.serialize({
|
const content = markdownSerializer.proseMirrorToMarkdown({
|
||||||
schema: this.editor.schema,
|
schema: this.editor.schema,
|
||||||
content: doc,
|
content: doc,
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,6 +21,8 @@ export const Paragraph = ({ editor }) => {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
console.log(getCurrentCaretTitle(editor));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
disabled={isTitleActive(editor)}
|
disabled={isTitleActive(editor)}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
> 将 HTML 转换成 prosemirror node
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Renderer } from './renderer';
|
||||||
|
|
||||||
|
const renderer = new Renderer();
|
||||||
|
|
||||||
|
export const htmlToPromsemirror = (body) => {
|
||||||
|
return renderer.render(body);
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import { Mark } from './Mark';
|
import { Mark } from './mark';
|
||||||
|
|
||||||
export class Bold extends Mark {
|
export class Bold extends Mark {
|
||||||
matching() {
|
matching() {
|
|
@ -1,4 +1,5 @@
|
||||||
import { Mark } from './Mark';
|
import { Mark } from './mark';
|
||||||
|
|
||||||
export class Code extends Mark {
|
export class Code extends Mark {
|
||||||
matching() {
|
matching() {
|
||||||
if (this.DOMNode.parentNode.nodeName === 'PRE') {
|
if (this.DOMNode.parentNode.nodeName === 'PRE') {
|
|
@ -1,4 +1,4 @@
|
||||||
import { Mark } from './Mark';
|
import { Mark } from './mark';
|
||||||
export class Italic extends Mark {
|
export class Italic extends Mark {
|
||||||
matching() {
|
matching() {
|
||||||
return this.DOMNode.nodeName === 'EM';
|
return this.DOMNode.nodeName === 'EM';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Mark } from './Mark';
|
import { Mark } from './mark';
|
||||||
export class Link extends Mark {
|
export class Link extends Mark {
|
||||||
matching() {
|
matching() {
|
||||||
return this.DOMNode.nodeName === 'A';
|
return this.DOMNode.nodeName === 'A';
|
|
@ -1,4 +1,7 @@
|
||||||
export class Mark {
|
export class Mark {
|
||||||
|
type: string;
|
||||||
|
DOMNode: HTMLElement;
|
||||||
|
|
||||||
constructor(DomNode) {
|
constructor(DomNode) {
|
||||||
this.type = 'mark';
|
this.type = 'mark';
|
||||||
this.DOMNode = DomNode;
|
this.DOMNode = DomNode;
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class Blockquote extends Node {
|
export class Blockquote extends Node {
|
||||||
type = 'blockquote';
|
type = 'blockquote';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class BulletList extends Node {
|
export class BulletList extends Node {
|
||||||
type = 'bulletList';
|
type = 'bulletList';
|
||||||
|
@ -6,10 +6,4 @@ export class BulletList extends Node {
|
||||||
matching() {
|
matching() {
|
||||||
return this.DOMNode.nodeName === 'UL';
|
return this.DOMNode.nodeName === 'UL';
|
||||||
}
|
}
|
||||||
|
|
||||||
// data() {
|
|
||||||
// return {
|
|
||||||
// type: 'bulletList',
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Node } from './node';
|
||||||
|
|
||||||
|
export class CodeBlock extends Node {
|
||||||
|
type = 'codeBlock';
|
||||||
|
|
||||||
|
matching() {
|
||||||
|
return this.DOMNode.nodeName === 'CODE' && this.DOMNode.parentNode.nodeName === 'PRE';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class CodeBlockWrapper extends Node {
|
export class CodeBlockWrapper extends Node {
|
||||||
matching() {
|
matching() {
|
||||||
return this.DOMNode.nodeName === 'PRE';
|
return this.DOMNode.nodeName === 'PRE';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class HardBreak extends Node {
|
export class HardBreak extends Node {
|
||||||
type = 'hardBreak';
|
type = 'hardBreak';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
export class Heading extends Node {
|
export class Heading extends Node {
|
||||||
type = 'heading';
|
type = 'heading';
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ export class Heading extends Node {
|
||||||
return Boolean(this.getLevel());
|
return Boolean(this.getLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
// data() {
|
data() {
|
||||||
// return {
|
return {
|
||||||
// type: 'heading',
|
type: 'heading',
|
||||||
// attrs: {
|
attrs: {
|
||||||
// level: this.getLevel(),
|
level: this.getLevel(),
|
||||||
// },
|
},
|
||||||
// };
|
};
|
||||||
// }
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Node } from './node';
|
||||||
|
|
||||||
|
export class HorizontalRule extends Node {
|
||||||
|
type = 'horizontalRule';
|
||||||
|
|
||||||
|
matching() {
|
||||||
|
return this.DOMNode.nodeName === 'HR';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class Image extends Node {
|
export class Image extends Node {
|
||||||
type = 'image';
|
type = 'image';
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Node } from './node';
|
||||||
|
|
||||||
|
export class Katex extends Node {
|
||||||
|
type = 'katex';
|
||||||
|
|
||||||
|
matching() {
|
||||||
|
return this.DOMNode.nodeName === 'SPAN' && this.DOMNode.classList.contains('katex');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Node } from './node';
|
||||||
|
|
||||||
|
export class ListItem extends Node {
|
||||||
|
constructor(DomNode) {
|
||||||
|
super(DomNode);
|
||||||
|
this.wrapper = {
|
||||||
|
type: 'paragraph',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type = 'listItem';
|
||||||
|
|
||||||
|
matching() {
|
||||||
|
return this.DOMNode.nodeName === 'LI';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { getAttributes } from '../utils';
|
import { getAttributes } from '../utils';
|
||||||
|
|
||||||
export class Node {
|
export class Node {
|
||||||
wrapper: null;
|
wrapper: unknown;
|
||||||
type = 'node';
|
type = 'node';
|
||||||
DOMNode: HTMLElement;
|
DOMNode: HTMLElement;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export class Node {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data() {
|
data(): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
type: this.type,
|
type: this.type,
|
||||||
attrs: getAttributes(this.type, this.DOMNode),
|
attrs: getAttributes(this.type, this.DOMNode),
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class OrderedList extends Node {
|
export class OrderedList extends Node {
|
||||||
type = 'orderedList';
|
type = 'orderedList';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
export class Paragraph extends Node {
|
export class Paragraph extends Node {
|
||||||
type = 'paragraph';
|
type = 'paragraph';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class Table extends Node {
|
export class Table extends Node {
|
||||||
type = 'table';
|
type = 'table';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class TableCell extends Node {
|
export class TableCell extends Node {
|
||||||
type = 'tableCell';
|
type = 'tableCell';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class TableHeader extends Node {
|
export class TableHeader extends Node {
|
||||||
type = 'tableHeader';
|
type = 'tableHeader';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class TableRow extends Node {
|
export class TableRow extends Node {
|
||||||
type = 'tableRow';
|
type = 'tableRow';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class TaskList extends Node {
|
export class TaskList extends Node {
|
||||||
type = 'taskList';
|
type = 'taskList';
|
|
@ -1,4 +1,4 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class TaskListItem extends Node {
|
export class TaskListItem extends Node {
|
||||||
type = 'taskItem';
|
type = 'taskItem';
|
|
@ -1,4 +1,5 @@
|
||||||
import { Node } from './Node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class Text extends Node {
|
export class Text extends Node {
|
||||||
matching() {
|
matching() {
|
||||||
return this.DOMNode.nodeName === '#text';
|
return this.DOMNode.nodeName === '#text';
|
|
@ -1,29 +1,42 @@
|
||||||
import { BulletList } from './Nodes/BulletList';
|
// nodes
|
||||||
import { CodeBlock } from './Nodes/CodeBlock';
|
import { CodeBlock } from './nodes/codeBlock';
|
||||||
import { CodeBlockWrapper } from './Nodes/CodeBlockWrapper';
|
import { CodeBlockWrapper } from './nodes/codeBlockWrapper';
|
||||||
import { HardBreak } from './Nodes/HardBreak';
|
import { HardBreak } from './nodes/hardBreak';
|
||||||
import { Heading } from './Nodes/Heading';
|
import { Heading } from './nodes/heading';
|
||||||
import { Image } from './Nodes/Image';
|
import { Image } from './nodes/image';
|
||||||
import { ListItem } from './Nodes/ListItem';
|
import { HorizontalRule } from './nodes/horizontalRule';
|
||||||
import { OrderedList } from './Nodes/OrderedList';
|
import { Blockquote } from './nodes/blockQuote';
|
||||||
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 { Katex } from './nodes/katex';
|
||||||
import { TableRow } from './Nodes/tableRow';
|
import { Paragraph } from './nodes/paragraph';
|
||||||
import { TableCell } from './Nodes/tableCell';
|
import { Text } from './nodes/text';
|
||||||
|
|
||||||
import { TaskList } from './Nodes/taskList';
|
// 表格
|
||||||
import { TaskListItem } from './Nodes/taskListItem';
|
import { Table } from './nodes/table';
|
||||||
|
import { TableHeader } from './nodes/tableHeader';
|
||||||
|
import { TableRow } from './nodes/tableRow';
|
||||||
|
import { TableCell } from './nodes/tableCell';
|
||||||
|
|
||||||
import { Bold } from './Marks/Bold';
|
// 列表
|
||||||
import { Code } from './Marks/Code';
|
import { TaskList } from './nodes/taskList';
|
||||||
import { Italic } from './Marks/Italic';
|
import { TaskListItem } from './nodes/taskListItem';
|
||||||
import { Link } from './Marks/Link';
|
import { ListItem } from './nodes/listItem';
|
||||||
|
import { OrderedList } from './nodes/orderedList';
|
||||||
|
import { BulletList } from './nodes/bulletList';
|
||||||
|
|
||||||
|
// marks
|
||||||
|
import { Bold } from './marks/bold';
|
||||||
|
import { Code } from './marks/code';
|
||||||
|
import { Italic } from './marks/italic';
|
||||||
|
import { Link } from './marks/link';
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
|
document: HTMLElement;
|
||||||
|
nodes = [];
|
||||||
|
marks = [];
|
||||||
|
storedMarks = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.document = undefined;
|
this.document = undefined;
|
||||||
this.storedMarks = [];
|
this.storedMarks = [];
|
||||||
|
@ -34,7 +47,11 @@ export class Renderer {
|
||||||
HardBreak,
|
HardBreak,
|
||||||
Heading,
|
Heading,
|
||||||
Image,
|
Image,
|
||||||
|
HorizontalRule,
|
||||||
|
|
||||||
|
Katex,
|
||||||
Paragraph,
|
Paragraph,
|
||||||
|
|
||||||
Text,
|
Text,
|
||||||
Blockquote,
|
Blockquote,
|
||||||
|
|
||||||
|
@ -59,22 +76,15 @@ export class Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
stripWhitespace(value) {
|
stripWhitespace(value) {
|
||||||
// return minify(value, {
|
|
||||||
// collapseWhitespace: true,
|
|
||||||
// });
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDocumentBody() {
|
getDocumentBody() {
|
||||||
return this.document;
|
return this.document;
|
||||||
// return this.document.window.document.querySelector('body');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(value) {
|
render(value) {
|
||||||
this.setDocument(value);
|
this.setDocument(value);
|
||||||
|
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
const content = this.renderChildren(this.getDocumentBody());
|
const content = this.renderChildren(this.getDocumentBody());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -151,7 +161,6 @@ export class Renderer {
|
||||||
for (let i in classes) {
|
for (let i in classes) {
|
||||||
const Class = classes[i];
|
const Class = classes[i];
|
||||||
const instance = new Class(node);
|
const instance = new Class(node);
|
||||||
// console.log(node);
|
|
||||||
if (instance.matching()) {
|
if (instance.matching()) {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { BaseKit } from '../../../basekit';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 tiptap extension 的配置从 DOM 节点上获取属性值
|
||||||
|
* @param element
|
||||||
|
* @param ret
|
||||||
|
* @param config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getAttribute = (
|
||||||
|
element: HTMLElement,
|
||||||
|
ret = {},
|
||||||
|
config: Record<string, { default: unknown; parseHTML?: (element: HTMLElement) => Record<string, unknown> }>
|
||||||
|
) => {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAttributes = (name: string, element: HTMLElement): Record<string, unknown> => {
|
||||||
|
const ext = BaseKit.find((ext) => ext.name === name);
|
||||||
|
|
||||||
|
if (!ext) return {};
|
||||||
|
|
||||||
|
let { config } = ext;
|
||||||
|
let parent = ext && ext.parent;
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
while (parent.parent) {
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
config = parent.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) return {};
|
||||||
|
|
||||||
|
const { addGlobalAttributes, addAttributes } = config;
|
||||||
|
const attrs = {};
|
||||||
|
|
||||||
|
if (addGlobalAttributes) {
|
||||||
|
getAttribute(element, attrs, addGlobalAttributes.call(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addAttributes) {
|
||||||
|
getAttribute(element, attrs, addAttributes.call(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
};
|
|
@ -1,21 +1,20 @@
|
||||||
|
import { sanitize } from 'dompurify';
|
||||||
import markdownit from 'markdown-it';
|
import markdownit from 'markdown-it';
|
||||||
import sub from 'markdown-it-sub';
|
import sub from 'markdown-it-sub';
|
||||||
import sup from 'markdown-it-sup';
|
import sup from 'markdown-it-sup';
|
||||||
import footnote from 'markdown-it-footnote';
|
|
||||||
import anchor from 'markdown-it-anchor';
|
import anchor from 'markdown-it-anchor';
|
||||||
import emoji from 'markdown-it-emoji';
|
import emoji from 'markdown-it-emoji';
|
||||||
import katex from '@traptitech/markdown-it-katex';
|
import katex from './markdownKatex';
|
||||||
import tasklist from './markdownTaskList';
|
import tasklist from './markdownTaskList';
|
||||||
import splitMixedLists from './markedownSplitMixedList';
|
import splitMixedLists from './markedownSplitMixedList';
|
||||||
import markdownUnderline from './markdownUnderline';
|
import markdownUnderline from './markdownUnderline';
|
||||||
import markdownBanner from './markdownBanner';
|
import markdownBanner from './markdownBanner';
|
||||||
import { markdownItTable } from './markdownTable';
|
import { markdownItTable } from './markdownTable';
|
||||||
|
|
||||||
export const markdown = markdownit('commonmark')
|
const markdown = markdownit('commonmark')
|
||||||
.enable('strikethrough')
|
.enable('strikethrough')
|
||||||
.use(sub)
|
.use(sub)
|
||||||
.use(sup)
|
.use(sup)
|
||||||
.use(footnote)
|
|
||||||
.use(anchor)
|
.use(anchor)
|
||||||
.use(tasklist)
|
.use(tasklist)
|
||||||
.use(splitMixedLists)
|
.use(splitMixedLists)
|
||||||
|
@ -25,4 +24,6 @@ export const markdown = markdownit('commonmark')
|
||||||
.use(emoji)
|
.use(emoji)
|
||||||
.use(katex);
|
.use(katex);
|
||||||
|
|
||||||
export * from './serializer';
|
export const markdownToHTML = (rawMarkdown) => {
|
||||||
|
return sanitize(markdown.render(rawMarkdown), {});
|
||||||
|
};
|
|
@ -0,0 +1,225 @@
|
||||||
|
// var katex = require('katex');
|
||||||
|
|
||||||
|
// Test if potential opening or closing delimieter
|
||||||
|
// Assumes that there is a "$" at state.src[pos]
|
||||||
|
function isValidDelim(state, pos) {
|
||||||
|
var prevChar,
|
||||||
|
nextChar,
|
||||||
|
max = state.posMax,
|
||||||
|
can_open = true,
|
||||||
|
can_close = true;
|
||||||
|
|
||||||
|
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
|
||||||
|
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
|
||||||
|
|
||||||
|
// Check non-whitespace conditions for opening and closing, and
|
||||||
|
// check that closing delimeter isn't followed by a number
|
||||||
|
if (
|
||||||
|
prevChar === 0x20 /* " " */ ||
|
||||||
|
prevChar === 0x09 /* \t */ ||
|
||||||
|
(nextChar >= 0x30 /* "0" */ && nextChar <= 0x39) /* "9" */
|
||||||
|
) {
|
||||||
|
can_close = false;
|
||||||
|
}
|
||||||
|
if (nextChar === 0x20 /* " " */ || nextChar === 0x09 /* \t */) {
|
||||||
|
can_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
can_open: can_open,
|
||||||
|
can_close: can_close,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function math_inline(state, silent) {
|
||||||
|
var start, match, token, res, pos, esc_count;
|
||||||
|
|
||||||
|
if (state.src[state.pos] !== '$') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = isValidDelim(state, state.pos);
|
||||||
|
if (!res.can_open) {
|
||||||
|
if (!silent) {
|
||||||
|
state.pending += '$';
|
||||||
|
}
|
||||||
|
state.pos += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check for and bypass all properly escaped delimieters
|
||||||
|
// This loop will assume that the first leading backtick can not
|
||||||
|
// be the first character in state.src, which is known since
|
||||||
|
// we have found an opening delimieter already.
|
||||||
|
start = state.pos + 1;
|
||||||
|
match = start;
|
||||||
|
while ((match = state.src.indexOf('$', match)) !== -1) {
|
||||||
|
// Found potential $, look for escapes, pos will point to
|
||||||
|
// first non escape when complete
|
||||||
|
pos = match - 1;
|
||||||
|
while (state.src[pos] === '\\') {
|
||||||
|
pos -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even number of escapes, potential closing delimiter found
|
||||||
|
if ((match - pos) % 2 == 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No closing delimter found. Consume $ and continue.
|
||||||
|
if (match === -1) {
|
||||||
|
if (!silent) {
|
||||||
|
state.pending += '$';
|
||||||
|
}
|
||||||
|
state.pos = start;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have empty content, ie: $$. Do not parse.
|
||||||
|
if (match - start === 0) {
|
||||||
|
if (!silent) {
|
||||||
|
state.pending += '$$';
|
||||||
|
}
|
||||||
|
state.pos = start + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid closing delimiter
|
||||||
|
res = isValidDelim(state, match);
|
||||||
|
if (!res.can_close) {
|
||||||
|
if (!silent) {
|
||||||
|
state.pending += '$';
|
||||||
|
}
|
||||||
|
state.pos = start;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
token = state.push('math_inline', 'math', 0);
|
||||||
|
token.markup = '$';
|
||||||
|
token.content = state.src.slice(start, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pos = match + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function math_block(state, start, end, silent) {
|
||||||
|
var firstLine,
|
||||||
|
lastLine,
|
||||||
|
next,
|
||||||
|
lastPos,
|
||||||
|
found = false,
|
||||||
|
token,
|
||||||
|
pos = state.bMarks[start] + state.tShift[start],
|
||||||
|
max = state.eMarks[start];
|
||||||
|
|
||||||
|
if (pos + 2 > max) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (state.src.slice(pos, pos + 2) !== '$$') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += 2;
|
||||||
|
firstLine = state.src.slice(pos, max);
|
||||||
|
|
||||||
|
if (silent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (firstLine.trim().slice(-2) === '$$') {
|
||||||
|
// Single line expression
|
||||||
|
firstLine = firstLine.trim().slice(0, -2);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (next = start; !found; ) {
|
||||||
|
next++;
|
||||||
|
|
||||||
|
if (next >= end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = state.bMarks[next] + state.tShift[next];
|
||||||
|
max = state.eMarks[next];
|
||||||
|
|
||||||
|
if (pos < max && state.tShift[next] < state.blkIndent) {
|
||||||
|
// non-empty line with negative indent should stop the list:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
|
||||||
|
lastPos = state.src.slice(0, max).lastIndexOf('$$');
|
||||||
|
lastLine = state.src.slice(pos, lastPos);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.line = next + 1;
|
||||||
|
|
||||||
|
token = state.push('math_block', 'math', 0);
|
||||||
|
token.block = true;
|
||||||
|
token.content =
|
||||||
|
(firstLine && firstLine.trim() ? firstLine + '\n' : '') +
|
||||||
|
state.getLines(start + 1, next, state.tShift[start], true) +
|
||||||
|
(lastLine && lastLine.trim() ? lastLine : '');
|
||||||
|
token.map = [start, state.line];
|
||||||
|
token.markup = '$$';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(unsafe) {
|
||||||
|
return unsafe
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
var katex = {
|
||||||
|
renderToString: (s, opts) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function math_plugin(md, options) {
|
||||||
|
// Default options
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
if (options.katex) {
|
||||||
|
katex = options.katex;
|
||||||
|
}
|
||||||
|
if (!options.blockClass) {
|
||||||
|
options.blockClass = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
var inlineRenderer = function (tokens, idx) {
|
||||||
|
return katexBlock(tokens[idx].content);
|
||||||
|
};
|
||||||
|
|
||||||
|
var katexBlock = function (latex) {
|
||||||
|
options.displayMode = true;
|
||||||
|
try {
|
||||||
|
return `<span class="katex ${options.blockClass}" data-text="${katex.renderToString(latex, options)}"></span>`;
|
||||||
|
} catch (error) {
|
||||||
|
if (options.throwOnError) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
return `<span class='katex katex-error ${options.blockClass}' data-error='${escapeHtml(
|
||||||
|
error.toString()
|
||||||
|
)}' data-text="${escapeHtml(latex)}"></span>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var blockRenderer = function (tokens, idx) {
|
||||||
|
return katexBlock(tokens[idx].content) + '\n';
|
||||||
|
};
|
||||||
|
|
||||||
|
md.inline.ruler.after('escape', 'math_inline', math_inline);
|
||||||
|
md.block.ruler.after('blockquote', 'math_block', math_block, {
|
||||||
|
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||||
|
});
|
||||||
|
md.renderer.rules.math_inline = inlineRenderer;
|
||||||
|
md.renderer.rules.math_block = blockRenderer;
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ export default function splitMixedLists(md) {
|
||||||
|
|
||||||
for (let i = 0; i < tokens.length; i++) {
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
const token = tokens[i];
|
const token = tokens[i];
|
||||||
if (token.attrGet('class') !== 'contains-task-list') {
|
if (token.attrGet('class') !== 'task-list') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const firstChild = tokens[i + 1];
|
const firstChild = tokens[i + 1];
|
|
@ -1,7 +1,4 @@
|
||||||
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
|
|
||||||
import { sanitize } from 'dompurify';
|
|
||||||
import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
||||||
import { markdown } from '.';
|
|
||||||
import { Attachment } from '../../extensions/attachment';
|
import { Attachment } from '../../extensions/attachment';
|
||||||
import { Banner } from '../../extensions/banner';
|
import { Banner } from '../../extensions/banner';
|
||||||
import { Blockquote } from '../../extensions/blockquote';
|
import { Blockquote } from '../../extensions/blockquote';
|
||||||
|
@ -48,12 +45,8 @@ import {
|
||||||
renderImage,
|
renderImage,
|
||||||
renderHTMLNode,
|
renderHTMLNode,
|
||||||
} from './serializerHelpers';
|
} from './serializerHelpers';
|
||||||
|
import { htmlToPromsemirror } from './htmlToProsemirror';
|
||||||
// import * as HTML/ from 'html-to-prosemirror'
|
import { markdownToHTML } from './markdownToHTML';
|
||||||
|
|
||||||
import { Renderer } from './src/Renderer';
|
|
||||||
|
|
||||||
const renderer = new Renderer();
|
|
||||||
|
|
||||||
const defaultSerializerConfig = {
|
const defaultSerializerConfig = {
|
||||||
marks: {
|
marks: {
|
||||||
|
@ -188,48 +181,41 @@ const defaultSerializerConfig = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderMarkdown = (rawMarkdown) => {
|
|
||||||
return sanitize(markdown.render(rawMarkdown), {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createMarkdownSerializer = () => ({
|
const createMarkdownSerializer = () => ({
|
||||||
// 将 markdown 字符串转换为 ProseMirror JSONDocument
|
// 将 markdown 字符串转换为 ProseMirror JSONDocument
|
||||||
deserialize: ({ schema, content, hasTitle }) => {
|
markdownToProsemirror: ({ schema, content, hasTitle }) => {
|
||||||
const html = renderMarkdown(content);
|
const html = markdownToHTML(content);
|
||||||
if (!html) return null;
|
if (!html) return null;
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const { body } = parser.parseFromString(html, 'text/html');
|
const { body } = parser.parseFromString(html, 'text/html');
|
||||||
body.append(document.createComment(content));
|
body.append(document.createComment(content));
|
||||||
const json = renderer.render(body);
|
const json = htmlToPromsemirror(body);
|
||||||
|
|
||||||
console.log({ hasTitle, json, body });
|
console.log({ hasTitle, json, body });
|
||||||
|
|
||||||
|
// 设置标题
|
||||||
if (!hasTitle) {
|
if (!hasTitle) {
|
||||||
const firstNode = json.content[0];
|
const firstNode = json.content[0];
|
||||||
|
|
||||||
if (firstNode) {
|
if (firstNode) {
|
||||||
if (firstNode.type === 'heading') {
|
if (firstNode.type === 'heading' || firstNode.type === 'paragraph') {
|
||||||
firstNode.type = 'title';
|
firstNode.type = 'title';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes = json.content;
|
const nodes = json.content;
|
||||||
|
|
||||||
const result = { type: 'doc', content: [] };
|
const result = { type: 'doc', content: [] };
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; ) {
|
for (let i = 0; i < nodes.length; ) {
|
||||||
const node = nodes[i];
|
const node = nodes[i];
|
||||||
|
// 目的:合并成 promirror 需要的 table 格式
|
||||||
if (node.type === 'tableRow') {
|
if (node.type === 'tableRow') {
|
||||||
const nextNode = nodes[i + 1];
|
const nextNode = nodes[i + 1];
|
||||||
|
|
||||||
if (nextNode && nextNode.type === 'table') {
|
if (nextNode && nextNode.type === 'table') {
|
||||||
nextNode.content.unshift(node);
|
nextNode.content.unshift(node);
|
||||||
result.content.push(nextNode);
|
result.content.push(nextNode);
|
||||||
i += 2;
|
i += 2;
|
||||||
} else {
|
|
||||||
// 出错了!!
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.content.push(node);
|
result.content.push(node);
|
||||||
|
@ -238,13 +224,10 @@ const createMarkdownSerializer = () => ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// const state = ProseMirrorDOMParser.fromSchema(schema).parse(body);
|
|
||||||
// return state.toJSON();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 将 ProseMirror JSONDocument 转换为 markdown 字符串
|
// 将 ProseMirror JSONDocument 转换为 markdown 字符串
|
||||||
serialize: ({ schema, content }) => {
|
proseMirrorToMarkdown: ({ schema, content }) => {
|
||||||
const serializer = new ProseMirrorMarkdownSerializer(
|
const serializer = new ProseMirrorMarkdownSerializer(
|
||||||
{
|
{
|
||||||
...defaultSerializerConfig.nodes,
|
...defaultSerializerConfig.nodes,
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
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',
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
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;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { BaseKit } from '../../../basekit';
|
|
||||||
|
|
||||||
export const getAttributes = (name: string, element: HTMLElement): Record<string, unknown> => {
|
|
||||||
const ext = BaseKit.find((ext) => ext.name === name);
|
|
||||||
|
|
||||||
const run = (
|
|
||||||
ret = {},
|
|
||||||
config: Record<string, { default: unknown; parseHTML?: (element: HTMLElement) => Record<string, unknown> }>
|
|
||||||
) => {
|
|
||||||
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;
|
|
||||||
};
|
|
|
@ -14,16 +14,15 @@ export const useTheme = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const body = document.body;
|
// const body = document.body;
|
||||||
if (theme === 'dark') {
|
// if (theme === 'dark') {
|
||||||
body.setAttribute('theme-mode', 'dark');
|
// body.setAttribute('theme-mode', 'dark');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
// if (theme === 'light') {
|
||||||
if (theme === 'light') {
|
// body.setAttribute('theme-mode', 'light');
|
||||||
body.setAttribute('theme-mode', 'light');
|
// return;
|
||||||
return;
|
// }
|
||||||
}
|
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -54,8 +54,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hr-line {
|
hr {
|
||||||
width: 100%;
|
border: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background: var(--semi-color-border);
|
background: var(--semi-color-border);
|
||||||
margin: 18px 0;
|
margin: 18px 0;
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class DocumentEntity {
|
||||||
@Column({ type: 'text', comment: '文档内容' })
|
@Column({ type: 'text', comment: '文档内容' })
|
||||||
public content: string;
|
public content: string;
|
||||||
|
|
||||||
@Column({ type: 'blob', comment: '文档内容' })
|
@Column({ type: 'longblob', comment: '文档内容' })
|
||||||
public state: Uint8Array;
|
public state: Uint8Array;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
|
|
Loading…
Reference in New Issue