Skip to content

Commit b16a9ac

Browse files
committed
refactor: remove the strongly typed tiptap node abstraction
1 parent 59a7a63 commit b16a9ac

File tree

8 files changed

+115
-154
lines changed

8 files changed

+115
-154
lines changed

packages/core/src/blocks/Table/block.ts

Lines changed: 52 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ import { TableHeader } from "@tiptap/extension-table-header";
44
import { DOMParser, Fragment, Node as PMNode, Schema } from "prosemirror-model";
55
import { TableView } from "prosemirror-tables";
66
import { NodeView } from "prosemirror-view";
7-
import {
8-
BlockSpec,
9-
createBlockSpecFromStronglyTypedTiptapNode,
10-
createStronglyTypedTiptapNode,
11-
} from "../../schema/index.js";
127
import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js";
8+
import { createBlockSpecFromTiptapNode } from "../../schema/index.js";
139
import { mergeCSSClasses } from "../../util/browser.js";
1410
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
1511
import { defaultProps } from "../defaultProps.js";
@@ -19,7 +15,7 @@ export const tablePropSchema = {
1915
textColor: defaultProps.textColor,
2016
};
2117

22-
export const TableNode = createStronglyTypedTiptapNode({
18+
export const TableNode = Node.create({
2319
name: "table",
2420
content: "tableRow+",
2521
group: "blockContent",
@@ -136,7 +132,7 @@ export const TableNode = createStronglyTypedTiptapNode({
136132
},
137133
});
138134

139-
const TableParagraphNode = createStronglyTypedTiptapNode({
135+
const TableParagraphNode = Node.create({
140136
name: "tableParagraph",
141137
group: "tableContent",
142138
content: "inline*",
@@ -246,59 +242,52 @@ function parseTableContent(node: HTMLElement, schema: Schema) {
246242
}
247243

248244
export const createTableBlockSpec = () =>
249-
createBlockSpecFromStronglyTypedTiptapNode(TableNode, tablePropSchema, [
250-
createBlockNoteExtension({
251-
key: "table-extensions",
252-
tiptapExtensions: [
253-
TableExtension,
254-
TableParagraphNode,
255-
TableHeader.extend({
256-
/**
257-
* We allow table headers and cells to have multiple tableContent nodes because
258-
* when merging cells, prosemirror-tables will concat the contents of the cells naively.
259-
* This would cause that content to overflow into other cells when prosemirror tries to enforce the cell structure.
260-
*
261-
* So, we manually fix this up when reading back in the `nodeToBlock` and only ever place a single tableContent back into the cell.
262-
*/
263-
content: "tableContent+",
264-
parseHTML() {
265-
return [
266-
{
267-
tag: "th",
268-
// As `th` elements can contain multiple paragraphs, we need to merge their contents
269-
// into a single one so that ProseMirror can parse everything correctly.
270-
getContent: (node, schema) =>
271-
parseTableContent(node as HTMLElement, schema),
272-
},
273-
];
274-
},
275-
}),
276-
TableCell.extend({
277-
content: "tableContent+",
278-
parseHTML() {
279-
return [
280-
{
281-
tag: "td",
282-
// As `td` elements can contain multiple paragraphs, we need to merge their contents
283-
// into a single one so that ProseMirror can parse everything correctly.
284-
getContent: (node, schema) =>
285-
parseTableContent(node as HTMLElement, schema),
286-
},
287-
];
288-
},
289-
}),
290-
TableRowNode,
291-
],
292-
}),
293-
]) as unknown as BlockSpec<
294-
"table",
295-
{
296-
textColor: {
297-
default: "default";
298-
};
299-
}
300-
> & {
301-
config: {
302-
content: "table";
303-
};
304-
};
245+
createBlockSpecFromTiptapNode(
246+
{ node: TableNode, type: "table", content: "table" },
247+
tablePropSchema,
248+
[
249+
createBlockNoteExtension({
250+
key: "table-extensions",
251+
tiptapExtensions: [
252+
TableExtension,
253+
TableParagraphNode,
254+
TableHeader.extend({
255+
/**
256+
* We allow table headers and cells to have multiple tableContent nodes because
257+
* when merging cells, prosemirror-tables will concat the contents of the cells naively.
258+
* This would cause that content to overflow into other cells when prosemirror tries to enforce the cell structure.
259+
*
260+
* So, we manually fix this up when reading back in the `nodeToBlock` and only ever place a single tableContent back into the cell.
261+
*/
262+
content: "tableContent+",
263+
parseHTML() {
264+
return [
265+
{
266+
tag: "th",
267+
// As `th` elements can contain multiple paragraphs, we need to merge their contents
268+
// into a single one so that ProseMirror can parse everything correctly.
269+
getContent: (node, schema) =>
270+
parseTableContent(node as HTMLElement, schema),
271+
},
272+
];
273+
},
274+
}),
275+
TableCell.extend({
276+
content: "tableContent+",
277+
parseHTML() {
278+
return [
279+
{
280+
tag: "td",
281+
// As `td` elements can contain multiple paragraphs, we need to merge their contents
282+
// into a single one so that ProseMirror can parse everything correctly.
283+
getContent: (node, schema) =>
284+
parseTableContent(node as HTMLElement, schema),
285+
},
286+
];
287+
},
288+
}),
289+
TableRowNode,
290+
],
291+
}),
292+
],
293+
);

packages/core/src/schema/blocks/createSpec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { Editor } from "@tiptap/core";
1+
import { Editor, Node } from "@tiptap/core";
22
import { DOMParser, Fragment, TagParseRule } from "@tiptap/pm/model";
33
import { NodeView } from "@tiptap/pm/view";
44
import { mergeParagraphs } from "../../blocks/defaultBlockHelpers.js";
55
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
66
import { PropSchema } from "../propTypes.js";
77
import {
8-
createStronglyTypedTiptapNode,
9-
createTypedBlockSpec,
108
getBlockFromPos,
119
propsToAttributes,
1210
wrapInBlockStructure,
1311
} from "./internal.js";
14-
import { BlockConfig, BlockImplementation, BlockSpec } from "./types.js";
12+
import {
13+
BlockConfig,
14+
BlockImplementation,
15+
BlockSpec,
16+
LooseBlockSpec,
17+
} from "./types.js";
1518

1619
// Function that causes events within non-selectable blocks to be handled by the
1720
// browser instead of the editor.
@@ -130,13 +133,10 @@ export function addNodeAndExtensionsToSpec<
130133
blockImplementation: BlockImplementation<TName, TProps, TContent>,
131134
extensions?: BlockNoteExtension<any>[],
132135
priority?: number,
133-
) {
136+
): LooseBlockSpec<TName, TProps, TContent> {
134137
const node =
135-
// Only table already has a node defined, so we just use that node instead of wrapping it from scratch
136-
((blockImplementation as any).node as ReturnType<
137-
typeof createStronglyTypedTiptapNode
138-
>) ||
139-
createStronglyTypedTiptapNode({
138+
((blockImplementation as any).node as Node) ||
139+
Node.create({
140140
name: blockConfig.type,
141141
content: (blockConfig.content === "inline"
142142
? "inline*"
@@ -215,9 +215,9 @@ export function addNodeAndExtensionsToSpec<
215215
);
216216
}
217217

218-
return createTypedBlockSpec(
219-
blockConfig,
220-
{
218+
return {
219+
config: blockConfig,
220+
implementation: {
221221
node,
222222
render(block, editor) {
223223
const blockContentDOMAttributes =
@@ -254,7 +254,7 @@ export function addNodeAndExtensionsToSpec<
254254
},
255255
},
256256
extensions,
257-
);
257+
};
258258
}
259259

260260
/**

packages/core/src/schema/blocks/internal.ts

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { Attribute, Attributes, Editor, Node, NodeConfig } from "@tiptap/core";
1+
import { Attribute, Attributes, Editor, Node } from "@tiptap/core";
22
import { defaultBlockToHTML } from "../../blocks/defaultBlockHelpers.js";
33
import { inheritedProps } from "../../blocks/defaultProps.js";
44
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
5+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
56
import { mergeCSSClasses } from "../../util/browser.js";
67
import { camelToDataKebab } from "../../util/string.js";
78
import { InlineContentSchema } from "../inlineContent/types.js";
89
import { PropSchema, Props } from "../propTypes.js";
910
import { StyleSchema } from "../styles/types.js";
1011
import {
1112
BlockConfig,
12-
BlockImplementation,
1313
BlockSchemaWithBlock,
14-
BlockSpec,
14+
LooseBlockSpec,
1515
SpecificBlock,
1616
} from "./types.js";
17-
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
1817

1918
// Function that uses the 'propSchema' of a blockConfig to create a TipTap
2019
// node's `addAttributes` property.
@@ -199,69 +198,29 @@ export function wrapInBlockStructure<
199198
};
200199
}
201200

202-
// Helper type to keep track of the `name` and `content` properties after calling Node.create.
203-
type StronglyTypedTipTapNode<
204-
Name extends string,
205-
Content extends
206-
| "inline*"
207-
| "tableRow+"
208-
| "blockContainer+"
209-
| "column column+"
210-
| "",
211-
> = Node & { name: Name; config: { content: Content } };
212-
213-
export function createStronglyTypedTiptapNode<
214-
Name extends string,
215-
Content extends
216-
| "inline*"
217-
| "tableRow+"
218-
| "blockContainer+"
219-
| "column column+"
220-
| "",
221-
>(config: NodeConfig & { name: Name; content: Content }) {
222-
return Node.create(config) as StronglyTypedTipTapNode<Name, Content>; // force re-typing (should be safe as it's type-checked from the config)
223-
}
224-
225-
// This helper function helps to instantiate a blockspec with a
226-
// config and implementation that conform to the type of Config
227-
export function createTypedBlockSpec<T extends BlockConfig>(
228-
config: T,
229-
implementation: BlockImplementation<
230-
T["type"],
231-
T["propSchema"],
232-
T["content"]
233-
> & {
201+
export function createBlockSpecFromTiptapNode<
202+
const T extends {
234203
node: Node;
204+
type: string;
205+
content: "inline" | "table" | "none";
235206
},
207+
P extends PropSchema,
208+
>(
209+
config: T,
210+
propSchema: P,
236211
extensions?: BlockNoteExtension<any>[],
237-
): BlockSpec<T["type"], T["propSchema"], T["content"]> {
212+
): LooseBlockSpec<T["type"], P, T["content"]> {
238213
return {
239-
config,
240-
implementation,
241-
extensions,
242-
};
243-
}
244-
245-
export function createBlockSpecFromStronglyTypedTiptapNode<
246-
T extends Node,
247-
P extends PropSchema,
248-
>(node: T, propSchema: P, extensions?: BlockNoteExtension<any>[]) {
249-
return createTypedBlockSpec(
250-
{
251-
type: node.name as T["name"],
252-
content:
253-
node.config.content === "inline*"
254-
? "inline"
255-
: node.config.content === "tableRow+"
256-
? "table"
257-
: "none",
214+
config: {
215+
type: config.type as T["type"],
216+
content: config.content,
258217
propSchema,
259218
},
260-
{
261-
node,
262-
render: defaultBlockToHTML as any,
263-
toExternalHTML: defaultBlockToHTML as any,
219+
implementation: {
220+
node: config.node,
221+
render: defaultBlockToHTML,
222+
toExternalHTML: defaultBlockToHTML,
264223
},
265224
extensions,
266-
);
225+
};
267226
}

packages/core/src/schema/blocks/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** Define the main block types **/
22
// import { Extension, Node } from "@tiptap/core";
3-
import type { NodeViewRendererProps } from "@tiptap/core";
3+
import type { Node, NodeViewRendererProps } from "@tiptap/core";
44
import type { Fragment, Schema } from "prosemirror-model";
55
import type { ViewMutationRecord } from "prosemirror-view";
66
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
@@ -142,6 +142,8 @@ export type LooseBlockSpec<
142142
contentDOM?: HTMLElement;
143143
}
144144
| undefined;
145+
146+
node: Node;
145147
};
146148
extensions?: BlockNoteExtension<any>[];
147149
};

packages/react/src/schema/ReactInlineContentSpec.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import {
22
addInlineContentAttributes,
33
addInlineContentKeyboardShortcuts,
4+
BlockNoteEditor,
45
camelToDataKebab,
56
createInternalInlineContentSpec,
6-
createStronglyTypedTiptapNode,
77
CustomInlineContentConfig,
88
getInlineContentParseRules,
99
InlineContentFromConfig,
10+
InlineContentSchemaWithInlineContent,
1011
inlineContentToNodes,
1112
nodeToCustomInlineContent,
1213
PartialCustomInlineContentFromConfig,
1314
Props,
1415
PropSchema,
1516
propsToAttributes,
1617
StyleSchema,
17-
BlockNoteEditor,
18-
InlineContentSchemaWithInlineContent,
1918
} from "@blocknote/core";
19+
import { Node } from "@tiptap/core";
2020
import {
2121
NodeViewProps,
2222
NodeViewWrapper,
@@ -106,7 +106,7 @@ export function createReactInlineContentSpec<
106106
inlineContentConfig: T,
107107
inlineContentImplementation: ReactInlineContentImplementation<T, S>,
108108
) {
109-
const node = createStronglyTypedTiptapNode({
109+
const node = Node.create({
110110
name: inlineContentConfig.type as T["type"],
111111
inline: true,
112112
group: "inline",

0 commit comments

Comments
 (0)