Skip to content

Commit ddd3adf

Browse files
fix: External HTML parse/export cases (#1991)
* WIP parse export fixes * Fixed color style parsing * Made default props get exported to inline styles in external HTML * Small fix * Updated snapshots * Updated snapshot * Updated e2e snaps * Fixed snapshot??? * fix: External HTML parse/export cases cont. (#1998) * Added plugin for collab schema migration * Moved color props to block content nodes * Fixed export for list items and updated unit test snapshots * Rename * Updated test snapshots * Implemented PR feedback * Fixed build
1 parent cd788ad commit ddd3adf

File tree

237 files changed

+3507
-2232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

237 files changed

+3507
-2232
lines changed

examples/03-ui-components/13-custom-ui/src/MUIFormattingToolbar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ function MUITextAlignButton(props: {
303303
editor.focus();
304304
}, [editor, props.textAlignment]);
305305

306+
if (!activeTextAlignment) {
307+
return null;
308+
}
309+
306310
return (
307311
<MUIToolbarButton
308312
tooltip={tooltip}

packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,7 @@ function serializeBlock<
232232
}
233233
fragment.append(list);
234234
}
235-
const li = doc.createElement("li");
236-
li.append(elementFragment);
237-
fragment.lastChild!.appendChild(li);
235+
fragment.lastChild!.appendChild(elementFragment);
238236
} else {
239237
fragment.append(elementFragment);
240238
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
createBlockConfig,
55
createBlockSpec,
66
} from "../../schema/index.js";
7-
import { defaultProps } from "../defaultProps.js";
7+
import { defaultProps, parseDefaultProps } from "../defaultProps.js";
88
import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js";
99
import { createFileBlockWrapper } from "../File/helpers/render/createFileBlockWrapper.js";
1010
import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js";
@@ -56,7 +56,12 @@ export const audioParse =
5656
return undefined;
5757
}
5858

59-
return parseAudioElement(element as HTMLAudioElement);
59+
const { backgroundColor } = parseDefaultProps(element);
60+
61+
return {
62+
...parseAudioElement(element as HTMLAudioElement),
63+
backgroundColor,
64+
};
6065
}
6166

6267
if (element.tagName === "FIGURE") {
@@ -67,8 +72,11 @@ export const audioParse =
6772

6873
const { targetElement, caption } = parsedFigure;
6974

75+
const { backgroundColor } = parseDefaultProps(element);
76+
7077
return {
7178
...parseAudioElement(targetElement as HTMLAudioElement),
79+
backgroundColor,
7280
caption,
7381
};
7482
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createBlockConfig, createBlockSpec } from "../../schema/index.js";
2-
import { defaultProps } from "../defaultProps.js";
2+
import { defaultProps, parseDefaultProps } from "../defaultProps.js";
33
import { parseEmbedElement } from "./helpers/parse/parseEmbedElement.js";
44
import { parseFigureElement } from "./helpers/parse/parseFigureElement.js";
55
import { createFileBlockWrapper } from "./helpers/render/createFileBlockWrapper.js";
@@ -37,7 +37,12 @@ export const fileParse = () => (element: HTMLElement) => {
3737
return undefined;
3838
}
3939

40-
return parseEmbedElement(element as HTMLEmbedElement);
40+
const { backgroundColor } = parseDefaultProps(element);
41+
42+
return {
43+
...parseEmbedElement(element as HTMLEmbedElement),
44+
backgroundColor,
45+
};
4146
}
4247

4348
if (element.tagName === "FIGURE") {
@@ -48,8 +53,11 @@ export const fileParse = () => (element: HTMLElement) => {
4853

4954
const { targetElement, caption } = parsedFigure;
5055

56+
const { backgroundColor } = parseDefaultProps(element);
57+
5158
return {
5259
...parseEmbedElement(targetElement as HTMLEmbedElement),
60+
backgroundColor,
5361
caption,
5462
};
5563
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createBlockConfig, createBlockSpec } from "../../schema/index.js";
22
import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js";
3-
import { defaultProps } from "../defaultProps.js";
3+
import {
4+
addDefaultPropsExternalHTML,
5+
defaultProps,
6+
parseDefaultProps,
7+
} from "../defaultProps.js";
48
import { createToggleWrapper } from "../ToggleWrapper/createToggleWrapper.js";
59

610
const HEADING_LEVELS = [1, 2, 3, 4, 5, 6] as const;
@@ -65,6 +69,7 @@ export const createHeadingBlockSpec = createBlockSpec(
6569
}
6670

6771
return {
72+
...parseDefaultProps(e),
6873
level,
6974
};
7075
},
@@ -83,6 +88,7 @@ export const createHeadingBlockSpec = createBlockSpec(
8388
},
8489
toExternalHTML(block) {
8590
const dom = document.createElement(`h${block.props.level}`);
91+
addDefaultPropsExternalHTML(block.props, dom);
8692

8793
return {
8894
dom,

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
createBlockConfig,
55
createBlockSpec,
66
} from "../../schema/index.js";
7-
import { defaultProps } from "../defaultProps.js";
7+
import { defaultProps, parseDefaultProps } from "../defaultProps.js";
88
import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js";
99
import { createResizableFileBlockWrapper } from "../File/helpers/render/createResizableFileBlockWrapper.js";
1010
import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js";
@@ -62,7 +62,12 @@ export const imageParse =
6262
return undefined;
6363
}
6464

