import type { HTMLRichTextMapSerializer, HTMLStrictRichTextMapSerializer } from "../helpers/asHTML" import type { LinkResolverFunction } from "../helpers/asLink" import { asLink } from "../helpers/asLink" import type { RichTextMapSerializer } from "../richtext/types" import { LinkType } from "../types/value/link" import type { RTAnyNode } from "../types/value/richText" import { escapeHTML } from "./escapeHTML" type Attributes = Record const formatAttributes = (node: RTAnyNode, attributes: Attributes): string => { const _attributes = { ...attributes } // Respect `ltr` and `rtl` direction if ("direction" in node && node.direction === "rtl") { _attributes.dir = node.direction } // Add label to attributes if ("data" in node && "label" in node.data && node.data.label) { _attributes.class = _attributes.class ? `${_attributes.class} ${node.data.label}` : node.data.label } const result = [] for (const key in _attributes) { const value = _attributes[key] if (value) { if (typeof value === "boolean") { result.push(key) } else { result.push(`${key}="${escapeHTML(value)}"`) } } } // Add a space at the beginning if there's any result if (result.length) { result.unshift("") } return result.join(" ") } const getGeneralAttributes = ( serializerOrShorthand?: HTMLRichTextMapSerializer[keyof HTMLRichTextMapSerializer], ): Attributes => { return serializerOrShorthand && typeof serializerOrShorthand !== "function" ? serializerOrShorthand : {} } export const serializeStandardTag = >( tag: string, serializerOrShorthand?: HTMLRichTextMapSerializer[BlockType], ): NonNullable => { const generalAttributes = getGeneralAttributes(serializerOrShorthand) return (({ node, children }) => { return `<${tag}${formatAttributes(node, generalAttributes)}>${children}` }) as NonNullable } export const serializePreFormatted = ( serializerOrShorthand?: HTMLRichTextMapSerializer["preformatted"], ): NonNullable => { const generalAttributes = getGeneralAttributes(serializerOrShorthand) return ({ node }) => { return `${escapeHTML(node.text)}` } } export const serializeImage = ( linkResolver: LinkResolverFunction | undefined | null, serializerOrShorthand?: HTMLRichTextMapSerializer["image"], ): NonNullable => { const generalAttributes = getGeneralAttributes(serializerOrShorthand) return ({ node }) => { const attributes = { ...generalAttributes, src: node.url, alt: node.alt, copyright: node.copyright, } let imageTag = `` // If the image has a link, we wrap it with an anchor tag if (node.linkTo) { imageTag = serializeHyperlink(linkResolver)({ type: "hyperlink", node: { type: "hyperlink", data: node.linkTo, start: 0, end: 0, }, text: "", children: imageTag, key: "", })! } return `

${imageTag}

` } } export const serializeEmbed = ( serializerOrShorthand?: HTMLRichTextMapSerializer["embed"], ): NonNullable => { const generalAttributes = getGeneralAttributes(serializerOrShorthand) return ({ node }) => { const attributes = { ...generalAttributes, ["data-oembed"]: node.oembed.embed_url, ["data-oembed-type"]: node.oembed.type, ["data-oembed-provider"]: node.oembed.provider_name, } return `${node.oembed.html}` } } export const serializeHyperlink = ( linkResolver: LinkResolverFunction | undefined | null, serializerOrShorthand?: HTMLRichTextMapSerializer["hyperlink"], ): NonNullable => { const generalAttributes = getGeneralAttributes(serializerOrShorthand) return ({ node, children }): string => { const attributes = { ...generalAttributes, } if (node.data.link_type === LinkType.Web) { attributes.href = node.data.url attributes.target = node.data.target attributes.rel = "noopener noreferrer" } else if (node.data.link_type === LinkType.Document) { attributes.href = asLink(node.data, { linkResolver }) } else if (node.data.link_type === LinkType.Media) { attributes.href = node.data.url } return `${children}` } } export const serializeSpan = (): NonNullable => { return ({ text }): string => { return text ? escapeHTML(text).replace(/\n/g, "
") : "" } }