Skip to content

feat: Inline style props in external HTML #1636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ function serializeBlock<
// We wrap the output in an `li` element for list items, and so we want to
// add the attributes to that element instead as it is the "root".
if (!listType) {
// TODO: This is specifically for default blocks, as default props get
// rendered out to inline styles instead of `data-*` attributes for
// external HTML. Will need to revisit this when we convert default
// blocks to use the custom block API.
const style = ret.dom.getAttribute("style");
if (style) {
(ret.dom.firstChild! as HTMLElement).setAttribute("style", style);
}

for (const attr of blockContentDataAttributes) {
(ret.dom.firstChild! as HTMLElement).setAttribute(
attr.name,
Expand Down Expand Up @@ -179,9 +188,20 @@ function serializeBlock<
fragment.append(list);
}
const li = doc.createElement("li");

// TODO: This is specifically for default blocks, as default props get
// rendered out to inline styles instead of `data-*` attributes for
// external HTML. Will need to revisit this when we convert default
// blocks to use the custom block API.
const style = ret.dom.getAttribute("style");
if (style) {
li.setAttribute("style", style);
}

for (const attr of blockContentDataAttributes) {
li.setAttribute(attr.name, attr.value);
}

li.append(elementFragment);
fragment.lastChild!.appendChild(li);
} else {
Expand Down
28 changes: 26 additions & 2 deletions packages/core/src/blocks/defaultBlockHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,17 @@ export function createDefaultBlockDOMOutputSpec(

// Function used to convert default blocks to HTML. It uses the corresponding
// node's `renderHTML` method to do the conversion by using a default
// `DOMSerializer`.
// `DOMSerializer`. The `external` flag is used to modify the resulting HTML for
// external use. This just involves changing props being rendered from `data-*`
// attributes to inline styles.
export const defaultBlockToHTML = <
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
>(
block: BlockNoDefaults<BSchema, I, S>,
editor: BlockNoteEditor<BSchema, I, S>
editor: BlockNoteEditor<BSchema, I, S>,
external = false
): {
dom: HTMLElement;
contentDOM?: HTMLElement;
Expand Down Expand Up @@ -90,6 +93,27 @@ export const defaultBlockToHTML = <
);
}

// TODO: This is obviously pretty hacky - will need to revisit this when we
// convert default blocks to use the custom block API.
if (external) {
const dom = renderSpec.dom as HTMLElement;

if (dom.hasAttribute("data-background-color")) {
dom.style.backgroundColor = dom.getAttribute("data-background-color")!;
dom.removeAttribute("data-background-color");
}

if (dom.hasAttribute("data-text-color")) {
dom.style.color = dom.getAttribute("data-text-color")!;
dom.removeAttribute("data-text-color");
}

if (dom.hasAttribute("data-text-alignment")) {
dom.style.textAlign = dom.getAttribute("data-text-alignment")!;
dom.removeAttribute("data-text-alignment");
}
}

return renderSpec as {
dom: HTMLElement;
contentDOM?: HTMLElement;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/schema/blocks/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ export function createBlockSpecFromStronglyTypedTiptapNode<
node,
requiredExtensions,
toInternalHTML: defaultBlockToHTML,
toExternalHTML: defaultBlockToHTML,
toExternalHTML: (block, editor) =>
defaultBlockToHTML(block, editor, true),
// parse: () => undefined, // parse rules are in node already
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`Test ServerBlockNoteEditor > converts to HTML (blocksToFullHTML) 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="heading" data-text-color="yellow" data-background-color="blue" data-text-alignment="right" data-level="2"><h2 class="bn-inline-content"><strong><u>Heading </u></strong><em><s>2</s></em></h2></div><div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="2"><div class="bn-block" data-node-type="blockContainer" data-id="2"><div class="bn-block-content" data-content-type="paragraph" data-background-color="red"><p class="bn-inline-content">Paragraph</p></div></div></div><div class="bn-block-outer" data-node-type="blockOuter" data-id="3"><div class="bn-block" data-node-type="blockContainer" data-id="3"><div class="bn-block-content" data-content-type="bulletListItem"><p class="bn-inline-content">list item</p></div></div></div></div></div></div><div class="bn-block-outer" data-node-type="blockOuter" data-id="4"><div class="bn-block" data-node-type="blockContainer" data-id="4"><div class="bn-block-content" data-content-type="image" data-name="Example" data-url="exampleURL" data-caption="Caption" data-preview-width="256" data-file-block=""><div class="bn-file-block-content-wrapper" style="width: 256px;"><div class="bn-visual-media-wrapper"><img class="bn-visual-media" src="exampleURL" alt="Example" draggable="false"></div><p class="bn-file-caption">Caption</p></div></div></div></div><div class="bn-block-outer" data-node-type="blockOuter" data-id="5"><div class="bn-block" data-node-type="blockContainer" data-id="5"><div class="bn-block-content" data-content-type="image" data-name="Example" data-url="exampleURL" data-caption="Caption" data-show-preview="false" data-preview-width="256" data-file-block=""><div class="bn-file-block-content-wrapper"><div class="bn-file-name-with-icon"><div class="bn-file-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M3 8L9.00319 2H19.9978C20.5513 2 21 2.45531 21 2.9918V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5501 3 20.9932V8ZM10 4V9H5V20H19V4H10Z"></path></svg></div><p class="bn-file-name">Example</p></div><p class="bn-file-caption">Caption</p></div></div></div></div></div>"`;

exports[`Test ServerBlockNoteEditor > converts to and from HTML (blocksToHTMLLossy) 1`] = `"<h2 data-text-color="yellow" data-background-color="blue" data-text-alignment="right" data-level="2"><strong><u>Heading </u></strong><em><s>2</s></em></h2><p data-background-color="red">Paragraph</p><ul><li><p>list item</p></li></ul><figure data-name="Example" data-url="exampleURL" data-caption="Caption" data-preview-width="256"><img src="exampleURL" alt="Example" width="256"><figcaption>Caption</figcaption></figure><div data-name="Example" data-url="exampleURL" data-caption="Caption" data-show-preview="false" data-preview-width="256"><a href="exampleURL">Example</a><p>Caption</p></div>"`;
exports[`Test ServerBlockNoteEditor > converts to and from HTML (blocksToHTMLLossy) 1`] = `"<h2 style="background-color: blue; color: yellow; text-align: right;" data-level="2"><strong><u>Heading </u></strong><em><s>2</s></em></h2><p style="background-color: red;">Paragraph</p><ul><li><p>list item</p></li></ul><figure data-name="Example" data-url="exampleURL" data-caption="Caption" data-preview-width="256"><img src="exampleURL" alt="Example" width="256"><figcaption>Caption</figcaption></figure><div data-name="Example" data-url="exampleURL" data-caption="Caption" data-show-preview="false" data-preview-width="256"><a href="exampleURL">Example</a><p>Caption</p></div>"`;

exports[`Test ServerBlockNoteEditor > converts to and from HTML (blocksToHTMLLossy) 2`] = `
[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<p data-text-color="red">Paragraph 1</p>
<p style="color: red;">Paragraph 1</p>
<h2 data-level="2">Heading 1</h2>
<ol start="2">
<li data-start="2">
<p>Numbered List Item 1</p>
</li>
</ol>
<ul>
<li data-background-color="red">
<li style="background-color: red;">
<p>Bullet List Item 1</p>
</li>
<li data-checked="true">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<h2 data-text-color="yellow" data-background-color="blue" data-text-alignment="right" data-level="2">
<h2 style="background-color: blue; color: yellow; text-align: right;" data-level="2">
<strong>
<u>Heading</u>
</strong>
<em>
<s>2</s>
</em>
</h2>
<p data-background-color="red">Paragraph</p>
<p style="background-color: red;">Paragraph</p>
<ul>
<li>
<p></p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<p
data-text-color="orange"
data-background-color="pink"
data-text-alignment="center"
>
<p style="background-color: pink; color: orange; text-align: center;">
Plain
<span data-text-color="red">Red Text</span>
<span data-background-color="blue">Blue Background</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<div data-editable="">React Context Paragraph</div>
<div data-editable="" style="white-space: normal;">React Context Paragraph</div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the white-space added?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that's added automatically to the React node view wrapper. Do we need to get rid of it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure; just wondering why it wasn't there before? (i.e.: what changed in this PR)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah - since we render some props out to inline styles for external HTML, I changed it so inline styles are also preserved (previously only attributes were). The React node view wrapper adds that white space style automatically, so it also gets preserved.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p class="react-custom-paragraph">Hello World</p>
<p class="react-custom-paragraph" style="white-space: normal;">Hello World</p>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p class="react-custom-paragraph">Hello World</p>
<p class="react-custom-paragraph" style="white-space: normal;">Hello World</p>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<p class="react-custom-paragraph">Hello World</p>
<p class="react-custom-paragraph">Hello World</p>
<p class="react-custom-paragraph">Hello World</p>
<p class="react-custom-paragraph" style="white-space: normal;">Hello World</p>
<p class="react-custom-paragraph" style="white-space: normal;">Hello World</p>
<p class="react-custom-paragraph" style="white-space: normal;">Hello World</p>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<p
class="react-custom-paragraph"
style="white-space: normal;"
data-text-alignment="center"
data-text-color="orange"
data-background-color="pink"
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<p class="simple-react-custom-paragraph" data-editable="">React Custom Paragraph</p>
<p
class="simple-react-custom-paragraph"
data-editable=""
style="white-space: normal;"
>React Custom Paragraph</p>
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
<p class="simple-react-custom-paragraph" data-editable="">Custom React Paragraph</p>
<p class="simple-react-custom-paragraph" data-editable="">Nested React Custom Paragraph 1</p>
<p class="simple-react-custom-paragraph" data-editable="">Nested React Custom Paragraph 2</p>
<p
class="simple-react-custom-paragraph"
data-editable=""
style="white-space: normal;"
>Custom React Paragraph</p>
<p
class="simple-react-custom-paragraph"
data-editable=""
style="white-space: normal;"
>Nested React Custom Paragraph 1</p>
<p
class="simple-react-custom-paragraph"
data-editable=""
style="white-space: normal;"
>Nested React Custom Paragraph 2</p>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<p
class="simple-react-custom-paragraph"
data-editable=""
style="white-space: normal;"
data-text-alignment="center"
data-text-color="orange"
data-background-color="pink"
Expand Down
Loading