diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
index ef1eefb176..17ab49fe69 100644
--- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
+++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
@@ -60,11 +60,14 @@ export function serializeInlineContentExternalHTML<
for (const node of nodes) {
// Check if this is a custom inline content node with toExternalHTML
- if (editor.schema.inlineContentSchema[node.type.name]) {
+ if (
+ node.type.name !== "text" &&
+ editor.schema.inlineContentSchema[node.type.name]
+ ) {
const inlineContentImplementation =
editor.schema.inlineContentSpecs[node.type.name].implementation;
- if (inlineContentImplementation?.toExternalHTML) {
+ if (inlineContentImplementation) {
// Convert the node to inline content format
const inlineContent = nodeToCustomInlineContent(
node,
@@ -72,11 +75,23 @@ export function serializeInlineContentExternalHTML<
editor.schema.styleSchema,
);
- // Use the custom toExternalHTML method
- const output = inlineContentImplementation.toExternalHTML(
- inlineContent as any,
- editor as any,
- );
+ // Use the custom toExternalHTML method or fallback to `render`
+ const output = inlineContentImplementation.toExternalHTML
+ ? inlineContentImplementation.toExternalHTML(
+ inlineContent as any,
+ editor as any,
+ )
+ : inlineContentImplementation.render.call(
+ {
+ renderType: "dom",
+ props: undefined,
+ },
+ inlineContent as any,
+ () => {
+ // No-op
+ },
+ editor as any,
+ );
if (output) {
fragment.appendChild(output.dom);
@@ -93,14 +108,40 @@ export function serializeInlineContentExternalHTML<
continue;
}
}
- }
+ } else if (node.type.name === "text") {
+ // We serialize text nodes manually as we need to serialize the styles/
+ // marks using `styleSpec.implementation.render`. When left up to
+ // ProseMirror, it'll use `toDOM` which is incorrect.
+ let dom: globalThis.Node | Text = document.createTextNode(
+ node.textContent,
+ );
+ // Reverse the order of marks to maintain the correct priority.
+ for (const mark of node.marks.toReversed()) {
+ if (mark.type.name in editor.schema.styleSpecs) {
+ const newDom = (
+ editor.schema.styleSpecs[mark.type.name].implementation
+ .toExternalHTML ??
+ editor.schema.styleSpecs[mark.type.name].implementation.render
+ )(mark.attrs["stringValue"], editor);
+ newDom.contentDOM!.appendChild(dom);
+ dom = newDom.dom;
+ } else {
+ const domOutputSpec = mark.type.spec.toDOM!(mark, true);
+ const newDom = DOMSerializer.renderSpec(document, domOutputSpec);
+ newDom.contentDOM!.appendChild(dom);
+ dom = newDom.dom;
+ }
+ }
- // Fall back to default serialization for this node
- const nodeFragment = serializer.serializeFragment(
- Fragment.from([node]),
- options,
- );
- fragment.appendChild(nodeFragment);
+ fragment.appendChild(dom);
+ } else {
+ // Fall back to default serialization for this node
+ const nodeFragment = serializer.serializeFragment(
+ Fragment.from([node]),
+ options,
+ );
+ fragment.appendChild(nodeFragment);
+ }
}
if (
diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
index bb158ad68c..bc39d70008 100644
--- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
+++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
@@ -48,7 +48,6 @@ export function serializeInlineContentInternalHTML<
// Check if this is a custom inline content node with toExternalHTML
if (
node.type.name !== "text" &&
- node.type.name !== "link" &&
editor.schema.inlineContentSchema[node.type.name]
) {
const inlineContentImplementation =
@@ -90,14 +89,38 @@ export function serializeInlineContentInternalHTML<
continue;
}
}
- }
+ } else if (node.type.name === "text") {
+ // We serialize text nodes manually as we need to serialize the styles/
+ // marks using `styleSpec.implementation.render`. When left up to
+ // ProseMirror, it'll use `toDOM` which is incorrect.
+ let dom: globalThis.Node | Text = document.createTextNode(
+ node.textContent,
+ );
+ // Reverse the order of marks to maintain the correct priority.
+ for (const mark of node.marks.toReversed()) {
+ if (mark.type.name in editor.schema.styleSpecs) {
+ const newDom = editor.schema.styleSpecs[
+ mark.type.name
+ ].implementation.render(mark.attrs["stringValue"], editor);
+ newDom.contentDOM!.appendChild(dom);
+ dom = newDom.dom;
+ } else {
+ const domOutputSpec = mark.type.spec.toDOM!(mark, true);
+ const newDom = DOMSerializer.renderSpec(document, domOutputSpec);
+ newDom.contentDOM!.appendChild(dom);
+ dom = newDom.dom;
+ }
+ }
- // Fall back to default serialization for this node
- const nodeFragment = serializer.serializeFragment(
- Fragment.from([node]),
- options,
- );
- fragment.appendChild(nodeFragment);
+ fragment.appendChild(dom);
+ } else {
+ // Fall back to default serialization for this node
+ const nodeFragment = serializer.serializeFragment(
+ Fragment.from([node]),
+ options,
+ );
+ fragment.appendChild(nodeFragment);
+ }
}
return fragment;
diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts
index 916da1a7fe..a2d01b92df 100644
--- a/packages/core/src/blocks/defaultBlocks.ts
+++ b/packages/core/src/blocks/defaultBlocks.ts
@@ -16,9 +16,8 @@ import {
createQuoteBlockSpec,
createToggleListItemBlockSpec,
createVideoBlockSpec,
+ defaultProps,
} from "./index.js";
-import { BackgroundColor } from "../extensions/BackgroundColor/BackgroundColorMark.js";
-import { TextColor } from "../extensions/TextColor/TextColorMark.js";
import {
BlockNoDefaults,
BlockSchema,
@@ -27,11 +26,13 @@ import {
PartialBlockNoDefaults,
StyleSchema,
StyleSpecs,
+ createStyleSpec,
createStyleSpecFromTipTapMark,
getInlineContentSchemaFromSpecs,
getStyleSchemaFromSpecs,
} from "../schema/index.js";
import { createTableBlockSpec } from "./Table/block.js";
+import { COLORS_DEFAULT } from "../editor/defaultColors.js";
export const defaultBlockSpecs = {
audio: createAudioBlockSpec(),
@@ -56,6 +57,78 @@ export type _DefaultBlockSchema = {
};
export type DefaultBlockSchema = _DefaultBlockSchema;
+const TextColor = createStyleSpec(
+ {
+ type: "textColor",
+ propSchema: "string",
+ },
+ {
+ render: () => {
+ const span = document.createElement("span");
+
+ return {
+ dom: span,
+ contentDOM: span,
+ };
+ },
+ toExternalHTML: (value) => {
+ const span = document.createElement("span");
+ if (value !== defaultProps.textColor.default) {
+ span.style.color =
+ value in COLORS_DEFAULT ? COLORS_DEFAULT[value].text : value;
+ }
+
+ return {
+ dom: span,
+ contentDOM: span,
+ };
+ },
+ parse: (element) => {
+ if (element.tagName === "SPAN" && element.style.color) {
+ return element.style.color;
+ }
+
+ return undefined;
+ },
+ },
+);
+
+const BackgroundColor = createStyleSpec(
+ {
+ type: "backgroundColor",
+ propSchema: "string",
+ },
+ {
+ render: () => {
+ const span = document.createElement("span");
+
+ return {
+ dom: span,
+ contentDOM: span,
+ };
+ },
+ toExternalHTML: (value) => {
+ const span = document.createElement("span");
+ if (value !== defaultProps.backgroundColor.default) {
+ span.style.backgroundColor =
+ value in COLORS_DEFAULT ? COLORS_DEFAULT[value].background : value;
+ }
+
+ return {
+ dom: span,
+ contentDOM: span,
+ };
+ },
+ parse: (element) => {
+ if (element.tagName === "SPAN" && element.style.backgroundColor) {
+ return element.style.backgroundColor;
+ }
+
+ return undefined;
+ },
+ },
+);
+
export const defaultStyleSpecs = {
bold: createStyleSpecFromTipTapMark(Bold, "boolean"),
italic: createStyleSpecFromTipTapMark(Italic, "boolean"),
diff --git a/packages/core/src/blocks/defaultProps.ts b/packages/core/src/blocks/defaultProps.ts
index 46c5417a85..5d55d21d35 100644
--- a/packages/core/src/blocks/defaultProps.ts
+++ b/packages/core/src/blocks/defaultProps.ts
@@ -92,20 +92,10 @@ export const getBackgroundColorAttribute = (
default: defaultProps.backgroundColor.default,
parseHTML: (element) => {
if (element.hasAttribute("data-background-color")) {
- return element.getAttribute("data-background-color");
+ return element.getAttribute("data-background-color")!;
}
if (element.style.backgroundColor) {
- // Check if `element.style.backgroundColor` matches the string:
- // `var(--blocknote-background-)`. If it does, return the color
- // name only. Otherwise, return `element.style.backgroundColor`.
- const match = element.style.backgroundColor.match(
- /var\(--blocknote-background-(.+)\)/,
- );
- if (match) {
- return match[1];
- }
-
return element.style.backgroundColor;
}
@@ -128,18 +118,10 @@ export const getTextColorAttribute = (
default: defaultProps.textColor.default,
parseHTML: (element) => {
if (element.hasAttribute("data-text-color")) {
- return element.getAttribute("data-text-color");
+ return element.getAttribute("data-text-color")!;
}
if (element.style.color) {
- // Check if `element.style.color` matches the string:
- // `var(--blocknote-text-)`. If it does, return the color name
- // only. Otherwise, return `element.style.color`.
- const match = element.style.color.match(/var\(--blocknote-text-(.+)\)/);
- if (match) {
- return match[1];
- }
-
return element.style.color;
}
@@ -149,6 +131,7 @@ export const getTextColorAttribute = (
if (attributes[attributeName] === defaultProps.textColor.default) {
return {};
}
+
return {
"data-text-color": attributes[attributeName],
};
@@ -174,6 +157,7 @@ export const getTextAlignmentAttribute = (
if (attributes[attributeName] === defaultProps.textAlignment.default) {
return {};
}
+
return {
"data-text-alignment": attributes[attributeName],
};
diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css
index 6ed5361621..f882ba0559 100644
--- a/packages/core/src/editor/Block.css
+++ b/packages/core/src/editor/Block.css
@@ -559,92 +559,110 @@ NESTED BLOCKS
/* TODO: should this be here? */
/* TEXT COLORS */
+[data-style-type="textColor"][data-value="gray"],
[data-text-color="gray"],
.bn-block:has(> .bn-block-content[data-text-color="gray"]) {
color: #9b9a97;
}
+[data-style-type="textColor"][data-value="brown"],
[data-text-color="brown"],
.bn-block:has(> .bn-block-content[data-text-color="brown"]) {
color: #64473a;
}
+[data-style-type="textColor"][data-value="red"],
[data-text-color="red"],
.bn-block:has(> .bn-block-content[data-text-color="red"]) {
color: #e03e3e;
}
+[data-style-type="textColor"][data-value="orange"],
[data-text-color="orange"],
.bn-block:has(> .bn-block-content[data-text-color="orange"]) {
color: #d9730d;
}
+[data-style-type="textColor"][data-value="yellow"],
[data-text-color="yellow"],
.bn-block:has(> .bn-block-content[data-text-color="yellow"]) {
color: #dfab01;
}
+[data-style-type="textColor"][data-value="green"],
[data-text-color="green"],
.bn-block:has(> .bn-block-content[data-text-color="green"]) {
color: #4d6461;
}
+[data-style-type="textColor"][data-value="blue"],
[data-text-color="blue"],
.bn-block:has(> .bn-block-content[data-text-color="blue"]) {
color: #0b6e99;
}
+[data-style-type="textColor"][data-value="purple"],
[data-text-color="purple"],
.bn-block:has(> .bn-block-content[data-text-color="purple"]) {
color: #6940a5;
}
+[data-style-type="textColor"][data-value="pink"],
[data-text-color="pink"],
.bn-block:has(> .bn-block-content[data-text-color="pink"]) {
color: #ad1a72;
}
/* BACKGROUND COLORS */
+[data-style-type="backgroundColor"][data-value="gray"],
[data-background-color="gray"],
.bn-block:has(> .bn-block-content[data-background-color="gray"]) {
background-color: #ebeced;
}
+[data-style-type="backgroundColor"][data-value="brown"],
[data-background-color="brown"],
.bn-block:has(> .bn-block-content[data-background-color="brown"]) {
background-color: #e9e5e3;
}
+[data-style-type="backgroundColor"][data-value="red"],
[data-background-color="red"],
.bn-block:has(> .bn-block-content[data-background-color="red"]) {
background-color: #fbe4e4;
}
+[data-style-type="backgroundColor"][data-value="orange"],
[data-background-color="orange"],
.bn-block:has(> .bn-block-content[data-background-color="orange"]) {
background-color: #f6e9d9;
}
+[data-style-type="backgroundColor"][data-value="yellow"],
[data-background-color="yellow"],
.bn-block:has(> .bn-block-content[data-background-color="yellow"]) {
background-color: #fbf3db;
}
+[data-style-type="backgroundColor"][data-value="green"],
[data-background-color="green"],
.bn-block:has(> .bn-block-content[data-background-color="green"]) {
background-color: #ddedea;
}
+[data-style-type="backgroundColor"][data-value="blue"],
[data-background-color="blue"],
.bn-block:has(> .bn-block-content[data-background-color="blue"]) {
background-color: #ddebf1;
}
+[data-style-type="backgroundColor"][data-value="purple"],
[data-background-color="purple"],
.bn-block:has(> .bn-block-content[data-background-color="purple"]) {
background-color: #eae4f2;
}
+[data-style-type="backgroundColor"][data-value="pink"],
[data-background-color="pink"],
.bn-block:has(> .bn-block-content[data-background-color="pink"]) {
background-color: #f4dfeb;
diff --git a/packages/core/src/schema/styles/createSpec.ts b/packages/core/src/schema/styles/createSpec.ts
index 8051f9f833..4bd83b3ae0 100644
--- a/packages/core/src/schema/styles/createSpec.ts
+++ b/packages/core/src/schema/styles/createSpec.ts
@@ -1,7 +1,6 @@
import { Mark } from "@tiptap/core";
-import { ParseRule } from "@tiptap/pm/model";
-import { UnreachableCaseError } from "../../util/typescript.js";
+import { ParseRule, TagParseRule } from "@tiptap/pm/model";
import {
addStyleAttributes,
createInternalStyleSpec,
@@ -10,21 +9,26 @@ import {
import { StyleConfig, StyleSpec } from "./types.js";
export type CustomStyleImplementation = {
- render: T["propSchema"] extends "boolean"
- ? () => {
- dom: HTMLElement;
- contentDOM?: HTMLElement;
- }
- : (value: string) => {
- dom: HTMLElement;
- contentDOM?: HTMLElement;
- };
+ render: (value: T["propSchema"] extends "boolean" ? undefined : string) => {
+ dom: HTMLElement;
+ contentDOM?: HTMLElement;
+ };
+ toExternalHTML?: (
+ value: T["propSchema"] extends "boolean" ? undefined : string,
+ ) => {
+ dom: HTMLElement;
+ contentDOM?: HTMLElement;
+ };
+ parse?: (
+ element: HTMLElement,
+ ) => (T["propSchema"] extends "boolean" ? true : string) | undefined;
};
-// TODO: support serialization
-
-export function getStyleParseRules(config: StyleConfig): ParseRule[] {
- return [
+export function getStyleParseRules(
+ config: T,
+ customParseFunction?: CustomStyleImplementation["parse"],
+): ParseRule[] {
+ const rules: TagParseRule[] = [
{
tag: `[data-style-type="${config.type}"]`,
contentElement: (element) => {
@@ -38,9 +42,29 @@ export function getStyleParseRules(config: StyleConfig): ParseRule[] {
},
},
];
+
+ if (customParseFunction) {
+ rules.push({
+ tag: "*",
+ getAttrs(node: string | HTMLElement) {
+ if (typeof node === "string") {
+ return false;
+ }
+
+ const stringValue = customParseFunction?.(node);
+
+ if (stringValue === undefined) {
+ return false;
+ }
+
+ return { stringValue };
+ },
+ });
+ }
+ return rules;
}
-export function createStyleSpec(
+export function createStyleSpec(
styleConfig: T,
styleImplementation: CustomStyleImplementation,
): StyleSpec {
@@ -52,22 +76,13 @@ export function createStyleSpec(
},
parseHTML() {
- return getStyleParseRules(styleConfig);
+ return getStyleParseRules(styleConfig, styleImplementation.parse);
},
renderHTML({ mark }) {
- let renderResult: {
- dom: HTMLElement;
- contentDOM?: HTMLElement;
- };
-
- if (styleConfig.propSchema === "boolean") {
- renderResult = styleImplementation.render(mark.attrs.stringValue);
- } else if (styleConfig.propSchema === "string") {
- renderResult = styleImplementation.render(mark.attrs.stringValue);
- } else {
- throw new UnreachableCaseError(styleConfig.propSchema);
- }
+ const renderResult = (
+ styleImplementation.toExternalHTML || styleImplementation.render
+ )(mark.attrs.stringValue);
return addStyleAttributes(
renderResult,
@@ -76,9 +91,44 @@ export function createStyleSpec(
styleConfig.propSchema,
);
},
+
+ addMarkView() {
+ return ({ mark }) => {
+ const renderResult = styleImplementation.render(mark.attrs.stringValue);
+
+ return addStyleAttributes(
+ renderResult,
+ styleConfig.type,
+ mark.attrs.stringValue,
+ styleConfig.propSchema,
+ );
+ };
+ },
});
return createInternalStyleSpec(styleConfig, {
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,
+ );
+ },
});
}
diff --git a/packages/core/src/schema/styles/internal.ts b/packages/core/src/schema/styles/internal.ts
index 0446db701b..9d5711bd6e 100644
--- a/packages/core/src/schema/styles/internal.ts
+++ b/packages/core/src/schema/styles/internal.ts
@@ -1,4 +1,5 @@
import { Attributes, Mark } from "@tiptap/core";
+import { DOMSerializer } from "@tiptap/pm/model";
import {
StyleConfig,
StyleImplementation,
@@ -66,7 +67,7 @@ export function addStyleAttributes<
// config and implementation that conform to the type of Config
export function createInternalStyleSpec(
config: T,
- implementation: StyleImplementation,
+ implementation: StyleImplementation,
) {
return {
config,
@@ -85,6 +86,64 @@ export function createStyleSpecFromTipTapMark<
},
{
mark,
+ render(value, editor) {
+ const toDOM = editor.pmSchema.marks[mark.name].spec.toDOM;
+
+ if (toDOM === undefined) {
+ throw new Error(
+ "This block has no default HTML serialization as its corresponding TipTap node doesn't implement `renderHTML`.",
+ );
+ }
+
+ const markInstance = editor.pmSchema.mark(mark.name, {
+ stringValue: value,
+ });
+
+ const renderSpec = DOMSerializer.renderSpec(
+ document,
+ toDOM(markInstance, true),
+ );
+
+ if (typeof renderSpec !== "object" || !("dom" in renderSpec)) {
+ throw new Error(
+ "Cannot use this block's default HTML serialization as its corresponding TipTap mark's `renderHTML` function does not return an object with the `dom` property.",
+ );
+ }
+
+ return renderSpec as {
+ dom: HTMLElement;
+ contentDOM?: HTMLElement;
+ };
+ },
+ toExternalHTML(value, editor) {
+ const toDOM = editor.pmSchema.marks[mark.name].spec.toDOM;
+
+ if (toDOM === undefined) {
+ throw new Error(
+ "This block has no default HTML serialization as its corresponding TipTap node doesn't implement `renderHTML`.",
+ );
+ }
+
+ const markInstance = editor.pmSchema.mark(mark.name, {
+ stringValue: value,
+ });
+
+ const renderSpec = DOMSerializer.renderSpec(
+ document,
+ toDOM(markInstance, true),
+ );
+
+ if (typeof renderSpec !== "object" || !("dom" in renderSpec)) {
+ throw new Error(
+ "Cannot use this block's default HTML serialization as its corresponding TipTap mark's `renderHTML` function does not return an object with the `dom` property.",
+ );
+ }
+
+ return renderSpec as {
+ dom: HTMLElement;
+ contentDOM?: HTMLElement;
+ };
+ },
},
);
}
diff --git a/packages/core/src/schema/styles/types.ts b/packages/core/src/schema/styles/types.ts
index 1a44e9ffd3..3be7cb9d6e 100644
--- a/packages/core/src/schema/styles/types.ts
+++ b/packages/core/src/schema/styles/types.ts
@@ -1,4 +1,5 @@
import { Mark } from "@tiptap/core";
+import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
export type StylePropSchema = "boolean" | "string"; // TODO: use PropSchema as name? Use objects as type similar to blocks?
@@ -11,15 +12,29 @@ export type StyleConfig = {
// StyleImplementation contains the "implementation" info about a Style element.
// Currently, the implementation is always a TipTap Mark
-export type StyleImplementation = {
+export type StyleImplementation = {
mark: Mark;
+ render: (
+ value: T["propSchema"] extends "boolean" ? undefined : string,
+ editor: BlockNoteEditor,
+ ) => {
+ dom: HTMLElement;
+ contentDOM?: HTMLElement;
+ };
+ toExternalHTML?: (
+ value: T["propSchema"] extends "boolean" ? undefined : string,
+ editor: BlockNoteEditor,
+ ) => {
+ dom: HTMLElement;
+ contentDOM?: HTMLElement;
+ };
};
// Container for both the config and implementation of a Style,
// and the type of `implementation` is based on that of the config
export type StyleSpec = {
config: T;
- implementation: StyleImplementation;
+ implementation: StyleImplementation;
};
// A Schema contains all the types (Configs) supported in an editor
diff --git a/packages/react/src/editor/styles.css b/packages/react/src/editor/styles.css
index 37044b5df6..eb61a59fa2 100644
--- a/packages/react/src/editor/styles.css
+++ b/packages/react/src/editor/styles.css
@@ -140,74 +140,92 @@
}
/* Highlight color styling */
+[data-style-type="textColor"][data-value="gray"],
[data-text-color="gray"] {
color: var(--bn-colors-highlights-gray-text);
}
+[data-style-type="textColor"][data-value="brown"],
[data-text-color="brown"] {
color: var(--bn-colors-highlights-brown-text);
}
+[data-style-type="textColor"][data-value="red"],
[data-text-color="red"] {
color: var(--bn-colors-highlights-red-text);
}
+[data-style-type="textColor"][data-value="orange"],
[data-text-color="orange"] {
color: var(--bn-colors-highlights-orange-text);
}
+[data-style-type="textColor"][data-value="yellow"],
[data-text-color="yellow"] {
color: var(--bn-colors-highlights-yellow-text);
}
+[data-style-type="textColor"][data-value="green"],
[data-text-color="green"] {
color: var(--bn-colors-highlights-green-text);
}
+[data-style-type="textColor"][data-value="blue"],
[data-text-color="blue"] {
color: var(--bn-colors-highlights-blue-text);
}
+[data-style-type="textColor"][data-value="purple"],
[data-text-color="purple"] {
color: var(--bn-colors-highlights-purple-text);
}
+[data-style-type="textColor"][data-value="pink"],
[data-text-color="pink"] {
color: var(--bn-colors-highlights-pink-text);
}
+[data-style-type="backgroundColor"][data-value="gray"],
[data-background-color="gray"] {
background-color: var(--bn-colors-highlights-gray-background);
}
+[data-style-type="backgroundColor"][data-value="brown"],
[data-background-color="brown"] {
background-color: var(--bn-colors-highlights-brown-background);
}
+[data-style-type="backgroundColor"][data-value="red"],
[data-background-color="red"] {
background-color: var(--bn-colors-highlights-red-background);
}
+[data-style-type="backgroundColor"][data-value="orange"],
[data-background-color="orange"] {
background-color: var(--bn-colors-highlights-orange-background);
}
+[data-style-type="backgroundColor"][data-value="yellow"],
[data-background-color="yellow"] {
background-color: var(--bn-colors-highlights-yellow-background);
}
+[data-style-type="backgroundColor"][data-value="green"],
[data-background-color="green"] {
background-color: var(--bn-colors-highlights-green-background);
}
+[data-style-type="backgroundColor"][data-value="blue"],
[data-background-color="blue"] {
background-color: var(--bn-colors-highlights-blue-background);
}
+[data-style-type="backgroundColor"][data-value="purple"],
[data-background-color="purple"] {
background-color: var(--bn-colors-highlights-purple-background);
}
+[data-style-type="backgroundColor"][data-value="pink"],
[data-background-color="pink"] {
background-color: var(--bn-colors-highlights-pink-background);
}
diff --git a/packages/react/src/schema/ReactStyleSpec.tsx b/packages/react/src/schema/ReactStyleSpec.tsx
index 53b9c85eba..b83f727319 100644
--- a/packages/react/src/schema/ReactStyleSpec.tsx
+++ b/packages/react/src/schema/ReactStyleSpec.tsx
@@ -117,5 +117,55 @@ export function createReactStyleSpec(
return createInternalStyleSpec(styleConfig, {
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,
+ );
+ },
});
}
diff --git a/packages/xl-ai/src/api/formats/html-blocks/htmlBlocks.test.ts b/packages/xl-ai/src/api/formats/html-blocks/htmlBlocks.test.ts
index aafb57b6ad..20e7914c69 100644
--- a/packages/xl-ai/src/api/formats/html-blocks/htmlBlocks.test.ts
+++ b/packages/xl-ai/src/api/formats/html-blocks/htmlBlocks.test.ts
@@ -120,21 +120,15 @@ describe("Models", () => {
describe(`${params.model.provider}/${params.model.modelId} (${
params.stream ? "streaming" : "non-streaming"
})`, () => {
- generateSharedTestCases(
- (editor, options) =>
- doLLMRequest(editor, {
- ...options,
- dataFormat: htmlBlockLLMFormat,
- model: params.model,
- maxRetries: 0,
- stream: params.stream,
- withDelays: false,
- }),
- // TODO: remove when matthew's parsing PR is merged
- {
- textAlignment: true,
- blockColor: true,
- },
+ generateSharedTestCases((editor, options) =>
+ doLLMRequest(editor, {
+ ...options,
+ dataFormat: htmlBlockLLMFormat,
+ model: params.model,
+ maxRetries: 0,
+ stream: params.stream,
+ withDelays: false,
+ }),
);
});
}
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html
index 699ad7f460..b0e77ba748 100644
--- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html
+++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html
@@ -10,10 +10,10 @@
>
Plain
- Red Text
- Blue Background
-
- Mixed Colors
+ Red Text
+ Blue Background
+
+ Mixed Colors
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html
index fd10eacf1a..637d51b2e0 100644
--- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html
+++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html
@@ -5,9 +5,29 @@
data-background-color="pink"
>
Plain
- Red Text
- Blue Background
-
- Mixed Colors
+ Red Text
+ Blue Background
+
+ Mixed Colors
\ No newline at end of file
diff --git a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts
index 0c89c74541..7a6781b938 100644
--- a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts
+++ b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts
@@ -870,14 +870,14 @@ With Hard Break
{
testCase: {
name: "textColorStyle",
- content: `Blue Text Blue Text
`,
+ content: `Blue Text Blue Text
`,
},
executeTest: testParseHTML,
},
{
testCase: {
name: "backgroundColorStyle",
- content: `Blue Background Blue Background
`,
+ content: `Blue Background Blue Background
`,
},
executeTest: testParseHTML,
},
diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html
index 654ebfd316..df960b11f1 100644
--- a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html
+++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html
@@ -12,10 +12,10 @@
>
Plain
- Red Text
- Blue Background
-
- Mixed Colors
+ Red Text
+ Blue Background
+
+ Mixed Colors
diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html
index 6a0a83a4cb..ef3d75ab28 100644
--- a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html
+++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html
@@ -12,10 +12,10 @@
>
Plain
- Red Text
- Blue Background
-
- Mixed Colors
+ Red Text
+ Blue Background
+
+ Mixed Colors
diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html
index c2a35ebaee..87efb71bf5 100644
--- a/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html
+++ b/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html
@@ -5,9 +5,29 @@
data-background-color="pink"
>
Plain
- Red Text
- Blue Background
-
- Mixed Colors
+ Red Text
+ Blue Background
+
+ Mixed Colors
\ No newline at end of file