Skip to content

Commit 43859e6

Browse files
use refs for blocks (#424)
* use refs for blocks * update react htmlConversion test * Custom inline content and styles commands/copy & paste fixes (#425) * Fixed commands and internal copy/paste for inline content * Fixed internal copy/paste for styles * Small cleanup * fix some tests --------- Co-authored-by: yousefed <yousefdardiry@gmail.com> --------- Co-authored-by: Matthew Lipski <50169049+matthewlipski@users.noreply.github.com>
1 parent 82e70ec commit 43859e6

File tree

40 files changed

+382
-253
lines changed

40 files changed

+382
-253
lines changed
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p class="bn-inline-content"><span style="font-size: 18px">This is text with a custom fontSize</span></p>
1+
<p class="bn-inline-content"><span style="font-size: 18px" class="bn-style" data-style-type="fontSize" data-value="18px">This is text with a custom fontSize</span></p>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content"><span style="font-size: 18px">This is text with a custom fontSize</span></p></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content"><span style="font-size: 18px" class="bn-style" data-style-type="fontSize" data-value="18px">This is text with a custom fontSize</span></p></div></div></div></div>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p class="bn-inline-content">I enjoy working with<span>@Matthew</span></p>
1+
<p class="bn-inline-content">I enjoy working with<span class="bn-inline-content-section" data-inline-content-type="mention" data-user="Matthew">@Matthew</span></p>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content">I enjoy working with<span>@Matthew</span></p></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content">I enjoy working with<span class="bn-inline-content-section" data-inline-content-type="mention" data-user="Matthew">@Matthew</span></p></div></div></div></div>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p class="bn-inline-content"><small>This is a small text</small></p>
1+
<p class="bn-inline-content"><small class="bn-style" data-style-type="small">This is a small text</small></p>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content"><small>This is a small text</small></p></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content"><small class="bn-style" data-style-type="small">This is a small text</small></p></div></div></div></div>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p class="bn-inline-content">I love <span>#<span>BlockNote</span></span></p>
1+
<p class="bn-inline-content">I love <span class="bn-inline-content-section" data-inline-content-type="tag">#<span>BlockNote</span></span></p>
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content">I love <span>#<span>BlockNote</span></span></p></div></div></div></div>
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="paragraph"><p class="bn-inline-content">I love <span class="bn-inline-content-section" data-inline-content-type="tag">#<span>BlockNote</span></span></p></div></div></div></div>

packages/core/src/api/exporters/html/htmlConversion.test.ts

-3
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,6 @@ describe("Test HTML conversion", () => {
369369
for (const document of testCase.documents) {
370370
// eslint-disable-next-line no-loop-func
371371
it("Convert " + document.name + " to HTML", async () => {
372-
if (document.name !== "complex/misc") {
373-
return;
374-
}
375372
const nameSplit = document.name.split("/");
376373
await convertToHTMLAndCompareSnapshots(
377374
editor,

packages/core/src/api/testCases/cases/customInlineContent.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export const customInlineContentTestCases: EditorTestCases<
8787
user: "Matthew",
8888
},
8989
content: undefined,
90-
} as any,
90+
} as any, // TODO
9191
],
9292
},
9393
],
@@ -103,7 +103,7 @@ export const customInlineContentTestCases: EditorTestCases<
103103
type: "tag",
104104
// props: {},
105105
content: "BlockNote",
106-
} as any,
106+
} as any, // TODO
107107
],
108108
},
109109
],

