UNPKG

@storyblok/richtext

Version:
715 lines (705 loc) 22.6 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let _tiptap_html = require("@tiptap/html"); let _tiptap_core = require("@tiptap/core"); let _tiptap_extension_list = require("@tiptap/extension-list"); let _tiptap_extension_details = require("@tiptap/extension-details"); let _tiptap_extension_table = require("@tiptap/extension-table"); let _tiptap_extension_blockquote = require("@tiptap/extension-blockquote"); _tiptap_extension_blockquote = __toESM(_tiptap_extension_blockquote); let _tiptap_extension_code_block = require("@tiptap/extension-code-block"); _tiptap_extension_code_block = __toESM(_tiptap_extension_code_block); let _tiptap_extension_document = require("@tiptap/extension-document"); _tiptap_extension_document = __toESM(_tiptap_extension_document); let _tiptap_extension_emoji = require("@tiptap/extension-emoji"); _tiptap_extension_emoji = __toESM(_tiptap_extension_emoji); let _tiptap_extension_hard_break = require("@tiptap/extension-hard-break"); _tiptap_extension_hard_break = __toESM(_tiptap_extension_hard_break); let _tiptap_extension_heading = require("@tiptap/extension-heading"); _tiptap_extension_heading = __toESM(_tiptap_extension_heading); let _tiptap_extension_horizontal_rule = require("@tiptap/extension-horizontal-rule"); _tiptap_extension_horizontal_rule = __toESM(_tiptap_extension_horizontal_rule); let _tiptap_extension_image = require("@tiptap/extension-image"); _tiptap_extension_image = __toESM(_tiptap_extension_image); let _tiptap_extension_paragraph = require("@tiptap/extension-paragraph"); _tiptap_extension_paragraph = __toESM(_tiptap_extension_paragraph); let _tiptap_extension_text = require("@tiptap/extension-text"); _tiptap_extension_text = __toESM(_tiptap_extension_text); let _tiptap_extension_text_align = require("@tiptap/extension-text-align"); _tiptap_extension_text_align = __toESM(_tiptap_extension_text_align); let _tiptap_extension_bold = require("@tiptap/extension-bold"); _tiptap_extension_bold = __toESM(_tiptap_extension_bold); let _tiptap_extension_code = require("@tiptap/extension-code"); _tiptap_extension_code = __toESM(_tiptap_extension_code); let _tiptap_extension_highlight = require("@tiptap/extension-highlight"); _tiptap_extension_highlight = __toESM(_tiptap_extension_highlight); let _tiptap_extension_italic = require("@tiptap/extension-italic"); _tiptap_extension_italic = __toESM(_tiptap_extension_italic); let _tiptap_extension_link = require("@tiptap/extension-link"); _tiptap_extension_link = __toESM(_tiptap_extension_link); let _tiptap_extension_strike = require("@tiptap/extension-strike"); _tiptap_extension_strike = __toESM(_tiptap_extension_strike); let _tiptap_extension_subscript = require("@tiptap/extension-subscript"); _tiptap_extension_subscript = __toESM(_tiptap_extension_subscript); let _tiptap_extension_superscript = require("@tiptap/extension-superscript"); _tiptap_extension_superscript = __toESM(_tiptap_extension_superscript); require("@tiptap/extension-text-style"); let _tiptap_extension_underline = require("@tiptap/extension-underline"); _tiptap_extension_underline = __toESM(_tiptap_extension_underline); //#region src/images-optimization.ts function optimizeImage(src, options) { if (!options) return { src, attrs: {} }; let w = 0; let h = 0; const attrs = {}; const filterParams = []; function validateAndPushFilterParam(value, min, max, filter, filterParams) { if (typeof value !== "number" || value <= min || value >= max) console.warn(`[StoryblokRichText] - ${filter.charAt(0).toUpperCase() + filter.slice(1)} value must be a number between ${min} and ${max} (inclusive)`); else filterParams.push(`${filter}(${value})`); } if (typeof options === "object") { if (options.width !== void 0) if (typeof options.width === "number" && options.width >= 0) { attrs.width = options.width; w = options.width; } else console.warn("[StoryblokRichText] - Width value must be a number greater than or equal to 0"); if (options.height !== void 0) if (typeof options.height === "number" && options.height >= 0) { attrs.height = options.height; h = options.height; } else console.warn("[StoryblokRichText] - Height value must be a number greater than or equal to 0"); if (options.height === 0 && options.width === 0) { delete attrs.width; delete attrs.height; console.warn("[StoryblokRichText] - Width and height values cannot both be 0"); } if (options.loading && ["lazy", "eager"].includes(options.loading)) attrs.loading = options.loading; if (options.class) attrs.class = options.class; if (options.filters) { const { filters } = options || {}; const { blur, brightness, fill, format, grayscale, quality, rotate } = filters || {}; if (blur) validateAndPushFilterParam(blur, 0, 100, "blur", filterParams); if (quality) validateAndPushFilterParam(quality, 0, 100, "quality", filterParams); if (brightness) validateAndPushFilterParam(brightness, 0, 100, "brightness", filterParams); if (fill) filterParams.push(`fill(${fill})`); if (grayscale) filterParams.push(`grayscale()`); if (rotate && [ 0, 90, 180, 270 ].includes(options.filters.rotate || 0)) filterParams.push(`rotate(${rotate})`); if (format && [ "webp", "png", "jpeg" ].includes(format)) filterParams.push(`format(${format})`); } if (options.srcset) attrs.srcset = options.srcset.map((entry) => { if (typeof entry === "number") return `${src}/m/${entry}x0/${filterParams.length > 0 ? `filters:${filterParams.join(":")}` : ""} ${entry}w`; if (Array.isArray(entry) && entry.length === 2) { const [entryWidth, entryHeight] = entry; return `${src}/m/${entryWidth}x${entryHeight}/${filterParams.length > 0 ? `filters:${filterParams.join(":")}` : ""} ${entryWidth}w`; } else { console.warn("[StoryblokRichText] - srcset entry must be a number or a tuple of two numbers"); return; } }).join(", "); if (options.sizes) attrs.sizes = options.sizes.join(", "); } let resultSrc = `${src}/m/`; if (w > 0 || h > 0) resultSrc = `${resultSrc}${w}x${h}/`; if (filterParams.length > 0) resultSrc = `${resultSrc}filters:${filterParams.join(":")}`; return { src: resultSrc, attrs }; } //#endregion //#region src/utils/index.ts /** * Converts an object of attributes to a CSS style string. * * @param {Record<string, string>} [attrs] * * @returns {string} The string representation of the CSS styles. * * @example * * ```typescript * const attrs = { * color: 'red', * fontSize: '16px', * } * * const styleString = attrsToStyle(attrs) * * console.log(styleString) // 'color: red; font-size: 16px' * ``` */ const attrsToStyle = (attrs = {}) => Object.keys(attrs).map((key) => `${key}: ${attrs[key]}`).join("; "); /** * Removes undefined values from an object. * * @param {Record<string, any>} obj * @return {*} {Record<string, any>} * * @example * * ```typescript * const obj = { * name: 'John', * age: undefined, * } * * const cleanedObj = cleanObject(obj) * * console.log(cleanedObj) // { name: 'John' } * ``` * */ const cleanObject = (obj) => { return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)); }; //#endregion //#region src/types/index.ts let LinkTypes = /* @__PURE__ */ function(LinkTypes) { LinkTypes["URL"] = "url"; LinkTypes["STORY"] = "story"; LinkTypes["ASSET"] = "asset"; LinkTypes["EMAIL"] = "email"; return LinkTypes; }({}); //#endregion //#region src/extensions/utils.ts /** * Processes block-level attributes, converting textAlign to inline style * and preserving class/id/existing style. */ function processBlockAttrs(attrs = {}) { const { textAlign, class: className, id: idName, style: existingStyle, ...rest } = attrs; const styles = []; if (existingStyle) styles.push(existingStyle.endsWith(";") ? existingStyle : `${existingStyle};`); if (textAlign) styles.push(`text-align: ${textAlign};`); return cleanObject({ ...rest, class: className, id: idName, ...styles.length > 0 ? { style: styles.join(" ") } : {} }); } /** * Resolves a Storyblok link's attributes into a final href and remaining attrs. */ function resolveStoryblokLink(attrs = {}) { const { linktype, href, anchor, uuid, custom, ...rest } = attrs; let finalHref = href ?? ""; switch (linktype) { case LinkTypes.ASSET: case LinkTypes.URL: break; case LinkTypes.EMAIL: if (finalHref && !finalHref.startsWith("mailto:")) finalHref = `mailto:${finalHref}`; break; case LinkTypes.STORY: if (anchor) finalHref = `${finalHref}#${anchor}`; break; default: break; } return { href: finalHref, rest: { ...rest, ...custom || {} } }; } /** * Computes table cell attributes, converting colwidth/backgroundColor/textAlign to CSS styles. */ function computeTableCellAttrs(attrs = {}) { const { colspan, rowspan, colwidth, backgroundColor, textAlign, ...rest } = attrs; const styles = []; if (colwidth) styles.push(`width: ${colwidth}px;`); if (backgroundColor) styles.push(`background-color: ${backgroundColor};`); if (textAlign) styles.push(`text-align: ${textAlign};`); return cleanObject({ ...rest, ...colspan > 1 ? { colspan } : {}, ...rowspan > 1 ? { rowspan } : {}, ...styles.length > 0 ? { style: styles.join(" ") } : {} }); } /** * List of supported HTML attributes by tag name, used by the Reporter mark. */ const supportedAttributesByTagName = { a: [ "href", "target", "data-uuid", "data-anchor", "data-linktype" ], img: [ "alt", "src", "title" ], span: ["class"] }; /** * Gets allowed style classes for an element, warning on invalid ones. */ function getAllowedStylesForElement(element, { allowedStyles }) { const classes = (element.getAttribute("class") || "").split(" ").filter(Boolean); if (!classes.length) return []; const invalidStyles = classes.filter((x) => !allowedStyles.includes(x)); for (const invalidStyle of invalidStyles) console.warn(`[StoryblokRichText] - \`class\` "${invalidStyle}" on \`<${element.tagName.toLowerCase()}>\` can not be transformed to rich text.`); return allowedStyles.filter((x) => classes.includes(x)); } //#endregion //#region src/extensions/nodes.ts const StoryblokTextAlign = _tiptap_extension_text_align.default.configure({ types: ["heading", "paragraph"] }); const StoryblokBlockquote = _tiptap_extension_blockquote.default.extend({ renderHTML({ HTMLAttributes }) { return [ "blockquote", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokParagraph = _tiptap_extension_paragraph.default.extend({ renderHTML({ HTMLAttributes }) { return [ "p", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokHeading = _tiptap_extension_heading.default.extend({ renderHTML({ node, HTMLAttributes }) { const { level, ...rest } = HTMLAttributes; return [ `h${node.attrs.level}`, processBlockAttrs(rest), 0 ]; } }); const StoryblokTableRow = _tiptap_extension_table.TableRow.extend({ renderHTML({ HTMLAttributes }) { return [ "tr", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokBulletList = _tiptap_extension_list.BulletList.extend({ name: "bullet_list", addOptions() { return { ...this.parent(), itemTypeName: "list_item" }; }, renderHTML({ HTMLAttributes }) { return [ "ul", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokOrderedList = _tiptap_extension_list.OrderedList.extend({ name: "ordered_list", addAttributes() { return { order: { default: 1 } }; }, addOptions() { return { ...this.parent(), itemTypeName: "list_item" }; }, renderHTML({ HTMLAttributes }) { return [ "ol", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokListItem = _tiptap_extension_list.ListItem.extend({ name: "list_item", addOptions() { return { ...this.parent(), bulletListTypeName: "bullet_list", orderedListTypeName: "ordered_list" }; }, renderHTML({ HTMLAttributes }) { return [ "li", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokCodeBlock = _tiptap_extension_code_block.default.extend({ name: "code_block", addAttributes() { return { class: { default: null } }; }, renderHTML({ node, HTMLAttributes }) { const { language: _, ...rest } = HTMLAttributes; const attrs = processBlockAttrs(rest); const lang = node.attrs.language; return [ "pre", attrs, [ "code", lang ? { class: `language-${lang}` } : {}, 0 ] ]; } }); const StoryblokHardBreak = _tiptap_extension_hard_break.default.extend({ name: "hard_break" }); const StoryblokHorizontalRule = _tiptap_extension_horizontal_rule.default.extend({ name: "horizontal_rule" }); const StoryblokTable = _tiptap_extension_table.Table.extend({ renderHTML({ HTMLAttributes }) { return [ "table", processBlockAttrs(HTMLAttributes), 0 ]; } }); const StoryblokTableCell = _tiptap_extension_table.TableCell.extend({ addAttributes() { return { ...this.parent?.(), colspan: { default: 1 }, rowspan: { default: 1 }, colwidth: { default: null, parseHTML: (element) => { const colwidth = element.getAttribute("colwidth"); return colwidth ? colwidth.split(",").map(Number) : null; } }, backgroundColor: { default: null } }; }, renderHTML({ HTMLAttributes }) { return [ "td", computeTableCellAttrs(HTMLAttributes), 0 ]; } }); const StoryblokTableHeader = _tiptap_extension_table.TableHeader.extend({ renderHTML({ HTMLAttributes }) { return [ "th", computeTableCellAttrs(HTMLAttributes), 0 ]; } }); const StoryblokImage = _tiptap_extension_image.default.extend({ addOptions() { return { ...this.parent?.(), optimizeImages: false }; }, renderHTML({ HTMLAttributes }) { const { src, alt, title, srcset, sizes } = HTMLAttributes; let finalSrc = src; let extraAttrs = {}; if (this.options.optimizeImages) { const result = optimizeImage(src, this.options.optimizeImages); finalSrc = result.src; extraAttrs = result.attrs; } return ["img", cleanObject({ src: finalSrc, alt, title, srcset, sizes, ...extraAttrs })]; } }); const StoryblokEmoji = _tiptap_extension_emoji.default.extend({ renderHTML({ HTMLAttributes }) { return ["img", { "data-emoji": HTMLAttributes.emoji, "data-name": HTMLAttributes.name, "src": HTMLAttributes.fallbackImage, "alt": HTMLAttributes.alt, "style": "width: 1.25em; height: 1.25em; vertical-align: text-top", "draggable": "false", "loading": "lazy" }]; } }); const ComponentBlok = _tiptap_core.Node.create({ name: "blok", group: "block", atom: true, addOptions() { return { renderComponent: null }; }, addAttributes() { return { id: { default: null }, body: { default: [] } }; }, parseHTML() { return [{ tag: "div[data-blok]" }]; }, renderHTML({ HTMLAttributes }) { console.warn("[StoryblokRichText] - BLOK resolver is not available for vanilla usage. Configure `renderComponent` option on the blok tiptapExtension."); return ["span", cleanObject({ "data-blok": JSON.stringify(HTMLAttributes?.body?.[0] ?? null), "data-blok-id": HTMLAttributes?.id, "style": "display: none" })]; } }); //#endregion //#region src/extensions/marks.ts const StoryblokHighlight = _tiptap_extension_highlight.default.extend({ addAttributes() { return { color: { default: null } }; } }); const StoryblokLink = _tiptap_extension_link.default.extend({ addAttributes() { return { href: { parseHTML: (element) => element.getAttribute("href") }, uuid: { default: null, parseHTML: (element) => element.getAttribute("data-uuid") || null }, anchor: { default: null, parseHTML: (element) => element.getAttribute("data-anchor") || null }, target: { parseHTML: (element) => element.getAttribute("target") || null }, linktype: { default: "url", parseHTML: (element) => element.getAttribute("data-linktype") || "url" } }; }, renderHTML({ HTMLAttributes }) { const { href, rest } = resolveStoryblokLink(HTMLAttributes); return [ "a", cleanObject({ ...href ? { href } : {}, ...rest }), 0 ]; } }); const StoryblokLinkWithCustomAttributes = StoryblokLink.extend({ addAttributes() { return { ...this.parent?.(), custom: { default: null, parseHTML: (element) => { const defaultLinkAttributes = supportedAttributesByTagName.a; const customAttributeNames = element.getAttributeNames().filter((n) => !defaultLinkAttributes.includes(n)); const customAttributes = {}; for (const attributeName of customAttributeNames) customAttributes[attributeName] = element.getAttribute(attributeName); return Object.keys(customAttributes).length ? customAttributes : null; } } }; } }); const StoryblokAnchor = _tiptap_core.Mark.create({ name: "anchor", addAttributes() { return { id: { default: null } }; }, parseHTML() { return [{ tag: "span[id]" }]; }, renderHTML({ HTMLAttributes }) { return [ "span", { id: HTMLAttributes.id }, 0 ]; } }); const StoryblokStyled = _tiptap_core.Mark.create({ name: "styled", addAttributes() { return { class: { parseHTML: (element) => { const styles = getAllowedStylesForElement(element, { allowedStyles: this.options.allowedStyles || [] }); return styles.length ? styles.join(" ") : null; } } }; }, parseHTML() { return [{ tag: "span", consuming: false, getAttrs: (element) => { return getAllowedStylesForElement(element, { allowedStyles: this.options.allowedStyles || [] }).length ? null : false; } }]; }, renderHTML({ HTMLAttributes }) { const { class: className, ...rest } = HTMLAttributes; return [ "span", cleanObject({ class: className, style: attrsToStyle(rest) || void 0 }), 0 ]; } }); const StoryblokTextStyle = _tiptap_core.Mark.create({ name: "textStyle", addAttributes() { return { class: { default: null }, id: { default: null }, color: { default: null } }; }, parseHTML() { return [{ tag: "span", consuming: false, getAttrs: (element) => { const style = element.getAttribute("style"); if (style && /color/i.test(style)) return null; return false; } }]; }, renderHTML({ HTMLAttributes }) { const { class: className, id: idName, ...styleAttrs } = HTMLAttributes; return [ "span", cleanObject({ class: className, id: idName, style: attrsToStyle(styleAttrs) || void 0 }), 0 ]; } }); const Reporter = _tiptap_core.Mark.create({ name: "reporter", priority: 0, addOptions() { return { allowCustomAttributes: false }; }, parseHTML() { return [{ tag: "*", consuming: false, getAttrs: (element) => { const tagName = element.tagName.toLowerCase(); if (tagName === "a" && this.options.allowCustomAttributes) return false; const unsupportedAttributes = element.getAttributeNames().filter((attr) => { return !(tagName in supportedAttributesByTagName ? supportedAttributesByTagName[tagName] : []).includes(attr); }); for (const attr of unsupportedAttributes) console.warn(`[StoryblokRichText] - \`${attr}\` "${element.getAttribute(attr)}" on \`<${tagName}>\` can not be transformed to rich text.`); return false; } }]; } }); //#endregion //#region src/extensions/index.ts const defaultExtensions = { document: _tiptap_extension_document.default, text: _tiptap_extension_text.default, paragraph: StoryblokParagraph, blockquote: StoryblokBlockquote, heading: StoryblokHeading, bulletList: StoryblokBulletList, orderedList: StoryblokOrderedList, listItem: StoryblokListItem, codeBlock: StoryblokCodeBlock, hardBreak: StoryblokHardBreak, horizontalRule: StoryblokHorizontalRule, image: StoryblokImage, emoji: StoryblokEmoji, table: StoryblokTable, tableRow: StoryblokTableRow, tableCell: StoryblokTableCell, tableHeader: StoryblokTableHeader, blok: ComponentBlok, details: _tiptap_extension_details.Details, detailsContent: _tiptap_extension_details.DetailsContent, detailsSummary: _tiptap_extension_details.DetailsSummary, bold: _tiptap_extension_bold.default, italic: _tiptap_extension_italic.default, strike: _tiptap_extension_strike.default, underline: _tiptap_extension_underline.default, code: _tiptap_extension_code.default, superscript: _tiptap_extension_superscript.default, subscript: _tiptap_extension_subscript.default, highlight: StoryblokHighlight, textStyle: StoryblokTextStyle, link: StoryblokLink, anchor: StoryblokAnchor, styled: StoryblokStyled, reporter: Reporter, textAlign: StoryblokTextAlign }; function getStoryblokExtensions(options = {}) { const Link = options.allowCustomAttributes ? StoryblokLinkWithCustomAttributes : StoryblokLink; return { ...defaultExtensions, image: StoryblokImage.configure({ optimizeImages: options.optimizeImages || false }), link: Link, styled: StoryblokStyled.configure({ allowedStyles: options.styleOptions?.map((o) => o.value) }), reporter: Reporter.configure({ allowCustomAttributes: options.allowCustomAttributes }) }; } //#endregion //#region src/html-parser.ts function htmlToStoryblokRichtext(html, options = {}) { const { preserveWhitespace, tiptapExtensions, ...extensionOptions } = options; const allExtensions = getStoryblokExtensions(extensionOptions); const finalExtensions = tiptapExtensions ? { ...allExtensions, ...tiptapExtensions } : allExtensions; return (0, _tiptap_html.generateJSON)(html, Object.values(finalExtensions), { preserveWhitespace: preserveWhitespace || false }); } //#endregion exports.htmlToStoryblokRichtext = htmlToStoryblokRichtext; //# sourceMappingURL=html-parser.cjs.map