@prismicio/client
Version:
The official JavaScript + TypeScript client library for Prismic
212 lines (184 loc) • 4.65 kB
text/typescript
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
}