65-
return parseImageElement(element as HTMLImageElement);
65+
const { backgroundColor } = parseDefaultProps(element);
66+
67+
return {
68+
...parseImageElement(element as HTMLImageElement),
69+
backgroundColor,
70+
};
6671
}
6772

6873
if (element.tagName === "FIGURE") {
@@ -73,8 +78,11 @@ export const imageParse =
7378

7479
const { targetElement, caption } = parsedFigure;
7580

81+
const { backgroundColor } = parseDefaultProps(element);
82+
7683
return {
7784
...parseImageElement(targetElement as HTMLImageElement),
85+
backgroundColor,
7886
caption,
7987
};
8088
}

packages/core/src/blocks/ListItem/BulletListItem/block.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js";
22
import { createBlockConfig, createBlockSpec } from "../../../schema/index.js";
3-
import { defaultProps } from "../../defaultProps.js";
3+
import {
4+
addDefaultPropsExternalHTML,
5+
defaultProps,
6+
parseDefaultProps,
7+
} from "../../defaultProps.js";
48
import { handleEnter } from "../../utils/listItemEnterHandler.js";
59
import { getListItemContent } from "../getListItemContent.js";
610

@@ -27,23 +31,23 @@ export const createBulletListItemBlockSpec = createBlockSpec(
2731
},
2832
parse(element) {
2933
if (element.tagName !== "LI") {
30-
return false;
34+
return undefined;
3135
}
3236

3337
const parent = element.parentElement;
3438

3539
if (parent === null) {
36-
return false;
40+
return undefined;
3741
}
3842

3943
if (
4044
parent.tagName === "UL" ||
4145
(parent.tagName === "DIV" && parent.parentElement?.tagName === "UL")
4246
) {
43-
return {};
47+
return parseDefaultProps(element);
4448
}
4549

46-
return false;
50+
return undefined;
4751
},
4852
// As `li` elements can contain multiple paragraphs, we need to merge their contents
4953
// into a single one so that ProseMirror can parse everything correctly.
@@ -60,6 +64,17 @@ export const createBulletListItemBlockSpec = createBlockSpec(
6064
contentDOM: dom,
6165
};
6266
},
67+
toExternalHTML(block) {
68+
const li = document.createElement("li");
69+
const p = document.createElement("p");
70+
addDefaultPropsExternalHTML(block.props, li);
71+
li.appendChild(p);
72+
73+
return {
74+
dom: li,
75+
contentDOM: p,
76+
};
77+
},
6378
},
6479
[
6580
createBlockNoteExtension({

packages/core/src/blocks/ListItem/CheckListItem/block.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js";
22
import { createBlockConfig, createBlockSpec } from "../../../schema/index.js";
3-
import { defaultProps } from "../../defaultProps.js";
3+
import {
4+
addDefaultPropsExternalHTML,
5+
defaultProps,
6+
parseDefaultProps,
7+
} from "../../defaultProps.js";
48
import { handleEnter } from "../../utils/listItemEnterHandler.js";
59
import { getListItemContent } from "../getListItemContent.js";
610

@@ -30,22 +34,22 @@ export const createCheckListItemBlockSpec = createBlockSpec(
3034
if (element.tagName === "input") {
3135
// Ignore if we already parsed an ancestor list item to avoid double-parsing.
3236
if (element.closest("[data-content-type]") || element.closest("li")) {
33-
return;
37+
return undefined;
3438
}
3539

3640
if ((element as HTMLInputElement).type === "checkbox") {
3741
return { checked: (element as HTMLInputElement).checked };
3842
}
39-
return;
43+
return undefined;
4044
}
4145
if (element.tagName !== "LI") {
42-
return;
46+
return undefined;
4347
}
4448

4549
const parent = element.parentElement;
4650

4751
if (parent === null) {
48-
return;
52+
return undefined;
4953
}
5054

5155
if (
@@ -57,10 +61,10 @@ export const createCheckListItemBlockSpec = createBlockSpec(
5761
null;
5862

5963
if (checkbox === null) {
60-
return;
64+
return undefined;
6165
}
6266

63-
return { checked: checkbox.checked };
67+
return { ...parseDefaultProps(element), checked: checkbox.checked };
6468
}
6569

6670
return;
@@ -69,14 +73,17 @@ export const createCheckListItemBlockSpec = createBlockSpec(
6973
// into a single one so that ProseMirror can parse everything correctly.
7074
parseContent: ({ el, schema }) =>
7175
getListItemContent(el, schema, "checkListItem"),
72-
render(block) {
76+
render(block, editor) {
7377
const dom = document.createDocumentFragment();
7478
const checkbox = document.createElement("input");
7579
checkbox.type = "checkbox";
7680
checkbox.checked = block.props.checked;
7781
if (block.props.checked) {
7882
checkbox.setAttribute("checked", "");
7983
}
84+
checkbox.addEventListener("change", () => {
85+
editor.updateBlock(block, { props: { checked: !block.props.checked } });
86+
});
8087
// We use a <p> tag, because for <li> tags we'd need a <ul> element to put
8188
// them in to be semantically correct, which we can't have due to the
8289
// schema.
@@ -90,6 +97,28 @@ export const createCheckListItemBlockSpec = createBlockSpec(
9097
contentDOM: paragraph,
9198
};
9299
},
100+
toExternalHTML(block) {
101+
const dom = document.createElement("li");
102+
const checkbox = document.createElement("input");
103+
checkbox.type = "checkbox";
104+
checkbox.checked = block.props.checked;
105+
if (block.props.checked) {
106+
checkbox.setAttribute("checked", "");
107+
}
108+
// We use a <p> tag, because for <li> tags we'd need a <ul> element to put
109+
// them in to be semantically correct, which we can't have due to the
110+
// schema.
111+
const paragraph = document.createElement("p");
112+
addDefaultPropsExternalHTML(block.props, dom);
113+
114+
dom.appendChild(checkbox);
115+
dom.appendChild(paragraph);
116+
117+
return {
118+
dom,
119+
contentDOM: paragraph,
120+
};
121+
},
93122
runsBefore: ["bulletListItem"],
94123
},
95124
[

packages/core/src/blocks/ListItem/NumberedListItem/block.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js";
22
import { createBlockConfig, createBlockSpec } from "../../../schema/index.js";
3-
import { defaultProps } from "../../defaultProps.js";
3+
import {
4+
addDefaultPropsExternalHTML,
5+
defaultProps,
6+
parseDefaultProps,
7+
} from "../../defaultProps.js";
48
import { handleEnter } from "../../utils/listItemEnterHandler.js";
59
import { getListItemContent } from "../getListItemContent.js";
610
import { NumberedListIndexingDecorationPlugin } from "./IndexingPlugin.js";
@@ -29,23 +33,34 @@ export const createNumberedListItemBlockSpec = createBlockSpec(
2933
},
3034
parse(element) {
3135
if (element.tagName !== "LI") {
32-
return false;
36+
return undefined;
3337
}
3438

3539
const parent = element.parentElement;
3640

3741
if (parent === null) {
38-
return false;
42+
return undefined;
3943
}
4044

4145
if (
4246
parent.tagName === "OL" ||
4347
(parent.tagName === "DIV" && parent.parentElement?.tagName === "OL")
4448
) {
45-
return {};
49+
const startIndex = parseInt(parent.getAttribute("start") || "1");
50+
51+
const defaultProps = parseDefaultProps(element);
52+
53+
if (element.previousElementSibling || startIndex === 1) {
54+
return defaultProps;
55+
}
56+
57+
return {
58+
...defaultProps,
59+
start: startIndex,
60+
};
4661
}
4762

48-
return false;
63+
return undefined;
4964
},
5065
// As `li` elements can contain multiple paragraphs, we need to merge their contents
5166
// into a single one so that ProseMirror can parse everything correctly.
@@ -62,6 +77,17 @@ export const createNumberedListItemBlockSpec = createBlockSpec(
6277
contentDOM: dom,
6378
};
6479
},
80+
toExternalHTML(block) {
81+
const li = document.createElement("li");
82+
const p = document.createElement("p");
83+
addDefaultPropsExternalHTML(block.props, li);
84+
li.appendChild(p);
85+
86+
return {
87+
dom: li,
88+
contentDOM: p,
89+
};
90+
},
6591
},
6692
[
6793
createBlockNoteExtension({
@@ -70,10 +96,11 @@ export const createNumberedListItemBlockSpec = createBlockSpec(
7096
{
7197
find: new RegExp(`^(\\d+)\\.\\s$`),
7298
replace({ match }) {
99+
const start = parseInt(match[1]);
73100
return {
74101
type: "numberedListItem",
75102
props: {
76-
start: parseInt(match[1]),
103+
start: start !== 1 ? start : undefined,
77104
},
78105
};
79106
},

0 commit comments

Comments
 (0)