Skip to content

Commit

Permalink
clean nodeconversions
Browse files Browse the repository at this point in the history
  • Loading branch information
YousefED committed Oct 15, 2024
1 parent 0a50308 commit 9ae58f6
Show file tree
Hide file tree
Showing 18 changed files with 290 additions and 278 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ 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 @@ -11,12 +11,13 @@ import { InlineContentSchema } from "../../../../schema/inlineContent/types.js";
import { StyleSchema } from "../../../../schema/styles/types.js";
import { UnreachableCaseError } from "../../../../util/typescript.js";
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

0 comments on commit 9ae58f6

Please sign in to comment.