From f97d8d66bb466b6b02551f5d620bde532d56b05a Mon Sep 17 00:00:00 2001 From: rovshanmuradov Date: Sat, 24 Oct 2026 08:01:40 +0400 Subject: [PATCH 1/6] fix(react): mount the content of inline content inserted after mount Once the editor has mounted, TipTap renders a React node view synchronously, before it creates the contentDOM, so the contentRef call can't mount it. TipTap then mounts it into a [data-node-view-content] element or, failing that, into the node view's root. Inline content specs didn't set that attribute (block specs do since #1954), so the content ended up outside the element the spec rendered for it, e.g. the source of inline math showed next to its preview after an undo, until the next re-render. --- packages/react/src/schema/ReactInlineContentSpec.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react/src/schema/ReactInlineContentSpec.tsx b/packages/react/src/schema/ReactInlineContentSpec.tsx index 863d5421a3..a705ab67ca 100644 --- a/packages/react/src/schema/ReactInlineContentSpec.tsx +++ b/packages/react/src/schema/ReactInlineContentSpec.tsx @@ -227,6 +227,11 @@ export function createReactInlineContentSpec< ref(element); if (element) { element.dataset.editable = ""; + // Inline content inserted after the editor has mounted + // renders before TipTap creates its contentDOM, so `ref` + // can't mount it yet. TipTap then looks for this + // attribute to know where to mount it instead. + element.dataset.nodeViewContent = ""; } }} editor={editor} From 9d371b5af5b6e841a10673d6d54301aeae10527e Mon Sep 17 00:00:00 2001 From: rovshanmuradov Date: Sat, 24 Oct 2026 06:58:35 +0400 Subject: [PATCH 2/6] fix(core): register extensions declared by inline content specs createReactInlineContentSpec accepts extensions and keeps them on the spec, but ExtensionManager only collected them from block specs, so they were silently dropped. This left the math-block inline input rules ($...$ and \(...\)) unregistered. --- .../src/editor/managers/ExtensionManager/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/core/src/editor/managers/ExtensionManager/index.ts b/packages/core/src/editor/managers/ExtensionManager/index.ts index 34e7ad4e76..8531d673fc 100644 --- a/packages/core/src/editor/managers/ExtensionManager/index.ts +++ b/packages/core/src/editor/managers/ExtensionManager/index.ts @@ -111,6 +111,19 @@ export class ExtensionManager { this.addExtension(extension); } } + + // Add the extensions from inline content specs (the built-in text and link + // specs have none) + for (const inlineContent of Object.values( + this.editor.schema.inlineContentSpecs, + )) { + if (!("extensions" in inlineContent)) { + continue; + } + for (const extension of inlineContent.extensions ?? []) { + this.addExtension(extension); + } + } } private mountExtension(extension: Extension): void { From ea782e43326dde5d87e008f78b5c350d588d01f2 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Sat, 24 Oct 2026 07:16:26 +0200 Subject: [PATCH 3/6] test: cover schema extension registration and inline node view content --- .../ExtensionManager.node.test.ts | 152 +++++++++++++++--- .../ReactInlineContentSpec.browser.test.tsx | 102 ++++++++++++ 2 files changed, 233 insertions(+), 21 deletions(-) create mode 100644 packages/react/src/schema/ReactInlineContentSpec.browser.test.tsx diff --git a/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts b/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts index ffa6b973ab..eac662f735 100644 --- a/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts +++ b/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts @@ -1,26 +1,32 @@ -// @vitest-environment node +// @vitest-environment jsdom import { Plugin, PluginKey } from "prosemirror-state"; import { Extension as TiptapExtension } from "@tiptap/core"; import { afterEach, describe, expect, it } from "vite-plus/test"; +import { BlockNoteSchema } from "../../../blocks/BlockNoteSchema.js"; +import { createBlockSpec } from "../../../schema/blocks/createSpec.js"; +import { createInlineContentSpec } from "../../../schema/inlineContent/createSpec.js"; +import { createStyleSpec } from "../../../schema/styles/createSpec.js"; import { createExtension } from "../../BlockNoteExtension.js"; import { BlockNoteEditor } from "../../BlockNoteEditor.js"; -const editors: BlockNoteEditor[] = []; +const editors: Array<{ unmount(): void }> = []; +const hosts: HTMLElement[] = []; afterEach(() => { for (const editor of editors.splice(0)) { - editor._tiptapEditor.destroy(); + editor.unmount(); + } + for (const host of hosts.splice(0)) { + host.remove(); } }); -function rebuildPlugins(editor: BlockNoteEditor) { - // This is the state-only step Tiptap performs on every mount. No view is - // required to prove that its plugin source disagrees with BlockNote's registry. - editor.prosemirrorView.updateState( - editor.prosemirrorState.reconfigure({ - plugins: editor._tiptapEditor.extensionManager.plugins, - }), - ); +function remountEditor(editor: Pick) { + editor.unmount(); + const host = document.createElement("div"); + document.body.append(host); + hosts.push(host); + editor.mount(host); } describe("runtime extension plugin source", () => { @@ -66,7 +72,7 @@ describe("runtime extension plugin source", () => { ], }); editors.push(editor); - rebuildPlugins(editor); + remountEditor(editor); editor.replaceBlocks(editor.document, [ { type: "paragraph", content: "Keep content" }, ]); @@ -78,7 +84,7 @@ describe("runtime extension plugin source", () => { expect(key.getState(editor.prosemirrorState)).toBe(2); expect(retainedKey.getState(editor.prosemirrorState)).toBe(99); expect(editor.prosemirrorState.doc.textContent).toBe("Keep content"); - rebuildPlugins(editor); + remountEditor(editor); expect(key.getState(editor.prosemirrorState)).toBe(2); }); it("does not reinstall a removed extension", () => { @@ -89,10 +95,10 @@ describe("runtime extension plugin source", () => { })); const editor = BlockNoteEditor.create({ extensions: [extension()] }); editors.push(editor); - rebuildPlugins(editor); + remountEditor(editor); expect(key.get(editor.prosemirrorState)).toBeDefined(); editor.unregisterExtension(extension); - rebuildPlugins(editor); + remountEditor(editor); expect(key.get(editor.prosemirrorState)).toBeUndefined(); }); @@ -107,13 +113,13 @@ describe("runtime extension plugin source", () => { }); const editor = BlockNoteEditor.create(); editors.push(editor); - rebuildPlugins(editor); + remountEditor(editor); editor.registerExtension({ key: "added", prosemirrorPlugins: [plugin] }); editor.replaceBlocks(editor.document, [ { type: "paragraph", content: "Changed" }, ]); expect(key.getState(editor.prosemirrorState)).toBe(1); - rebuildPlugins(editor); + remountEditor(editor); expect(key.get(editor.prosemirrorState)).toBe(plugin); expect(key.getState(editor.prosemirrorState)).toBe(1); }); @@ -130,12 +136,12 @@ describe("runtime extension plugin source", () => { }; const editor = BlockNoteEditor.create({ extensions: [() => original] }); editors.push(editor); - rebuildPlugins(editor); + remountEditor(editor); editor.replaceExtension(original, replacement); expect(editor.prosemirrorState.plugins.at(-1)).toBe( replacement.prosemirrorPlugins[0], ); - rebuildPlugins(editor); + remountEditor(editor); expect(key.get(editor.prosemirrorState)).toBe( replacement.prosemirrorPlugins[0], ); @@ -162,10 +168,10 @@ describe("runtime extension plugin source", () => { ], }); editors.push(editor); - rebuildPlugins(editor); + remountEditor(editor); editor.registerExtension({ key: "runtime", prosemirrorPlugins: [added] }); expect(editor.prosemirrorState.plugins.at(-1)).toBe(added); - rebuildPlugins(editor); + remountEditor(editor); expect(editor.prosemirrorState.plugins.at(-1)).toBe(added); }); @@ -207,3 +213,107 @@ describe("runtime extension plugin source", () => { expect(remove(editor, ["second"])).toEqual([second]); }); }); + +it("registers extensions and plugins from editor options, nested extensions, and schema specs", () => { + function render() { + const dom = document.createElement("span"); + return { dom, contentDOM: dom }; + } + + function registration(key: string) { + const plugin = new Plugin({ key: new PluginKey(key) }); + const nestedPlugin = new Plugin({ key: new PluginKey(`${key}-nested`) }); + const extension = createExtension(() => ({ + key, + prosemirrorPlugins: [plugin], + tiptapExtensions: [ + TiptapExtension.create({ + name: `${key}-tiptap`, + addExtensions: () => [ + TiptapExtension.create({ + name: `${key}-nested`, + addProseMirrorPlugins: () => [nestedPlugin], + }), + ], + }), + ], + })); + return { extension, plugin, nestedPlugin }; + } + + const options = registration("options"); + const nested = registration("nested"); + const block = registration("block"); + const inline = registration("inline"); + const blockSpec = createBlockSpec( + { type: "customBlock", propSchema: {}, content: "inline" }, + { render }, + )(); + const inlineSpec = createInlineContentSpec( + { type: "customInline", propSchema: {}, content: "styled" }, + { render }, + ); + const styleSpec = createStyleSpec( + { type: "customStyle", propSchema: "boolean" }, + { render }, + ); + const inlinePlugin = new Plugin({ key: new PluginKey("inline-node") }); + const stylePlugin = new Plugin({ key: new PluginKey("style-mark") }); + const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + customBlock: { ...blockSpec, extensions: [block.extension()] }, + }, + inlineContentSpecs: { + customInline: { + ...inlineSpec, + extensions: [inline.extension()], + implementation: { + ...inlineSpec.implementation, + node: inlineSpec.implementation.node.extend({ + addProseMirrorPlugins: () => [inlinePlugin], + }), + }, + }, + }, + styleSpecs: { + customStyle: { + ...styleSpec, + implementation: { + render, + mark: styleSpec.implementation.mark.extend({ + addProseMirrorPlugins: () => [stylePlugin], + }), + }, + }, + }, + }); + const editor = BlockNoteEditor.create({ + schema, + extensions: [ + options.extension(), + createExtension({ + key: "parent", + blockNoteExtensions: [nested.extension()], + }), + ], + }); + editors.push(editor); + remountEditor(editor); + const plugins = editor.prosemirrorState.plugins; + + expect(editor.pmSchema.nodes.customBlock).toBeDefined(); + expect(editor.pmSchema.nodes.customInline).toBeDefined(); + expect(editor.pmSchema.marks.customStyle).toBeDefined(); + for (const { extension, plugin, nestedPlugin } of [ + options, + nested, + block, + inline, + ]) { + expect(editor.getExtension(extension)).toBeDefined(); + expect(plugins).toContain(plugin); + expect(plugins).toContain(nestedPlugin); + } + expect(plugins).toContain(inlinePlugin); + expect(plugins).toContain(stylePlugin); +}); diff --git a/packages/react/src/schema/ReactInlineContentSpec.browser.test.tsx b/packages/react/src/schema/ReactInlineContentSpec.browser.test.tsx new file mode 100644 index 0000000000..63ea726f60 --- /dev/null +++ b/packages/react/src/schema/ReactInlineContentSpec.browser.test.tsx @@ -0,0 +1,102 @@ +import { BlockNoteEditor, BlockNoteSchema } from "@blocknote/core"; +import { flushSync } from "react-dom"; +import { createRoot } from "react-dom/client"; +import { expect, it } from "vite-plus/test"; + +import { BlockNoteViewRaw } from "../editor/BlockNoteView.js"; +import { createReactInlineContentSpec } from "./ReactInlineContentSpec.js"; + +function renderSource({ + contentRef, +}: { + contentRef: (node: HTMLElement | null) => void; +}) { + return ( + + Preview + + + ); +} + +it.each([ + ["plain", "insertion"], + ["styled", "insertion"], + ["plain", "undo"], + ["styled", "undo"], +] as const)( + "mounts %s inline content inside its contentRef after %s", + async (content, action) => { + const schema = BlockNoteSchema.create().extend({ + inlineContentSpecs: { + plain: createReactInlineContentSpec( + { type: "plain", propSchema: {}, content: "plain" }, + { render: renderSource }, + ), + styled: createReactInlineContentSpec( + { type: "styled", propSchema: {}, content: "styled" }, + { render: renderSource }, + ), + }, + }); + const editor = BlockNoteEditor.create({ + schema, + trailingBlock: false, + initialContent: [ + { + id: "paragraph", + type: "paragraph", + content: + action === "undo" + ? [{ type: content, props: {}, content: "x^2" }] + : [], + }, + ], + }); + const host = document.createElement("div"); + document.body.append(host); + const root = createRoot(host); + + async function expectContentMounted() { + await expect + .poll( + () => + host.querySelector( + `[data-inline-content-type="${content}"] .source`, + )?.textContent, + ) + .toBe("x^2"); + const inline = host.querySelector( + `[data-inline-content-type="${content}"]`, + ); + const source = inline?.querySelector(".source"); + expect(source?.textContent).toBe("x^2"); + // Checking the actual parent, not the marker attribute, catches content + // appended beside the preview when TipTap creates contentDOM after React. + expect( + inline?.querySelector("[data-node-view-content-react]")?.parentElement, + ).toBe(source); + expect(inline?.textContent).toBe("Previewx^2"); + } + + try { + flushSync(() => root.render()); + if (action === "insertion") { + editor.setTextCursorPosition("paragraph", "start"); + editor.insertInlineContent([ + { type: content, props: {}, content: "x^2" }, + ]); + } else { + await expectContentMounted(); + editor.removeBlocks(["paragraph"]); + expect(host.querySelector(".source")).toBeNull(); + expect(editor.undo()).toBe(true); + } + await expectContentMounted(); + } finally { + root.unmount(); + editor.unmount(); + host.remove(); + } + }, +); From bdaed98c8e3c5eb6f94936426f33707a53fd0f90 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Sat, 24 Oct 2026 07:22:17 +0200 Subject: [PATCH 4/6] feat: support extensions in style specs --- .../ExtensionManager.node.test.ts | 11 ++ .../editor/managers/ExtensionManager/index.ts | 7 ++ packages/core/src/schema/styles/createSpec.ts | 55 +++++---- packages/core/src/schema/styles/internal.ts | 6 + packages/core/src/schema/styles/types.ts | 5 + packages/react/src/schema/ReactStyleSpec.tsx | 110 ++++++++++-------- 6 files changed, 120 insertions(+), 74 deletions(-) diff --git a/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts b/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts index eac662f735..a225205177 100644 --- a/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts +++ b/packages/core/src/editor/managers/ExtensionManager/ExtensionManager.node.test.ts @@ -245,6 +245,8 @@ it("registers extensions and plugins from editor options, nested extensions, and const nested = registration("nested"); const block = registration("block"); const inline = registration("inline"); + const style = registration("style"); + const styleChild = registration("style-child"); const blockSpec = createBlockSpec( { type: "customBlock", propSchema: {}, content: "inline" }, { render }, @@ -256,6 +258,13 @@ it("registers extensions and plugins from editor options, nested extensions, and const styleSpec = createStyleSpec( { type: "customStyle", propSchema: "boolean" }, { render }, + [ + style.extension(), + createExtension({ + key: "style-parent", + blockNoteExtensions: [styleChild.extension()], + }), + ], ); const inlinePlugin = new Plugin({ key: new PluginKey("inline-node") }); const stylePlugin = new Plugin({ key: new PluginKey("style-mark") }); @@ -309,6 +318,8 @@ it("registers extensions and plugins from editor options, nested extensions, and nested, block, inline, + style, + styleChild, ]) { expect(editor.getExtension(extension)).toBeDefined(); expect(plugins).toContain(plugin); diff --git a/packages/core/src/editor/managers/ExtensionManager/index.ts b/packages/core/src/editor/managers/ExtensionManager/index.ts index 8531d673fc..7835f76026 100644 --- a/packages/core/src/editor/managers/ExtensionManager/index.ts +++ b/packages/core/src/editor/managers/ExtensionManager/index.ts @@ -124,6 +124,13 @@ export class ExtensionManager { this.addExtension(extension); } } + + // Add the extensions from style specs + for (const style of Object.values(this.editor.schema.styleSpecs)) { + for (const extension of style.extensions ?? []) { + this.addExtension(extension); + } + } } private mountExtension(extension: Extension): void { diff --git a/packages/core/src/schema/styles/createSpec.ts b/packages/core/src/schema/styles/createSpec.ts index c8700da0f2..265b037aad 100644 --- a/packages/core/src/schema/styles/createSpec.ts +++ b/packages/core/src/schema/styles/createSpec.ts @@ -1,4 +1,8 @@ import { Mark } from "@tiptap/core"; +import type { + Extension, + ExtensionFactoryInstance, +} from "../../editor/BlockNoteExtension.js"; import { ParseRule, TagParseRule } from "@tiptap/pm/model"; import { @@ -72,6 +76,7 @@ export function getStyleParseRules( export function createStyleSpec( styleConfig: T, styleImplementation: CustomStyleImplementation, + extensions?: (Extension | ExtensionFactoryInstance)[], ): StyleSpec { const mark = Mark.create({ name: styleConfig.type, @@ -111,30 +116,34 @@ export function createStyleSpec( }, }); - return createInternalStyleSpec(styleConfig, { - ...styleImplementation, - mark, - render: (value) => { - const renderResult = styleImplementation.render(value as any); + return createInternalStyleSpec( + styleConfig, + { + ...styleImplementation, + mark, + render: (value) => { + const renderResult = styleImplementation.render(value as any); - return addStyleAttributes( - renderResult, - styleConfig.type, - value, - styleConfig.propSchema, - ); - }, - toExternalHTML: (value) => { - const renderResult = ( - styleImplementation.toExternalHTML || styleImplementation.render - )(value as any); + return addStyleAttributes( + renderResult, + styleConfig.type, + value, + styleConfig.propSchema, + ); + }, + toExternalHTML: (value) => { + const renderResult = ( + styleImplementation.toExternalHTML || styleImplementation.render + )(value as any); - return addStyleAttributes( - renderResult, - styleConfig.type, - value, - styleConfig.propSchema, - ); + return addStyleAttributes( + renderResult, + styleConfig.type, + value, + styleConfig.propSchema, + ); + }, }, - }); + extensions, + ); } diff --git a/packages/core/src/schema/styles/internal.ts b/packages/core/src/schema/styles/internal.ts index 9d5711bd6e..e47ec1c39b 100644 --- a/packages/core/src/schema/styles/internal.ts +++ b/packages/core/src/schema/styles/internal.ts @@ -1,5 +1,9 @@ import { Attributes, Mark } from "@tiptap/core"; import { DOMSerializer } from "@tiptap/pm/model"; +import type { + Extension, + ExtensionFactoryInstance, +} from "../../editor/BlockNoteExtension.js"; import { StyleConfig, StyleImplementation, @@ -68,10 +72,12 @@ export function addStyleAttributes< export function createInternalStyleSpec( config: T, implementation: StyleImplementation, + extensions?: (Extension | ExtensionFactoryInstance)[], ) { return { config, implementation, + extensions, } satisfies StyleSpec; } diff --git a/packages/core/src/schema/styles/types.ts b/packages/core/src/schema/styles/types.ts index a0eeb7e416..f709093e1f 100644 --- a/packages/core/src/schema/styles/types.ts +++ b/packages/core/src/schema/styles/types.ts @@ -1,5 +1,9 @@ import { Mark } from "@tiptap/core"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import type { + Extension, + ExtensionFactoryInstance, +} from "../../editor/BlockNoteExtension.js"; export type StylePropSchema = "boolean" | "string"; // TODO: use PropSchema as name? Use objects as type similar to blocks? @@ -36,6 +40,7 @@ export type StyleImplementation = { export type StyleSpec = { config: T; implementation: StyleImplementation; + extensions?: (Extension | ExtensionFactoryInstance)[]; }; // A Schema contains all the types (Configs) supported in an editor diff --git a/packages/react/src/schema/ReactStyleSpec.tsx b/packages/react/src/schema/ReactStyleSpec.tsx index 1b6825e0b5..b780b2c9a9 100644 --- a/packages/react/src/schema/ReactStyleSpec.tsx +++ b/packages/react/src/schema/ReactStyleSpec.tsx @@ -1,5 +1,7 @@ import { BlockNoteEditor, + type Extension, + type ExtensionFactoryInstance, StyleConfig, addStyleAttributes, createInternalStyleSpec, @@ -35,11 +37,13 @@ export type ReactCustomStyleImplementation = { * `propSchema` (`"boolean"` or `"string"`). * @param styleImplementation - The React implementation, including a `render` * component that receives the style value, a `contentRef`, and the editor. + * @param extensions - BlockNote extensions registered with this style. * @returns A style spec that can be passed to the editor's schema. */ export function createReactStyleSpec( styleConfig: T, styleImplementation: ReactCustomStyleImplementation, + extensions?: (Extension | ExtensionFactoryInstance)[], ) { const mark = Mark.create({ name: styleConfig.type, @@ -125,58 +129,62 @@ export function createReactStyleSpec( }, }); - return createInternalStyleSpec(styleConfig, { - ...styleImplementation, - mark, - render(value, editor) { - const Content = styleImplementation.render; - const output = renderToDOMSpec( - (ref) => ( - { - ref(element); - if (element) { - element.dataset.editable = ""; - } - }} - /> - ), - editor, - ); + return createInternalStyleSpec( + styleConfig, + { + ...styleImplementation, + mark, + render(value, editor) { + const Content = styleImplementation.render; + const output = renderToDOMSpec( + (ref) => ( + { + ref(element); + if (element) { + element.dataset.editable = ""; + } + }} + /> + ), + editor, + ); - return addStyleAttributes( - output, - styleConfig.type, - value, - styleConfig.propSchema, - ); - }, - toExternalHTML(value, editor) { - const Content = styleImplementation.render; - const output = renderToDOMSpec( - (ref) => ( - { - ref(element); - if (element) { - element.dataset.editable = ""; - } - }} - /> - ), - editor, - ); + return addStyleAttributes( + output, + styleConfig.type, + value, + styleConfig.propSchema, + ); + }, + toExternalHTML(value, editor) { + const Content = styleImplementation.render; + const output = renderToDOMSpec( + (ref) => ( + { + ref(element); + if (element) { + element.dataset.editable = ""; + } + }} + /> + ), + editor, + ); - return addStyleAttributes( - output, - styleConfig.type, - value, - styleConfig.propSchema, - ); + return addStyleAttributes( + output, + styleConfig.type, + value, + styleConfig.propSchema, + ); + }, }, - }); + extensions, + ); } From 78d1fa06c25f98304f94e7934d4471ec59ac7b18 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Sat, 24 Oct 2026 07:31:43 +0200 Subject: [PATCH 5/6] refactor: unify schema spec extension registration --- .../editor/managers/ExtensionManager/index.ts | 32 ++++++------------- .../core/src/schema/inlineContent/types.ts | 4 +-- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/packages/core/src/editor/managers/ExtensionManager/index.ts b/packages/core/src/editor/managers/ExtensionManager/index.ts index 7835f76026..76a8674cc4 100644 --- a/packages/core/src/editor/managers/ExtensionManager/index.ts +++ b/packages/core/src/editor/managers/ExtensionManager/index.ts @@ -105,30 +105,16 @@ export class ExtensionManager { this.addExtension(extension); } - // Add the extensions from blocks specs - for (const block of Object.values(this.editor.schema.blockSpecs)) { - for (const extension of block.extensions ?? []) { - this.addExtension(extension); - } - } - - // Add the extensions from inline content specs (the built-in text and link - // specs have none) - for (const inlineContent of Object.values( + // Add the extensions from block, inline content, and style specs + for (const specs of [ + this.editor.schema.blockSpecs, this.editor.schema.inlineContentSpecs, - )) { - if (!("extensions" in inlineContent)) { - continue; - } - for (const extension of inlineContent.extensions ?? []) { - this.addExtension(extension); - } - } - - // Add the extensions from style specs - for (const style of Object.values(this.editor.schema.styleSpecs)) { - for (const extension of style.extensions ?? []) { - this.addExtension(extension); + this.editor.schema.styleSpecs, + ]) { + for (const spec of Object.values(specs)) { + for (const extension of spec.extensions ?? []) { + this.addExtension(extension); + } } } } diff --git a/packages/core/src/schema/inlineContent/types.ts b/packages/core/src/schema/inlineContent/types.ts index bcc670fe88..7523471fcc 100644 --- a/packages/core/src/schema/inlineContent/types.ts +++ b/packages/core/src/schema/inlineContent/types.ts @@ -94,8 +94,8 @@ export type InlineContentSpec = { export type InlineContentSchema = Record; export type InlineContentSpecs = { - text: { config: "text"; implementation: undefined }; - link: { config: "link"; implementation: undefined }; + text: InlineContentSpec<"text"> & { implementation: undefined }; + link: InlineContentSpec<"link"> & { implementation: undefined }; } & Record>; export type InlineContentSchemaFromSpecs = { From c09b2616ce2521b3ed465f7b95d1a300fd79d567 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Sat, 24 Oct 2026 07:53:41 +0200 Subject: [PATCH 6/6] test: update style spec snapshot for extensions --- tests/src/unit/core/schema/__snapshots__/styles.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/src/unit/core/schema/__snapshots__/styles.json b/tests/src/unit/core/schema/__snapshots__/styles.json index f18eb02114..258208471c 100644 --- a/tests/src/unit/core/schema/__snapshots__/styles.json +++ b/tests/src/unit/core/schema/__snapshots__/styles.json @@ -4,6 +4,7 @@ "propSchema": "string", "type": "backgroundColor", }, + "extensions": undefined, "implementation": { "mark": null, "parse": [Function], @@ -16,6 +17,7 @@ "propSchema": "boolean", "type": "bold", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function], @@ -27,6 +29,7 @@ "propSchema": "boolean", "type": "code", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function], @@ -38,6 +41,7 @@ "propSchema": "string", "type": "fontSize", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function], @@ -49,6 +53,7 @@ "propSchema": "boolean", "type": "italic", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function], @@ -60,6 +65,7 @@ "propSchema": "boolean", "type": "small", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function], @@ -71,6 +77,7 @@ "propSchema": "boolean", "type": "strike", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function], @@ -82,6 +89,7 @@ "propSchema": "string", "type": "textColor", }, + "extensions": undefined, "implementation": { "mark": null, "parse": [Function], @@ -94,6 +102,7 @@ "propSchema": "boolean", "type": "underline", }, + "extensions": undefined, "implementation": { "mark": null, "render": [Function],