@storyblok/richtext
Version:
Storyblok RichText Resolver
1 lines • 50.4 kB
Source Map (JSON)
{"version":3,"file":"static.mjs","names":[],"sources":["../src/static/dynamic-resolvers.ts","../src/static/render-map.generated.ts","../src/static/style.ts","../src/static/attribute.ts","../src/utils/index.ts","../src/static/node-helpers.ts","../src/images-optimization.ts","../src/static/util.ts","../src/static/render-richtext.ts"],"sourcesContent":["export const resolveHeadingTag = (attrs: { level?: number }): string => {\n const level\n = typeof attrs?.level === 'number'\n ? Math.min(6, Math.max(1, attrs.level))\n : 1;\n\n return `h${level}`;\n};\n","// THIS FILE IS AUTO-GENERATED. DO NOT EDIT.\nimport { resolveHeadingTag } from './dynamic-resolvers';\n/**\n * Render config for Tiptap nodes\n */\n export const NODE_RENDER_MAP = {\n paragraph: {\n \"tag\": \"p\",\n \"content\": true\n},\n doc: null,\n text: null,\n blockquote: {\n \"tag\": \"blockquote\",\n \"content\": true\n},\n heading: {\n resolve: resolveHeadingTag,\n },\n bullet_list: {\n \"tag\": \"ul\",\n \"content\": true\n},\n ordered_list: {\n \"tag\": \"ol\",\n \"attrs\": {\n \"order\": 1\n },\n \"content\": true\n},\n list_item: {\n \"tag\": \"li\",\n \"content\": true\n},\n code_block: {\n \"tag\": \"pre\",\n \"children\": [\n {\n \"tag\": \"code\",\n \"content\": true\n }\n ]\n},\n hard_break: {\n \"tag\": \"br\"\n},\n horizontal_rule: {\n \"tag\": \"hr\"\n},\n image: {\n \"tag\": \"img\"\n},\n emoji: {\n \"tag\": \"img\",\n \"attrs\": {\n \"style\": \"width: 1.25em; height: 1.25em; vertical-align: text-top;\",\n \"draggable\": \"false\",\n \"loading\": \"lazy\"\n }\n},\n table: {\n \"tag\": \"table\",\n \"content\": true\n},\n tableRow: {\n \"tag\": \"tr\",\n \"content\": true\n},\n tableCell: {\n \"tag\": \"td\",\n \"content\": true\n},\n tableHeader: {\n \"tag\": \"th\",\n \"content\": true\n},\n blok: {\n \"tag\": \"span\",\n \"attrs\": {\n \"style\": \"display: none;\"\n }\n},\n details: {\n \"tag\": \"details\",\n \"content\": true\n},\n detailsContent: {\n \"tag\": \"div\",\n \"attrs\": {\n \"data-type\": \"detailsContent\"\n },\n \"content\": true\n},\n detailsSummary: {\n \"tag\": \"summary\",\n \"content\": true\n},\n} as const;\n\n/**\n * Render config for Tiptap marks\n */\n export const MARK_RENDER_MAP = {\n link: {\n \"tag\": \"a\",\n \"content\": true\n},\n bold: {\n \"tag\": \"strong\",\n \"content\": true\n},\n italic: {\n \"tag\": \"em\",\n \"content\": true\n},\n strike: {\n \"tag\": \"s\",\n \"content\": true\n},\n underline: {\n \"tag\": \"u\",\n \"content\": true\n},\n code: {\n \"tag\": \"code\",\n \"content\": true\n},\n superscript: {\n \"tag\": \"sup\",\n \"content\": true\n},\n subscript: {\n \"tag\": \"sub\",\n \"content\": true\n},\n highlight: {\n \"tag\": \"mark\",\n \"content\": true\n},\n textStyle: {\n \"tag\": \"span\",\n \"content\": true\n},\n anchor: {\n \"tag\": \"span\",\n \"content\": true\n},\n styled: {\n \"tag\": \"span\",\n \"content\": true\n},\n reporter: null,\n} as const;\n\n","import type { AttrValue } from './types';\n\n/**\n * Converts a style object to a CSS string.\n * @param style - The style object to convert.\n * @returns A CSS string representation of the style object.\n * @example\n * const styleObj = { color: 'red', fontSize: '16px' };\n * const cssString = styleToString(styleObj);\n * console.log(cssString); // Output: \"color: red; font-size: 16px\"\n */\nexport function styleToString(\n style: Record<string, AttrValue>,\n) {\n return Object.entries(style)\n .filter(([, value]) => isValidStyleValue(value))\n .map(\n ([key, value]) =>\n `${camelToKebab(key)}: ${value};`,\n )\n .join(' ');\n}\n\n/**\n * Converts a CSS string to a style object.\n * @param style - The CSS string to convert.\n * @returns A style object representation of the CSS string.\n * @example\n * const cssString = \"color: red; font-size: 16px\";\n * const styleObj = stringToStyle(cssString);\n * console.log(styleObj); // Output: { color: 'red', fontSize: '16px' }\n */\nexport function stringToStyle(style: string): Record<string, string> {\n return style\n .split(';')\n .map(rule => rule.trim())\n .filter(Boolean)\n .reduce<Record<string, string>>((acc, rule) => {\n const colonIdx = rule.indexOf(':');\n\n // ignore invalid declarations like \"color\" or \": red\"\n if (colonIdx === -1) {\n return acc;\n }\n\n const key = rule.slice(0, colonIdx).trim();\n const value = rule.slice(colonIdx + 1).trim();\n\n if (!key || !value) {\n return acc;\n }\n\n acc[kebabToCamel(key)] = value;\n return acc;\n }, {});\n}\n\nfunction kebabToCamel(str: string) {\n return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());\n}\nfunction camelToKebab(str: string) {\n return str.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);\n}\nexport function isValidStyleValue(value: unknown) {\n return value !== null && value !== undefined && value !== '';\n}\n","import { NODE_RENDER_MAP } from './render-map.generated';\nimport { isValidStyleValue, stringToStyle } from './style';\nimport type { AttrValue, SbRichTextElement } from './types';\n\ntype StyleMap = Partial<{\n [K in SbRichTextElement]: Record<string, string>;\n}>;\nexport type AttrMap = Record<string, string>;\n\n/**\n * Maps Tiptap attribute names to CSS property names for specific element types.\n */\nconst STYLE_MAP: StyleMap = {\n highlight: {\n color: 'backgroundColor',\n },\n textStyle: {\n color: 'color',\n },\n paragraph: {\n textAlign: 'textAlign',\n },\n heading: {\n textAlign: 'textAlign',\n },\n tableCell: {\n backgroundColor: 'backgroundColor',\n colwidth: 'width',\n },\n tableHeader: {\n colwidth: 'width',\n },\n};\n\n/**\n * Maps Tiptap attribute names to HTML attribute names.\n */\nconst DEFAULT_ATTR_MAP: AttrMap = {\n fallbackImage: 'src',\n body: 'data-body',\n colspan: 'colSpan',\n rowspan: 'rowSpan',\n name: 'data-name',\n emoji: 'data-emoji',\n};\n\n/**\n * Attributes that should be excluded from the output.\n */\nexport const EXCLUDED_ATTRS = new Set(['level', 'linktype', 'uuid', 'anchor', 'meta_data', 'copyright', 'source']);\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Resolves the href for Storyblok link types (story, email).\n * @returns The resolved href, or undefined if no special handling is needed.\n */\nfunction resolveStoryblokLinkHref(attrs: Record<string, unknown>): string | undefined {\n const { linktype, href, anchor } = attrs;\n\n if (linktype === 'story') {\n const base = typeof href === 'string' ? href : '';\n const hash = typeof anchor === 'string' && anchor ? `#${anchor}` : '';\n return `${base}${hash}`;\n }\n\n if (linktype === 'email' && typeof href === 'string') {\n const email = href.replace(/^mailto:/, '');\n return `mailto:${email}`;\n }\n\n return undefined;\n}\n\n/**\n * Extracts static attributes and styles from the render map for a given element type.\n */\nfunction getStaticAttrsFromRenderMap(\n type: SbRichTextElement,\n): { staticAttrs: Record<string, unknown>; staticStyle: Record<string, AttrValue> } {\n const staticStyle: Record<string, AttrValue> = {};\n let staticAttrs: Record<string, unknown> = {};\n\n if (!(type in NODE_RENDER_MAP)) {\n return { staticAttrs, staticStyle };\n }\n\n const renderMap = NODE_RENDER_MAP[type as keyof typeof NODE_RENDER_MAP];\n if (!renderMap || !('attrs' in renderMap)) {\n return { staticAttrs, staticStyle };\n }\n\n const renderAttrs = renderMap.attrs || {};\n const rawStyle = 'style' in renderAttrs && typeof renderAttrs.style === 'string'\n ? renderAttrs.style\n : '';\n\n const { style: _style, ...rest } = renderAttrs as Record<string, unknown>;\n staticAttrs = rest;\n\n if (rawStyle) {\n Object.assign(staticStyle, stringToStyle(rawStyle));\n }\n\n return { staticAttrs, staticStyle };\n}\n\n/**\n * Converts an attribute value to a CSS value based on the style map.\n * Handles arrays (e.g., colwidth) and primitive values.\n */\nfunction convertToStyleValue(value: unknown): AttrValue | undefined {\n if (Array.isArray(value)) {\n return value[0] != null ? `${value[0]}px` : undefined;\n }\n\n if (typeof value === 'number' || typeof value === 'string') {\n return value;\n }\n\n return String(value);\n}\n\n/**\n * Processes a single attribute and adds it to either the style or rest object.\n */\nfunction processAttribute(\n key: string,\n value: unknown,\n type: SbRichTextElement,\n styleMap: Record<string, string>,\n attrMap: AttrMap,\n style: Record<string, AttrValue>,\n rest: Record<string, unknown>,\n): void {\n if (!isValidStyleValue(value) || EXCLUDED_ATTRS.has(key)) {\n return;\n }\n\n // Handle style-mapped attributes\n if (key in styleMap) {\n const cssProp = styleMap[key]!;\n const cssValue = convertToStyleValue(value);\n if (cssValue !== undefined && isValidStyleValue(cssValue)) {\n style[cssProp] = cssValue;\n }\n return;\n }\n\n const attrName = attrMap[key] ?? key;\n\n // Handle custom attributes for links (spread them as individual attributes)\n if (attrName === 'custom' && type === 'link' && typeof value === 'object' && value !== null) {\n for (const [customKey, customValue] of Object.entries(value)) {\n rest[customKey] = String(customValue);\n }\n return;\n }\n\n // Handle other object values (stringify them)\n if (typeof value === 'object' && value !== null) {\n rest[attrName] = JSON.stringify(value);\n return;\n }\n\n // Default: pass through as-is\n rest[attrName] = value;\n}\n\n// ============================================================================\n// Main Export\n// ============================================================================\n\n/**\n * Process Tiptap attributes into HTML attributes and inline styles.\n * Applies internal style mappings and allows extending or overriding\n * default attribute mappings via `extendAttrMap`.\n *\n * @param type - {@link SbRichTextElement}\n * @param attrs - Attributes from the node/mark\n * @param extendAttrMap - {@link AttrMap} Additional attribute mappings (overrides defaults)\n * @returns Processed attributes with optional `style` object\n */\nexport function processAttrs(\n type: SbRichTextElement,\n attrs: Record<string, unknown> = {},\n extendAttrMap: AttrMap = {},\n): Record<string, unknown> {\n const { staticAttrs, staticStyle } = getStaticAttrsFromRenderMap(type);\n const style: Record<string, AttrValue> = { ...staticStyle };\n const rest: Record<string, unknown> = {};\n\n const styleMap = STYLE_MAP[type] || {};\n const attrMap = { ...DEFAULT_ATTR_MAP, ...extendAttrMap };\n const mergedAttrs = { ...attrs, ...staticAttrs };\n\n for (const [key, value] of Object.entries(mergedAttrs)) {\n processAttribute(key, value, type, styleMap, attrMap, style, rest);\n }\n\n // Special handling for Storyblok links\n if (type === 'link') {\n const linkHref = resolveStoryblokLinkHref(attrs);\n if (linkHref !== undefined) {\n rest.href = linkHref;\n }\n }\n\n return {\n ...rest,\n ...(Object.keys(style).length > 0 && { style }),\n };\n}\n\n/**\n * Escapes special HTML characters in attribute values.\n */\nexport const escapeAttr = (value: unknown): string =>\n String(value).replace(/[&\"'<>]/g, (char) => {\n switch (char) {\n case '&': return '&';\n case '\"': return '"';\n case '\\'': return ''';\n case '<': return '<';\n case '>': return '>';\n default: return char;\n }\n });\n","import type { BlockAttributes, MarkNode, StoryblokRichTextNode, TextNode } from '../types';\n\n/**\n * Deep equality comparison for plain objects, arrays, and primitives.\n */\nexport function deepEqual(a: any, b: any): boolean {\n if (a === b) {\n return true;\n }\n if (a === null || a === undefined || b === null || b === undefined) {\n return a === b;\n }\n if (typeof a !== typeof b) {\n return false;\n }\n if (typeof a !== 'object') {\n return false;\n }\n if (Array.isArray(a) !== Array.isArray(b)) {\n return false;\n }\n if (Array.isArray(a)) {\n if (a.length !== (b as any[]).length) {\n return false;\n }\n return a.every((v: any, i: number) => deepEqual(v, (b as any[])[i]));\n }\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) {\n return false;\n }\n return aKeys.every(k => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k]));\n}\n\n/** Checks if two marks are equal by comparing their type and attrs. */\nexport function markEquals<T>(a: MarkNode<T>, b: MarkNode<T>): boolean {\n return a.type === b.type && deepEqual(a.attrs, b.attrs);\n}\n\n/** Type guard: checks if a node is a text node with at least one mark. */\nexport function isMarkedTextNode<T>(node: StoryblokRichTextNode<T>): node is TextNode<T> {\n return node.type === 'text' && !!(node as TextNode<T>).marks?.length;\n}\n\n/** Returns marks unique to a node (not in the shared set), or undefined if all marks are shared. */\nexport function getUniqueMarks<T>(marks: MarkNode<T>[], shared: MarkNode<T>[]): MarkNode<T>[] | undefined {\n const unique = marks.filter(m => !shared.some(s => markEquals(s, m)));\n return unique.length ? unique : undefined;\n}\n\nexport interface MarkedTextGroup<T> {\n group: TextNode<T>[];\n shared: MarkNode<T>[];\n endIndex: number;\n}\n\n/**\n * Starting at `fromIndex`, collects adjacent marked text nodes that share at least one common mark.\n * Returns null if the node at `fromIndex` is not a marked text node.\n */\nexport function collectMarkedTextGroup<T>(\n children: StoryblokRichTextNode<T>[],\n fromIndex: number,\n): MarkedTextGroup<T> | null {\n const child = children[fromIndex];\n if (!isMarkedTextNode(child)) {\n return null;\n }\n\n const group: TextNode<T>[] = [child];\n let shared: MarkNode<T>[] = child.marks!;\n let j = fromIndex + 1;\n while (j < children.length) {\n const next = children[j];\n if (!isMarkedTextNode(next)) {\n break;\n }\n const nextShared = shared.filter(m =>\n next.marks!.some(n => markEquals(m, n)),\n );\n if (nextShared.length === 0) {\n break;\n }\n shared = nextShared;\n group.push(next);\n j++;\n }\n\n return { group, shared, endIndex: j };\n}\n\nexport const SELF_CLOSING_TAGS = [\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n];\n\nexport const BLOCK_LEVEL_TAGS = [\n 'address',\n 'article',\n 'aside',\n 'blockquote',\n 'canvas',\n 'dd',\n 'div',\n 'dl',\n 'dt',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hgroup',\n 'hr',\n 'li',\n 'main',\n 'nav',\n 'noscript',\n 'ol',\n 'output',\n 'p',\n 'pre',\n 'section',\n 'table',\n 'tfoot',\n 'ul',\n 'video',\n];\n\n/**\n * Converts an object of attributes to a string.\n *\n * @param {Record<string, string>} [attrs]\n *\n * @returns {string} The string representation of the attributes.\n *\n * @example\n *\n * ```typescript\n * const attrs = {\n * class: 'text-red',\n * style: 'color: red',\n * }\n *\n * const attrsString = attrsToString(attrs)\n *\n * console.log(attrsString) // 'class=\"text-red\" style=\"color: red\"'\n *\n * ```\n *\n */\nexport const attrsToString = (attrs: BlockAttributes = {}) => {\n const { custom, ...attrsWithoutCustom } = attrs;\n const normalizedAttrs = { ...attrsWithoutCustom, ...custom };\n return Object.keys(normalizedAttrs)\n .filter(key => normalizedAttrs[key] != null)\n .map(key => `${key}=\"${String(normalizedAttrs[key]).replace(/&/g, '&').replace(/\"/g, '"')}\"`)\n .join(' ');\n};\n\n/**\n * Converts an object of attributes to a CSS style string.\n *\n * @param {Record<string, string>} [attrs]\n *\n * @returns {string} The string representation of the CSS styles.\n *\n * @example\n *\n * ```typescript\n * const attrs = {\n * color: 'red',\n * fontSize: '16px',\n * }\n *\n * const styleString = attrsToStyle(attrs)\n *\n * console.log(styleString) // 'color: red; font-size: 16px'\n * ```\n */\nexport const attrsToStyle = (attrs: Record<string, string> = {}) => Object.keys(attrs)\n .map(key => `${key}: ${attrs[key]}`)\n .join('; ');\n\n/**\n * Escapes HTML entities in a string.\n *\n * @param {string} unsafeText\n * @return {*} {string}\n *\n * @example\n *\n * ```typescript\n * const unsafeText = '<script>alert(\"Hello\")</script>'\n *\n * const safeText = escapeHtml(unsafeText)\n *\n * console.log(safeText) // '<script>alert(\"Hello\")</script>'\n * ```\n */\nexport function escapeHtml(unsafeText: string): string {\n return unsafeText\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Removes undefined values from an object.\n *\n * @param {Record<string, any>} obj\n * @return {*} {Record<string, any>}\n *\n * @example\n *\n * ```typescript\n * const obj = {\n * name: 'John',\n * age: undefined,\n * }\n *\n * const cleanedObj = cleanObject(obj)\n *\n * console.log(cleanedObj) // { name: 'John' }\n * ```\n *\n */\nexport const cleanObject = (obj: Record<string, any>) => {\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null));\n};\n","import type { PMMark } from './types.generated';\nimport type { SbRichTextDoc } from './types';\nimport { deepEqual } from '../utils';\n\n// ============================================================================\n// Link Mark Helpers\n// ============================================================================\n\n/**\n * Gets the link mark from a text node, or null if not present.\n * @param node - The node to check\n * @returns The link mark if found, null otherwise\n */\nexport function getTextNodeLinkMark(node: SbRichTextDoc): LinkMark | null {\n if (node.type !== 'text' || !node.marks) {\n return null;\n }\n\n for (const mark of node.marks) {\n if (mark.type === 'link') {\n return mark;\n }\n }\n return null;\n}\nexport type LinkMark = PMMark & { type: 'link' };\n\n/**\n * Checks if two link marks have identical attributes.\n * Used for merging adjacent text nodes with the same link.\n * @param markA - First link mark\n * @param markB - Second link mark\n * @returns True if the marks have identical attributes\n */\nexport function areLinkMarksEqual(markA: LinkMark | null, markB: LinkMark | null): boolean {\n if (!markA || !markB) {\n return false;\n }\n\n return deepEqual(markA.attrs ?? {}, markB.attrs ?? {});\n}\n\n/**\n * Gets non-link marks from a text node.\n * Used when rendering text inside a merged link group.\n * @param node - The text node\n * @returns Array of marks excluding the link mark\n */\nexport function getInnerMarks(node: SbRichTextDoc): PMMark[] {\n if (node.type !== 'text' || !node.marks) {\n return [];\n }\n return node.marks.filter(m => m.type !== 'link');\n}\n\n/**\n * Identifies groups of adjacent text nodes that share the same link mark.\n * Returns an array of groups where each group is either:\n * - A single non-text node or text node without link\n * - Multiple consecutive text nodes with identical link marks\n *\n * @param children - Array of child nodes to group\n * @returns Array of node groups for rendering\n */\nexport function groupLinkNodes(children: SbRichTextDoc[]): Array<{\n nodes: SbRichTextDoc[];\n linkMark: LinkMark | null;\n}> {\n const groups: Array<{ nodes: SbRichTextDoc[]; linkMark: LinkMark | null }> = [];\n let i = 0;\n const len = children.length;\n\n while (i < len) {\n const node = children[i];\n const linkMark = getTextNodeLinkMark(node);\n\n if (linkMark) {\n // Find end of link group (consecutive text nodes with same link)\n const groupNodes: SbRichTextDoc[] = [node];\n let end = i + 1;\n\n while (end < len && areLinkMarksEqual(linkMark, getTextNodeLinkMark(children[end]))) {\n groupNodes.push(children[end]);\n end++;\n }\n\n groups.push({ nodes: groupNodes, linkMark });\n i = end;\n }\n else {\n groups.push({ nodes: [node], linkMark: null });\n i++;\n }\n }\n\n return groups;\n}\n\n// ============================================================================\n// Table Helpers\n// ============================================================================\n\n/**\n * Checks if a table row contains only tableHeader cells.\n * Used to determine which rows belong in thead vs tbody.\n * @param row - The table row node to check\n * @returns True if all cells are tableHeader type\n */\nexport function isTableHeaderRow(row: SbRichTextDoc): boolean {\n const cells = row.content;\n if (!cells?.length) {\n return false;\n }\n\n for (const cell of cells) {\n if (cell.type !== 'tableHeader') {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Splits table rows into header rows and body rows.\n * Header rows are contiguous tableHeader rows at the start.\n * @param rows - Array of table row nodes\n * @returns Object with headerRows and bodyRows arrays\n */\nexport function splitTableRows(rows: SbRichTextDoc[] | undefined): {\n headerRows: SbRichTextDoc[];\n bodyRows: SbRichTextDoc[];\n} {\n if (!rows?.length) {\n return { headerRows: [], bodyRows: [] };\n }\n\n // Find where header rows end (contiguous tableHeader rows at start)\n let headerEnd = 0;\n while (headerEnd < rows.length && isTableHeaderRow(rows[headerEnd])) {\n headerEnd++;\n }\n\n return {\n headerRows: rows.slice(0, headerEnd),\n bodyRows: rows.slice(headerEnd),\n };\n}\n","import type { StoryblokRichTextImageOptimizationOptions } from './types';\n\nexport function optimizeImage(src: string, options?: boolean | Partial<StoryblokRichTextImageOptimizationOptions>): { src: string; attrs: Record<string, any> } {\n if (!options) {\n return { src, attrs: {} };\n }\n let w = 0;\n let h = 0;\n const attrs: Record<string, unknown> = {};\n const filterParams: string[] = [];\n\n function validateAndPushFilterParam(value: number, min: number, max: number, filter: string, filterParams: string[]) {\n if (typeof value !== 'number' || value <= min || value >= max) {\n console.warn(`[StoryblokRichText] - ${filter.charAt(0).toUpperCase() + filter.slice(1)} value must be a number between ${min} and ${max} (inclusive)`);\n }\n else {\n filterParams.push(`${filter}(${value})`);\n }\n }\n\n if (typeof options === 'object') {\n if (options.width !== undefined) {\n if (typeof options.width === 'number' && options.width >= 0) {\n attrs.width = options.width;\n w = options.width;\n }\n else {\n console.warn('[StoryblokRichText] - Width value must be a number greater than or equal to 0');\n }\n }\n if (options.height !== undefined) {\n if (typeof options.height === 'number' && options.height >= 0) {\n attrs.height = options.height;\n h = options.height;\n }\n else {\n console.warn('[StoryblokRichText] - Height value must be a number greater than or equal to 0');\n }\n }\n if (options.height === 0 && options.width === 0) {\n delete attrs.width;\n delete attrs.height;\n console.warn('[StoryblokRichText] - Width and height values cannot both be 0');\n }\n if (options.loading && ['lazy', 'eager'].includes(options.loading)) {\n attrs.loading = options.loading;\n }\n if (options.class) {\n attrs.class = options.class;\n }\n\n if (options.filters) {\n const { filters } = options || {};\n const { blur, brightness, fill, format, grayscale, quality, rotate } = filters || {};\n\n if (blur) {\n validateAndPushFilterParam(blur, 0, 100, 'blur', filterParams);\n }\n if (quality) {\n validateAndPushFilterParam(quality, 0, 100, 'quality', filterParams);\n }\n if (brightness) {\n validateAndPushFilterParam(brightness, 0, 100, 'brightness', filterParams);\n }\n if (fill) {\n filterParams.push(`fill(${fill})`);\n }\n if (grayscale) {\n filterParams.push(`grayscale()`);\n }\n if (rotate && [0, 90, 180, 270].includes(options.filters.rotate || 0)) {\n filterParams.push(`rotate(${rotate})`);\n }\n if (format && ['webp', 'png', 'jpeg'].includes(format)) {\n filterParams.push(`format(${format})`);\n }\n }\n\n // Construct srcset attribute\n if (options.srcset) {\n attrs.srcset = options.srcset.map((entry): string | undefined => {\n if (typeof entry === 'number') {\n return `${src}/m/${entry}x0/${filterParams.length > 0 ? `filters:${filterParams.join(':')}` : ''} ${entry}w`;\n }\n if (Array.isArray(entry) && entry.length === 2) {\n const [entryWidth, entryHeight] = entry;\n return `${src}/m/${entryWidth}x${entryHeight}/${filterParams.length > 0 ? `filters:${filterParams.join(':')}` : ''} ${entryWidth}w`;\n }\n else {\n console.warn('[StoryblokRichText] - srcset entry must be a number or a tuple of two numbers');\n return undefined;\n }\n }).join(', ');\n }\n\n // Construct sizes attribute\n if (options.sizes) {\n attrs.sizes = options.sizes.join(', ');\n }\n }\n\n // server-side WebP support detection https://www.storyblok.com/docs/image-service/#optimize\n // https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg/m/\n let resultSrc = `${src}/m/`;\n if (w > 0 || h > 0) {\n resultSrc = `${resultSrc}${w}x${h}/`;\n }\n if (filterParams.length > 0) {\n resultSrc = `${resultSrc}filters:${filterParams.join(':')}`;\n }\n\n return {\n src: resultSrc,\n attrs,\n };\n}\n","import { MARK_RENDER_MAP, NODE_RENDER_MAP } from './render-map.generated';\nimport type { PMMark, PMNode } from './types.generated';\nimport { SELF_CLOSING_TAGS } from '../utils';\nimport type { HtmlTag, SbRichTextComponents, SbRichTextElement } from './types';\n\n/**\n * Resolves a component from the provided components map based on the type.\n * @param type - The type of the component to resolve.\n * @param components - The components map to search in.\n * @returns The resolved component or undefined if not found.\n * @example\n * const components = {\n * 'heading': MyCustomHeading,\n * };\n * const resolvedComponent = resolveComponent('heading', components);\n * console.log(resolvedComponent); // Output: MyCustomHeading\n */\nexport function resolveComponent<\n K extends SbRichTextElement,\n TComponent,\n>(\n type: K,\n components?: SbRichTextComponents<TComponent>,\n): SbRichTextComponents<TComponent>[K] | undefined {\n return components?.[type];\n}\n\n/**\n * Resolves the HTML tag for a given Richtext node or mark.\n * @param node - The Richtext node or mark to resolve the tag for.\n * @returns The resolved HTML tag as a string, or null if no tag could be resolved.\n * @example\n * const node = { type: 'paragraph', attrs: {} };\n * const tag = resolveTag(node);\n * console.log(tag); // Output: \"p\"\n */\nexport function resolveTag(node: PMNode | PMMark): HtmlTag | null {\n const type = node.type;\n\n const entry\n = NODE_RENDER_MAP[type as keyof typeof NODE_RENDER_MAP]\n ?? MARK_RENDER_MAP[type as keyof typeof MARK_RENDER_MAP];\n\n if (!entry) {\n return null;\n }\n\n if ('resolve' in entry && typeof entry.resolve === 'function') {\n return entry.resolve(node.attrs as Parameters<typeof entry.resolve>[0]) as HtmlTag;\n }\n\n if ('tag' in entry && typeof entry.tag === 'string') {\n return entry.tag as HtmlTag;\n }\n\n return null;\n}\n\n/**\n * Checks if a given HTML tag is self-closing.\n * @param tag - The HTML tag to check.\n * @returns True if the tag is self-closing, false otherwise.\n * @example\n * console.log(isSelfClosing('img')); // Output: true\n * console.log(isSelfClosing('div')); // Output: false\n *\n */\nexport function isSelfClosing(tag: HtmlTag | string): boolean {\n return SELF_CLOSING_TAGS.includes(tag);\n}\n\n/**\n * Returns static child definitions for a given RichText node.\n *\n * @param node - The RichText node\n * @returns Static child render specs, or null if none exist\n *\n * @example\n * const children = getStaticChildren({ type: 'table', attrs: {} });\n * // [{ tag: 'tbody', content: true }]\n */\nexport function getStaticChildren(node: PMNode) {\n const renderMap = NODE_RENDER_MAP[node.type as keyof typeof NODE_RENDER_MAP];\n const staticChildren = renderMap && 'children' in renderMap ? renderMap.children : null;\n return staticChildren;\n}\n","import { escapeHtml } from '../utils';\nimport { optimizeImage } from '../images-optimization';\nimport { escapeAttr, processAttrs } from './attribute';\nimport { areLinkMarksEqual, getTextNodeLinkMark, isTableHeaderRow } from './node-helpers';\nimport { styleToString } from './style';\nimport type { AttrValue, RenderSpec, SbRichTextDoc, SbRichTextElement, SbRichTextOptions } from './types';\nimport type { PMMark, PMNode } from './types.generated';\nimport { getStaticChildren, isSelfClosing, resolveTag } from './util';\n\ntype TextNode = PMNode & { type: 'text' };\n\n/**\n * Renders a Storyblok RichText JSON document to an HTML string.\n *\n * @param document - RichText JSON document, array of nodes, or nullish value\n * @param options - Renderer configuration with custom node/mark renderers\n * @returns Rendered HTML string\n *\n * @example\n * ```ts\n * const html = renderRichText({\n * type: 'doc',\n * content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }]\n * });\n * // => '<p>Hello</p>'\n * ```\n */\nexport function renderRichText(\n document: SbRichTextDoc | SbRichTextDoc[] | null | undefined,\n options?: SbRichTextOptions,\n): string {\n if (!document) {\n return '';\n }\n\n if (Array.isArray(document)) {\n return renderChildren(document, options);\n }\n\n const nodes = document.type === 'doc' ? document.content : [document];\n return nodes?.length ? renderChildren(nodes, options) : '';\n}\n\n/** Renders a single node to HTML. */\nfunction renderNode(node: SbRichTextDoc, options?: SbRichTextOptions): string {\n if (node.type === 'text') {\n return renderTextNode(node as TextNode, node.marks, options);\n }\n\n // Custom renderer takes full control\n const customRenderer = options?.renderers?.[node.type as keyof typeof options.renderers];\n if (customRenderer) {\n return (customRenderer as (props: typeof node) => string)(node);\n }\n\n if (node.type === 'blok') {\n console.warn('Rendering of \"blok\" nodes is not supported in richTextRenderer.');\n return '';\n }\n\n const tag = resolveTag(node);\n\n // No tag (e.g., nested doc): render children directly\n if (!tag) {\n return node.content ? renderChildren(node.content, options) : '';\n }\n\n if (node.type === 'image' && options?.optimizeImages) {\n return renderOptimizedImage(node, options);\n }\n\n const htmlAttrs = buildHtmlAttrs(node.type, node.attrs);\n\n if (isSelfClosing(tag)) {\n return `<${tag}${htmlAttrs}>`;\n }\n\n if (node.type === 'table') {\n return `<${tag}${htmlAttrs}>${renderTableRows(node.content, options)}</${tag}>`;\n }\n\n const content = node.content ? renderChildren(node.content, options) : '';\n\n const staticChildren = getStaticChildren(node);\n if (staticChildren) {\n const inner = renderStaticStructure(node.type, staticChildren, node.attrs, content);\n return `<${tag}>${inner}</${tag}>`;\n }\n\n return `<${tag}${htmlAttrs}>${content}</${tag}>`;\n}\n\n/** Renders an image node with optimization applied. */\nfunction renderOptimizedImage(\n node: SbRichTextDoc,\n options: SbRichTextOptions,\n): string {\n const attrs = node.attrs as Record<string, unknown> | undefined;\n const src = attrs?.src as string | undefined;\n\n let finalAttrs: Record<string, unknown> | undefined = attrs;\n\n if (src) {\n const { src: optimizedSrc, attrs: extraAttrs } = optimizeImage(\n src,\n options.optimizeImages,\n );\n\n finalAttrs = {\n ...attrs,\n src: optimizedSrc,\n ...extraAttrs,\n };\n }\n\n const htmlAttrs = buildHtmlAttrs('image', finalAttrs);\n return htmlAttrs ? `<img${htmlAttrs}>` : '<img>';\n}\n\n/**\n * Renders child nodes, merging adjacent text nodes that share the same link mark.\n * This produces cleaner HTML: `<a href=\"...\">text <b>bold</b> more</a>`\n * instead of: `<a>text</a><a><b>bold</b></a><a>more</a>`\n */\nfunction renderChildren(children: SbRichTextDoc[], options?: SbRichTextOptions): string {\n let result = '';\n let i = 0;\n const len = children.length;\n\n while (i < len) {\n const node = children[i];\n const linkMark = getTextNodeLinkMark(node);\n\n if (linkMark) {\n // Find end of link group (consecutive text nodes with same link)\n let end = i + 1;\n while (end < len && areLinkMarksEqual(linkMark, getTextNodeLinkMark(children[end]))) {\n end++;\n }\n result += renderLinkGroup(children, i, end, linkMark, options);\n i = end;\n }\n else {\n result += renderNode(node, options);\n i++;\n }\n }\n\n return result;\n}\n\n/** Renders a text node with its marks. */\nfunction renderTextNode(\n node: TextNode,\n marks: PMMark[] | undefined,\n options?: SbRichTextOptions,\n): string {\n let html = escapeHtml(node.text);\n\n if (!marks?.length) {\n return html;\n }\n\n for (const mark of marks) {\n html = wrapWithMark(html, mark, options);\n }\n\n return html;\n}\n\n/** Wraps content with a single mark tag. */\nfunction wrapWithMark(\n content: string,\n mark: PMMark,\n options?: SbRichTextOptions,\n): string {\n // Custom mark renderer\n const customRenderer = options?.renderers?.[mark.type as keyof typeof options.renderers];\n if (customRenderer) {\n return (customRenderer as (props: typeof mark & { children: string }) => string)({\n ...mark,\n children: content,\n });\n }\n\n const tag = resolveTag(mark);\n if (!tag) {\n return content;\n }\n\n const htmlAttrs = buildHtmlAttrs(mark.type, mark.attrs);\n return `<${tag}${htmlAttrs}>${content}</${tag}>`;\n}\n\n/** Link Mark Merging */\n\n/** Renders consecutive text nodes (from start to end) under a single link tag. */\nfunction renderLinkGroup(\n children: SbRichTextDoc[],\n start: number,\n end: number,\n linkMark: PMMark,\n options?: SbRichTextOptions,\n): string {\n let inner = '';\n for (let i = start; i < end; i++) {\n const node = children[i] as TextNode;\n const innerMarks = node.marks?.filter(m => m.type !== 'link');\n inner += renderTextNode(node, innerMarks, options);\n }\n\n const tag = resolveTag(linkMark);\n if (!tag) {\n return inner;\n }\n\n const htmlAttrs = buildHtmlAttrs(linkMark.type, linkMark.attrs);\n return `<${tag}${htmlAttrs}>${inner}</${tag}>`;\n}\n\n/** Table Rendering */\n\n/** Renders table rows with thead/tbody grouping based on cell types. */\nfunction renderTableRows(\n rows: SbRichTextDoc[] | undefined,\n options?: SbRichTextOptions,\n): string {\n if (!rows?.length) {\n return '';\n }\n\n // Find where header rows end (contiguous tableHeader rows at start)\n let headerEnd = 0;\n while (headerEnd < rows.length && isTableHeaderRow(rows[headerEnd])) {\n headerEnd++;\n }\n\n let result = '';\n\n if (headerEnd > 0) {\n result += '<thead>';\n for (let i = 0; i < headerEnd; i++) {\n result += renderNode(rows[i], options);\n }\n result += '</thead>';\n }\n\n if (headerEnd < rows.length) {\n result += '<tbody>';\n for (let i = headerEnd; i < rows.length; i++) {\n result += renderNode(rows[i], options);\n }\n result += '</tbody>';\n }\n\n return result;\n}\n\n// Static Children (e.g., pre > code)\n\n/** Renders nested static structure defined in render map. */\nfunction renderStaticStructure(\n type: SbRichTextElement,\n specs: readonly RenderSpec[],\n parentAttrs: Record<string, unknown> | undefined,\n content: string,\n): string {\n let result = '';\n\n for (const spec of specs) {\n const { tag, children, attrs: specAttrs } = spec;\n const mergedAttrs = { ...specAttrs, ...parentAttrs };\n const htmlAttrs = buildHtmlAttrs(type, mergedAttrs);\n\n if (isSelfClosing(tag)) {\n result += `<${tag}${htmlAttrs}>`;\n }\n else {\n const inner = children\n ? renderStaticStructure(type, children, parentAttrs, content)\n : content;\n result += `<${tag}${htmlAttrs}>${inner}</${tag}>`;\n }\n }\n\n return result;\n}\n\n/** Builds HTML attribute string from node/mark type and attrs. */\nfunction buildHtmlAttrs(type: SbRichTextElement, attrs: Record<string, unknown> | undefined): string {\n const processed = processAttrs(type, attrs, {\n colspan: 'colspan',\n rowspan: 'rowspan',\n });\n\n const styleObj = processed.style as Record<string, AttrValue> | undefined;\n const finalAttrs: Record<string, unknown> = { ...processed };\n\n if (styleObj) {\n finalAttrs.style = styleToString(styleObj);\n }\n\n return attrsToHtmlString(finalAttrs);\n}\n\n/** Converts attribute record to HTML string: ` key=\"value\" key2=\"value2\"` */\nfunction attrsToHtmlString(attrs: Record<string, unknown>): string {\n let result = '';\n\n for (const key in attrs) {\n const value = attrs[key];\n if (value != null) {\n result += ` ${key}=\"${escapeAttr(value)}\"`;\n }\n }\n\n return result;\n}\n"],"mappings":"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