UNPKG

datocms-structured-text-utils

Version:

A set of Typescript types and helpers to work with DatoCMS Structured Text fields.

271 lines 10.5 kB
var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; import { hasChildren, isBlock, isCode, isDocument, isHeading, isInlineBlock, isItemLink, isLink, isList, isParagraph, isSpan, } from './guards'; /** * Default configuration values. */ var DEFAULT_CONFIG = { /** Maximum width for inspect output */ MAX_WIDTH: 80, }; /** * Type guard to check if a node has children and provides proper type narrowing. * This replaces the need for unsafe type assertions when accessing children. * * @param node - The node to check * @returns True if the node has children, with proper type narrowing */ function nodeHasChildren(node) { return hasChildren(node); } /** * Default block formatter that displays items in a structured format. * * @template BlockItemType - Type of block items * @template InlineBlockItemType - Type of inline block items * @param item - The item to format * @param maxLineWidth - Suggested maximum line width (unused in default formatter) * @returns Formatted string representation */ function defaultBlockFormatter(item) { return "(item: " + JSON.stringify(item) + ")"; } /** * Safely extracts the root node from either a Document or Node input. * * @template BlockItemType - Type of block items * @template InlineBlockItemType - Type of inline block items * @param input - Either a Document or Node * @returns The root node */ function extractNode(input) { return isDocument(input) ? input.document : input; } /** * Inspects and renders a structured text document or node as a tree structure. * Skips the root node and returns its children directly. * * @template BlockItemType - Type of block items (defaults to BlockId) * @template InlineBlockItemType - Type of inline block items (defaults to BlockId) * @param input - The structured text document or node to inspect * @param options - Configuration options for the inspector * @returns A TreeNode representation of the structure (without root node) * * @example * ```typescript * const tree = inspectionTreeNodes(document, { * blockFormatter: (item) => typeof item === 'string' ? item : item.title * }); * console.log(formatAsTree(tree)); * ``` */ export function inspectionTreeNodes(input, options) { if (options === void 0) { options = {}; } var rootNode = extractNode(input); var rootTreeNode = buildTreeNode(rootNode, options); // Skip the root node and return a wrapper containing its children if (rootTreeNode.nodes && rootTreeNode.nodes.length > 0) { // If there's only one child, return it directly if (rootTreeNode.nodes.length === 1) { return rootTreeNode.nodes[0]; } // If there are multiple children, create a wrapper node with empty label return { label: '', nodes: rootTreeNode.nodes, }; } // Fallback: return empty wrapper if no children return { label: '' }; } /** * Inspects and formats a structured text document or node as a tree string. * This is a convenience function that combines inspectionTreeNodes and formatAsTree. * * @template BlockItemType - Type of block items (defaults to BlockId) * @template InlineBlockItemType - Type of inline block items (defaults to BlockId) * @param input - The structured text document or node to inspect * @param options - Configuration options for the inspector * @returns A formatted tree string representation * * @example * ```typescript * const treeString = inspect(document, { * blockFormatter: (item) => typeof item === 'string' ? item : item.title * }); * console.log(treeString); * ``` */ export function inspect(input, options) { if (options === void 0) { options = {}; } return formatAsTree(inspectionTreeNodes(input, options)); } /** * Recursively builds a TreeNode representation of a node and its children. * * @template BlockItemType - Type of block items * @template InlineBlockItemType - Type of inline block items * @param node - The current node to process * @param options - Configuration options * @returns TreeNode representation of the node and its subtree */ function buildTreeNode(node, options) { var _a; var _b; // Calculate available width for block content // Mimicking the old calculation: prefix + connector + space (typically around 6-8 characters for deeper nodes) var maxWidth = (_b = options.maxWidth) !== null && _b !== void 0 ? _b : DEFAULT_CONFIG.MAX_WIDTH; var availableWidth = Math.max(20, maxWidth - 8); var nodeLabel = buildNodeLabel(node, options, availableWidth); // Create the TreeNode with the label var treeNode = { label: nodeLabel, }; // Add children if the node has them if (nodeHasChildren(node)) { var children = node.children; if (children.length > 0) { treeNode.nodes = []; for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { var child = children_1[_i]; treeNode.nodes.push(buildTreeNode(child, options)); } } } // Handle TreeNode returns from blockFormatter for block/inlineBlock nodes if ((isBlock(node) || isInlineBlock(node)) && options.blockFormatter) { var formattedContent = options.blockFormatter(node.item, availableWidth); if (typeof formattedContent !== 'string') { // Initialize nodes array if it doesn't exist if (!treeNode.nodes) { treeNode.nodes = []; } // Handle single TreeNode or array of TreeNodes if (Array.isArray(formattedContent)) { (_a = treeNode.nodes).push.apply(_a, formattedContent); } else { treeNode.nodes.push(formattedContent); } } } return treeNode; } /** * Builds a descriptive label for a node based on its type and properties. * * @template BlockItemType - Type of block items * @template InlineBlockItemType - Type of inline block items * @param node - The node to create a label for * @param options - Configuration options including block formatter * @param availableWidth - Available width for content formatting * @returns A formatted string label for the node */ function buildNodeLabel(node, options, availableWidth) { var metaInfo = []; var content = ''; if (isSpan(node)) { if (node.marks && node.marks.length > 0) { metaInfo.push("marks: " + node.marks.join(', ')); } content = " \"" + truncateText(node.value, availableWidth) + "\""; } else if (isCode(node)) { if (node.language) { metaInfo.push("language: \"" + node.language + "\""); } content = " \"" + truncateText(node.code, availableWidth) + "\""; } else if (isHeading(node)) { metaInfo.push("level: " + node.level); } else if (isList(node)) { metaInfo.push("style: " + node.style); } else if (isLink(node)) { metaInfo.push("url: \"" + node.url + "\""); if (node.meta && node.meta.length > 0) { var metaEntries = node.meta .map(function (m) { return m.id + "=\"" + m.value + "\""; }) .join(', '); metaInfo.push("meta: {" + metaEntries + "}"); } } else if (isItemLink(node)) { metaInfo.push("item: \"" + node.item + "\""); if (node.meta && node.meta.length > 0) { var metaEntries = node.meta .map(function (m) { return m.id + "=\"" + m.value + "\""; }) .join(', '); metaInfo.push("meta: {" + metaEntries + "}"); } } else if (isBlock(node) || isInlineBlock(node)) { var formatter = options.blockFormatter || defaultBlockFormatter; var formattedContent = formatter(node.item, availableWidth); // Handle string returns - always treat as single-line (strip newlines) if (typeof formattedContent === 'string') { var singleLineContent = formattedContent.replace(/\n/g, ' ').trim(); content = " " + singleLineContent; } // TreeNode/TreeNode[] returns will be handled in buildTreeNode function } else if (isParagraph(node)) { if (node.style) { metaInfo.push("style: \"" + node.style + "\""); } } var metaString = metaInfo.length > 0 ? " (" + metaInfo.join(', ') + ")" : ''; return "" + node.type + metaString + content; } function truncateText(text, maxLength) { if (text.length <= maxLength) { return text; } return text.substring(0, maxLength - 3) + '...'; } export function formatAsTree(input) { var root = typeof input === 'string' ? { label: input } : input; var out = []; function render(node, ancestorsHasNext, isLast) { var label = node.label.trim(); var children = node.nodes || []; // ⬇️ Skip wrapper nodes (no label but has children) if (!label && children.length > 0) { children.forEach(function (child, idx) { var childIsLast = idx === children.length - 1; render(child, ancestorsHasNext, childIsLast); }); return; } // Build ancestor prefix from booleans: true -> '│ ', false -> ' ' var ancestorPrefix = ancestorsHasNext .map(function (h) { return (h ? '│ ' : ' '); }) .join(''); var branch = isLast ? '└' : '├'; var lines = label.split('\n'); // First line out.push("" + ancestorPrefix + branch + " " + lines[0]); // Continuations var contPrefix = ancestorPrefix + (isLast ? ' ' : '│ '); for (var i = 1; i < lines.length; i++) { out.push("" + contPrefix + lines[i]); } // Recurse children children.forEach(function (child, idx) { var childIsLast = idx === children.length - 1; render(child, __spreadArrays(ancestorsHasNext, [!isLast]), childIsLast); }); } // Root as a top element render(root, [], true); return out.join('\n') + '\n'; } //# sourceMappingURL=inspector.js.map