UNPKG

@portabletext/toolkit

Version:

Toolkit of handy utility functions for dealing with Portable Text

180 lines (179 loc) 7.45 kB
function isPortableTextSpan(node) { return node._type === "span" && "text" in node && typeof node.text == "string" && (node.marks === void 0 || Array.isArray(node.marks) && node.marks.every((mark) => typeof mark == "string")); } function isPortableTextBlock(node) { return typeof node._type == "string" && node._type[0] !== "@" && (!("markDefs" in node) || !node.markDefs || Array.isArray(node.markDefs) && node.markDefs.every((def) => typeof def._key == "string")) && "children" in node && Array.isArray(node.children) && node.children.every((child) => typeof child == "object" && "_type" in child); } function isPortableTextListItemBlock(block) { return isPortableTextBlock(block) && "listItem" in block && typeof block.listItem == "string" && (block.level === void 0 || typeof block.level == "number"); } function isPortableTextToolkitList(block) { return block._type === "@list"; } function isPortableTextToolkitSpan(span) { return span._type === "@span"; } function isPortableTextToolkitTextNode(node) { return node._type === "@text"; } const knownDecorators = [ "strong", "em", "code", "underline", "strike-through" ]; function sortMarksByOccurences(span, index, blockChildren) { if (!isPortableTextSpan(span) || !span.marks || !span.marks.length) return []; let marks = span.marks.slice(), occurences = {}; return marks.forEach((mark) => { occurences[mark] = 1; for (let siblingIndex = index + 1; siblingIndex < blockChildren.length; siblingIndex++) { let sibling = blockChildren[siblingIndex]; if (sibling && isPortableTextSpan(sibling) && Array.isArray(sibling.marks) && sibling.marks.indexOf(mark) !== -1) occurences[mark]++; else break; } }), marks.sort((markA, markB) => sortMarks(occurences, markA, markB)); } function sortMarks(occurences, markA, markB) { let aOccurences = occurences[markA], bOccurences = occurences[markB]; if (aOccurences !== bOccurences) return bOccurences - aOccurences; let aKnownPos = knownDecorators.indexOf(markA), bKnownPos = knownDecorators.indexOf(markB); return aKnownPos === bKnownPos ? markA.localeCompare(markB) : aKnownPos - bKnownPos; } function buildMarksTree(block) { let { children } = block, markDefs = block.markDefs ?? []; if (!children || !children.length) return []; let sortedMarks = children.map(sortMarksByOccurences), rootNode = { _type: "@span", children: [], markType: "<unknown>" }, nodeStack = [rootNode]; for (let i = 0; i < children.length; i++) { let span = children[i]; if (!span) continue; let marksNeeded = sortedMarks[i] || [], pos = 1; if (nodeStack.length > 1) for (; pos < nodeStack.length; pos++) { let mark = nodeStack[pos]?.markKey || "", index = marksNeeded.indexOf(mark); if (index === -1) break; marksNeeded.splice(index, 1); } nodeStack = nodeStack.slice(0, pos); let currentNode = nodeStack[nodeStack.length - 1]; if (currentNode) { for (let markKey of marksNeeded) { let markDef = markDefs?.find((def) => def._key === markKey), node = { _type: "@span", _key: span._key, children: [], markDef, markType: markDef ? markDef._type : markKey, markKey }; currentNode.children.push(node), nodeStack.push(node), currentNode = node; } if (isPortableTextSpan(span)) { let lines = span.text.split("\n"); for (let line = lines.length; line-- > 1;) lines.splice(line, 0, "\n"); currentNode.children = currentNode.children.concat(lines.map((text) => ({ _type: "@text", text }))); } else currentNode.children = currentNode.children.concat(span); } } return rootNode.children; } function nestLists(blocks, mode) { let tree = [], currentList; for (let i = 0; i < blocks.length; i++) { let block = blocks[i]; if (block) { if (!isPortableTextListItemBlock(block)) { tree.push(block), currentList = void 0; continue; } if (!currentList) { currentList = listFromBlock(block, i, mode), tree.push(currentList); continue; } if (blockMatchesList(block, currentList)) { currentList.children.push(block); continue; } if ((block.level || 1) > currentList.level) { let newList = listFromBlock(block, i, mode); if (mode === "html") { let lastListItem = currentList.children[currentList.children.length - 1], newLastChild = { ...lastListItem, children: [...lastListItem.children, newList] }; currentList.children[currentList.children.length - 1] = newLastChild; } else currentList.children.push(newList); currentList = newList; continue; } if ((block.level || 1) < currentList.level) { let matchingBranch = tree[tree.length - 1], match = matchingBranch && findListMatching(matchingBranch, block); if (match) { currentList = match, currentList.children.push(block); continue; } currentList = listFromBlock(block, i, mode), tree.push(currentList); continue; } if (block.listItem !== currentList.listItem) { let matchingBranch = tree[tree.length - 1], match = matchingBranch && findListMatching(matchingBranch, { level: block.level || 1 }); if (match && match.listItem === block.listItem) { currentList = match, currentList.children.push(block); continue; } else { currentList = listFromBlock(block, i, mode), tree.push(currentList); continue; } } console.warn("Unknown state encountered for block", block), tree.push(block); } } return tree; } function blockMatchesList(block, list) { return (block.level || 1) === list.level && block.listItem === list.listItem; } function listFromBlock(block, index, mode) { return { _type: "@list", _key: `${block._key || `${index}`}-parent`, mode, level: block.level || 1, listItem: block.listItem, children: [block] }; } function findListMatching(rootNode, matching) { let level = matching.level || 1, style = matching.listItem || "normal", filterOnType = typeof matching.listItem == "string"; if (isPortableTextToolkitList(rootNode) && (rootNode.level || 1) === level && filterOnType && (rootNode.listItem || "normal") === style) return rootNode; if (!("children" in rootNode)) return; let node = rootNode.children[rootNode.children.length - 1]; return node && !isPortableTextSpan(node) ? findListMatching(node, matching) : void 0; } function spanToPlainText(span) { let text = ""; return span.children.forEach((current) => { isPortableTextToolkitTextNode(current) ? text += current.text : isPortableTextToolkitSpan(current) && (text += spanToPlainText(current)); }), text; } const leadingSpace = /^\s/, trailingSpace = /\s$/; function toPlainText(block) { let blocks = Array.isArray(block) ? block : [block], text = ""; return blocks.forEach((current, index) => { if (!isPortableTextBlock(current)) return; let pad = !1; current.children.forEach((span) => { isPortableTextSpan(span) ? (text += pad && text && !trailingSpace.test(text) && !leadingSpace.test(span.text) ? " " : "", text += span.text, pad = !1) : pad = !0; }), index !== blocks.length - 1 && (text += "\n\n"); }), text; } const LIST_NEST_MODE_HTML = "html", LIST_NEST_MODE_DIRECT = "direct"; export { LIST_NEST_MODE_DIRECT, LIST_NEST_MODE_HTML, buildMarksTree, isPortableTextBlock, isPortableTextListItemBlock, isPortableTextSpan, isPortableTextToolkitList, isPortableTextToolkitSpan, isPortableTextToolkitTextNode, nestLists, sortMarksByOccurences, spanToPlainText, toPlainText }; //# sourceMappingURL=index.js.map