Skip to content
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

clean nodeconversions #1151

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
Expand Up @@ -8,10 +8,8 @@ import {
InlineContentSchema,
StyleSchema,
} from "../../../../schema/index.js";
import {
blockToNode,
nodeToBlock,
} from "../../../nodeConversions/nodeConversions.js";
import { blockToNode } from "../../../nodeConversions/blockToNode.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";
import { getNodeById } from "../../../nodeUtil.js";

export function insertBlocks<
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Node } from "prosemirror-model";
import { Transaction } from "prosemirror-state";

import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import { Block } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import {
BlockIdentifier,
BlockSchema,
InlineContentSchema,
StyleSchema,
} from "../../../../schema/index.js";
import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";

export function removeBlocksWithCallback<
BSchema extends BlockSchema,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Node } from "prosemirror-model";

import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import {
BlockIdentifier,
BlockSchema,
InlineContentSchema,
StyleSchema,
} from "../../../../schema/index.js";
import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import { Node } from "prosemirror-model";
import {
blockToNode,
nodeToBlock,
} from "../../../nodeConversions/nodeConversions.js";
import { blockToNode } from "../../../nodeConversions/blockToNode.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";
import { removeBlocksWithCallback } from "../removeBlocks/removeBlocks.js";

export function replaceBlocks<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js";
import {
blockToNode,
inlineContentToNodes,
nodeToBlock,
tableContentToNodes,
} from "../../../nodeConversions/nodeConversions.js";
} from "../../../nodeConversions/blockToNode.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";
import { getNodeById } from "../../../nodeUtil.js";

export const updateBlockCommand =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getBlockInfo,
getBlockInfoFromSelection,
} from "../../../getBlockInfoFromPos.js";
import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";
import { getNodeById } from "../../../nodeUtil.js";

export function getTextCursorPosition<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { fragmentToBlocks } from "../../nodeConversions/fragmentToBlocks.js";
import {
contentNodeToInlineContent,
contentNodeToTableContent,
} from "../../nodeConversions/nodeConversions.js";
} from "../../nodeConversions/nodeToBlock.js";

async function fragmentToExternalHTML<
BSchema extends BlockSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { UnreachableCaseError } from "../../../../util/typescript.js";
import {
inlineContentToNodes,
tableContentToNodes,
} from "../../../nodeConversions/nodeConversions.js";
} from "../../../nodeConversions/blockToNode.js";

export function serializeInlineContent<
BSchema extends BlockSchema,
Expand Down
257 changes: 257 additions & 0 deletions packages/core/src/api/nodeConversions/blockToNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import { Mark, Node, Schema } from "@tiptap/pm/model";

import UniqueID from "../../extensions/UniqueID/UniqueID.js";
import type {
InlineContentSchema,
PartialCustomInlineContentFromConfig,
PartialInlineContent,
PartialLink,
PartialTableContent,
StyleSchema,
StyledText,
} from "../../schema";

import type { PartialBlock } from "../../blocks/defaultBlocks";
import {
isPartialLinkInlineContent,
isStyledTextInlineContent,
} from "../../schema/inlineContent/types.js";
import { UnreachableCaseError } from "../../util/typescript.js";

/**
* Convert a StyledText inline element to a
* prosemirror text node with the appropriate marks
*/
function styledTextToNodes<T extends StyleSchema>(
styledText: StyledText<T>,
schema: Schema,
styleSchema: T
): Node[] {
const marks: Mark[] = [];

for (const [style, value] of Object.entries(styledText.styles)) {
const config = styleSchema[style];
if (!config) {
throw new Error(`style ${style} not found in styleSchema`);
}

if (config.propSchema === "boolean") {
marks.push(schema.mark(style));
} else if (config.propSchema === "string") {
marks.push(schema.mark(style, { stringValue: value }));
} else {
throw new UnreachableCaseError(config.propSchema);
}
}

return (
styledText.text
// Splits text & line breaks.
.split(/(\n)/g)
// If the content ends with a line break, an empty string is added to the
// end, which this removes.
.filter((text) => text.length > 0)
// Converts text & line breaks to nodes.
.map((text) => {
if (text === "\n") {
return schema.nodes["hardBreak"].create();
} else {
return schema.text(text, marks);
}
})
);
}

/**
* Converts a Link inline content element to
* prosemirror text nodes with the appropriate marks
*/
function linkToNodes(
link: PartialLink<StyleSchema>,
schema: Schema,
styleSchema: StyleSchema
): Node[] {
const linkMark = schema.marks.link.create({
href: link.href,
});

return styledTextArrayToNodes(link.content, schema, styleSchema).map(
(node) => {
if (node.type.name === "text") {
return node.mark([...node.marks, linkMark]);
}

if (node.type.name === "hardBreak") {
return node;
}
throw new Error("unexpected node type");
}
);
}

