UNPKG

@prismicio/richtext

Version:

A parser and serializer for Prismic's Rich Text format

211 lines (183 loc) 4.64 kB
import { RichTextNodeType, RTAnyNode, RTBlockNode, RTInlineNode, RTListItemNode, RTNode, RTOListItemNode, RTTextNode, } from "@prismicio/types"; import { Tree, TreeNode } from "./types"; const uuid = (): string => { return (++uuid.i).toString(); }; uuid.i = 0; /** * Parses a rich text or title field into a tree * * @remarks * This is a low level helper mainly intended to be used by higher level * packages. Most users aren't expected to this function directly. * @param nodes - A rich text or title field from Prismic * * @returns Tree from given rich text or title field */ export const asTree = (nodes: RTNode[]): Tree => { const preparedNodes = prepareNodes(nodes); const children: TreeNode[] = []; for (let i = 0; i < preparedNodes.length; i++) { children.push(nodeToTreeNode(preparedNodes[i])); } return { key: uuid(), children, }; }; const createTreeNode = ( node: RTAnyNode, children: TreeNode[] = [], ): TreeNode => { return { key: uuid(), type: node.type, text: "text" in node ? node.text : undefined, node, children, }; }; const createTextTreeNode = (text: string): TreeNode => { return createTreeNode({ type: RichTextNodeType.span, text, spans: [], }); }; const prepareNodes = (nodes: RTNode[]): RTBlockNode[] => { const mutNodes: RTBlockNode[] = nodes.slice(0); for (let i = 0; i < mutNodes.length; i++) { const node = mutNodes[i]; if ( node.type === RichTextNodeType.listItem || node.type === RichTextNodeType.oListItem ) { const items: (RTListItemNode | RTOListItemNode)[] = [ node as RTListItemNode | RTOListItemNode, ]; while (mutNodes[i + 1] && mutNodes[i + 1].type === node.type) { items.push(mutNodes[i + 1] as RTListItemNode | RTOListItemNode); mutNodes.splice(i, 1); } if (node.type === RichTextNodeType.listItem) { mutNodes[i] = { type: RichTextNodeType.list, items: items as RTListItemNode[], }; } else { mutNodes[i] = { type: RichTextNodeType.oList, items: items as RTOListItemNode[], }; } } } return mutNodes; }; const nodeToTreeNode = (node: RTBlockNode): TreeNode => { if ("text" in node) { return createTreeNode( node, textNodeSpansToTreeNodeChildren(node.spans, node), ); } if ("items" in node) { const children: TreeNode[] = []; for (let i = 0; i < node.items.length; i++) { children.push(nodeToTreeNode(node.items[i])); } return createTreeNode(node, children); } return createTreeNode(node); }; const textNodeSpansToTreeNodeChildren = ( spans: RTInlineNode[], node: RTTextNode, parentSpan?: RTInlineNode, ): TreeNode[] => { if (!spans.length) { return [createTextTreeNode(node.text)]; } const mutSpans: RTInlineNode[] = spans.slice(0); // Sort spans using the following criteria: // // 1. By start index (ascending) // 2. If start indices are equal, by end index (descending) // // If start and end indices of more than one span are equal, use what // the API gives without modifications. // // Sorting using this algorithm ensures proper detection of child // spans. mutSpans.sort((a, b) => a.start - b.start || b.end - a.end); const children: TreeNode[] = []; for (let i = 0; i < mutSpans.length; i++) { const span = mutSpans[i]; const parentSpanStart = (parentSpan && parentSpan.start) || 0; const spanStart = span.start - parentSpanStart; const spanEnd = span.end - parentSpanStart; const text = node.text.slice(spanStart, spanEnd); const childSpans: RTInlineNode[] = []; for (let j = i; j < mutSpans.length; j++) { const siblingSpan = mutSpans[j]; if (siblingSpan !== span) { if (siblingSpan.start >= span.start && siblingSpan.end <= span.end) { childSpans.push(siblingSpan); mutSpans.splice(j, 1); j--; } else if ( siblingSpan.start < span.end && siblingSpan.end > span.start ) { childSpans.push({ ...siblingSpan, end: span.end, }); mutSpans[j] = { ...siblingSpan, start: span.end, }; } } } if (i === 0 && spanStart > 0) { children.push(createTextTreeNode(node.text.slice(0, spanStart))); } const spanWithText = { ...span, text }; children.push( createTreeNode( spanWithText, textNodeSpansToTreeNodeChildren( childSpans, { ...node, text, }, span, ), ), ); if (spanEnd < node.text.length) { children.push( createTextTreeNode( node.text.slice( spanEnd, mutSpans[i + 1] ? mutSpans[i + 1].start - parentSpanStart : undefined, ), ), ); } } return children; };