From c8b224806620df8d9c71f7da815ad3bad0897a33 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 7 Jul 2026 09:41:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20runnable=20=E5=9D=97=20classList?= =?UTF-8?q?.add=20=E6=8A=9B=20InvalidCharacterError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeBlockNodeView 构造时把完整 info string 原样拼进 class (`language-python runnable {...}`),classList.add 拒绝含空格 token, 导致编辑模式回填含 runnable 块的文章时崩溃(setMarkdown → addNode)。 改用 extractLang 只挂纯语言名 class。高亮靠 CodeBlockLowlight decoration, 本就不依赖此 class。补回归测试。 --- .../src/__tests__/code-block-view.test.ts | 11 +++++++++++ libs/tiptap-editor/src/code-block-view.ts | 9 ++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/libs/tiptap-editor/src/__tests__/code-block-view.test.ts b/libs/tiptap-editor/src/__tests__/code-block-view.test.ts index 846dcea..a8b9399 100644 --- a/libs/tiptap-editor/src/__tests__/code-block-view.test.ts +++ b/libs/tiptap-editor/src/__tests__/code-block-view.test.ts @@ -86,6 +86,17 @@ describe('CodeBlockNodeView', () => { expect(view.contentDOM?.tagName).toBe('CODE'); }); + it('runnable 块的 code class 只含纯语言名(不抛 InvalidCharacterError)', () => { + // 回归:完整 info string `python runnable {...}` 含空格, + // classList.add 会拒绝含空格的 token。必须用 extractLang 提取首 token。 + const view = new CodeBlockNodeView({ + node: mockNode('python runnable {"timeout_secs":10}'), + editor: mockEditor(), + } as any); + expect(view.contentDOM?.classList.contains('language-python')).toBe(true); + expect(view.contentDOM?.classList.contains('runnable')).toBe(false); + }); + it('ignoreMutation 返回 true(工具栏不触发编辑事务)', () => { const view = new CodeBlockNodeView({ node: mockNode('python'), diff --git a/libs/tiptap-editor/src/code-block-view.ts b/libs/tiptap-editor/src/code-block-view.ts index 4b21a7d..ecf55c8 100644 --- a/libs/tiptap-editor/src/code-block-view.ts +++ b/libs/tiptap-editor/src/code-block-view.ts @@ -73,9 +73,12 @@ export class CodeBlockNodeView { // pre > code(contentDOM,decoration 在此生效) this.pre = document.createElement('pre'); this.code = document.createElement('code'); - const lang = (this.node.attrs.language as string) ?? ''; - if (lang) { - this.code.classList.add(`language-${lang}`); + // 只挂纯语言名的 class(extractLang 提取首个 token)。 + // 不能用完整 info string——`python runnable {...}` 含空格,classList.add 会抛 + // InvalidCharacterError。高亮靠 CodeBlockLowlight 的 decoration,不依赖此 class。 + const langClass = extractLang((this.node.attrs.language as string) ?? ''); + if (langClass) { + this.code.classList.add(`language-${langClass}`); } this.pre.appendChild(this.code); this.container.appendChild(this.pre);