UNPKG

@prismicio/client

Version:

The official JavaScript + TypeScript client library for Prismic

212 lines (184 loc) 4.65 kB
import type { RTAnyNode, RTBlockNode, RTInlineNode, RTListItemNode, RTNode, RTOListItemNode, RTTextNode, } from "../types/value/richText" import { RichTextNodeType } from "../types/value/richText" import type { 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 }