feat: improve copy paste

This commit is contained in:
fantasticit 2022-03-22 20:31:21 +08:00
parent 187cdaf17c
commit 9ddab5a134
50 changed files with 473 additions and 227 deletions

View File

@ -1,5 +1,5 @@
.wrap {
margin: 8px 0;
display: flex;
display: inline-flex;
justify-content: center;
}

View File

@ -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 }) => {
<span contentEditable={false}></span>
);
// useEffect(() => {
// updateAttributes(node.attrs);
// }, []);
return (
<NodeViewWrapper as="div" className={styles.wrap} contentEditable={false}>
<NodeViewWrapper as="span" className={styles.wrap} contentEditable={false}>
{isEditable ? (
<Popover
showArrow

View File

@ -1,7 +1,7 @@
import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react';
import { BannerWrapper } from '../components/banner';
import { typesAvailable } from '../services/markdown/markdownBanner';
import { typesAvailable } from '../services/markdown/markdownToHTML/markdownBanner';
declare module '@tiptap/core' {
interface Commands<ReturnType> {

View File

@ -19,18 +19,16 @@ export const HorizontalRule = Node.create<HorizontalRuleOptions>({
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() {

View File

@ -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

View File

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

View File

@ -21,6 +21,8 @@ export const Paragraph = ({ editor }) => {
}
}, []);
console.log(getCurrentCaretTitle(editor));
return (
<Select
disabled={isTitleActive(editor)}

View File

@ -0,0 +1 @@
> 将 HTML 转换成 prosemirror node

View File

@ -0,0 +1,7 @@
import { Renderer } from './renderer';
const renderer = new Renderer();
export const htmlToPromsemirror = (body) => {
return renderer.render(body);
};

View File

@ -1,4 +1,4 @@
import { Mark } from './Mark';
import { Mark } from './mark';
export class Bold extends Mark {
matching() {

View File

@ -1,4 +1,5 @@
import { Mark } from './Mark';
import { Mark } from './mark';
export class Code extends Mark {
matching() {
if (this.DOMNode.parentNode.nodeName === 'PRE') {

View File

@ -1,4 +1,4 @@
import { Mark } from './Mark';
import { Mark } from './mark';
export class Italic extends Mark {
matching() {
return this.DOMNode.nodeName === 'EM';

View File

@ -1,4 +1,4 @@
import { Mark } from './Mark';
import { Mark } from './mark';
export class Link extends Mark {
matching() {
return this.DOMNode.nodeName === 'A';

View File

@ -1,4 +1,7 @@
export class Mark {
type: string;
DOMNode: HTMLElement;
constructor(DomNode) {
this.type = 'mark';
this.DOMNode = DomNode;

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class Blockquote extends Node {
type = 'blockquote';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class BulletList extends Node {
type = 'bulletList';
@ -6,10 +6,4 @@ export class BulletList extends Node {
matching() {
return this.DOMNode.nodeName === 'UL';
}
// data() {
// return {
// type: 'bulletList',
// };
// }
}

View File

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

View File

@ -1,4 +1,5 @@
import { Node } from './Node';
import { Node } from './node';
export class CodeBlockWrapper extends Node {
matching() {
return this.DOMNode.nodeName === 'PRE';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class HardBreak extends Node {
type = 'hardBreak';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class Heading extends Node {
type = 'heading';
@ -11,12 +11,12 @@ export class Heading extends Node {
return Boolean(this.getLevel());
}
// data() {
// return {
// type: 'heading',
// attrs: {
// level: this.getLevel(),
// },
// };
// }
data() {
return {
type: 'heading',
attrs: {
level: this.getLevel(),
},
};
}
}

View File

@ -0,0 +1,9 @@
import { Node } from './node';
export class HorizontalRule extends Node {
type = 'horizontalRule';
matching() {
return this.DOMNode.nodeName === 'HR';
}
}

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class Image extends Node {
type = 'image';

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { getAttributes } from '../utils';
export class Node {
wrapper: null;
wrapper: unknown;
type = 'node';
DOMNode: HTMLElement;
@ -14,7 +14,7 @@ export class Node {
return false;
}
data() {
data(): Record<string, unknown> {
return {
type: this.type,
attrs: getAttributes(this.type, this.DOMNode),

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class OrderedList extends Node {
type = 'orderedList';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class Paragraph extends Node {
type = 'paragraph';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class Table extends Node {
type = 'table';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class TableCell extends Node {
type = 'tableCell';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class TableHeader extends Node {
type = 'tableHeader';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class TableRow extends Node {
type = 'tableRow';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class TaskList extends Node {
type = 'taskList';

View File

@ -1,4 +1,4 @@
import { Node } from './Node';
import { Node } from './node';
export class TaskListItem extends Node {
type = 'taskItem';

View File

@ -1,4 +1,5 @@
import { Node } from './Node';
import { Node } from './node';
export class Text extends Node {
matching() {
return this.DOMNode.nodeName === '#text';

View File

@ -1,29 +1,42 @@
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';
// nodes
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 { HorizontalRule } from './nodes/horizontalRule';
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 { Katex } from './nodes/katex';
import { Paragraph } from './nodes/paragraph';
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 { Italic } from './Marks/Italic';
import { Link } from './Marks/Link';
// 列表
import { TaskList } from './nodes/taskList';
import { TaskListItem } from './nodes/taskListItem';
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 {
document: HTMLElement;
nodes = [];
marks = [];
storedMarks = [];
constructor() {
this.document = undefined;
this.storedMarks = [];
@ -34,7 +47,11 @@ export class Renderer {
HardBreak,
Heading,
Image,
HorizontalRule,
Katex,
Paragraph,
Text,
Blockquote,
@ -59,22 +76,15 @@ export class Renderer {
}
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 {
@ -151,7 +161,6 @@ export class Renderer {
for (let i in classes) {
const Class = classes[i];
const instance = new Class(node);
// console.log(node);
if (instance.matching()) {
return instance;
}

View File

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

View File

@ -1,21 +1,20 @@
import { sanitize } from 'dompurify';
import markdownit from 'markdown-it';
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 emoji from 'markdown-it-emoji';
import katex from '@traptitech/markdown-it-katex';
import katex from './markdownKatex';
import tasklist from './markdownTaskList';
import splitMixedLists from './markedownSplitMixedList';
import markdownUnderline from './markdownUnderline';
import markdownBanner from './markdownBanner';
import { markdownItTable } from './markdownTable';
export const markdown = markdownit('commonmark')
const markdown = markdownit('commonmark')
.enable('strikethrough')
.use(sub)
.use(sup)
.use(footnote)
.use(anchor)
.use(tasklist)
.use(splitMixedLists)
@ -25,4 +24,6 @@ export const markdown = markdownit('commonmark')
.use(emoji)
.use(katex);
export * from './serializer';
export const markdownToHTML = (rawMarkdown) => {
return sanitize(markdown.render(rawMarkdown), {});
};

View File

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
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;
}

View File

@ -7,7 +7,7 @@ export default function splitMixedLists(md) {
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token.attrGet('class') !== 'contains-task-list') {
if (token.attrGet('class') !== 'task-list') {
continue;
}
const firstChild = tokens[i + 1];

View File

@ -1,7 +1,4 @@
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
import { sanitize } from 'dompurify';
import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { markdown } from '.';
import { Attachment } from '../../extensions/attachment';
import { Banner } from '../../extensions/banner';
import { Blockquote } from '../../extensions/blockquote';
@ -48,12 +45,8 @@ import {
renderImage,
renderHTMLNode,
} from './serializerHelpers';
// import * as HTML/ from 'html-to-prosemirror'
import { Renderer } from './src/Renderer';
const renderer = new Renderer();
import { htmlToPromsemirror } from './htmlToProsemirror';
import { markdownToHTML } from './markdownToHTML';
const defaultSerializerConfig = {
marks: {
@ -188,48 +181,41 @@ const defaultSerializerConfig = {
},
};
const renderMarkdown = (rawMarkdown) => {
return sanitize(markdown.render(rawMarkdown), {});
};
const createMarkdownSerializer = () => ({
// 将 markdown 字符串转换为 ProseMirror JSONDocument
deserialize: ({ schema, content, hasTitle }) => {
const html = renderMarkdown(content);
markdownToProsemirror: ({ schema, content, hasTitle }) => {
const html = markdownToHTML(content);
if (!html) return null;
const parser = new DOMParser();
const { body } = parser.parseFromString(html, 'text/html');
body.append(document.createComment(content));
const json = renderer.render(body);
const json = htmlToPromsemirror(body);
console.log({ hasTitle, json, body });
// 设置标题
if (!hasTitle) {
const firstNode = json.content[0];
if (firstNode) {
if (firstNode.type === 'heading') {
if (firstNode.type === 'heading' || firstNode.type === 'paragraph') {
firstNode.type = 'title';
}
}
}
const nodes = json.content;
const result = { type: 'doc', content: [] };
for (let i = 0; i < nodes.length; ) {
const node = nodes[i];
// 目的:合并成 promirror 需要的 table 格式
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);
@ -238,13 +224,10 @@ const createMarkdownSerializer = () => ({
}
return result;
// const state = ProseMirrorDOMParser.fromSchema(schema).parse(body);
// return state.toJSON();
},
// 将 ProseMirror JSONDocument 转换为 markdown 字符串
serialize: ({ schema, content }) => {
proseMirrorToMarkdown: ({ schema, content }) => {
const serializer = new ProseMirrorMarkdownSerializer(
{
...defaultSerializerConfig.nodes,

View File

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

View File

@ -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;
// }
// }
}

View File

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

View File

@ -14,16 +14,15 @@ export const useTheme = () => {
};
useEffect(() => {
const body = document.body;
if (theme === 'dark') {
body.setAttribute('theme-mode', 'dark');
return;
}
if (theme === 'light') {
body.setAttribute('theme-mode', 'light');
return;
}
// const body = document.body;
// if (theme === 'dark') {
// body.setAttribute('theme-mode', 'dark');
// return;
// }
// if (theme === 'light') {
// body.setAttribute('theme-mode', 'light');
// return;
// }
}, [theme]);
useEffect(() => {

View File

@ -54,8 +54,8 @@
}
}
.hr-line {
width: 100%;
hr {
border: 0;
height: 2px;
background: var(--semi-color-border);
margin: 18px 0;

View File

@ -25,7 +25,7 @@ export class DocumentEntity {
@Column({ type: 'text', comment: '文档内容' })
public content: string;
@Column({ type: 'blob', comment: '文档内容' })
@Column({ type: 'longblob', comment: '文档内容' })
public state: Uint8Array;
@Column({