From f1848d5e0adfdd2e860328cb1da07a79af77b3bf Mon Sep 17 00:00:00 2001 From: cycleccc <2991205548@qq.com> Date: Tue, 29 Oct 2024 11:00:47 +0800 Subject: [PATCH] feat: elem id add type (#296) * feat: elem id add type * Create loud-dogs-fly.md * style: refactoring the empty state checks * style: enhancing type safety * style: simplifying the list element check * chore: add sh dev build * perf: consistency with isDOMText * perf: adding parameter validation * style: simplify the node type checking * chore: add build status tracking and summary * perf: optimizing the selector performance * chore: enhance build function with better error handling and build options --- .changeset/loud-dogs-fly.md | 6 + package.json | 1 + .../core/__tests__/editor/dom-editor.test.ts | 5 +- packages/core/src/editor/dom-editor.ts | 3 +- .../core/src/editor/plugins/with-content.ts | 119 +++++++++++------- .../src/editor/plugins/with-max-length.ts | 35 ++++-- .../src/parse-html/parse-common-elem-html.ts | 36 ++++-- .../core/src/parse-html/parse-elem-html.ts | 4 +- .../core/src/render/element/renderElement.tsx | 24 ++-- packages/core/src/render/helper.ts | 6 +- packages/core/src/utils/dom.ts | 37 +++--- packages/custom-types.d.ts | 2 + scripts/build-base.sh | 88 +++++++++++++ 13 files changed, 263 insertions(+), 103 deletions(-) create mode 100644 .changeset/loud-dogs-fly.md create mode 100644 scripts/build-base.sh diff --git a/.changeset/loud-dogs-fly.md b/.changeset/loud-dogs-fly.md new file mode 100644 index 000000000..bbe4dfc16 --- /dev/null +++ b/.changeset/loud-dogs-fly.md @@ -0,0 +1,6 @@ +--- +"@wangeditor-next/editor": patch +"@wangeditor-next/core": patch +--- + +feat: elem id add type diff --git a/package.json b/package.json index 4456d6649..1f8d2d9d9 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "cross-env NODE_OPTIONS=--unhandled-rejections=warn vitest run --passWithNoTests --dangerouslyIgnoreUnhandledErrors", "test-c": "cross-env NODE_OPTIONS=--unhandled-rejections=warn vitest run --passWithNoTests --dangerouslyIgnoreUnhandledErrors --coverage", "dev": "turbo run dev", + "dev:sh": "sh scripts/build-base.sh dev", "build": "turbo run build", "publish": "yarn changeset publish", "prerelease": "yarn build", diff --git a/packages/core/__tests__/editor/dom-editor.test.ts b/packages/core/__tests__/editor/dom-editor.test.ts index 659938060..d5c47b0a8 100644 --- a/packages/core/__tests__/editor/dom-editor.test.ts +++ b/packages/core/__tests__/editor/dom-editor.test.ts @@ -5,6 +5,7 @@ import { Editor, Range as SlateRange } from 'slate' +import { CustomElement } from '../../../custom-types' import { DomEditor } from '../../src/editor/dom-editor' import { IDomEditor } from '../../src/editor/interface' import { Key } from '../../src/utils/key' @@ -148,14 +149,14 @@ describe('Core DomEditor', () => { }) test('toDOMNode', () => { - const p = editor.children[0] + const p = editor.children[0] as CustomElement const key = DomEditor.findKey(editor, p) const domNode = DomEditor.toDOMNode(editor, p) expect(domNode.tagName).toBe('DIV') - expect(domNode.id).toBe(`w-e-element-${key.id}`) + expect(domNode.id).toBe(`w-e-element-${p.type}-${key.id}`) }) test('hasDOMNode', () => { diff --git a/packages/core/src/editor/dom-editor.ts b/packages/core/src/editor/dom-editor.ts index 1b13cdfdb..dae9c0d61 100644 --- a/packages/core/src/editor/dom-editor.ts +++ b/packages/core/src/editor/dom-editor.ts @@ -22,6 +22,7 @@ import $, { isDocument, isDOMElement, isDOMSelection, + isDOMText, isShadowRoot, normalizeDOMPoint, walkTextNodes, @@ -733,7 +734,7 @@ export const DomEditor = { if (childNodes) { for (const node of Array.from(childNodes)) { - if (node.nodeType === 3) { + if (isDOMText(node)) { node.remove() } else { break diff --git a/packages/core/src/editor/plugins/with-content.ts b/packages/core/src/editor/plugins/with-content.ts index 91a665ab0..931e5aece 100644 --- a/packages/core/src/editor/plugins/with-content.ts +++ b/packages/core/src/editor/plugins/with-content.ts @@ -3,20 +3,26 @@ * @author wangfupeng */ -import { Editor, Node, Text, Path, Operation, Range, Transforms, Element, Descendant } from 'slate' -import { DomEditor } from '../dom-editor' +import { + Editor, Element, Node, Operation, Path, Range, Text, Transforms, +} from 'slate' + import { IDomEditor } from '../..' -import { EDITOR_TO_SELECTION, NODE_TO_KEY } from '../../utils/weak-maps' -import node2html from '../../to-html/node2html' +import { IGNORE_TAGS } from '../../constants' +import { htmlToContent } from '../../create/helper' +import { PARSE_ELEM_HTML_CONF, TEXT_TAGS } from '../../parse-html/index' +import parseElemHtml from '../../parse-html/parse-elem-html' import { genElemId } from '../../render/helper' +import node2html from '../../to-html/node2html' +import $, { + DOMElement, isDOMElement, isDOMText, + isUnprocessedListElement, +} from '../../utils/dom' import { Key } from '../../utils/key' -import $, { DOMElement, NodeType } from '../../utils/dom' import { findCurrentLineRange } from '../../utils/line' +import { EDITOR_TO_SELECTION, NODE_TO_KEY } from '../../utils/weak-maps' +import { DomEditor } from '../dom-editor' import { ElementWithId } from '../interface' -import { PARSE_ELEM_HTML_CONF, TEXT_TAGS } from '../../parse-html/index' -import parseElemHtml from '../../parse-html/parse-elem-html' -import { htmlToContent } from '../../create/helper' -import { IGNORE_TAGS } from '../../constants' /** * 把 elem 插入到编辑器 @@ -29,7 +35,7 @@ function insertElemToEditor(editor: IDomEditor, elem: Element) { editor.insertNode(elem) // link 特殊处理,否则后面插入的文字全都在 a 里面 issue#4573 - if (elem.type === 'link') editor.insertFragment([{ text: '' }]) + if (elem.type === 'link') { editor.insertFragment([{ text: '' }]) } } else { // block elem ,另起一行插入 —— 重要 Transforms.insertNodes(editor, elem, { mode: 'highest' }) @@ -38,11 +44,14 @@ function insertElemToEditor(editor: IDomEditor, elem: Element) { export const withContent = (editor: T) => { const e = editor as T & IDomEditor - const { onChange, insertText, apply, deleteBackward } = e + const { + onChange, insertText, apply, deleteBackward, + } = e e.insertText = (text: string) => { const { readOnly } = e.getConfig() - if (readOnly) return + + if (readOnly) { return } insertText(text) } @@ -60,6 +69,7 @@ export const withContent = (editor: T) => { for (const [node, path] of Editor.levels(e, { at: op.path })) { // 在当前节点寻找 const key = DomEditor.findKey(e, node) + matches.push([path, key]) } break @@ -72,6 +82,7 @@ export const withContent = (editor: T) => { for (const [node, path] of Editor.levels(e, { at: Path.parent(op.path) })) { // 在父节点寻找 const key = DomEditor.findKey(e, node) + matches.push([path, key]) } break @@ -82,10 +93,12 @@ export const withContent = (editor: T) => { at: Path.common(Path.parent(op.path), Path.parent(op.newPath)), })) { const key = DomEditor.findKey(e, node) + matches.push([path, key]) } break } + default: } // 执行原本的 apply - 重要!!! @@ -94,6 +107,7 @@ export const withContent = (editor: T) => { // 绑定 node 和 key for (const [path, key] of matches) { const [node] = Editor.node(e, path) + NODE_TO_KEY.set(node, key) } } @@ -126,6 +140,7 @@ export const withContent = (editor: T) => { e.onChange = () => { // 记录当前选区 const { selection } = e + if (selection != null) { EDITOR_TO_SELECTION.set(e, selection) } @@ -145,19 +160,22 @@ export const withContent = (editor: T) => { e.getHtml = (): string => { const { children = [] } = e const html = children.map(child => node2html(child, e)).join('') + return html } // 获取 text e.getText = (): string => { const { children = [] } = e + return children.map(child => Node.string(child)).join('\n') } // 获取选区文字 e.getSelectionText = (): string => { const { selection } = e - if (selection == null) return '' + + if (selection == null) { return '' } return Editor.string(editor, selection) } @@ -170,14 +188,17 @@ export const withContent = (editor: T) => { at: [], universal: true, }) - for (let nodeEntry of nodeEntries) { + + for (const nodeEntry of nodeEntries) { const [node] = nodeEntry + if (Element.isElement(node)) { // 判断 type (前缀 or 全等) - let flag = isPrefix ? node.type.indexOf(type) >= 0 : node.type === type + const flag = isPrefix ? node.type.indexOf(type) >= 0 : node.type === type + if (flag) { const key = DomEditor.findKey(e, node) - const id = genElemId(key.id) + const id = genElemId(node.type, key.id) // node + id elems.push({ @@ -201,22 +222,20 @@ export const withContent = (editor: T) => { */ e.isEmpty = () => { const { children = [] } = e - if (children.length > 1) return false // >1 个顶级节点 + + if (children.length > 1) { return false } // >1 个顶级节点 const firstNode = children[0] - if (firstNode == null) return true // editor.children 空数组 - if (Element.isElement(firstNode) && firstNode.type === 'paragraph') { - const { children: texts = [] } = firstNode - if (texts.length > 1) return false // >1 text node + if (firstNode == null) { return true } // editor.children 空数组 - const t = texts[0] - if (t == null) return true // 无 text 节点 + if (!Element.isElement(firstNode) || firstNode.type !== 'paragraph') { return false } + const { children: texts = [] } = firstNode - if (Text.isText(t) && t.text === '') return true // 只有一个 text 且是空字符串 - } + if (texts.length > 1) { return false } // >1 text node + const t = texts[0] - return false + return t == null || (Text.isText(t) && t.text === '') // 无 text 节点 or 只有一个 text 且是空字符串 } /** @@ -252,39 +271,43 @@ export const withContent = (editor: T) => { * @param isRecursive 是否递归调用(内部使用,使用者不要传参) */ e.dangerouslyInsertHtml = (html: string = '', isRecursive = false) => { - if (!html) return + if (!html) { return } // ------------- 把 html 转换为 DOM nodes ------------- const div = document.createElement('div') + div.innerHTML = html let domNodes = Array.from(div.childNodes) // 过滤一下,只保留 elem 和 text ,并却掉一些无用标签(如 style script 等) domNodes = domNodes.filter(n => { - const { nodeType, nodeName } = n + const { nodeName } = n // Text Node - if (nodeType === NodeType.TEXT_NODE) return true + + if (isDOMText(n)) { return true } // Element Node - if (nodeType === NodeType.ELEMENT_NODE) { + if (isDOMElement(n)) { // 过滤掉忽略的 tag - if (IGNORE_TAGS.has(nodeName.toLowerCase())) return false - else return true + if (IGNORE_TAGS.has(nodeName.toLowerCase())) { return false } + return true } return false }) - if (domNodes.length === 0) return + if (domNodes.length === 0) { return } // ------------- 把 DOM nodes 转换为 slate nodes ,并插入到编辑器 ------------- const { selection } = e - if (selection == null) return + + if (selection == null) { return } let curEmptyParagraphPath: Path | null = null // 是否当前选中了一个空 p (如果是,后面会删掉) // 递归调用时不判断 if (DomEditor.isSelectedEmptyParagraph(e) && !isRecursive) { const { focus } = selection + curEmptyParagraphPath = [focus.path[0]] // 只记录顶级 path 即可 } @@ -292,15 +315,16 @@ export const withContent = (editor: T) => { document.body.appendChild(div) let insertedElemNum = 0 // 记录插入 elem 的数量 ( textNode 不算 ) + domNodes.forEach((n, index) => { - const { nodeType, nodeName, textContent = '' } = n + const { nodeName, textContent = '' } = n // ------ Text node ------ - if (nodeType === NodeType.TEXT_NODE) { - if (!textContent || !textContent.trim()) return // 无内容的 Text + if (isDOMText(n)) { + if (!textContent || !textContent.trim()) { return } // 无内容的 Text // 插入文本 - //【注意】insertNode 和 insertText 有区别:后者会继承光标处的文本样式(如加粗);前者会加入纯文本,无样式; + // 【注意】insertNode 和 insertText 有区别:后者会继承光标处的文本样式(如加粗);前者会加入纯文本,无样式; e.insertNode({ text: textContent }) return } @@ -314,11 +338,12 @@ export const withContent = (editor: T) => { // 判断当前的 el 是否是可识别的 tag const el = n as DOMElement let isParseMatch = false + if (TEXT_TAGS.includes(nodeName.toLowerCase())) { // text elem,如 isParseMatch = true } else { - for (let selector in PARSE_ELEM_HTML_CONF) { + for (const selector in PARSE_ELEM_HTML_CONF) { if (el.matches(selector)) { // 普通 elem,如

等(非 text elem) isParseMatch = true @@ -334,31 +359,30 @@ export const withContent = (editor: T) => { const parsedRes = parseElemHtml($el, e) as Element if (Array.isArray(parsedRes)) { - parsedRes.forEach(el => insertElemToEditor(e, el)) - insertedElemNum++ // 记录数量 + parsedRes.forEach(parsedEl => insertElemToEditor(e, parsedEl)) + insertedElemNum += 1 // 记录数量 } else { insertElemToEditor(e, parsedRes) - insertedElemNum++ // 记录数量 + insertedElemNum += 1 // 记录数量 } // 如果当前选中 void node ,则选区移动一下 - if (DomEditor.isSelectedVoidNode(e)) e.move(1) + if (DomEditor.isSelectedVoidNode(e)) { e.move(1) } return } // 没有匹配上(如 div ) const display = window.getComputedStyle(el).display + if (!DomEditor.isSelectedEmptyParagraph(e)) { // 当前不是空行,且 非 inline - 则换行 if (display.indexOf('inline') < 0) { if (index >= 1) { const prevEl = domNodes[index - 1] as DOMElement // 如果是 list 列表需要多插入一个回车,模拟双回车删除空 list - if ( - 'matches' in prevEl && - prevEl.matches('ul:not([data-w-e-type]),ol:not([data-w-e-type])') - ) { + + if (isUnprocessedListElement(prevEl)) { e.insertBreak() } } @@ -397,6 +421,7 @@ export const withContent = (editor: T) => { e.clear() // 设置新内容 const newContent = htmlToContent(e, html == null ? '' : html) + Transforms.insertFragment(e, newContent) // 恢复编辑器状态和选区 diff --git a/packages/core/src/editor/plugins/with-max-length.ts b/packages/core/src/editor/plugins/with-max-length.ts index ae50593f6..280463763 100644 --- a/packages/core/src/editor/plugins/with-max-length.ts +++ b/packages/core/src/editor/plugins/with-max-length.ts @@ -3,26 +3,31 @@ * @author wangfupeng */ -//【注意】拼音输入时 maxLength 限制在 CompositionEnd 事件中处理 +// 【注意】拼音输入时 maxLength 限制在 CompositionEnd 事件中处理 import { Editor, Node } from 'slate' -import { IDomEditor, DomEditor } from '../..' + +import { DomEditor, IDomEditor } from '../..' import { IGNORE_TAGS } from '../../constants' -import { NodeType } from '../../utils/dom' +import { isDOMElement, isDOMText } from '../../utils/dom' export const withMaxLength = (editor: T) => { const e = editor as T & IDomEditor - const { insertText, insertNode, insertFragment, dangerouslyInsertHtml } = e + const { + insertText, insertNode, insertFragment, dangerouslyInsertHtml, + } = e // 处理 text e.insertText = (text: string) => { const { maxLength } = e.getConfig() + if (!maxLength) { insertText(text) return } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (leftLength <= 0) { // 已经触发 maxLength ,不再输入文字 return @@ -40,18 +45,21 @@ export const withMaxLength = (editor: T) => { // 处理 node e.insertNode = (node: Node) => { const { maxLength } = e.getConfig() + if (!maxLength) { insertNode(node) return } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (leftLength <= 0) { // 已经触发 maxLength ,不再插入 return } const text = Node.string(node) + if (leftLength < text.length) { // 剩余长度,不够 node text 长度,不再插入 return @@ -63,6 +71,7 @@ export const withMaxLength = (editor: T) => { // 处理 fragment e.insertFragment = (fragment: Node[]) => { const { maxLength } = e.getConfig() + if (!maxLength) { // 无 maxLength insertFragment(fragment) @@ -84,22 +93,24 @@ export const withMaxLength = (editor: T) => { insertFragment([firstNode]) // 从第二个节点开始,使用 e.insertNode - for (let i = 1; i < fragment.length; i++) { + for (let i = 1; i < fragment.length; i += 1) { e.insertNode(fragment[i]) } } } e.dangerouslyInsertHtml = (html: string = '', isRecursive = false) => { - if (!html) return + if (!html) { return } const { maxLength } = e.getConfig() + if (!maxLength) { // 无 maxLength dangerouslyInsertHtml(html, isRecursive) return } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (leftLength <= 0) { // 已经触发 maxLength ,不再输入文字 return @@ -107,20 +118,22 @@ export const withMaxLength = (editor: T) => { // ------------- 把 html 转换为 DOM nodes ------------- const div = document.createElement('div') + div.innerHTML = html const text = Array.from(div.childNodes).reduce((acc, node) => { - const { nodeType, nodeName } = node + const { nodeName } = node + if (!node) { return acc } // Text Node - if (nodeType === NodeType.TEXT_NODE) return acc + (node.textContent || '') + if (isDOMText(node)) { return acc + (node.textContent || '') } // Element Node - if (nodeType === NodeType.ELEMENT_NODE) { + if (isDOMElement(node)) { // 过滤掉忽略的 tag - if (IGNORE_TAGS.has(nodeName.toLowerCase())) return acc - else return acc + (node.textContent || '') + if (IGNORE_TAGS.has(nodeName.toLowerCase())) { return acc } + return acc + (node.textContent || '') } return acc }, '') diff --git a/packages/core/src/parse-html/parse-common-elem-html.ts b/packages/core/src/parse-html/parse-common-elem-html.ts index c72b84020..00039c3f2 100644 --- a/packages/core/src/parse-html/parse-common-elem-html.ts +++ b/packages/core/src/parse-html/parse-common-elem-html.ts @@ -4,12 +4,17 @@ */ import $, { Dom7Array } from 'dom7' -import { Editor, Element, Descendant, Text } from 'slate' +import { + Descendant, Editor, Element, Text, +} from 'slate' + import { IDomEditor } from '../editor/interface' -import parseElemHtml from './parse-elem-html' -import { PARSE_ELEM_HTML_CONF, ParseElemHtmlFnType, PARSE_STYLE_HTML_FN_LIST } from './index' -import { NodeType, DOMElement } from '../utils/dom' +import { + DOMElement, isDOMElement, isDOMText, +} from '../utils/dom' import { replaceSpace160 } from './helper' +import { PARSE_ELEM_HTML_CONF, PARSE_STYLE_HTML_FN_LIST, ParseElemHtmlFnType } from './index' +import parseElemHtml from './parse-elem-html' /** * 往 children 最后一个 item(如果是 text node) 插入文字 @@ -19,13 +24,16 @@ import { replaceSpace160 } from './helper' */ function tryInsertTextToChildrenLastItem(children: Descendant[], str: string): boolean { const len = children.length + if (len) { const lastItem = children[len - 1] + if (Text.isText(lastItem)) { const keys = Object.keys(lastItem) + if (keys.length === 1 && keys[0] === 'text') { // lastItem 必须是纯文本,没有 marks - lastItem.text = lastItem.text + str + lastItem.text += str return true } } @@ -43,6 +51,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // void node( html 中编辑的,如 video 的 html 中会有 data-w-e-is-void 属性 ),不需要生成 children const isVoid = $elem.attr('data-w-e-is-void') != null + if (isVoid) { return children } @@ -59,11 +68,12 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // 遍历 DOM 子节点,生成 slate elem node children childNodes.forEach(child => { - if (child.nodeType === NodeType.ELEMENT_NODE) { + if (isDOMElement(child)) { //
,则往 children 最后一个元素(如果是 text )追加 `\n` if (child.nodeName === 'BR') { // 尝试把 text 插入到最后一个 children const res = tryInsertTextToChildrenLastItem(children, '\n') + if (!res) { // 若插入失败,则新建 item children.push({ text: '\n' }) @@ -74,6 +84,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // 其他 elem const $child = $(child) const parsedRes = parseElemHtml($child, editor) + if (Array.isArray(parsedRes)) { parsedRes.forEach(el => children.push(el)) } else { @@ -81,9 +92,10 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { } return } - if (child.nodeType === NodeType.TEXT_NODE) { + if (isDOMText(child)) { // text let text = child.textContent || '' + if (text.trim() === '' && text.indexOf('\n') >= 0) { // 有换行,但无实际内容 return @@ -95,12 +107,13 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // 尝试把 text 插入到最后一个 children const res = tryInsertTextToChildrenLastItem(children, text) + if (!res) { // 若插入失败,则新建 item children.push({ text }) } } - return + } }) return children @@ -111,7 +124,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { * @param elem elem * @param children children */ -function defaultParser(elem: DOMElement, children: Descendant[], editor: IDomEditor): Element { +function defaultParser(elem: DOMElement, _children: Descendant[], _editor: IDomEditor): Element { return { type: 'paragraph', children: [{ text: $(elem).text().replace(/\s+/gm, ' ') }], @@ -123,7 +136,7 @@ function defaultParser(elem: DOMElement, children: Descendant[], editor: IDomEdi * @param $elem $elem */ function getParser($elem: Dom7Array): ParseElemHtmlFnType { - for (let selector in PARSE_ELEM_HTML_CONF) { + for (const selector in PARSE_ELEM_HTML_CONF) { if ($elem[0].matches(selector)) { return PARSE_ELEM_HTML_CONF[selector] } @@ -144,10 +157,11 @@ function parseCommonElemHtml($elem: Dom7Array, editor: IDomEditor): Element[] { const parser = getParser($elem) let parsedRes = parser($elem[0], children, editor) - if (!Array.isArray(parsedRes)) parsedRes = [parsedRes] // 临时处理为数组 + if (!Array.isArray(parsedRes)) { parsedRes = [parsedRes] } // 临时处理为数组 parsedRes.forEach(elem => { const isVoid = Editor.isVoid(editor, elem) + if (!isVoid) { // 非 void ,如果没有 children ,则取纯文本 if (children.length === 0) { diff --git a/packages/core/src/parse-html/parse-elem-html.ts b/packages/core/src/parse-html/parse-elem-html.ts index e90022d1a..e1dfda84c 100644 --- a/packages/core/src/parse-html/parse-elem-html.ts +++ b/packages/core/src/parse-html/parse-elem-html.ts @@ -8,7 +8,7 @@ import { Descendant } from 'slate' import { IDomEditor } from '../editor/interface' import { PRE_PARSE_HTML_CONF_LIST, TEXT_TAGS } from '../index' -import { getTagName, NodeType } from '../utils/dom' +import { getTagName, isDOMText } from '../utils/dom' import parseCommonElemHtml from './parse-common-elem-html' import parseTextElemHtml from './parse-text-elem-html' @@ -41,7 +41,7 @@ function parseElemHtml($elem: Dom7Array, editor: IDomEditor): Descendant | Desce return Array.from(childNodes).map(child => { const $childElem = $(child) - if ($childElem[0].nodeType === NodeType.TEXT_NODE) { return { text: $childElem[0].textContent || '' } } + if (isDOMText($childElem[0])) { return { text: $childElem[0].textContent || '' } } return parseTextElemHtml($childElem, editor) }) } diff --git a/packages/core/src/render/element/renderElement.tsx b/packages/core/src/render/element/renderElement.tsx index 46ccb118d..6dddfdc2a 100644 --- a/packages/core/src/render/element/renderElement.tsx +++ b/packages/core/src/render/element/renderElement.tsx @@ -3,23 +3,25 @@ * @author wangfupeng */ -import { Editor, Node, Element as SlateElement } from 'slate' +import { Editor, Element as SlateElement, Node } from 'slate' +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { jsx, VNode } from 'snabbdom' -import { node2Vnode } from '../node2Vnode' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' +import { getElementById } from '../../utils/dom' +import { promiseResolveThen } from '../../utils/util' import { + ELEMENT_TO_NODE, KEY_TO_ELEMENT, NODE_TO_ELEMENT, - ELEMENT_TO_NODE, NODE_TO_INDEX, NODE_TO_PARENT, } from '../../utils/weak-maps' +import { genElemId } from '../helper' +import { node2Vnode } from '../node2Vnode' import getRenderElem from './getRenderElem' import renderStyle from './renderStyle' -import { promiseResolveThen } from '../../utils/util' -import { genElemId } from '../helper' -import { getElementById } from '../../utils/dom' interface IAttrs { id: string @@ -35,7 +37,7 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { // const readOnly = editor.isDisabled() const isInline = editor.isInline(elemNode) const isVoid = Editor.isVoid(editor, elemNode) - const domId = genElemId(key.id) + const domId = genElemId(elemNode.type, key.id) const attrs: IAttrs = { id: domId, key: key.id, @@ -45,9 +47,10 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { // 根据 type 生成 vnode 的函数 const { type, children = [] } = elemNode - let renderElem = getRenderElem(type) + const renderElem = getRenderElem(type) let childrenVnode + if (isVoid) { childrenVnode = null // void 节点 render elem 时不传入 children } else { @@ -101,7 +104,7 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { } // 添加 element 属性 - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } Object.assign(vnode.data, attrs) // 添加文本相关的样式,如 text-align @@ -114,7 +117,8 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { promiseResolveThen(() => { // 异步,否则拿不到 DOM 节点 const dom = getElementById(domId) - if (dom == null) return + + if (dom == null) { return } KEY_TO_ELEMENT.set(key, dom) NODE_TO_ELEMENT.set(elemNode, dom) ELEMENT_TO_NODE.set(dom, elemNode) diff --git a/packages/core/src/render/helper.ts b/packages/core/src/render/helper.ts index ac66dbb59..9869ad169 100644 --- a/packages/core/src/render/helper.ts +++ b/packages/core/src/render/helper.ts @@ -3,8 +3,10 @@ * @author wangfupeng */ -export function genElemId(id: string) { - return `w-e-element-${id}` +import { ElementType } from 'packages/custom-types' + +export function genElemId(type:ElementType, id: string) { + return `w-e-element-${type}-${id}` } export function genTextId(id: string) { diff --git a/packages/core/src/utils/dom.ts b/packages/core/src/utils/dom.ts index 087605ec2..01c460c11 100644 --- a/packages/core/src/utils/dom.ts +++ b/packages/core/src/utils/dom.ts @@ -84,6 +84,17 @@ if (empty) { $.fn.empty = empty } export default $ +export enum NodeType { + ELEMENT_NODE = 1, + TEXT_NODE = 3, + CDATA_SECTION_NODE = 4, + PROCESSING_INSTRUCTION_NODE = 7, + COMMENT_NODE = 8, + DOCUMENT_NODE = 9, + DOCUMENT_TYPE_NODE = 10, + DOCUMENT_FRAGMENT_NODE = 11, +} + export const isDocument = (value: any): value is Document => { return toString(value) === '[object HTMLDocument]' } @@ -98,6 +109,9 @@ export const isDataTransfer = (value: any): value is DataTransfer => { const HTML_ELEMENT_STR_REG_EXP = /\[object HTML([A-Z][a-z]*)*Element\]/ +export const isUnprocessedListElement = (el: Element): boolean => { + return 'matches' in el && /^[ou]l$/i.test(el.tagName) && !el.hasAttribute('data-w-e-type') +} export const isHTMLElememt = (value: any): value is HTMLElement => { return HTML_ELEMENT_STR_REG_EXP.test(toString(value)) } @@ -125,14 +139,14 @@ export const isDOMNode = (value: any): value is DOMNode => { * Check if a DOM node is a comment node. */ export const isDOMComment = (value: any): value is DOMComment => { - return isDOMNode(value) && value.nodeType === 8 + return isDOMNode(value) && value.nodeType === NodeType.COMMENT_NODE } /** * Check if a DOM node is an element node. */ export const isDOMElement = (value: any): value is DOMElement => { - return isDOMNode(value) && value.nodeType === 1 + return isDOMNode(value) && value.nodeType === NodeType.ELEMENT_NODE } /** @@ -146,7 +160,7 @@ export const isDOMSelection = (value: any): value is DOMSelection => { * Check if a DOM node is an element node. */ export const isDOMText = (value: any): value is DOMText => { - return isDOMNode(value) && value.nodeType === 3 + return isDOMNode(value) && value.nodeType === NodeType.TEXT_NODE } /** @@ -331,7 +345,7 @@ export function getFirstVoidChild(elem: DOMElement): DOMElement | null { const { nodeName, nodeType } = curElem - if (nodeType === 1) { + if (nodeType === NodeType.ELEMENT_NODE) { const name = nodeName.toLowerCase() if ( @@ -377,27 +391,16 @@ export function walkTextNodes( const node = nodes[i] const nodeType = node.nodeType - if (nodeType === 3) { + if (isDOMText(node)) { // 匹配到 text node ,执行函数 handler(node, elem) - } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { + } else if ([NodeType.ELEMENT_NODE, NodeType.DOCUMENT_NODE, NodeType.DOCUMENT_FRAGMENT_NODE].includes(nodeType)) { // 继续遍历子节点 walkTextNodes(node as DOMElement, handler) } } } -export enum NodeType { - ELEMENT_NODE = 1, - TEXT_NODE = 3, - CDATA_SECTION_NODE = 4, - PROCESSING_INSTRUCTION_NODE = 7, - COMMENT_NODE = 8, - DOCUMENT_NODE = 9, - DOCUMENT_TYPE_NODE = 10, - DOCUMENT_FRAGMENT_NODE = 11, -} - /** * 获取 tagName lower-case * @param $elem $elem diff --git a/packages/custom-types.d.ts b/packages/custom-types.d.ts index 3aa682d10..1484197f1 100644 --- a/packages/custom-types.d.ts +++ b/packages/custom-types.d.ts @@ -65,6 +65,8 @@ type CustomElement = | TableElement | ListItemElement +type ElementType = CustomElement['type']; + declare global { interface Window { MSStream: boolean diff --git a/scripts/build-base.sh b/scripts/build-base.sh new file mode 100644 index 000000000..0f2d9690b --- /dev/null +++ b/scripts/build-base.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +## One-click build script for all packages + +# 获取 yarn dev/build 类型 +buildType=build +if [ -z "$1" ]; then + echo "No build type specified, defaulting to 'build'" +elif [ "$1" != "dev" ] && [ "$1" != "build" ]; then + echo "Error: Build type must be either 'dev' or 'build'" + exit 1 +else + buildType=$1 +fi + +cd ./packages || { echo "Error: packages directory not found"; exit 1; } + + +# Initialize build status tracking +declare -A build_status +build_start_time=$(date +%s) + +# Function to log build status +log_status() { + local package=$1 + local status=$2 + build_status["$package"]=$status +} + +# Function to print build summary +print_summary() { + build_end_time=$(date +%s) + build_duration=$((build_end_time - build_start_time)) + + echo -e "\nBuild Summary:" + echo "==============" + for package in "${!build_status[@]}"; do + status="${build_status[$package]}" + if [ "$status" = "success" ]; then + echo "✅ $package: Success" + else + echo "❌ $package: Failed" + fi + done + echo -e "\nTotal build time: ${build_duration}s" +} + +# Update build_package function +build_package() { + local package_name=$1 + local timeout=1800 # 30 minutes timeout + echo "Building package: $package_name" + if cd "$package_name"; then + rm -rf dist + if timeout $timeout yarn "$buildType" 2> build_error.log; then + rm -f build_error.log + log_status "$package_name" "success" + else + if [ -f build_error.log ]; then + echo "Build failed for $package_name. Error:" + cat build_error.log + rm -f build_error.log + fi + log_status "$package_name" "failed" + cd .. + return 1 + fi + log_status "$package_name" "success" + cd .. + else + echo "Failed to enter directory: $package_name" + log_status "$package_name" "failed" + return 1 + fi +} + +# Build packages in dependency order +build_package "core" +build_package "basic-modules" +build_package "code-highlight" +build_package "list-module" +build_package "table-module" +build_package "upload-image-module" +build_package "video-module" +build_package "editor" + +# Add trap for summary printing +trap print_summary EXIT