Skip to content
New issue

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

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

Already on GitHub? # to your account

Custom inline content and styles commands/copy & paste fixes #425

Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1 +1 @@
<p class="bn-inline-content"><span style="font-size: 18px">This is text with a custom fontSize</span></p>
<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 number Diff line number Diff line change
@@ -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>
<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 number Diff line number Diff line change
@@ -1 +1 @@
<p class="bn-inline-content">I enjoy working with<span>@Matthew</span></p>
<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 number Diff line number Diff line change
@@ -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>
<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 number Diff line number Diff line change
@@ -1 +1 @@
<p class="bn-inline-content"><small>This is a small text</small></p>
<p class="bn-inline-content"><small class="bn-style" data-style-type="small">This is a small text</small></p>
Original file line number Diff line number Diff line change
@@ -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>
<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 number Diff line number Diff line change
@@ -1 +1 @@
<p class="bn-inline-content">I love <span>#<span>BlockNote</span></span></p>
<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 number Diff line number Diff line change
@@ -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>
<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>
3 changes: 0 additions & 3 deletions packages/core/src/api/exporters/html/htmlConversion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,6 @@ describe("Test HTML conversion", () => {
for (const document of testCase.documents) {
// eslint-disable-next-line no-loop-func
it("Convert " + document.name + " to HTML", async () => {
if (document.name !== "complex/misc") {
return;
}
const nameSplit = document.name.split("/");
await convertToHTMLAndCompareSnapshots(
editor,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/api/testCases/cases/customInlineContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const customInlineContentTestCases: EditorTestCases<
user: "Matthew",
},
content: undefined,
} as any,
} as any, // TODO
],
},
],
Expand All @@ -103,7 +103,7 @@ export const customInlineContentTestCases: EditorTestCases<
type: "tag",
// props: {},
content: "BlockNote",
} as any,
} as any, // TODO
],
},
],
Expand Down
26 changes: 13 additions & 13 deletions packages/core/src/api/testCases/cases/defaultSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "paragraph/empty",
blocks: [
{
type: "paragraph" as const,
type: "paragraph",
},
],
},
{
name: "paragraph/basic",
blocks: [
{
type: "paragraph" as const,
type: "paragraph",
content: "Paragraph",
},
],
Expand All @@ -41,12 +41,12 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "paragraph/styled",
blocks: [
{
type: "paragraph" as const,
type: "paragraph",
props: {
textAlignment: "center",
textColor: "orange",
backgroundColor: "pink",
} as const,
},
content: [
{
type: "text",
Expand Down Expand Up @@ -83,15 +83,15 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "paragraph/nested",
blocks: [
{
type: "paragraph" as const,
type: "paragraph",
content: "Paragraph",
children: [
{
type: "paragraph" as const,
type: "paragraph",
content: "Nested Paragraph 1",
},
{
type: "paragraph" as const,
type: "paragraph",
content: "Nested Paragraph 2",
},
],
Expand All @@ -102,15 +102,15 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "image/button",
blocks: [
{
type: "image" as const,
type: "image",
},
],
},
{
name: "image/basic",
blocks: [
{
type: "image" as const,
type: "image",
props: {
url: "exampleURL",
caption: "Caption",
Expand All @@ -123,20 +123,20 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "image/nested",
blocks: [
{
type: "image" as const,
type: "image",
props: {
url: "exampleURL",
caption: "Caption",
width: 256,
} as const,
},
children: [
{
type: "image" as const,
type: "image",
props: {
url: "exampleURL",
caption: "Caption",
width: 256,
} as const,
},
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Node } from "@tiptap/core";
import { ParseRule } from "@tiptap/pm/model";
import { nodeToCustomInlineContent } from "../../../../api/nodeConversions/nodeConversions";
import { propsToAttributes } from "../blocks/internal";
import { Props } from "../blocks/types";
import { StyleSchema } from "../styles/types";
import { createInlineContentSpecFromTipTapNode } from "./internal";
import {
addInlineContentAttributes,
createInlineContentSpecFromTipTapNode,
} from "./internal";
import {
InlineContentConfig,
InlineContentFromConfig,
Expand Down Expand Up @@ -37,6 +42,16 @@ export type CustomInlineContentImplementation<
};
};

export function getInlineContentParseRules(
config: InlineContentConfig
): ParseRule[] {
return [
{
tag: `.bn-inline-content-section[data-inline-content-type="${config.type}"]`,
},
];
}

export function createInlineContentSpec<
T extends InlineContentConfig,
S extends StyleSchema
Expand All @@ -57,6 +72,10 @@ export function createInlineContentSpec<
return propsToAttributes(inlineContentConfig.propSchema);
},

parseHTML() {
return getInlineContentParseRules(inlineContentConfig);
},

renderHTML({ node }) {
const editor = this.options.editor;

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

return output;
return {
dom: addInlineContentAttributes(
output.dom,
inlineContentConfig.type,
node.attrs as Props<T["propSchema"]>,
inlineContentConfig.propSchema
),
contentDOM: output.contentDOM,
};
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
import { Node } from "@tiptap/core";
import { PropSchema } from "../blocks/types";
import { camelToDataKebab } from "../blocks/internal";
import { Props, PropSchema } from "../blocks/types";
import {
InlineContentConfig,
InlineContentImplementation,
InlineContentSchemaFromSpecs,
InlineContentSpec,
InlineContentSpecs,
} from "./types";
import { mergeCSSClasses } from "../../../../shared/utils";

// Function that adds necessary classes and attributes to the `dom` element
// returned from a custom inline content's 'render' function, to ensure no data
// is lost on internal copy & paste.
export function addInlineContentAttributes<
IType extends string,
PSchema extends PropSchema
>(
element: HTMLElement,
inlineContentType: IType,
inlineContentProps: Props<PSchema>,
propSchema: PSchema
): HTMLElement {
// Sets inline content section class
element.className = mergeCSSClasses(
"bn-inline-content-section",
element.className
);
// Sets content type attribute
element.setAttribute("data-inline-content-type", inlineContentType);
// Adds props as HTML attributes in kebab-case with "data-" prefix. Skips props
// set to their default values.
Object.entries(inlineContentProps)
.filter(([prop, value]) => value !== propSchema[prop].default)
.map(([prop, value]) => {
return [camelToDataKebab(prop), value];
})
.forEach(([prop, value]) => element.setAttribute(prop, value));

return element;
}

// This helper function helps to instantiate a InlineContentSpec with a
// config and implementation that conform to the type of Config
Expand Down
43 changes: 27 additions & 16 deletions packages/core/src/extensions/Blocks/api/styles/createSpec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Mark } from "@tiptap/core";
import { ParseRule } from "@tiptap/pm/model";
import { UnreachableCaseError } from "../../../../shared/utils";
import { createInternalStyleSpec } from "./internal";
import {
addStyleAttributes,
createInternalStyleSpec,
stylePropsToAttributes,
} from "./internal";
import { StyleConfig, StyleSpec } from "./types";

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

// TODO: support serialization

export function getStyleParseRules(config: StyleConfig): ParseRule[] {
return [
{
tag: `.bn-style[data-style-type="${config.type}"]`,
},
];
}

export function createStyleSpec<T extends StyleConfig>(
styleConfig: T,
styleImplementation: CustomStyleImplementation<T>
Expand All @@ -25,21 +38,11 @@ export function createStyleSpec<T extends StyleConfig>(
name: styleConfig.type,

addAttributes() {
if (styleConfig.propSchema === "boolean") {
return {};
}
return {
stringValue: {
default: undefined,
// TODO: parsing
return stylePropsToAttributes(styleConfig.propSchema);
},

// parseHTML: (element) =>
// element.getAttribute(`data-${styleConfig.type}`),
// renderHTML: (attributes) => ({
// [`data-${styleConfig.type}`]: attributes.stringValue,
// }),
},
};
parseHTML() {
return getStyleParseRules(styleConfig);
},

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

// const renderResult = styleImplementation.render();
return renderResult;
return {
dom: addStyleAttributes(
renderResult.dom,
styleConfig.type,
mark.attrs.stringValue,
styleConfig.propSchema
),
contentDOM: renderResult.contentDOM,
};
},
});

Expand Down
Loading