/**
* Converts an array of StyledText inline content elements to
* prosemirror text nodes with the appropriate marks
*/
function styledTextArrayToNodes<S extends StyleSchema>(
content: string | StyledText<S>[],
schema: Schema,
styleSchema: S
): Node[] {
const nodes: Node[] = [];

if (typeof content === "string") {
nodes.push(
...styledTextToNodes(
{ type: "text", text: content, styles: {} },
schema,
styleSchema
)
);
return nodes;
}

for (const styledText of content) {
nodes.push(...styledTextToNodes(styledText, schema, styleSchema));
}
return nodes;
}

/**
* converts an array of inline content elements to prosemirror nodes
*/
export function inlineContentToNodes<
I extends InlineContentSchema,
S extends StyleSchema
>(
blockContent: PartialInlineContent<I, S>,
schema: Schema,
styleSchema: S
): Node[] {
const nodes: Node[] = [];

for (const content of blockContent) {
if (typeof content === "string") {
nodes.push(...styledTextArrayToNodes(content, schema, styleSchema));
} else if (isPartialLinkInlineContent(content)) {
nodes.push(...linkToNodes(content, schema, styleSchema));
} else if (isStyledTextInlineContent(content)) {
nodes.push(...styledTextArrayToNodes([content], schema, styleSchema));
} else {
nodes.push(
blockOrInlineContentToContentNode(content, schema, styleSchema)
);
}
}
return nodes;
}

/**
* converts an array of inline content elements to prosemirror nodes
*/
export function tableContentToNodes<
I extends InlineContentSchema,
S extends StyleSchema
>(
tableContent: PartialTableContent<I, S>,
schema: Schema,
styleSchema: StyleSchema
): Node[] {
const rowNodes: Node[] = [];

for (const row of tableContent.rows) {
const columnNodes: Node[] = [];
for (const cell of row.cells) {
let pNode: Node;
if (!cell) {
pNode = schema.nodes["tableParagraph"].create({});
} else if (typeof cell === "string") {
pNode = schema.nodes["tableParagraph"].create({}, schema.text(cell));
} else {
const textNodes = inlineContentToNodes(cell, schema, styleSchema);
pNode = schema.nodes["tableParagraph"].create({}, textNodes);
}

const cellNode = schema.nodes["tableCell"].create({}, pNode);
columnNodes.push(cellNode);
}
const rowNode = schema.nodes["tableRow"].create({}, columnNodes);
rowNodes.push(rowNode);
}
return rowNodes;
}

function blockOrInlineContentToContentNode(
block:
| PartialBlock<any, any, any>
| PartialCustomInlineContentFromConfig<any, any>,
schema: Schema,
styleSchema: StyleSchema
) {
let contentNode: Node;
let type = block.type;

// TODO: needed? came from previous code
if (type === undefined) {
type = "paragraph";
}

if (!schema.nodes[type]) {
throw new Error(`node type ${type} not found in schema`);
}

if (!block.content) {
contentNode = schema.nodes[type].create(block.props);
} else if (typeof block.content === "string") {
const nodes = inlineContentToNodes([block.content], schema, styleSchema);
contentNode = schema.nodes[type].create(block.props, nodes);
} else if (Array.isArray(block.content)) {
const nodes = inlineContentToNodes(block.content, schema, styleSchema);
contentNode = schema.nodes[type].create(block.props, nodes);
} else if (block.content.type === "tableContent") {
const nodes = tableContentToNodes(block.content, schema, styleSchema);
contentNode = schema.nodes[type].create(block.props, nodes);
} else {
throw new UnreachableCaseError(block.content.type);
}
return contentNode;
}

/**
* Converts a BlockNote block to a TipTap node.
*/
export function blockToNode(
block: PartialBlock<any, any, any>,
schema: Schema,
styleSchema: StyleSchema
) {
let id = block.id;

if (id === undefined) {
id = UniqueID.options.generateID();
}

const contentNode = blockOrInlineContentToContentNode(
block,
schema,
styleSchema
);

const children: Node[] = [];

if (block.children) {
for (const child of block.children) {
children.push(blockToNode(child, schema, styleSchema));
}
}

const groupNode = schema.nodes["blockGroup"].create({}, children);

return schema.nodes["blockContainer"].create(
{
id: id,
...block.props,
},
children.length > 0 ? [contentNode, groupNode] : contentNode
);
}
2 changes: 1 addition & 1 deletion packages/core/src/api/nodeConversions/fragmentToBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
InlineContentSchema,
StyleSchema,
} from "../../schema/index.js";
import { nodeToBlock } from "./nodeConversions.js";
import { nodeToBlock } from "./nodeToBlock.js";

/**
* Converts all Blocks within a fragment to BlockNote blocks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
addIdsToBlock,
partialBlockToBlockForTesting,
} from "../testUtil/partialBlockTestUtil.js";
import { blockToNode, nodeToBlock } from "./nodeConversions.js";
import { blockToNode } from "./blockToNode.js";
import { nodeToBlock } from "./nodeToBlock.js";

function validateConversion(
block: PartialBlock<any, any, any>,
Expand Down
Loading
Loading