@happyvertical/smrt-content 0.51.39 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +76 -0
  3. package/dist/body-format.d.ts +104 -0
  4. package/dist/body-format.d.ts.map +1 -1
  5. package/dist/body-format.js +439 -48
  6. package/dist/body-format.js.map +1 -1
  7. package/dist/chunks/content-query-BqLHbZYv.js +4131 -0
  8. package/dist/chunks/content-query-BqLHbZYv.js.map +1 -0
  9. package/dist/chunks/{content-versions-CvOoDLSK.js → content-versions-DatN0G28.js} +2 -2
  10. package/dist/chunks/{content-versions-CvOoDLSK.js.map → content-versions-DatN0G28.js.map} +1 -1
  11. package/dist/chunks/{contents-BB7RIBx2.js → contents-xas83N8M.js} +4 -6
  12. package/dist/chunks/contents-xas83N8M.js.map +1 -0
  13. package/dist/content-query.d.ts.map +1 -1
  14. package/dist/content.d.ts +13 -1
  15. package/dist/content.d.ts.map +1 -1
  16. package/dist/contents.d.ts.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +6 -6
  20. package/dist/manifest.json +113 -23
  21. package/dist/server/content-list-data-surface.d.ts +1 -1
  22. package/dist/server/content-list-data-surface.d.ts.map +1 -1
  23. package/dist/server.js +3 -3
  24. package/dist/server.js.map +1 -1
  25. package/dist/smrt-knowledge.json +92 -10
  26. package/dist/svelte/components/ContentBodyEditor.svelte +898 -38
  27. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +67 -0
  28. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/ContentBodyRenderer.svelte +36 -11
  30. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +2 -0
  31. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -1
  32. package/dist/svelte/components/ContentClaimAuditTool.svelte +45 -2
  33. package/dist/svelte/components/ContentCorrectionsTool.svelte +24 -2
  34. package/dist/svelte/components/ContentEditor.svelte +67 -17
  35. package/dist/svelte/components/ContentEditor.svelte.d.ts +12 -0
  36. package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -1
  37. package/dist/svelte/components/ContentGovernancePanel.svelte +4 -2
  38. package/dist/svelte/components/ContentMetadataFields.svelte +141 -51
  39. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +20 -0
  40. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -1
  41. package/dist/svelte/components/ContentPictureDrawer.svelte +611 -0
  42. package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts +68 -0
  43. package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts.map +1 -0
  44. package/dist/svelte/components/ContentReviewStatusTray.svelte +2 -2
  45. package/dist/svelte/components/ContentStatusFields.svelte +85 -38
  46. package/dist/svelte/components/ContentStatusFields.svelte.d.ts +10 -0
  47. package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -1
  48. package/dist/svelte/components/ContentTitleField.svelte +109 -12
  49. package/dist/svelte/components/ContentTitleField.svelte.d.ts +12 -0
  50. package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -1
  51. package/dist/svelte/components/ContentTransparencyReport.svelte +3 -1
  52. package/dist/svelte/components/ContentTransparencyTool.svelte +18 -3
  53. package/dist/svelte/components/ContentVersionsTool.svelte +26 -2
  54. package/dist/svelte/components/content-field-mode.d.ts +20 -0
  55. package/dist/svelte/components/content-field-mode.d.ts.map +1 -0
  56. package/dist/svelte/components/content-field-mode.js +26 -0
  57. package/dist/svelte/content-editor-media.d.ts +14 -0
  58. package/dist/svelte/content-editor-media.d.ts.map +1 -1
  59. package/dist/svelte/content-editor-media.js +41 -0
  60. package/dist/svelte/content-editor-state.svelte.d.ts +17 -0
  61. package/dist/svelte/content-editor-state.svelte.d.ts.map +1 -1
  62. package/dist/svelte/content-editor-state.svelte.js +46 -0
  63. package/dist/svelte/i18n.editor.d.ts +49 -0
  64. package/dist/svelte/i18n.editor.d.ts.map +1 -1
  65. package/dist/svelte/i18n.editor.js +53 -1
  66. package/dist/svelte/index.d.ts +10 -5
  67. package/dist/svelte/index.d.ts.map +1 -1
  68. package/dist/svelte/index.js +5 -3
  69. package/package.json +27 -25
  70. package/dist/chunks/content-query-DPJCcD1V.js +0 -4092
  71. package/dist/chunks/content-query-DPJCcD1V.js.map +0 -1
  72. package/dist/chunks/contents-BB7RIBx2.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"body-format.js","names":["images"],"sources":["../src/body-format.ts"],"sourcesContent":["export type ContentBodyFormat = 'markdown' | 'html';\nexport type ContentBodyImagePlacement =\n | 'block'\n | 'left'\n | 'right'\n | 'center'\n | 'full';\n\nexport interface ContentBodyImage {\n src: string;\n alt: string;\n title?: string;\n assetId?: string;\n placement?: ContentBodyImagePlacement;\n width?: number;\n index: number;\n}\n\nexport const DEFAULT_CONTENT_BODY_FORMAT: ContentBodyFormat = 'html';\n\nconst HTML_TAG_PATTERN =\n /<\\/?(?:article|aside|blockquote|br|div|figure|figcaption|h[1-6]|hr|img|li|ol|p|pre|section|span|strong|em|b|i|u|a|ul|table|tbody|td|th|thead|tr)(?:\\s[^>]*)?>/i;\n// HTML attribute boundary: the HTML5 tree builder starts a new attribute after\n// whitespace, `/`, OR the closing quote/backtick of the previous value. A naive\n// `\\s+`-only boundary lets `src=\"x\"onerror=\"...\"` survive sanitization because\n// `onerror` is glued to the preceding `\"` — the browser still parses it as a\n// separate (executing) attribute. Match either runs of whitespace/`/`\n// (captured in group 1 and consumed) OR a non-consuming lookbehind for a\n// quote/backtick (group 1 undefined; the char is the previous value's own\n// closing delimiter and must stay). `reemitSeparator()` turns the captured\n// group back into a single separating space when one was consumed (S5 #1388).\nconst ATTR_BOUNDARY = '(?:([\\\\s/]+)|(?<=[\"\\'`]))';\n\n// Re-emit an attribute separator after a removed/rewritten attribute. When the\n// boundary consumed whitespace/`/` (group 1 present), emit one space so the\n// rebuilt attribute doesn't glue onto the tag name or previous attribute. When\n// the boundary was a non-consumed quote/backtick (group 1 undefined), that\n// delimiter already separates the attributes, so emit nothing.\nfunction reemitSeparator(consumed: string | undefined): string {\n return consumed ? ' ' : '';\n}\n\nconst URL_ATTRIBUTE_PATTERN = new RegExp(\n `${ATTR_BOUNDARY}(href|src|xlink:href|formaction|action|poster)\\\\s*=\\\\s*(\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`,\n 'gi',\n);\nconst SRCSET_ATTRIBUTE_PATTERN = new RegExp(\n `${ATTR_BOUNDARY}srcset\\\\s*=\\\\s*(\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`,\n 'gi',\n);\n\nconst BLOCK_TAGS = [\n 'address',\n 'article',\n 'aside',\n 'blockquote',\n 'div',\n 'dl',\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 'hr',\n 'li',\n 'main',\n 'nav',\n 'ol',\n 'p',\n 'pre',\n 'section',\n 'table',\n 'tbody',\n 'td',\n 'th',\n 'thead',\n 'tr',\n 'ul',\n];\n\nexport function isContentBodyFormat(\n value: unknown,\n): value is ContentBodyFormat {\n return value === 'markdown' || value === 'html';\n}\n\nexport function looksLikeHtml(value: unknown): boolean {\n return typeof value === 'string' && HTML_TAG_PATTERN.test(value);\n}\n\nexport function resolveBodyFormat(\n format: unknown,\n body: unknown = '',\n): ContentBodyFormat {\n if (isContentBodyFormat(format)) {\n return format;\n }\n\n if (typeof format === 'string' && isContentBodyFormat(format.toLowerCase())) {\n return format.toLowerCase() as ContentBodyFormat;\n }\n\n if (looksLikeHtml(body)) {\n return 'html';\n }\n\n return body ? 'markdown' : DEFAULT_CONTENT_BODY_FORMAT;\n}\n\nexport function escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => {\n switch (char) {\n case '&':\n return '&amp;';\n case '<':\n return '&lt;';\n case '>':\n return '&gt;';\n case '\"':\n return '&quot;';\n case \"'\":\n return '&#39;';\n default:\n return char;\n }\n });\n}\n\nexport function escapeAttribute(value: string): string {\n return escapeHtml(value).replace(/`/g, '&#96;');\n}\n\nfunction decodeBasicEntities(value: string): string {\n const decodeCodePoint = (codePoint: number) =>\n Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff\n ? String.fromCodePoint(codePoint)\n : '';\n\n return value\n .replace(/&#x([0-9a-f]+);?/gi, (_match, hex: string) => {\n const codePoint = Number.parseInt(hex, 16);\n return decodeCodePoint(codePoint);\n })\n .replace(/&#(\\d+);?/g, (_match, decimal: string) => {\n const codePoint = Number.parseInt(decimal, 10);\n return decodeCodePoint(codePoint);\n })\n .replace(/&nbsp;/gi, ' ')\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&apos;/gi, \"'\")\n .replace(/&lt;/gi, '<')\n .replace(/&gt;/gi, '>')\n .replace(/&amp;/gi, '&');\n}\n\nfunction sanitizeUrl(value: string): string {\n const trimmed = decodeBasicEntities(value).trim();\n if (!trimmed) {\n return '';\n }\n\n let compactScheme = '';\n for (const char of trimmed) {\n const code = char.charCodeAt(0);\n if (code <= 31 || code === 127 || char.trim() === '') {\n continue;\n }\n compactScheme += char.toLowerCase();\n }\n\n if (/^(?:javascript|vbscript):/.test(compactScheme)) {\n return '#';\n }\n\n if (\n compactScheme.startsWith('data:') &&\n !/^data:image\\/(?:png|gif|jpe?g|webp);/.test(compactScheme)\n ) {\n return '#';\n }\n\n return trimmed;\n}\n\nfunction sanitizeSrcset(value: string): string {\n return decodeBasicEntities(value)\n .split(',')\n .map((candidate) => {\n const parts = candidate.trim().split(/\\s+/);\n const url = sanitizeUrl(parts.shift() || '');\n if (!url || url === '#') {\n return '';\n }\n\n const descriptors = parts.filter((part) =>\n /^(?:\\d+(?:\\.\\d+)?x|\\d+w)$/.test(part),\n );\n return [url, ...descriptors].join(' ');\n })\n .filter(Boolean)\n .join(', ');\n}\n\nfunction sanitizeStyle(value: string): string {\n const safeRules: string[] = [];\n\n for (const rawRule of decodeBasicEntities(value).split(';')) {\n const [rawName, ...rawValueParts] = rawRule.split(':');\n const name = rawName?.trim().toLowerCase();\n const ruleValue = rawValueParts.join(':').trim().toLowerCase();\n\n if (!name || !ruleValue) {\n continue;\n }\n\n if (\n (name === 'width' || name === 'max-width') &&\n /^(?:\\d{1,4}(?:\\.\\d+)?px|100%)$/.test(ruleValue)\n ) {\n safeRules.push(`${name}: ${ruleValue}`);\n continue;\n }\n\n if (name === 'height' && ruleValue === 'auto') {\n safeRules.push('height: auto');\n }\n }\n\n return safeRules.join('; ');\n}\n\nexport function sanitizeHtml(value: string): string {\n if (!value || typeof value !== 'string') {\n return '';\n }\n\n let html = value;\n html = html.replace(/<!--[\\s\\S]*?-->/g, '');\n // This lightweight sanitizer intentionally removes SVG/MathML instead of\n // attempting namespace-aware SVG/MathML sanitization. Consumers needing rich\n // inline diagrams should run a dedicated sanitizer before storing content.\n html = html.replace(\n /<\\s*(script|style|iframe|object|embed|link|meta|base|svg|math)\\b[^>]*>[\\s\\S]*?<\\s*\\/\\s*\\1\\s*>/gi,\n '',\n );\n html = html.replace(\n /<\\s*(script|style|iframe|object|embed|link|meta|base|svg|math)\\b[^>]*\\/?>/gi,\n '',\n );\n // Drop event handlers (`onclick`, etc.) and internal editor markers entirely.\n // A consumed whitespace/`/` separator and a non-consumed quote boundary both\n // collapse to nothing: the attribute that follows (if any) keeps its own\n // leading boundary, so removal never needs to leave a separator behind.\n html = html.replace(\n new RegExp(\n `${ATTR_BOUNDARY}on[a-z]+\\\\s*=\\\\s*(?:\"[^\"]*\"|'[^']*'|[^\\\\s>]*)`,\n 'gi',\n ),\n '',\n );\n html = html.replace(\n new RegExp(\n `${ATTR_BOUNDARY}data-smrt-(?:selected|moving|resizing)\\\\s*=\\\\s*(?:\"[^\"]*\"|'[^']*'|[^\\\\s>]*)`,\n 'gi',\n ),\n '',\n );\n html = html.replace(\n new RegExp(\n `${ATTR_BOUNDARY}style\\\\s*=\\\\s*(\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`,\n 'gi',\n ),\n (\n _match,\n separator: string | undefined,\n _raw: string,\n doubleValue = '',\n singleValue = '',\n bareValue = '',\n ) => {\n const safeStyle = sanitizeStyle(doubleValue || singleValue || bareValue);\n const sep = reemitSeparator(separator);\n return safeStyle ? `${sep}style=\"${escapeAttribute(safeStyle)}\"` : sep;\n },\n );\n html = html.replace(\n SRCSET_ATTRIBUTE_PATTERN,\n (\n _match,\n separator: string | undefined,\n _raw: string,\n doubleValue = '',\n singleValue = '',\n bareValue = '',\n ) => {\n const safeSrcset = sanitizeSrcset(\n doubleValue || singleValue || bareValue,\n );\n const sep = reemitSeparator(separator);\n return safeSrcset ? `${sep}srcset=\"${escapeAttribute(safeSrcset)}\"` : sep;\n },\n );\n html = html.replace(\n URL_ATTRIBUTE_PATTERN,\n (\n _match,\n separator: string | undefined,\n name: string,\n _raw: string,\n doubleValue = '',\n singleValue = '',\n bareValue = '',\n ) => {\n const quote = doubleValue ? '\"' : singleValue ? \"'\" : '\"';\n const rawValue = doubleValue || singleValue || bareValue;\n return `${reemitSeparator(separator)}${name}=${quote}${escapeAttribute(sanitizeUrl(rawValue))}${quote}`;\n },\n );\n\n return html.trim();\n}\n\nfunction renderInlineMarkdown(value: string): string {\n let html = value;\n\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(([^)\\s]+)(?:\\s+\"([^\"]*)\")?\\)/g,\n (_match, alt: string, src: string, title = '') => {\n const safeSrc = sanitizeUrl(src);\n const safeAlt = escapeAttribute(decodeBasicEntities(alt));\n const titleAttr = title\n ? ` title=\"${escapeAttribute(decodeBasicEntities(title))}\"`\n : '';\n return `<img src=\"${escapeAttribute(safeSrc)}\" alt=\"${safeAlt}\"${titleAttr}>`;\n },\n );\n html = html.replace(\n /\\[([^\\]]+)\\]\\(([^)\\s]+)(?:\\s+\"([^\"]*)\")?\\)/g,\n (_match, label: string, href: string, title = '') => {\n const titleAttr = title\n ? ` title=\"${escapeAttribute(decodeBasicEntities(title))}\"`\n : '';\n return `<a href=\"${escapeAttribute(sanitizeUrl(href))}\"${titleAttr}>${label}</a>`;\n },\n );\n html = html.replace(/`([^`]+)`/g, '<code>$1</code>');\n html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*([^*]+)\\*/g, '<em>$1</em>');\n\n return html;\n}\n\nexport function renderMarkdownToHtml(markdown: string): string {\n if (!markdown || typeof markdown !== 'string') {\n return '';\n }\n\n const lines = escapeHtml(markdown).split('\\n');\n const result: string[] = [];\n let inList = false;\n let inParagraph = false;\n\n function closeParagraph() {\n if (inParagraph) {\n result.push('</p>');\n inParagraph = false;\n }\n }\n\n function closeList() {\n if (inList) {\n result.push('</ul>');\n inList = false;\n }\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (!trimmed) {\n closeList();\n closeParagraph();\n continue;\n }\n\n const headingMatch = /^(#{1,3})\\s+(.+)$/.exec(trimmed);\n if (headingMatch) {\n closeList();\n closeParagraph();\n const level = headingMatch[1].length;\n result.push(\n `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`,\n );\n continue;\n }\n\n const listMatch = /^[-*]\\s+(.+)$/.exec(trimmed);\n if (listMatch) {\n closeParagraph();\n if (!inList) {\n result.push('<ul>');\n inList = true;\n }\n result.push(`<li>${renderInlineMarkdown(listMatch[1])}</li>`);\n continue;\n }\n\n closeList();\n if (!inParagraph) {\n result.push('<p>');\n inParagraph = true;\n } else {\n result.push('<br>');\n }\n result.push(renderInlineMarkdown(line));\n }\n\n closeList();\n closeParagraph();\n\n return sanitizeHtml(result.join('\\n'));\n}\n\nfunction parseHtmlAttributes(value: string): Record<string, string> {\n const attributes: Record<string, string> = {};\n value.replace(\n /([:\\w-]+)\\s*=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+))/g,\n (\n _match,\n name: string,\n _raw: string,\n doubleValue = '',\n singleValue = '',\n bareValue = '',\n ) => {\n attributes[name.toLowerCase()] = decodeBasicEntities(\n doubleValue || singleValue || bareValue || '',\n );\n return '';\n },\n );\n return attributes;\n}\n\nfunction normalizeImagePlacement(\n value: unknown,\n): ContentBodyImagePlacement | undefined {\n return value === 'block' ||\n value === 'left' ||\n value === 'right' ||\n value === 'center' ||\n value === 'full'\n ? value\n : undefined;\n}\n\nfunction parseImageWidth(value: unknown): number | undefined {\n if (typeof value !== 'string' && typeof value !== 'number') {\n return undefined;\n }\n\n const match = String(value).match(/(\\d+(?:\\.\\d+)?)/);\n if (!match) {\n return undefined;\n }\n\n const width = Math.round(Number(match[1]));\n return Number.isFinite(width) && width > 0 ? width : undefined;\n}\n\nfunction parseStyleWidth(value: unknown): number | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n\n const match = value.match(\n /(?:^|;)\\s*(?:max-)?width\\s*:\\s*(\\d+(?:\\.\\d+)?)px/i,\n );\n if (!match) {\n return undefined;\n }\n\n return parseImageWidth(match[1]);\n}\n\nfunction fallbackHtmlToMarkdown(html: string): string {\n let markdown = sanitizeHtml(html);\n\n markdown = markdown.replace(/<img\\b([^>]*)>/gi, (_match, attrs: string) => {\n const parsed = parseHtmlAttributes(attrs);\n const src = sanitizeUrl(parsed.src || '');\n if (!src) {\n return '';\n }\n return `\\n\\n![${parsed.alt || ''}](${src})\\n\\n`;\n });\n markdown = markdown.replace(\n /<a\\b([^>]*)>([\\s\\S]*?)<\\/a>/gi,\n (_match, attrs: string, label: string) => {\n const parsed = parseHtmlAttributes(attrs);\n const href = sanitizeUrl(parsed.href || '');\n const text = stripHtml(label).trim() || href;\n return href ? `[${text}](${href})` : text;\n },\n );\n markdown = markdown.replace(/<h1\\b[^>]*>([\\s\\S]*?)<\\/h1>/gi, '\\n\\n# $1\\n\\n');\n markdown = markdown.replace(/<h2\\b[^>]*>([\\s\\S]*?)<\\/h2>/gi, '\\n\\n## $1\\n\\n');\n markdown = markdown.replace(\n /<h3\\b[^>]*>([\\s\\S]*?)<\\/h3>/gi,\n '\\n\\n### $1\\n\\n',\n );\n markdown = markdown.replace(/<li\\b[^>]*>([\\s\\S]*?)<\\/li>/gi, '\\n- $1');\n markdown = markdown.replace(/<\\/(?:p|div|section|article|ul|ol)>/gi, '\\n\\n');\n markdown = markdown.replace(/<br\\s*\\/?>/gi, '\\n');\n markdown = markdown.replace(\n /<(strong|b)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/gi,\n '**$2**',\n );\n markdown = markdown.replace(/<(em|i)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/gi, '*$2*');\n markdown = stripHtml(markdown);\n\n return normalizeMarkdownWhitespace(markdown);\n}\n\nexport function stripHtml(value: string): string {\n return decodeBasicEntities(value.replace(/<[^>]*>/g, ''));\n}\n\nfunction normalizeMarkdownWhitespace(value: string): string {\n return decodeBasicEntities(value)\n .replace(/\\r\\n?/g, '\\n')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nconst MARKDOWN_TEXT_NODE = 3;\nconst MARKDOWN_ELEMENT_NODE = 1;\n\ninterface MarkdownDomNode {\n nodeType: number;\n textContent?: string | null;\n childNodes?: ArrayLike<MarkdownDomNode>;\n tagName?: string;\n getAttribute?: (name: string) => string | null;\n}\n\nfunction nodeToMarkdown(node: MarkdownDomNode): string {\n if (node.nodeType === MARKDOWN_TEXT_NODE) {\n return node.textContent || '';\n }\n\n if (node.nodeType !== MARKDOWN_ELEMENT_NODE) {\n return '';\n }\n\n const tag = node.tagName?.toLowerCase() || '';\n const children = Array.from(node.childNodes || [])\n .map(nodeToMarkdown)\n .join('');\n const trimmedChildren = children.trim();\n\n switch (tag) {\n case 'br':\n return '\\n';\n case 'h1':\n return `\\n\\n# ${trimmedChildren}\\n\\n`;\n case 'h2':\n return `\\n\\n## ${trimmedChildren}\\n\\n`;\n case 'h3':\n return `\\n\\n### ${trimmedChildren}\\n\\n`;\n case 'p':\n return `\\n\\n${trimmedChildren}\\n\\n`;\n case 'strong':\n case 'b':\n return `**${trimmedChildren}**`;\n case 'em':\n case 'i':\n return `*${trimmedChildren}*`;\n case 'code':\n return `\\`${trimmedChildren}\\``;\n case 'a': {\n const href = sanitizeUrl(node.getAttribute?.('href') || '');\n return href ? `[${trimmedChildren || href}](${href})` : trimmedChildren;\n }\n case 'img': {\n const src = sanitizeUrl(node.getAttribute?.('src') || '');\n if (!src) {\n return '';\n }\n const alt = node.getAttribute?.('alt') || '';\n return `\\n\\n![${alt}](${src})\\n\\n`;\n }\n case 'li':\n return `- ${trimmedChildren}\\n`;\n case 'ul':\n case 'ol':\n return `\\n${children}\\n`;\n default:\n return BLOCK_TAGS.includes(tag) ? `\\n\\n${trimmedChildren}\\n\\n` : children;\n }\n}\n\nexport function htmlToMarkdown(html: string): string {\n const sanitized = sanitizeHtml(html);\n if (!sanitized) {\n return '';\n }\n\n const Parser = (globalThis as Record<string, unknown>).DOMParser;\n if (typeof Parser !== 'function') {\n return fallbackHtmlToMarkdown(sanitized);\n }\n\n // Structural view of the DOMParser API we rely on; avoids depending on the\n // DOM lib in this environment-agnostic module.\n const parser = new (\n Parser as new () => {\n parseFromString: (\n source: string,\n mimeType: string,\n ) => { body?: { childNodes?: ArrayLike<MarkdownDomNode> } | null };\n }\n )();\n const document = parser.parseFromString(\n `<body>${sanitized}</body>`,\n 'text/html',\n );\n const markdown = Array.from(\n (document.body?.childNodes || []) as ArrayLike<MarkdownDomNode>,\n )\n .map(nodeToMarkdown)\n .join('');\n\n return normalizeMarkdownWhitespace(markdown);\n}\n\nexport function normalizeEditorHtml(html: string): string {\n const sanitized = sanitizeHtml(html);\n if (!sanitized) {\n return '';\n }\n\n return sanitized.replace(\n /<p>\\s*((?:<br\\s*\\/?>\\s*)?<img\\b[^>]*>(?:\\s*<br\\s*\\/?>)?)\\s*<\\/p>/gi,\n (_match, imageHtml: string) =>\n imageHtml.replace(/^\\s*<br\\s*\\/?>\\s*|\\s*<br\\s*\\/?>\\s*$/gi, ''),\n );\n}\n\nexport function bodyToEditorHtml(\n body: string,\n format: ContentBodyFormat,\n): string {\n return format === 'markdown'\n ? normalizeEditorHtml(renderMarkdownToHtml(body || ''))\n : normalizeEditorHtml(body || '');\n}\n\nexport function editorHtmlToBody(\n html: string,\n format: ContentBodyFormat,\n): string {\n return format === 'markdown'\n ? htmlToMarkdown(html || '')\n : normalizeEditorHtml(html || '');\n}\n\nexport function extractBodyImages(\n body: string,\n format: ContentBodyFormat = resolveBodyFormat(undefined, body),\n): ContentBodyImage[] {\n if (!body || typeof body !== 'string') {\n return [];\n }\n\n if (format === 'markdown') {\n const images: ContentBodyImage[] = [];\n body.replace(\n /!\\[([^\\]]*)\\]\\(([^)\\s]+)(?:\\s+\"([^\"]*)\")?\\)/g,\n (_match, alt: string, src: string, title = '') => {\n const safeSrc = sanitizeUrl(src);\n if (safeSrc) {\n images.push({\n src: safeSrc,\n alt: decodeBasicEntities(alt),\n ...(title ? { title: decodeBasicEntities(title) } : {}),\n index: images.length,\n });\n }\n return '';\n },\n );\n return images;\n }\n\n const images: ContentBodyImage[] = [];\n const bodyWithoutFigures = body.replace(\n /<figure\\b([^>]*)>([\\s\\S]*?)<\\/figure>/gi,\n (_match, figureAttrs: string, figureContent: string) => {\n const imageMatch = /<img\\b([^>]*)>/i.exec(figureContent);\n if (!imageMatch) {\n return '';\n }\n\n const parsedFigure = parseHtmlAttributes(figureAttrs);\n const parsedImage = parseHtmlAttributes(imageMatch[1] || '');\n const src = sanitizeUrl(parsedImage.src || '');\n if (src) {\n const placement = normalizeImagePlacement(\n parsedFigure['data-smrt-placement'] ||\n parsedImage['data-smrt-placement'],\n );\n const width =\n parseImageWidth(\n parsedFigure['data-smrt-width'] || parsedImage['data-smrt-width'],\n ) ||\n parseStyleWidth(parsedFigure.style) ||\n parseStyleWidth(parsedImage.style);\n images.push({\n src,\n alt: parsedImage.alt || '',\n ...(parsedImage.title ? { title: parsedImage.title } : {}),\n ...(parsedImage['data-smrt-asset-id']\n ? { assetId: parsedImage['data-smrt-asset-id'] }\n : {}),\n ...(placement ? { placement } : {}),\n ...(width ? { width } : {}),\n index: images.length,\n });\n }\n\n return '';\n },\n );\n\n bodyWithoutFigures.replace(/<img\\b([^>]*)>/gi, (_match, attrs: string) => {\n const parsed = parseHtmlAttributes(attrs);\n const src = sanitizeUrl(parsed.src || '');\n if (src) {\n const placement = normalizeImagePlacement(parsed['data-smrt-placement']);\n const width =\n parseImageWidth(parsed['data-smrt-width']) ||\n parseStyleWidth(parsed.style);\n images.push({\n src,\n alt: parsed.alt || '',\n ...(parsed.title ? { title: parsed.title } : {}),\n ...(parsed['data-smrt-asset-id']\n ? { assetId: parsed['data-smrt-asset-id'] }\n : {}),\n ...(placement ? { placement } : {}),\n ...(width ? { width } : {}),\n index: images.length,\n });\n }\n return '';\n });\n\n return images;\n}\n\n/**\n * Structural view of the asset-like inputs accepted by the image helpers.\n * Callers pass assorted asset shapes (Asset models, plain DTOs, etc.); only\n * these optional fields are read.\n */\nexport interface ImageAssetLike {\n id?: unknown;\n sourceUri?: unknown;\n url?: unknown;\n src?: unknown;\n alt?: unknown;\n name?: unknown;\n title?: unknown;\n width?: unknown;\n}\n\nexport function getImageSource(\n asset: ImageAssetLike | null | undefined,\n): string {\n return String(asset?.sourceUri || asset?.url || asset?.src || '');\n}\n\nexport function getImageAlt(asset: ImageAssetLike | null | undefined): string {\n return String(asset?.alt || asset?.name || asset?.title || 'Image');\n}\n\nexport function imageAssetToHtml(\n asset: ImageAssetLike | null | undefined,\n): string {\n const src = sanitizeUrl(getImageSource(asset));\n if (!src) {\n return '';\n }\n\n const assetId = asset?.id\n ? ` data-smrt-asset-id=\"${escapeAttribute(String(asset.id))}\"`\n : '';\n const width = Math.max(\n 160,\n Math.min(520, Math.round(Number(asset?.width) || 520)),\n );\n\n return `<img src=\"${escapeAttribute(src)}\" alt=\"${escapeAttribute(getImageAlt(asset))}\"${assetId} data-smrt-inline-image=\"true\" data-smrt-placement=\"block\" data-smrt-width=\"${width}\" style=\"width: ${width}px; max-width: 100%; height: auto\">`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"body-format.js","names":["images","block","trimmed","at"],"sources":["../src/body-format.ts"],"sourcesContent":["import sanitize from 'sanitize-html';\n\nexport type ContentBodyFormat = 'markdown' | 'html';\nexport type ContentBodyImagePlacement =\n | 'block'\n | 'left'\n | 'right'\n | 'center'\n | 'full';\n\nexport interface ContentBodyImage {\n src: string;\n alt: string;\n title?: string;\n assetId?: string;\n placement?: ContentBodyImagePlacement;\n width?: number;\n /** True for the content's thumbnail block (see `placeThumbnailInBody`). */\n thumbnail?: boolean;\n /**\n * True for a picture in the story the person chose as the main picture\n * (see `setBodyMainImage`).\n */\n main?: boolean;\n index: number;\n}\n\nexport const DEFAULT_CONTENT_BODY_FORMAT: ContentBodyFormat = 'html';\n\nconst HTML_TAG_PATTERN =\n /<\\/?(?:article|aside|blockquote|br|div|figure|figcaption|h[1-6]|hr|img|li|ol|p|pre|section|span|strong|em|b|i|u|a|ul|table|tbody|td|th|thead|tr)(?:\\s[^>]*)?>/i;\nconst BLOCK_TAGS = [\n 'address',\n 'article',\n 'aside',\n 'blockquote',\n 'div',\n 'dl',\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 'hr',\n 'li',\n 'main',\n 'nav',\n 'ol',\n 'p',\n 'pre',\n 'section',\n 'table',\n 'tbody',\n 'td',\n 'th',\n 'thead',\n 'tr',\n 'ul',\n];\n\nexport function isContentBodyFormat(\n value: unknown,\n): value is ContentBodyFormat {\n return value === 'markdown' || value === 'html';\n}\n\nexport function looksLikeHtml(value: unknown): boolean {\n return typeof value === 'string' && HTML_TAG_PATTERN.test(value);\n}\n\nexport function resolveBodyFormat(\n format: unknown,\n body: unknown = '',\n): ContentBodyFormat {\n if (isContentBodyFormat(format)) {\n return format;\n }\n\n if (typeof format === 'string' && isContentBodyFormat(format.toLowerCase())) {\n return format.toLowerCase() as ContentBodyFormat;\n }\n\n if (looksLikeHtml(body)) {\n return 'html';\n }\n\n return body ? 'markdown' : DEFAULT_CONTENT_BODY_FORMAT;\n}\n\nexport function escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => {\n switch (char) {\n case '&':\n return '&amp;';\n case '<':\n return '&lt;';\n case '>':\n return '&gt;';\n case '\"':\n return '&quot;';\n case \"'\":\n return '&#39;';\n default:\n return char;\n }\n });\n}\n\nexport function escapeAttribute(value: string): string {\n return escapeHtml(value).replace(/`/g, '&#96;');\n}\n\nfunction decodeBasicEntities(value: string): string {\n const decodeCodePoint = (codePoint: number) =>\n Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff\n ? String.fromCodePoint(codePoint)\n : '';\n\n return value\n .replace(/&#x([0-9a-f]+);?/gi, (_match, hex: string) => {\n const codePoint = Number.parseInt(hex, 16);\n return decodeCodePoint(codePoint);\n })\n .replace(/&#(\\d+);?/g, (_match, decimal: string) => {\n const codePoint = Number.parseInt(decimal, 10);\n return decodeCodePoint(codePoint);\n })\n .replace(/&nbsp;/gi, ' ')\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&apos;/gi, \"'\")\n .replace(/&lt;/gi, '<')\n .replace(/&gt;/gi, '>')\n .replace(/&amp;/gi, '&');\n}\n\nfunction sanitizeUrl(value: string): string {\n const trimmed = decodeBasicEntities(value).trim();\n if (!trimmed) {\n return '';\n }\n\n let compactScheme = '';\n for (const char of trimmed) {\n const code = char.charCodeAt(0);\n if (code <= 31 || code === 127 || char.trim() === '') {\n continue;\n }\n compactScheme += char.toLowerCase();\n }\n\n if (/^(?:javascript|vbscript):/.test(compactScheme)) {\n return '#';\n }\n\n if (\n compactScheme.startsWith('data:') &&\n !/^data:image\\/(?:png|gif|jpe?g|webp);/.test(compactScheme)\n ) {\n return '#';\n }\n\n return trimmed;\n}\n\nfunction sanitizeStyle(value: string): string {\n const safeRules: string[] = [];\n\n for (const rawRule of decodeBasicEntities(value).split(';')) {\n const [rawName, ...rawValueParts] = rawRule.split(':');\n const name = rawName?.trim().toLowerCase();\n const ruleValue = rawValueParts.join(':').trim().toLowerCase();\n\n if (!name || !ruleValue) {\n continue;\n }\n\n if (\n (name === 'width' || name === 'max-width') &&\n /^(?:\\d{1,4}(?:\\.\\d+)?px|100%)$/.test(ruleValue)\n ) {\n safeRules.push(`${name}: ${ruleValue}`);\n continue;\n }\n\n if (name === 'height' && ruleValue === 'auto') {\n safeRules.push('height: auto');\n }\n }\n\n return safeRules.join('; ');\n}\n\n// ---------------------------------------------------------------------------\n// Body sanitizer\n//\n// Bodies come from editors, AI drafts and scraped feeds and are rendered with\n// `{@html}` — on public, prerendered pages too — so they go through a real\n// HTML parser (sanitize-html / htmlparser2) with an allowlist, never through\n// tag- or attribute-stripping regexes (nested input such as\n// `<scr<script>ipt>` reassembles after a single regex pass). The parser\n// re-serializes the tree: text and attribute values come out escaped, and no\n// raw-text or foreign-content element (script, style, svg, math, iframe,\n// noscript, template, textarea, …) is ever emitted, so the browser re-parses\n// exactly the tree that was checked.\n// ---------------------------------------------------------------------------\n\nconst INLINE_TAGS = [\n 'a',\n 'abbr',\n 'b',\n 'br',\n 'code',\n 'del',\n 'em',\n 'i',\n 'img',\n 'ins',\n 'kbd',\n 'mark',\n 's',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'sup',\n 'u',\n];\n\nconst BLOCK_CONTENT_TAGS = [\n 'article',\n 'aside',\n 'blockquote',\n 'caption',\n 'col',\n 'colgroup',\n 'dd',\n 'div',\n 'dl',\n 'dt',\n 'figcaption',\n 'figure',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'hr',\n 'li',\n 'ol',\n 'p',\n 'pre',\n 'section',\n 'table',\n 'tbody',\n 'td',\n 'tfoot',\n 'th',\n 'thead',\n 'tr',\n 'ul',\n];\n\n/** Editor layout markers kept on images and their figures. */\nconst IMAGE_MARKER_ATTRIBUTES = [\n 'data-smrt-asset-id',\n 'data-smrt-inline-image',\n 'data-smrt-placement',\n 'data-smrt-width',\n 'data-smrt-thumbnail',\n 'data-smrt-main',\n];\n\nconst SANITIZER_ALLOWED_ATTRIBUTES: Record<string, string[]> = {\n a: ['href', 'title', 'rel', 'target'],\n abbr: ['title'],\n img: [\n 'src',\n 'srcset',\n 'alt',\n 'title',\n 'width',\n 'height',\n 'loading',\n 'style',\n ...IMAGE_MARKER_ATTRIBUTES,\n ],\n figure: ['style', ...IMAGE_MARKER_ATTRIBUTES],\n ol: ['start', 'reversed', 'type'],\n td: ['colspan', 'rowspan'],\n th: ['colspan', 'rowspan', 'scope'],\n col: ['span'],\n colgroup: ['span'],\n};\n\n/**\n * Disallowed elements whose content is dropped along with the tag (other\n * disallowed tags keep their text). Covers every raw-text / foreign-content\n * element where parser differentials (mXSS) live.\n */\nconst SANITIZER_DROP_CONTENT_TAGS = [\n 'script',\n 'style',\n 'textarea',\n 'option',\n 'select',\n 'noscript',\n 'noembed',\n 'noframes',\n 'template',\n 'title',\n 'xmp',\n 'plaintext',\n 'iframe',\n 'object',\n 'embed',\n 'svg',\n 'math',\n 'head',\n];\n\nconst LINK_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);\nconst IMAGE_SCHEMES = new Set(['http', 'https']);\n/**\n * Raster `data:` images stay allowed in image sources: the editor's upload\n * path can store an image asset whose source is a data URL. SVG (`image/svg+xml`)\n * and every other data type are refused.\n */\nconst SAFE_DATA_IMAGE_PATTERN = /^data:image\\/(?:png|gif|jpe?g|webp|avif);/;\nconst IMAGE_PLACEMENTS = new Set(['block', 'left', 'right', 'center', 'full']);\n\n/**\n * The URL when it is safe for a link (`http(s)`, `mailto`, `tel`, relative)\n * or an image (`http(s)`, raster `data:image/*`, relative); otherwise ''.\n * `value` is the parser-decoded attribute value — exactly what the browser\n * sees after the escaped re-serialization.\n */\nfunction allowedUrl(value: string | undefined, kind: 'link' | 'image'): string {\n const url = (value || '').trim();\n if (!url) {\n return '';\n }\n\n let compact = '';\n for (const char of decodeBasicEntities(url)) {\n const code = char.charCodeAt(0);\n if (code <= 0x20 || code === 0x7f) {\n continue;\n }\n compact += char.toLowerCase();\n }\n\n const scheme = /^([a-z][a-z0-9+.-]*):/.exec(compact)?.[1];\n if (!scheme) {\n // Relative, root-relative, protocol-relative or fragment URL. A colon\n // before the first `/`, `?` or `#` would make it a scheme, so the regex\n // above already caught any `javascript:`-style value.\n return url;\n }\n\n if (kind === 'image') {\n if (IMAGE_SCHEMES.has(scheme)) {\n return url;\n }\n return scheme === 'data' && SAFE_DATA_IMAGE_PATTERN.test(compact)\n ? url\n : '';\n }\n\n return LINK_SCHEMES.has(scheme) ? url : '';\n}\n\nfunction allowedSrcset(value: string): string {\n return value\n .split(',')\n .map((candidate) => {\n const parts = candidate.trim().split(/\\s+/);\n const url = allowedUrl(parts.shift(), 'image');\n if (!url || url.startsWith('data:')) {\n return '';\n }\n const descriptors = parts.filter((part) =>\n /^(?:\\d+(?:\\.\\d+)?x|\\d+w)$/.test(part),\n );\n return [url, ...descriptors].join(' ');\n })\n .filter(Boolean)\n .join(', ');\n}\n\nconst POSITIVE_INTEGER = /^\\d{1,5}$/;\n\n/** Attributes whose empty value is meaningful (other empties are dropped). */\nconst EMPTY_VALUE_ATTRIBUTES = new Set([\n 'alt',\n 'data-smrt-inline-image',\n 'data-smrt-thumbnail',\n 'data-smrt-main',\n]);\n\n/**\n * Validate one allowed attribute's value. `null` — or '' outside\n * {@link EMPTY_VALUE_ATTRIBUTES} — drops the attribute.\n */\nfunction allowedAttributeValue(name: string, value: string): string | null {\n switch (name) {\n case 'href':\n return allowedUrl(value, 'link');\n case 'src':\n return allowedUrl(value, 'image');\n case 'srcset':\n return allowedSrcset(value);\n case 'style':\n return sanitizeStyle(value);\n case 'width':\n case 'height':\n case 'start':\n case 'span':\n case 'colspan':\n case 'rowspan':\n case 'data-smrt-width':\n return POSITIVE_INTEGER.test(value.trim()) ? value.trim() : '';\n case 'loading':\n return value === 'lazy' || value === 'eager' ? value : '';\n case 'target':\n return value === '_blank' ? value : '';\n case 'rel':\n return value\n .split(/\\s+/)\n .filter((token) => /^[a-z-]{1,32}$/i.test(token))\n .join(' ');\n case 'scope':\n return /^(?:row|col|rowgroup|colgroup)$/.test(value) ? value : '';\n case 'type':\n return /^[1aAiI]$/.test(value) ? value : '';\n case 'data-smrt-placement':\n return IMAGE_PLACEMENTS.has(value) ? value : '';\n case 'data-smrt-inline-image':\n case 'data-smrt-thumbnail':\n case 'data-smrt-main':\n // Boolean markers: keep the bare / `=\"true\"` forms the editor writes.\n return value === '' || value === 'true' ? value : null;\n case 'data-smrt-asset-id':\n return /^[\\w.:-]{1,128}$/.test(value) ? value : '';\n default:\n return value;\n }\n}\n\ninterface SanitizeBodyOptions {\n /** Replace the thumbnail block image's `src` (already URL-checked). */\n thumbnailSrc?: string;\n}\n\nfunction transformBodyTag(\n tagName: string,\n attribs: Record<string, string>,\n options: SanitizeBodyOptions,\n): { tagName: string; attribs: Record<string, string> } {\n const allowed = SANITIZER_ALLOWED_ATTRIBUTES[tagName] || [];\n const safe: Record<string, string> = {};\n\n for (const [rawName, rawValue] of Object.entries(attribs)) {\n const name = rawName.toLowerCase();\n if (!allowed.includes(name)) {\n continue;\n }\n const value = allowedAttributeValue(name, String(rawValue ?? ''));\n if (value === null) {\n continue;\n }\n if (value === '' && !EMPTY_VALUE_ATTRIBUTES.has(name)) {\n continue;\n }\n safe[name] = value;\n }\n\n if (\n tagName === 'img' &&\n options.thumbnailSrc &&\n BODY_THUMBNAIL_ATTRIBUTE in safe\n ) {\n safe.src = options.thumbnailSrc;\n }\n\n if (tagName === 'a' && safe.target === '_blank') {\n const rel = new Set((safe.rel || '').split(/\\s+/).filter(Boolean));\n rel.add('noopener');\n rel.add('noreferrer');\n safe.rel = [...rel].join(' ');\n }\n\n return { tagName, attribs: safe };\n}\n\nfunction sanitizeBodyHtml(\n value: unknown,\n options: SanitizeBodyOptions = {},\n): string {\n if (!value || typeof value !== 'string') {\n return '';\n }\n\n const html = sanitize(value, {\n allowedTags: [...INLINE_TAGS, ...BLOCK_CONTENT_TAGS],\n allowedAttributes: SANITIZER_ALLOWED_ATTRIBUTES,\n allowedClasses: {},\n allowedSchemes: [...LINK_SCHEMES],\n allowedSchemesByTag: { img: ['http', 'https', 'data'] },\n allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],\n allowProtocolRelative: true,\n // `style` values are rewritten by `sanitizeStyle` in the transform (a\n // width / max-width / `height: auto` allowlist), so the library's CSS\n // parser is not needed.\n parseStyleAttributes: false,\n disallowedTagsMode: 'discard',\n nonTextTags: SANITIZER_DROP_CONTENT_TAGS,\n transformTags: {\n '*': (tagName, attribs) =>\n transformBodyTag(tagName, attribs as Record<string, string>, options),\n },\n // An image whose source was refused is dropped rather than left broken.\n exclusiveFilter: (frame) => frame.tag === 'img' && !frame.attribs.src,\n });\n\n // sanitize-html serializes void elements XHTML-style (`<br />`). Emit the\n // HTML5 form the editor writes so bodies round-trip unchanged. Safe on the\n // serializer's output: `<` and `>` never appear raw inside text or\n // attribute values there, so this can only match a real tag.\n return html\n .replace(/<(img|br|hr|col)((?:\\s+[^\\s<>][^<>]*?)?)\\s*\\/>/g, '<$1$2>')\n .trim();\n}\n\n/**\n * Allowlist-sanitize body HTML for rendering with `{@html}`: headings,\n * paragraphs, lists, quotes, links (`http(s)`/`mailto`/`tel`/relative),\n * images (`http(s)`/relative/raster `data:`) with the editor's layout and\n * thumbnail markers, figures, tables, code, and inline emphasis. Everything\n * else — scripts, event handlers, `javascript:` URLs, iframes/embeds,\n * SVG/MathML, forms, classes, and all CSS except image width — is removed.\n */\nexport function sanitizeHtml(value: string): string {\n return sanitizeBodyHtml(value);\n}\n\nfunction renderInlineMarkdown(value: string): string {\n let html = value;\n\n // Markdown is HTML-escaped before inline rendering, so an image title's\n // quotes arrive as `&quot;`; accept both spellings.\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(([^)\\s]+)(?:\\s+(?:\"|&quot;)(.*?)(?:\"|&quot;))?\\)/g,\n (_match, alt: string, src: string, title = '') => {\n const safeSrc = sanitizeUrl(src);\n const safeAlt = escapeAttribute(decodeBasicEntities(alt));\n const decodedTitle = decodeBasicEntities(title);\n const markers = parseMarkdownImageTitle(decodedTitle);\n const assetAttr = markers?.assetId\n ? ` data-smrt-asset-id=\"${escapeAttribute(markers.assetId)}\"`\n : '';\n if (markers?.thumbnail) {\n return `<img src=\"${escapeAttribute(safeSrc)}\" alt=\"${safeAlt}\"${assetAttr} ${BODY_THUMBNAIL_ATTRIBUTE}=\"true\" data-smrt-inline-image=\"true\" data-smrt-placement=\"${markers.thumbnail}\">`;\n }\n if (markers) {\n return `<img src=\"${escapeAttribute(safeSrc)}\" alt=\"${safeAlt}\"${assetAttr}${markers.main ? ` ${BODY_MAIN_IMAGE_ATTRIBUTE}=\"true\"` : ''}>`;\n }\n const titleAttr = decodedTitle\n ? ` title=\"${escapeAttribute(decodedTitle)}\"`\n : '';\n return `<img src=\"${escapeAttribute(safeSrc)}\" alt=\"${safeAlt}\"${titleAttr}>`;\n },\n );\n html = html.replace(\n /\\[([^\\]]+)\\]\\(([^)\\s]+)(?:\\s+\"([^\"]*)\")?\\)/g,\n (_match, label: string, href: string, title = '') => {\n const titleAttr = title\n ? ` title=\"${escapeAttribute(decodeBasicEntities(title))}\"`\n : '';\n return `<a href=\"${escapeAttribute(sanitizeUrl(href))}\"${titleAttr}>${label}</a>`;\n },\n );\n html = html.replace(/`([^`]+)`/g, '<code>$1</code>');\n html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*([^*]+)\\*/g, '<em>$1</em>');\n\n return html;\n}\n\nexport function renderMarkdownToHtml(markdown: string): string {\n if (!markdown || typeof markdown !== 'string') {\n return '';\n }\n\n const lines = escapeHtml(markdown).split('\\n');\n const result: string[] = [];\n let inList = false;\n let inParagraph = false;\n\n function closeParagraph() {\n if (inParagraph) {\n result.push('</p>');\n inParagraph = false;\n }\n }\n\n function closeList() {\n if (inList) {\n result.push('</ul>');\n inList = false;\n }\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (!trimmed) {\n closeList();\n closeParagraph();\n continue;\n }\n\n const headingMatch = /^(#{1,3})\\s+(.+)$/.exec(trimmed);\n if (headingMatch) {\n closeList();\n closeParagraph();\n const level = headingMatch[1].length;\n result.push(\n `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`,\n );\n continue;\n }\n\n const listMatch = /^[-*]\\s+(.+)$/.exec(trimmed);\n if (listMatch) {\n closeParagraph();\n if (!inList) {\n result.push('<ul>');\n inList = true;\n }\n result.push(`<li>${renderInlineMarkdown(listMatch[1])}</li>`);\n continue;\n }\n\n closeList();\n if (!inParagraph) {\n result.push('<p>');\n inParagraph = true;\n } else {\n result.push('<br>');\n }\n result.push(renderInlineMarkdown(line));\n }\n\n closeList();\n closeParagraph();\n\n return sanitizeHtml(result.join('\\n'));\n}\n\nfunction parseHtmlAttributes(value: string): Record<string, string> {\n const attributes: Record<string, string> = {};\n value.replace(\n /([:\\w-]+)\\s*=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+))/g,\n (\n _match,\n name: string,\n _raw: string,\n doubleValue = '',\n singleValue = '',\n bareValue = '',\n ) => {\n attributes[name.toLowerCase()] = decodeBasicEntities(\n doubleValue || singleValue || bareValue || '',\n );\n return '';\n },\n );\n return attributes;\n}\n\nfunction normalizeImagePlacement(\n value: unknown,\n): ContentBodyImagePlacement | undefined {\n return value === 'block' ||\n value === 'left' ||\n value === 'right' ||\n value === 'center' ||\n value === 'full'\n ? value\n : undefined;\n}\n\nfunction parseImageWidth(value: unknown): number | undefined {\n if (typeof value !== 'string' && typeof value !== 'number') {\n return undefined;\n }\n\n const match = String(value).match(/(\\d+(?:\\.\\d+)?)/);\n if (!match) {\n return undefined;\n }\n\n const width = Math.round(Number(match[1]));\n return Number.isFinite(width) && width > 0 ? width : undefined;\n}\n\nfunction parseStyleWidth(value: unknown): number | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n\n const match = value.match(\n /(?:^|;)\\s*(?:max-)?width\\s*:\\s*(\\d+(?:\\.\\d+)?)px/i,\n );\n if (!match) {\n return undefined;\n }\n\n return parseImageWidth(match[1]);\n}\n\nfunction fallbackHtmlToMarkdown(html: string): string {\n let markdown = sanitizeHtml(html);\n\n markdown = markdown.replace(/<img\\b([^>]*)>/gi, (_match, attrs: string) => {\n const parsed = parseHtmlAttributes(attrs);\n const src = sanitizeUrl(parsed.src || '');\n if (!src) {\n return '';\n }\n const thumbnailTitle = markdownImageTitle({\n thumbnail: parsed[BODY_THUMBNAIL_ATTRIBUTE],\n placement: parsed['data-smrt-placement'],\n assetId: parsed['data-smrt-asset-id'],\n main: parsed[BODY_MAIN_IMAGE_ATTRIBUTE],\n });\n return `\\n\\n![${parsed.alt || ''}](${src}${thumbnailTitle})\\n\\n`;\n });\n markdown = markdown.replace(\n /<a\\b([^>]*)>([\\s\\S]*?)<\\/a>/gi,\n (_match, attrs: string, label: string) => {\n const parsed = parseHtmlAttributes(attrs);\n const href = sanitizeUrl(parsed.href || '');\n const text = stripHtml(label).trim() || href;\n return href ? `[${text}](${href})` : text;\n },\n );\n markdown = markdown.replace(/<h1\\b[^>]*>([\\s\\S]*?)<\\/h1>/gi, '\\n\\n# $1\\n\\n');\n markdown = markdown.replace(/<h2\\b[^>]*>([\\s\\S]*?)<\\/h2>/gi, '\\n\\n## $1\\n\\n');\n markdown = markdown.replace(\n /<h3\\b[^>]*>([\\s\\S]*?)<\\/h3>/gi,\n '\\n\\n### $1\\n\\n',\n );\n markdown = markdown.replace(/<li\\b[^>]*>([\\s\\S]*?)<\\/li>/gi, '\\n- $1');\n markdown = markdown.replace(/<\\/(?:p|div|section|article|ul|ol)>/gi, '\\n\\n');\n markdown = markdown.replace(/<br\\s*\\/?>/gi, '\\n');\n markdown = markdown.replace(\n /<(strong|b)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/gi,\n '**$2**',\n );\n markdown = markdown.replace(/<(em|i)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/gi, '*$2*');\n markdown = stripHtml(markdown);\n\n return normalizeMarkdownWhitespace(markdown);\n}\n\nexport function stripHtml(value: string): string {\n return decodeBasicEntities(value.replace(/<[^>]*>/g, ''));\n}\n\nfunction normalizeMarkdownWhitespace(value: string): string {\n return decodeBasicEntities(value)\n .replace(/\\r\\n?/g, '\\n')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nconst MARKDOWN_TEXT_NODE = 3;\nconst MARKDOWN_ELEMENT_NODE = 1;\n\ninterface MarkdownDomNode {\n nodeType: number;\n textContent?: string | null;\n childNodes?: ArrayLike<MarkdownDomNode>;\n tagName?: string;\n getAttribute?: (name: string) => string | null;\n}\n\nfunction nodeToMarkdown(node: MarkdownDomNode): string {\n if (node.nodeType === MARKDOWN_TEXT_NODE) {\n return node.textContent || '';\n }\n\n if (node.nodeType !== MARKDOWN_ELEMENT_NODE) {\n return '';\n }\n\n const tag = node.tagName?.toLowerCase() || '';\n const children = Array.from(node.childNodes || [])\n .map(nodeToMarkdown)\n .join('');\n const trimmedChildren = children.trim();\n\n switch (tag) {\n case 'br':\n return '\\n';\n case 'h1':\n return `\\n\\n# ${trimmedChildren}\\n\\n`;\n case 'h2':\n return `\\n\\n## ${trimmedChildren}\\n\\n`;\n case 'h3':\n return `\\n\\n### ${trimmedChildren}\\n\\n`;\n case 'p':\n return `\\n\\n${trimmedChildren}\\n\\n`;\n case 'strong':\n case 'b':\n return `**${trimmedChildren}**`;\n case 'em':\n case 'i':\n return `*${trimmedChildren}*`;\n case 'code':\n return `\\`${trimmedChildren}\\``;\n case 'a': {\n const href = sanitizeUrl(node.getAttribute?.('href') || '');\n return href ? `[${trimmedChildren || href}](${href})` : trimmedChildren;\n }\n case 'img': {\n const src = sanitizeUrl(node.getAttribute?.('src') || '');\n if (!src) {\n return '';\n }\n const alt = node.getAttribute?.('alt') || '';\n const thumbnailTitle = markdownImageTitle({\n thumbnail: node.getAttribute?.(BODY_THUMBNAIL_ATTRIBUTE),\n placement: node.getAttribute?.('data-smrt-placement'),\n assetId: node.getAttribute?.('data-smrt-asset-id'),\n main: node.getAttribute?.(BODY_MAIN_IMAGE_ATTRIBUTE),\n });\n return `\\n\\n![${alt}](${src}${thumbnailTitle})\\n\\n`;\n }\n case 'li':\n return `- ${trimmedChildren}\\n`;\n case 'ul':\n case 'ol':\n return `\\n${children}\\n`;\n default:\n return BLOCK_TAGS.includes(tag) ? `\\n\\n${trimmedChildren}\\n\\n` : children;\n }\n}\n\nexport function htmlToMarkdown(html: string): string {\n const sanitized = sanitizeHtml(html);\n if (!sanitized) {\n return '';\n }\n\n const Parser = (globalThis as Record<string, unknown>).DOMParser;\n if (typeof Parser !== 'function') {\n return fallbackHtmlToMarkdown(sanitized);\n }\n\n // Structural view of the DOMParser API we rely on; avoids depending on the\n // DOM lib in this environment-agnostic module.\n const parser = new (\n Parser as new () => {\n parseFromString: (\n source: string,\n mimeType: string,\n ) => { body?: { childNodes?: ArrayLike<MarkdownDomNode> } | null };\n }\n )();\n const document = parser.parseFromString(\n `<body>${sanitized}</body>`,\n 'text/html',\n );\n const markdown = Array.from(\n (document.body?.childNodes || []) as ArrayLike<MarkdownDomNode>,\n )\n .map(nodeToMarkdown)\n .join('');\n\n return normalizeMarkdownWhitespace(markdown);\n}\n\nexport function normalizeEditorHtml(html: string): string {\n const sanitized = sanitizeHtml(html);\n if (!sanitized) {\n return '';\n }\n\n return sanitized.replace(\n /<p>\\s*((?:<br\\s*\\/?>\\s*)?<img\\b[^>]*>(?:\\s*<br\\s*\\/?>)?)\\s*<\\/p>/gi,\n (_match, imageHtml: string) =>\n imageHtml.replace(/^\\s*<br\\s*\\/?>\\s*|\\s*<br\\s*\\/?>\\s*$/gi, ''),\n );\n}\n\nexport function bodyToEditorHtml(\n body: string,\n format: ContentBodyFormat,\n): string {\n return format === 'markdown'\n ? normalizeEditorHtml(renderMarkdownToHtml(body || ''))\n : normalizeEditorHtml(body || '');\n}\n\nexport function editorHtmlToBody(\n html: string,\n format: ContentBodyFormat,\n): string {\n return format === 'markdown'\n ? htmlToMarkdown(html || '')\n : normalizeEditorHtml(html || '');\n}\n\nexport function extractBodyImages(\n body: string,\n format: ContentBodyFormat = resolveBodyFormat(undefined, body),\n): ContentBodyImage[] {\n if (!body || typeof body !== 'string') {\n return [];\n }\n\n if (format === 'markdown') {\n const images: ContentBodyImage[] = [];\n body.replace(\n /!\\[([^\\]]*)\\]\\(([^)\\s]+)(?:\\s+\"([^\"]*)\")?\\)/g,\n (_match, alt: string, src: string, title = '') => {\n const safeSrc = sanitizeUrl(src);\n if (safeSrc) {\n const decodedTitle = decodeBasicEntities(title);\n const markers = parseMarkdownImageTitle(decodedTitle);\n const thumbnailPlacement = markers?.thumbnail;\n images.push({\n src: safeSrc,\n alt: decodeBasicEntities(alt),\n ...(decodedTitle && !markers ? { title: decodedTitle } : {}),\n ...(markers?.assetId ? { assetId: markers.assetId } : {}),\n ...(markers?.main ? { main: true } : {}),\n ...(thumbnailPlacement\n ? { placement: thumbnailPlacement, thumbnail: true }\n : {}),\n index: images.length,\n });\n }\n return '';\n },\n );\n return images;\n }\n\n const images: ContentBodyImage[] = [];\n const bodyWithoutFigures = body.replace(\n /<figure\\b([^>]*)>([\\s\\S]*?)<\\/figure>/gi,\n (_match, figureAttrs: string, figureContent: string) => {\n const imageMatch = /<img\\b([^>]*)>/i.exec(figureContent);\n if (!imageMatch) {\n return '';\n }\n\n const parsedFigure = parseHtmlAttributes(figureAttrs);\n const parsedImage = parseHtmlAttributes(imageMatch[1] || '');\n const src = sanitizeUrl(parsedImage.src || '');\n if (src) {\n const placement = normalizeImagePlacement(\n parsedFigure['data-smrt-placement'] ||\n parsedImage['data-smrt-placement'],\n );\n const width =\n parseImageWidth(\n parsedFigure['data-smrt-width'] || parsedImage['data-smrt-width'],\n ) ||\n parseStyleWidth(parsedFigure.style) ||\n parseStyleWidth(parsedImage.style);\n images.push({\n src,\n alt: parsedImage.alt || '',\n ...(parsedImage.title ? { title: parsedImage.title } : {}),\n ...(parsedImage['data-smrt-asset-id']\n ? { assetId: parsedImage['data-smrt-asset-id'] }\n : {}),\n ...(placement ? { placement } : {}),\n ...(width ? { width } : {}),\n ...(isThumbnailMarker(\n parsedFigure[BODY_THUMBNAIL_ATTRIBUTE] ||\n parsedImage[BODY_THUMBNAIL_ATTRIBUTE],\n )\n ? { thumbnail: true }\n : {}),\n ...(isThumbnailMarker(\n parsedFigure[BODY_MAIN_IMAGE_ATTRIBUTE] ||\n parsedImage[BODY_MAIN_IMAGE_ATTRIBUTE],\n )\n ? { main: true }\n : {}),\n index: images.length,\n });\n }\n\n return '';\n },\n );\n\n bodyWithoutFigures.replace(/<img\\b([^>]*)>/gi, (_match, attrs: string) => {\n const parsed = parseHtmlAttributes(attrs);\n const src = sanitizeUrl(parsed.src || '');\n if (src) {\n const placement = normalizeImagePlacement(parsed['data-smrt-placement']);\n const width =\n parseImageWidth(parsed['data-smrt-width']) ||\n parseStyleWidth(parsed.style);\n images.push({\n src,\n alt: parsed.alt || '',\n ...(parsed.title ? { title: parsed.title } : {}),\n ...(parsed['data-smrt-asset-id']\n ? { assetId: parsed['data-smrt-asset-id'] }\n : {}),\n ...(placement ? { placement } : {}),\n ...(width ? { width } : {}),\n ...(isThumbnailMarker(parsed[BODY_THUMBNAIL_ATTRIBUTE])\n ? { thumbnail: true }\n : {}),\n ...(isThumbnailMarker(parsed[BODY_MAIN_IMAGE_ATTRIBUTE])\n ? { main: true }\n : {}),\n index: images.length,\n });\n }\n return '';\n });\n\n return images;\n}\n\n/**\n * Structural view of the asset-like inputs accepted by the image helpers.\n * Callers pass assorted asset shapes (Asset models, plain DTOs, etc.); only\n * these optional fields are read.\n */\nexport interface ImageAssetLike {\n id?: unknown;\n sourceUri?: unknown;\n url?: unknown;\n src?: unknown;\n alt?: unknown;\n name?: unknown;\n title?: unknown;\n width?: unknown;\n}\n\nexport function getImageSource(\n asset: ImageAssetLike | null | undefined,\n): string {\n return String(asset?.sourceUri || asset?.url || asset?.src || '');\n}\n\nexport function getImageAlt(asset: ImageAssetLike | null | undefined): string {\n return String(asset?.alt || asset?.name || asset?.title || 'Image');\n}\n\nexport function imageAssetToHtml(\n asset: ImageAssetLike | null | undefined,\n): string {\n const src = sanitizeUrl(getImageSource(asset));\n if (!src) {\n return '';\n }\n\n const assetId = asset?.id\n ? ` data-smrt-asset-id=\"${escapeAttribute(String(asset.id))}\"`\n : '';\n const width = Math.max(\n 160,\n Math.min(520, Math.round(Number(asset?.width) || 520)),\n );\n\n return `<img src=\"${escapeAttribute(src)}\" alt=\"${escapeAttribute(getImageAlt(asset))}\"${assetId} data-smrt-inline-image=\"true\" data-smrt-placement=\"block\" data-smrt-width=\"${width}\" style=\"width: ${width}px; max-width: 100%; height: auto\">`;\n}\n\n// ---------------------------------------------------------------------------\n// Thumbnail block\n//\n// When content gets a thumbnail (featured image) it is also shown in the body\n// at a standard spot chosen from its shape: a wide picture becomes a\n// full-width header image at the top; a portrait or square one floats at the\n// top right of the first paragraph so the text wraps beside it (renderers\n// stack it full width on phones). The block carries a stable marker so it is\n// replaced — never duplicated — when the thumbnail changes, and removed when\n// the thumbnail is cleared. HTML bodies mark the `<img>` with\n// `data-smrt-thumbnail=\"true\"`; Markdown bodies use the image title\n// `smrt-thumbnail:<placement>`, which the Markdown renderer turns back into\n// the same marked `<img>`.\n// ---------------------------------------------------------------------------\n\n/** Where the thumbnail block sits: header (`full`) or floated `right`. */\nexport type ContentBodyThumbnailPlacement = 'full' | 'right';\n\n/** Width ÷ height at or above which a thumbnail counts as wide (a header). */\nexport const THUMBNAIL_WIDE_ASPECT_RATIO = 1.3;\n\n/** Attribute that marks the thumbnail block's `<img>` in HTML bodies. */\nexport const BODY_THUMBNAIL_ATTRIBUTE = 'data-smrt-thumbnail';\n\n/**\n * Attribute that marks a picture already in the story as the one the person\n * chose as the main picture (HTML bodies). It travels with the picture, so a\n * choice sticks when pictures are moved or reordered.\n */\nexport const BODY_MAIN_IMAGE_ATTRIBUTE = 'data-smrt-main';\n\nconst MARKDOWN_THUMBNAIL_TITLE_PREFIX = 'smrt-thumbnail:';\n\nexport interface ContentBodyThumbnail {\n /** Image URL written into the body. */\n src: string;\n /** Alternative text (default: empty — the article title usually says it). */\n alt?: string | null;\n /** Asset id, kept on the HTML `<img>` as `data-smrt-asset-id`. */\n assetId?: string | null;\n /** Natural width in pixels, used with `height` to choose the placement. */\n width?: number | null;\n /** Natural height in pixels. */\n height?: number | null;\n /** Force a placement instead of deriving it from `width`/`height`. */\n placement?: ContentBodyThumbnailPlacement;\n}\n\n/**\n * The standard thumbnail placement for an image of this size: wide\n * (ratio ≥ {@link THUMBNAIL_WIDE_ASPECT_RATIO}) → `full` header; portrait or\n * square → `right`. Unknown sizes default to `full`.\n */\nexport function thumbnailPlacementForSize(\n width: unknown,\n height: unknown,\n): ContentBodyThumbnailPlacement {\n const w = Number(width);\n const h = Number(height);\n if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {\n return 'full';\n }\n return w / h >= THUMBNAIL_WIDE_ASPECT_RATIO ? 'full' : 'right';\n}\n\nfunction isThumbnailMarker(value: unknown): boolean {\n return value === 'true' || value === '' || value === true;\n}\n\n/**\n * Editor markers a Markdown image carries in its title (Markdown has no\n * attributes): `smrt-thumbnail:<placement>[ <asset id>]` for the thumbnail\n * block, `smrt-image <asset id>[ main]` for a stored picture in the story\n * (`main`: the chosen main picture). Any other title is an ordinary title.\n */\ninterface MarkdownImageMarkers {\n thumbnail?: ContentBodyThumbnailPlacement;\n assetId?: string;\n main?: boolean;\n}\n\nconst MARKDOWN_THUMBNAIL_TITLE_PATTERN =\n /^smrt-thumbnail:(full|right)(?: ([\\w.:-]{1,128}))?$/;\nconst MARKDOWN_IMAGE_TITLE_PATTERN = /^smrt-image ([\\w.:-]{1,128})( main)?$/;\n\nfunction parseMarkdownImageTitle(title: string): MarkdownImageMarkers | null {\n const thumbnail = MARKDOWN_THUMBNAIL_TITLE_PATTERN.exec(title);\n if (thumbnail) {\n return {\n thumbnail: thumbnail[1] as ContentBodyThumbnailPlacement,\n ...(thumbnail[2] ? { assetId: thumbnail[2] } : {}),\n };\n }\n const image = MARKDOWN_IMAGE_TITLE_PATTERN.exec(title);\n if (image) {\n return { assetId: image[1], ...(image[2] ? { main: true } : {}) };\n }\n return null;\n}\n\nfunction parseMarkdownThumbnailTitle(\n title: string,\n): ContentBodyThumbnailPlacement | undefined {\n return parseMarkdownImageTitle(title)?.thumbnail;\n}\n\n/** The ` \"title\"` a Markdown image gets for an `<img>`'s editor markers. */\nfunction markdownImageTitle(attributes: {\n thumbnail: string | null | undefined;\n placement: string | null | undefined;\n assetId: string | null | undefined;\n main: string | null | undefined;\n}): string {\n const assetId =\n attributes.assetId && /^[\\w.:-]{1,128}$/.test(attributes.assetId)\n ? attributes.assetId\n : '';\n if (isThumbnailMarker(attributes.thumbnail)) {\n const placement: ContentBodyThumbnailPlacement =\n attributes.placement === 'right' ? 'right' : 'full';\n return ` \"${MARKDOWN_THUMBNAIL_TITLE_PREFIX}${placement}${assetId ? ` ${assetId}` : ''}\"`;\n }\n if (!assetId) {\n return '';\n }\n return ` \"smrt-image ${assetId}${isThumbnailMarker(attributes.main) ? ' main' : ''}\"`;\n}\n\nconst HTML_THUMBNAIL_FIGURE_PATTERN =\n /\\s*<figure\\b[^>]*>(?:(?!<\\/figure>)[\\s\\S])*?<img\\b[^>]*\\bdata-smrt-thumbnail\\s*=[^>]*>[\\s\\S]*?<\\/figure>\\s*/gi;\nconst HTML_THUMBNAIL_MARKED_FIGURE_PATTERN =\n /\\s*<figure\\b[^>]*\\bdata-smrt-thumbnail\\s*=[^>]*>[\\s\\S]*?<\\/figure>\\s*/gi;\nconst HTML_THUMBNAIL_PARAGRAPH_PATTERN =\n /\\s*<p\\b[^>]*>\\s*(?:<br\\s*\\/?>\\s*)?<img\\b[^>]*\\bdata-smrt-thumbnail\\s*=[^>]*>\\s*(?:<br\\s*\\/?>\\s*)?<\\/p>\\s*/gi;\nconst HTML_THUMBNAIL_IMAGE_PATTERN =\n /\\s*<img\\b[^>]*\\bdata-smrt-thumbnail\\s*=[^>]*>\\s*/gi;\nconst MARKDOWN_THUMBNAIL_LINE_PATTERN =\n /^[ \\t]*!\\[[^\\]]*\\]\\([^)\\s]+\\s+\"smrt-thumbnail:(?:full|right)(?: [\\w.:-]{1,128})?\"\\)[ \\t]*(?:\\n|$)/gm;\n\n/** True when the body already contains a thumbnail block. */\nexport function bodyHasThumbnail(\n body: string | null | undefined,\n format?: ContentBodyFormat | null,\n): boolean {\n if (!body) {\n return false;\n }\n return extractBodyImages(body, resolveBodyFormat(format, body)).some(\n (image) => image.thumbnail,\n );\n}\n\n/** The body without its thumbnail block (unchanged when it has none). */\nexport function removeThumbnailFromBody(\n body: string | null | undefined,\n format?: ContentBodyFormat | null,\n): string {\n const source = body || '';\n if (!source) {\n return '';\n }\n const resolved = resolveBodyFormat(format, source);\n if (resolved === 'markdown') {\n if (!source.match(MARKDOWN_THUMBNAIL_LINE_PATTERN)) {\n return source;\n }\n return source\n .replace(MARKDOWN_THUMBNAIL_LINE_PATTERN, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/^\\n+/, '');\n }\n\n const stripped = source\n .replace(HTML_THUMBNAIL_MARKED_FIGURE_PATTERN, '\\n')\n .replace(HTML_THUMBNAIL_FIGURE_PATTERN, '\\n')\n .replace(HTML_THUMBNAIL_PARAGRAPH_PATTERN, '\\n')\n .replace(HTML_THUMBNAIL_IMAGE_PATTERN, '\\n');\n return stripped === source ? source : stripped.trim();\n}\n\nfunction thumbnailHtml(\n thumbnail: ContentBodyThumbnail,\n placement: ContentBodyThumbnailPlacement,\n): string {\n const src = sanitizeUrl(thumbnail.src || '');\n if (!src) {\n return '';\n }\n const assetId = thumbnail.assetId\n ? ` data-smrt-asset-id=\"${escapeAttribute(String(thumbnail.assetId))}\"`\n : '';\n return `<img src=\"${escapeAttribute(src)}\" alt=\"${escapeAttribute(String(thumbnail.alt || ''))}\"${assetId} ${BODY_THUMBNAIL_ATTRIBUTE}=\"true\" data-smrt-inline-image=\"true\" data-smrt-placement=\"${placement}\">`;\n}\n\nfunction thumbnailMarkdown(\n thumbnail: ContentBodyThumbnail,\n placement: ContentBodyThumbnailPlacement,\n): string {\n const src = sanitizeUrl(thumbnail.src || '');\n if (!src || /\\s/.test(src)) {\n return '';\n }\n const alt = String(thumbnail.alt || '').replace(/[[\\]\\n]/g, ' ');\n const assetId =\n thumbnail.assetId && /^[\\w.:-]{1,128}$/.test(String(thumbnail.assetId))\n ? ` ${thumbnail.assetId}`\n : '';\n return `![${alt}](${src} \"${MARKDOWN_THUMBNAIL_TITLE_PREFIX}${placement}${assetId}\")`;\n}\n\n/** Index of the first Markdown paragraph (not a heading, list or image). */\nfunction firstMarkdownParagraphOffset(markdown: string): number {\n const blockPattern = /(^|\\n\\n)([^\\n][\\s\\S]*?)(?=\\n\\n|$)/g;\n for (const match of markdown.matchAll(blockPattern)) {\n const block = match[2] || '';\n const trimmed = block.trimStart();\n if (!trimmed || /^(?:#{1,6}\\s|[-*]\\s|!\\[|>)/.test(trimmed)) {\n continue;\n }\n return (match.index ?? 0) + (match[1] || '').length;\n }\n return -1;\n}\n\n/**\n * Put `thumbnail` into `body` at its standard spot, replacing any previous\n * thumbnail block: `full` goes first in the body; `right` goes immediately\n * before the first paragraph, floated so that paragraph wraps beside it.\n * The placement comes from `thumbnail.placement` or its width/height\n * ({@link thumbnailPlacementForSize}). Returns the body unchanged (minus any\n * old block) when the image has no usable `src`.\n */\nexport function placeThumbnailInBody(\n body: string | null | undefined,\n format: ContentBodyFormat | null | undefined,\n thumbnail: ContentBodyThumbnail,\n): string {\n const source = body || '';\n const resolved = resolveBodyFormat(format, source);\n const withoutOld = removeThumbnailFromBody(source, resolved);\n const placement =\n thumbnail.placement ??\n thumbnailPlacementForSize(thumbnail.width, thumbnail.height);\n\n if (resolved === 'markdown') {\n const block = thumbnailMarkdown(thumbnail, placement);\n if (!block) {\n return withoutOld;\n }\n const trimmed = withoutOld.replace(/^\\n+/, '');\n if (!trimmed) {\n return block;\n }\n const offset =\n placement === 'right' ? firstMarkdownParagraphOffset(trimmed) : 0;\n const at = offset < 0 ? 0 : offset;\n return `${trimmed.slice(0, at)}${block}\\n\\n${trimmed.slice(at)}`;\n }\n\n const block = thumbnailHtml(thumbnail, placement);\n if (!block) {\n return withoutOld;\n }\n const trimmed = withoutOld.trim();\n if (!trimmed) {\n return block;\n }\n let at = 0;\n if (placement === 'right') {\n const paragraph = /<p\\b/i.exec(trimmed);\n at = paragraph ? paragraph.index : 0;\n }\n return `${trimmed.slice(0, at)}${block}\\n${trimmed.slice(at)}`;\n}\n\nexport interface RenderContentBodyOptions {\n /**\n * Replace the thumbnail block's image URL — for renderers (e.g. a public\n * site) that serve assets from a different origin than the editor that\n * wrote the body.\n */\n thumbnailSrc?: string | null;\n}\n\n/**\n * Sanitized HTML for a stored body in either format — what a public page\n * renders. Markdown thumbnail blocks come out as the same marked `<img>` as\n * HTML ones, so one stylesheet handles both.\n */\nexport function renderContentBodyHtml(\n body: string | null | undefined,\n format?: ContentBodyFormat | null,\n options: RenderContentBodyOptions = {},\n): string {\n const source = body || '';\n if (!source) {\n return '';\n }\n const resolved = resolveBodyFormat(format, source);\n const replacement = allowedUrl(options.thumbnailSrc || '', 'image');\n // The thumbnail swap happens on the parsed attribute inside the sanitizer,\n // never by pattern-matching the serialized HTML (an `alt` text containing\n // `src=` could otherwise be rewritten into a new attribute).\n return sanitizeBodyHtml(\n resolved === 'markdown' ? renderMarkdownToHtml(source) : source,\n replacement ? { thumbnailSrc: replacement } : {},\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main picture\n//\n// The first picture in the story is the main picture (the thumbnail) unless\n// the person chose one: either a picture in the story marked with\n// `data-smrt-main=\"true\"` (`setBodyMainImage`), or a thumbnail block placed\n// for a picture that is not otherwise in the story (`placeThumbnailInBody`).\n// A choice sticks until it is cleared, whatever order the pictures are in.\n// Only pictures with an asset id count (Markdown images carry theirs in the\n// title, `smrt-image <id>`).\n// ---------------------------------------------------------------------------\n\n/** How the main picture was decided. */\nexport type ContentMainPictureMode = 'chosen' | 'automatic' | 'none';\n\nexport interface ContentMainPicture {\n /** The main picture's asset id, or null when there is none. */\n assetId: string | null;\n /**\n * `chosen`: the person picked it. `automatic`: the first picture in the\n * story. `none`: the story has no pictures with an asset id, so the\n * current value (passed in) is kept as it is.\n */\n mode: ContentMainPictureMode;\n}\n\n/**\n * The content's main picture according to its body: the chosen picture when\n * there is one, otherwise the first picture in the story. Returns `none`\n * with `currentAssetId` when the story has no pictures with asset ids, so\n * a caller never clears a thumbnail set some other way.\n */\nexport function resolveBodyMainPicture(\n body: string | null | undefined,\n format?: ContentBodyFormat | null,\n currentAssetId: string | null = null,\n): ContentMainPicture {\n const source = body || '';\n const images = source\n ? extractBodyImages(source, resolveBodyFormat(format, source)).filter(\n (image) => Boolean(image.assetId),\n )\n : [];\n const chosen =\n images.find((image) => image.thumbnail) ??\n images.find((image) => image.main);\n if (chosen?.assetId) {\n return { assetId: chosen.assetId, mode: 'chosen' };\n }\n if (images[0]?.assetId) {\n return { assetId: images[0].assetId, mode: 'automatic' };\n }\n return { assetId: currentAssetId, mode: 'none' };\n}\n\nconst MAIN_IMAGE_ATTRIBUTE_PATTERN =\n /\\s+data-smrt-main(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'>]+))?/gi;\n\nfunction withoutMainMarker(tag: string): string {\n return tag.replace(MAIN_IMAGE_ATTRIBUTE_PATTERN, '');\n}\n\n/**\n * Mark the first picture in the story with `assetId` as the chosen main\n * picture, clearing the mark from every other picture. `null` clears the\n * choice, so the first picture is the main picture again. Works on HTML\n * bodies and on Markdown ones (where the mark is the image title\n * `smrt-image <id> main`); a body without that picture is returned with only\n * the old marks cleared.\n */\nexport function setBodyMainImage(\n body: string | null | undefined,\n format: ContentBodyFormat | null | undefined,\n assetId: string | null,\n): string {\n const source = body || '';\n if (!source) {\n return source;\n }\n if (resolveBodyFormat(format, source) === 'markdown') {\n let markedMarkdown = false;\n return source.replace(\n /!\\[([^\\]]*)\\]\\(([^)\\s]+)\\s+\"([^\"]*)\"\\)/g,\n (image, alt: string, src: string, title: string) => {\n const markers = parseMarkdownImageTitle(title);\n if (!markers?.assetId || markers.thumbnail) {\n return image;\n }\n const main = !markedMarkdown && markers.assetId === assetId;\n markedMarkdown ||= main;\n return `![${alt}](${src} \"smrt-image ${markers.assetId}${main ? ' main' : ''}\")`;\n },\n );\n }\n const cleared = source.replace(/<(img|figure)\\b[^>]*>/gi, withoutMainMarker);\n if (!assetId) {\n return cleared;\n }\n let marked = false;\n return cleared.replace(\n /<img\\b([^>]*?)(\\s*\\/?)>/gi,\n (tag, attrs: string, end: string) => {\n if (marked) {\n return tag;\n }\n const parsed = parseHtmlAttributes(attrs);\n if (\n parsed['data-smrt-asset-id'] !== assetId ||\n isThumbnailMarker(parsed[BODY_THUMBNAIL_ATTRIBUTE])\n ) {\n return tag;\n }\n marked = true;\n return `<img${attrs} ${BODY_MAIN_IMAGE_ATTRIBUTE}=\"true\"${end}>`;\n },\n );\n}\n\n/** True when the story contains the picture with this asset id (not the thumbnail block). */\nexport function bodyHasImage(\n body: string | null | undefined,\n format: ContentBodyFormat | null | undefined,\n assetId: string,\n): boolean {\n const source = body || '';\n if (!source || !assetId) {\n return false;\n }\n return extractBodyImages(source, resolveBodyFormat(format, source)).some(\n (image) => !image.thumbnail && image.assetId === assetId,\n );\n}\n"],"mappings":"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"}