@prismicio/client
Version:
The official JavaScript + TypeScript client library for Prismic
179 lines (150 loc) • 4.93 kB
text/typescript
import type { RichTextMapSerializer } from "../richtext/types"
import { LinkType } from "../types/value/link"
import type { RTAnyNode } from "../types/value/richText"
import type {
HTMLRichTextMapSerializer,
HTMLStrictRichTextMapSerializer,
} from "../helpers/asHTML"
import type { LinkResolverFunction } from "../helpers/asLink"
import { asLink } from "../helpers/asLink"
import { escapeHTML } from "./escapeHTML"
type Attributes = Record<string, string | boolean | null | undefined>
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 = <
BlockType extends keyof RichTextMapSerializer<string>,
>(
tag: string,
serializerOrShorthand?: HTMLRichTextMapSerializer[BlockType],
): NonNullable<HTMLStrictRichTextMapSerializer[BlockType]> => {
const generalAttributes = getGeneralAttributes(serializerOrShorthand)
return (({ node, children }) => {
return `<${tag}${formatAttributes(
node,
generalAttributes,
)}>${children}</${tag}>`
}) as NonNullable<HTMLStrictRichTextMapSerializer[BlockType]>
}
export const serializePreFormatted = (
serializerOrShorthand?: HTMLRichTextMapSerializer["preformatted"],
): NonNullable<HTMLStrictRichTextMapSerializer["preformatted"]> => {
const generalAttributes = getGeneralAttributes(serializerOrShorthand)
return ({ node }) => {
return `<pre${formatAttributes(node, generalAttributes)}>${escapeHTML(
node.text,
)}</pre>`
}
}
export const serializeImage = (
linkResolver:
| LinkResolverFunction<string | null | undefined>
| undefined
| null,
serializerOrShorthand?: HTMLRichTextMapSerializer["image"],
): NonNullable<HTMLStrictRichTextMapSerializer["image"]> => {
const generalAttributes = getGeneralAttributes(serializerOrShorthand)
return ({ node }) => {
const attributes = {
...generalAttributes,
src: node.url,
alt: node.alt,
copyright: node.copyright,
}
let imageTag = `<img${formatAttributes(node, attributes)} />`
// 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 `<p class="block-img">${imageTag}</p>`
}
}
export const serializeEmbed = (
serializerOrShorthand?: HTMLRichTextMapSerializer["embed"],
): NonNullable<HTMLStrictRichTextMapSerializer["embed"]> => {
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 `<div${formatAttributes(node, attributes)}>${node.oembed.html}</div>`
}
}
export const serializeHyperlink = (
linkResolver:
| LinkResolverFunction<string | null | undefined>
| undefined
| null,
serializerOrShorthand?: HTMLRichTextMapSerializer["hyperlink"],
): NonNullable<HTMLStrictRichTextMapSerializer["hyperlink"]> => {
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 `<a${formatAttributes(node, attributes)}>${children}</a>`
}
}
export const serializeSpan = (): NonNullable<
HTMLStrictRichTextMapSerializer["span"]
> => {
return ({ text }): string => {
return text ? escapeHTML(text).replace(/\n/g, "<br />") : ""
}
}