packages/core/src/api/testCases/cases/defaultSchema.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ export const defaultSchemaTestCases: EditorTestCases<
2424
name: "paragraph/empty",
2525
blocks: [
2626
{
27-
type: "paragraph" as const,
27+
type: "paragraph",
2828
},
2929
],
3030
},
3131
{
3232
name: "paragraph/basic",
3333
blocks: [
3434
{
35-
type: "paragraph" as const,
35+
type: "paragraph",
3636
content: "Paragraph",
3737
},
3838
],
@@ -41,12 +41,12 @@ export const defaultSchemaTestCases: EditorTestCases<
4141
name: "paragraph/styled",
4242
blocks: [
4343
{
44-
type: "paragraph" as const,
44+
type: "paragraph",
4545
props: {
4646
textAlignment: "center",
4747
textColor: "orange",
4848
backgroundColor: "pink",
49-
} as const,
49+
},
5050
content: [
5151
{
5252
type: "text",
@@ -83,15 +83,15 @@ export const defaultSchemaTestCases: EditorTestCases<
8383
name: "paragraph/nested",
8484
blocks: [
8585
{
86-
type: "paragraph" as const,
86+
type: "paragraph",
8787
content: "Paragraph",
8888
children: [
8989
{
90-
type: "paragraph" as const,
90+
type: "paragraph",
9191
content: "Nested Paragraph 1",
9292
},
9393
{
94-
type: "paragraph" as const,
94+
type: "paragraph",
9595
content: "Nested Paragraph 2",
9696
},
9797
],
@@ -102,15 +102,15 @@ export const defaultSchemaTestCases: EditorTestCases<
102102
name: "image/button",
103103
blocks: [
104104
{
105-
type: "image" as const,
105+
type: "image",
106106
},
107107
],
108108
},
109109
{
110110
name: "image/basic",
111111
blocks: [
112112
{
113-
type: "image" as const,
113+
type: "image",
114114
props: {
115115
url: "exampleURL",
116116
caption: "Caption",
@@ -123,20 +123,20 @@ export const defaultSchemaTestCases: EditorTestCases<
123123
name: "image/nested",
124124
blocks: [
125125
{
126-
type: "image" as const,
126+
type: "image",
127127
props: {
128128
url: "exampleURL",
129129
caption: "Caption",
130130
width: 256,
131-
} as const,
131+
},
132132
children: [
133133
{
134-
type: "image" as const,
134+
type: "image",
135135
props: {
136136
url: "exampleURL",
137137
caption: "Caption",
138138
width: 256,
139-
} as const,
139+
},
140140
},
141141
],
142142
},

packages/core/src/extensions/Blocks/api/inlineContent/createSpec.ts

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { Node } from "@tiptap/core";
2+
import { ParseRule } from "@tiptap/pm/model";
23
import { nodeToCustomInlineContent } from "../../../../api/nodeConversions/nodeConversions";
34
import { propsToAttributes } from "../blocks/internal";
5+
import { Props } from "../blocks/types";
46
import { StyleSchema } from "../styles/types";
5-
import { createInlineContentSpecFromTipTapNode } from "./internal";
7+
import {
8+
addInlineContentAttributes,
9+
createInlineContentSpecFromTipTapNode,
10+
} from "./internal";
611
import {
712
InlineContentConfig,
813
InlineContentFromConfig,
@@ -37,6 +42,16 @@ export type CustomInlineContentImplementation<
3742
};
3843
};
3944

45+
export function getInlineContentParseRules(
46+
config: InlineContentConfig
47+
): ParseRule[] {
48+
return [
49+
{
50+
tag: `.bn-inline-content-section[data-inline-content-type="${config.type}"]`,
51+
},
52+
];
53+
}
54+
4055
export function createInlineContentSpec<
4156
T extends InlineContentConfig,
4257
S extends StyleSchema
@@ -57,6 +72,10 @@ export function createInlineContentSpec<
5772
return propsToAttributes(inlineContentConfig.propSchema);
5873
},
5974

75+
parseHTML() {
76+
return getInlineContentParseRules(inlineContentConfig);
77+
},
78+
6079
renderHTML({ node }) {
6180
const editor = this.options.editor;
6281

@@ -68,7 +87,15 @@ export function createInlineContentSpec<
6887
) as any as InlineContentFromConfig<T, S> // TODO: fix cast
6988
);
7089

71-
return output;
90+
return {
91+
dom: addInlineContentAttributes(
92+
output.dom,
93+
inlineContentConfig.type,
94+
node.attrs as Props<T["propSchema"]>,
95+
inlineContentConfig.propSchema
96+
),
97+
contentDOM: output.contentDOM,
98+
};
7299
},
73100
});
74101

packages/core/src/extensions/Blocks/api/inlineContent/internal.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,45 @@
11
import { Node } from "@tiptap/core";
2-
import { PropSchema } from "../blocks/types";
2+
import { camelToDataKebab } from "../blocks/internal";
3+
import { Props, PropSchema } from "../blocks/types";
34
import {
45
InlineContentConfig,
56
InlineContentImplementation,
67
InlineContentSchemaFromSpecs,
78
InlineContentSpec,
89
InlineContentSpecs,
910
} from "./types";
11+
import { mergeCSSClasses } from "../../../../shared/utils";
12+
13+
// Function that adds necessary classes and attributes to the `dom` element
14+
// returned from a custom inline content's 'render' function, to ensure no data
15+
// is lost on internal copy & paste.
16+
export function addInlineContentAttributes<
17+
IType extends string,
18+
PSchema extends PropSchema
19+
>(
20+
element: HTMLElement,
21+
inlineContentType: IType,
22+
inlineContentProps: Props<PSchema>,
23+
propSchema: PSchema
24+
): HTMLElement {
25+
// Sets inline content section class
26+
element.className = mergeCSSClasses(
27+
"bn-inline-content-section",
28+
element.className
29+
);
30+
// Sets content type attribute
31+
element.setAttribute("data-inline-content-type", inlineContentType);
32+
// Adds props as HTML attributes in kebab-case with "data-" prefix. Skips props
33+
// set to their default values.
34+
Object.entries(inlineContentProps)
35+
.filter(([prop, value]) => value !== propSchema[prop].default)
36+
.map(([prop, value]) => {
37+
return [camelToDataKebab(prop), value];
38+
})
39+
.forEach(([prop, value]) => element.setAttribute(prop, value));
40+
41+
return element;
42+
}
1043

1144
// This helper function helps to instantiate a InlineContentSpec with a
1245
// config and implementation that conform to the type of Config

packages/core/src/extensions/Blocks/api/styles/createSpec.ts

+27-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Mark } from "@tiptap/core";
2+
import { ParseRule } from "@tiptap/pm/model";
23
import { UnreachableCaseError } from "../../../../shared/utils";
3-
import { createInternalStyleSpec } from "./internal";
4+
import {
5+
addStyleAttributes,
6+
createInternalStyleSpec,
7+
stylePropsToAttributes,
8+
} from "./internal";
49
import { StyleConfig, StyleSpec } from "./types";
510

611
export type CustomStyleImplementation<T extends StyleConfig> = {
@@ -17,6 +22,14 @@ export type CustomStyleImplementation<T extends StyleConfig> = {
1722

1823
// TODO: support serialization
1924

25+
export function getStyleParseRules(config: StyleConfig): ParseRule[] {
26+
return [
27+
{
28+
tag: `.bn-style[data-style-type="${config.type}"]`,
29+
},
30+
];
31+
}
32+
2033
export function createStyleSpec<T extends StyleConfig>(
2134
styleConfig: T,
2235
styleImplementation: CustomStyleImplementation<T>
@@ -25,21 +38,11 @@ export function createStyleSpec<T extends StyleConfig>(
2538
name: styleConfig.type,
2639

2740
addAttributes() {
28-
if (styleConfig.propSchema === "boolean") {
29-
return {};
30-
}
31-
return {
32-
stringValue: {
33-
default: undefined,
34-
// TODO: parsing
41+
return stylePropsToAttributes(styleConfig.propSchema);
42+
},
3543

36-
// parseHTML: (element) =>
37-
// element.getAttribute(`data-${styleConfig.type}`),
38-
// renderHTML: (attributes) => ({
39-
// [`data-${styleConfig.type}`]: attributes.stringValue,
40-
// }),
41-
},
42-
};
44+
parseHTML() {
45+
return getStyleParseRules(styleConfig);
4346
},
4447

4548
renderHTML({ mark }) {
@@ -58,7 +61,15 @@ export function createStyleSpec<T extends StyleConfig>(
5861
}
5962

6063
// const renderResult = styleImplementation.render();
61-
return renderResult;
64+
return {
65+
dom: addStyleAttributes(
66+
renderResult.dom,
67+
styleConfig.type,
68+
mark.attrs.stringValue,
69+
styleConfig.propSchema
70+
),
71+
contentDOM: renderResult.contentDOM,
72+
};
6273
},
6374
});
6475

0 commit comments

Comments
 (0)