Skip to content

feat: Better parsing of external HTML attributes & inline styles #1605

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
30 changes: 20 additions & 10 deletions examples/03-ui-components/13-custom-ui/MUIFormattingToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,18 +262,24 @@ function MUITextAlignButton(props: {
const editor = useBlockNoteEditor<TextBlockSchema>();

// The text alignment of the block currently containing the text cursor.
const [activeTextAlignment, setActiveTextAlignment] = useState(
() => editor.getTextCursorPosition().block.props.textAlignment
);
const [activeTextAlignment, setActiveTextAlignment] = useState(() => {
const blockProps = editor.getTextCursorPosition().block.props;

if ("textAlignment" in blockProps) {
return blockProps.textAlignment;
}

return undefined;
});

// Updates the text alignment when the editor content or selection changes.
useEditorContentOrSelectionChange(
() =>
setActiveTextAlignment(
editor.getTextCursorPosition().block.props.textAlignment
),
editor
);
useEditorContentOrSelectionChange(() => {
const blockProps = editor.getTextCursorPosition().block.props;

if ("textAlignment" in blockProps) {
setActiveTextAlignment(blockProps.textAlignment);
}
}, editor);

// Tooltip for the button.
const tooltip = useMemo(
Expand All @@ -293,6 +299,10 @@ function MUITextAlignButton(props: {
editor.focus();
}, [editor, props.textAlignment]);

if (!activeTextAlignment) {
return null;
}

return (
<MUIToolbarButton
tooltip={tooltip}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({
const startIndex =
parseInt(parent.getAttribute("start") || "1") || 1;

if (element.previousSibling || startIndex === 1) {
if (element.previousElementSibling || startIndex === 1) {
return {};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { updateBlockCommand } from "../../api/blockManipulation/commands/updateB
import { InputRule } from "@tiptap/core";

export const quotePropSchema = {
...defaultProps,
backgroundColor: defaultProps.backgroundColor,
textColor: defaultProps.textColor,
};

export const QuoteBlockContent = createStronglyTypedTiptapNode({
Expand Down
85 changes: 81 additions & 4 deletions packages/core/src/blocks/defaultProps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Attribute } from "@tiptap/core";

import type { Props, PropSchema } from "../schema/index.js";

// TODO: this system should probably be moved / refactored.
Expand All @@ -18,7 +20,82 @@ export const defaultProps = {

export type DefaultProps = Props<typeof defaultProps>;

// Default props which are set on `blockContainer` nodes rather than
// `blockContent` nodes. Ensures that they are not redundantly added to
// a custom block's TipTap node attributes.
export const inheritedProps = ["backgroundColor", "textColor"];
const getBackgroundColorAttribute = (
attributeName = "backgroundColor"
): Attribute => ({
default: defaultProps.backgroundColor.default,
parseHTML: (element) => {
if (element.hasAttribute("data-background-color")) {
return element.getAttribute("data-background-color");
}

if (element.style.backgroundColor) {
return element.style.backgroundColor;
}

return defaultProps.backgroundColor.default;
},
renderHTML: (attributes) => {
if (attributes[attributeName] === defaultProps.backgroundColor.default) {
return {};
}

return {
"data-background-color": attributes[attributeName],
};
},
});

const getTextColorAttribute = (attributeName = "textColor"): Attribute => ({
default: defaultProps.textColor.default,
parseHTML: (element) => {
if (element.hasAttribute("data-text-color")) {
return element.getAttribute("data-text-color");
}

if (element.style.color) {
return element.style.color;
}

return defaultProps.textColor.default;
},
renderHTML: (attributes) => {
if (attributes[attributeName] === defaultProps.textColor.default) {
return {};
}
return {
"data-text-color": attributes[attributeName],
};
},
});

const getTextAlignmentAttribute = (
attributeName = "textAlignment"
): Attribute => ({
default: defaultProps.textAlignment.default,
parseHTML: (element) => {
if (element.hasAttribute("data-text-alignment")) {
return element.getAttribute("data-text-alignment");
}

if (element.style.textAlign) {
return element.style.textAlign;
}

return defaultProps.textAlignment.default;
},
renderHTML: (attributes) => {
if (attributes[attributeName] === defaultProps.textAlignment.default) {
return {};
}
return {
"data-text-alignment": attributes[attributeName],
};
},
});

export const getAttributeFromDefaultProps = {
backgroundColor: getBackgroundColorAttribute,
textColor: getTextColorAttribute,
textAlignment: getTextAlignmentAttribute,
};
58 changes: 36 additions & 22 deletions packages/core/src/editor/Block.css
Original file line number Diff line number Diff line change
Expand Up @@ -437,97 +437,111 @@ NESTED BLOCKS
/* TODO: should this be here? */

/* TEXT COLORS */
[data-text-color="gray"] {
[data-text-color="gray"],
.bn-block:has(> .bn-block-content[data-text-color="gray"]) {
color: #9b9a97;
}

[data-text-color="brown"] {
[data-text-color="brown"],
.bn-block:has(> .bn-block-content[data-text-color="brown"]) {
color: #64473a;
}

[data-text-color="red"] {
[data-text-color="red"],
.bn-block:has(> .bn-block-content[data-text-color="red"]) {
color: #e03e3e;
}

[data-text-color="orange"] {
[data-text-color="orange"],
.bn-block:has(> .bn-block-content[data-text-color="orange"]) {
color: #d9730d;
}

[data-text-color="yellow"] {
[data-text-color="yellow"],
.bn-block:has(> .bn-block-content[data-text-color="yellow"]) {
color: #dfab01;
}

[data-text-color="green"] {
[data-text-color="green"],
.bn-block:has(> .bn-block-content[data-text-color="green"]) {
color: #4d6461;
}

[data-text-color="blue"] {
[data-text-color="blue"],
.bn-block:has(> .bn-block-content[data-text-color="blue"]) {
color: #0b6e99;
}

[data-text-color="purple"] {
[data-text-color="purple"],
.bn-block:has(> .bn-block-content[data-text-color="purple"]) {
color: #6940a5;
}

[data-text-color="pink"] {
[data-text-color="pink"],
.bn-block:has(> .bn-block-content[data-text-color="pink"]) {
color: #ad1a72;
}

/* BACKGROUND COLORS */
[data-background-color="gray"] {
[data-background-color="gray"],
.bn-block:has(> .bn-block-content[data-background-color="gray"]) {
background-color: #ebeced;
}

[data-background-color="brown"] {
[data-background-color="brown"],
.bn-block:has(> .bn-block-content[data-background-color="brown"]) {
background-color: #e9e5e3;
}

[data-background-color="red"] {
[data-background-color="red"],
.bn-block:has(> .bn-block-content[data-background-color="red"]) {
background-color: #fbe4e4;
}

[data-background-color="orange"] {
[data-background-color="orange"],
.bn-block:has(> .bn-block-content[data-background-color="orange"]) {
background-color: #f6e9d9;
}

[data-background-color="yellow"] {
[data-background-color="yellow"],
.bn-block:has(> .bn-block-content[data-background-color="yellow"]) {
background-color: #fbf3db;
}

[data-background-color="green"] {
[data-background-color="green"],
.bn-block:has(> .bn-block-content[data-background-color="green"]) {
background-color: #ddedea;
}

[data-background-color="blue"] {
[data-background-color="blue"],
.bn-block:has(> .bn-block-content[data-background-color="blue"]) {
background-color: #ddebf1;
}

[data-background-color="purple"] {
[data-background-color="purple"],
.bn-block:has(> .bn-block-content[data-background-color="purple"]) {
background-color: #eae4f2;
}

[data-background-color="pink"] {
[data-background-color="pink"],
.bn-block:has(> .bn-block-content[data-background-color="pink"]) {
background-color: #f4dfeb;
}

/* TEXT ALIGNMENT */
[data-text-alignment="left"] {
justify-content: flex-start !important;
text-align: left !important;
}

[data-text-alignment="center"] {
justify-content: center !important;
text-align: center !important;
}

[data-text-alignment="right"] {
justify-content: flex-end !important;
text-align: right !important;
}

[data-text-alignment="justify"] {
justify-content: flex-start !important;
text-align: justify !important;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
import { Extension } from "@tiptap/core";
import { defaultProps } from "../../blocks/defaultProps.js";

import { getAttributeFromDefaultProps } from "../../blocks/defaultProps.js";

export const BackgroundColorExtension = Extension.create({
name: "blockBackgroundColor",

addGlobalAttributes() {
return [
{
types: ["blockContainer", "tableCell", "tableHeader"],
types: [
"paragraph",
"heading",
"bulletListItem",
"numberedListItem",
"checkListItem",
"quote",
"tableCell",
"tableHeader",
],
attributes: {
backgroundColor: {
default: defaultProps.backgroundColor.default,
parseHTML: (element) =>
element.hasAttribute("data-background-color")
? element.getAttribute("data-background-color")
: defaultProps.backgroundColor.default,
renderHTML: (attributes) => {
if (
attributes.backgroundColor ===
defaultProps.backgroundColor.default
) {
return {};
}
return {
"data-background-color": attributes.backgroundColor,
};
},
},
backgroundColor: getAttributeFromDefaultProps["backgroundColor"](),
},
},
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { Mark } from "@tiptap/core";

import { getAttributeFromDefaultProps } from "../../blocks/defaultProps.js";
import { createStyleSpecFromTipTapMark } from "../../schema/index.js";

const BackgroundColorMark = Mark.create({
name: "backgroundColor",

addAttributes() {
return {
stringValue: {
default: undefined,
parseHTML: (element) => element.getAttribute("data-background-color"),
renderHTML: (attributes) => ({
"data-background-color": attributes.stringValue,
}),
},
stringValue:
getAttributeFromDefaultProps["backgroundColor"]("stringValue"),
};
},

Expand All @@ -25,10 +22,11 @@ const BackgroundColorMark = Mark.create({
return false;
}

if (element.hasAttribute("data-background-color")) {
return {
stringValue: element.getAttribute("data-background-color"),
};
if (
element.hasAttribute("data-background-color") ||
element.style.backgroundColor
) {
return {};
}

return false;
Expand Down
Loading
Loading