Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@portabletext/to-html

Package Overview
Dependencies
Maintainers
10
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@portabletext/to-html - npm Package Compare versions

Comparing version
4.0.1
to
5.0.0
+254
dist/index.d.ts
import { ToolkitPortableTextList, ToolkitPortableTextListItem } from "@portabletext/toolkit";
import { ArbitraryTypedObject, PortableTextBlock, PortableTextBlockStyle, PortableTextListItemBlock, PortableTextListItemType, TypedObject } from "@portabletext/types";
type LooseRecord<K extends string, V> = Record<string, V> & { [P in K]?: V };
/**
* Options for the Portable Text to HTML function
*/
interface PortableTextOptions {
/**
* Component functions to use for rendering
*/
components?: Partial<PortableTextHtmlComponents>;
/**
* Function to call when encountering unknown unknown types, eg blocks, marks,
* block style, list styles without an associated component function.
*
* Will print a warning message to the console by default.
* Pass `false` to disable.
*/
onMissingComponent?: MissingComponentHandler | false;
}
/**
* Generic type for portable text components that takes blocks/inline blocks
*
* @template N Node types we expect to be rendering (`PortableTextBlock` should usually be part of this)
*/
type PortableTextComponent<N> = (options: PortableTextComponentOptions<N>) => string;
/**
* Component function type for rendering portable text blocks (paragraphs, headings, blockquotes etc)
*/
type PortableTextBlockComponent = PortableTextComponent<PortableTextBlock>;
/**
* Component function type for rendering (virtual, not part of the spec) portable text lists
*/
type PortableTextListComponent = PortableTextComponent<HtmlPortableTextList>;
/**
* Component function type for rendering portable text list items
*/
type PortableTextListItemComponent = PortableTextComponent<PortableTextListItemBlock>;
/**
* Component function type for rendering portable text marks and/or decorators
*
* @template M The mark type we expect
*/
type PortableTextMarkComponent<M extends TypedObject = any> = (options: PortableTextMarkComponentOptions<M>) => string;
type PortableTextTypeComponent<V extends TypedObject = any> = (options: PortableTextTypeComponentOptions<V>) => string;
/**
* Object defining the different component functions to use for rendering various aspects
* of Portable Text and user-provided types, where only the overrides needs to be provided.
*/
type PortableTextComponents = Partial<PortableTextHtmlComponents>;
/**
* Object definining the different component functions to use for rendering various aspects
* of Portable Text and user-provided types.
*/
interface PortableTextHtmlComponents {
/**
* Object of component functions that renders different types of objects that might appear
* both as part of the blocks array, or as inline objects _inside_ of a block,
* alongside text spans.
*
* Use the `isInline` property to check whether or not this is an inline object or a block
*
* The object has the shape `{typeName: ComponentFn}`, where `typeName` is the value set
* in individual `_type` attributes.
*/
types: Record<string, PortableTextTypeComponent | undefined>;
/**
* Object of component functions that renders different types of marks that might appear in spans.
*
* The object has the shape `{markName: ComponentFn}`, where `markName` is the value set
* in individual `_type` attributes, values being stored in the parent blocks `markDefs`.
*/
marks: Record<string, PortableTextMarkComponent | undefined>;
/**
* Object of component functions that renders blocks with different `style` properties.
*
* The object has the shape `{styleName: ComponentFn}`, where `styleName` is the value set
* in individual `style` attributes on blocks.
*
* Can also be set to a single component function, which would handle block styles of _any_ type.
*/
block: LooseRecord<PortableTextBlockStyle, PortableTextBlockComponent | undefined> | PortableTextBlockComponent;
/**
* Object of component functions used to render lists of different types (bulleted vs numbered,
* for instance, which by default is `<ul>` and `<ol>`, respectively)
*
* There is no actual "list" node type in the Portable Text specification, but a series of
* list item blocks with the same `level` and `listItem` properties will be grouped into a
* virtual one inside of this library.
*
* Can also be set to a single component function, which would handle lists of _any_ type.
*/
list: LooseRecord<PortableTextListItemType, PortableTextListComponent | undefined> | PortableTextListComponent;
/**
* Object of component functions used to render different list item styles.
*
* The object has the shape `{listItemType: ComponentFn}`, where `listItemType` is the value
* set in individual `listItem` attributes on blocks.
*
* Can also be set to a single component function, which would handle list items of _any_ type.
*/
listItem: LooseRecord<PortableTextListItemType, PortableTextListItemComponent | undefined> | PortableTextListItemComponent;
/**
* Component to use for rendering "hard breaks", eg `\n` inside of text spans
* Will by default render a `<br />`. Pass `false` to render as-is (`\n`)
*/
hardBreak: (() => string) | false;
/**
* Used when rendering text nodes to HTML
*/
escapeHTML: (html: string) => string;
/**
* Component function used when encountering a mark type there is no registered component for
* in the `components.marks` prop.
*/
unknownMark: PortableTextMarkComponent;
/**
* Component function used when encountering an object type there is no registered component for
* in the `components.types` prop.
*/
unknownType: PortableTextComponent<UnknownNodeType>;
/**
* Component function used when encountering a block style there is no registered component for
* in the `components.block` prop. Only used if `components.block` is an object.
*/
unknownBlockStyle: PortableTextComponent<PortableTextBlock>;
/**
* Component function used when encountering a list style there is no registered component for
* in the `components.list` prop. Only used if `components.list` is an object.
*/
unknownList: PortableTextComponent<HtmlPortableTextList>;
/**
* Component function used when encountering a list item style there is no registered component for
* in the `components.listItem` prop. Only used if `components.listItem` is an object.
*/
unknownListItem: PortableTextComponent<PortableTextListItemBlock>;
}
/**
* Options received by most Portable Text components
*
* @template T Type of data this component will receive in its `value` property
*/
interface PortableTextComponentOptions<T> {
/**
* Data associated with this portable text node, eg the raw JSON value of a block/type
*/
value: T;
/**
* Index within its parent
*/
index: number;
/**
* Whether or not this node is "inline" - ie as a child of a text block,
* alongside text spans, or a block in and of itself.
*/
isInline: boolean;
/**
* Serialized HTML of child nodes of this block/type
*/
children?: string;
/**
* Function used to render any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans. You will rarely need
* to use this.
*/
renderNode: NodeRenderer;
}
/**
* Options received by any user-defined type in the input array that is not a text block
*
* @template T Type of data this component will receive in its `value` property
*/
type PortableTextTypeComponentOptions<T> = Omit<PortableTextComponentOptions<T>, "children">;
/**
* Options received by Portable Text mark components
*
* @template M Shape describing the data associated with this mark, if it is an annotation
*/
interface PortableTextMarkComponentOptions<M extends TypedObject = ArbitraryTypedObject> {
/**
* Mark definition, eg the actual data of the annotation. If the mark is a simple decorator, this will be `undefined`
*/
value?: M;
/**
* Text content of this mark
*/
text: string;
/**
* Key for this mark. The same key can be used amongst multiple text spans within the same block, so don't rely on this to be unique.
*/
markKey?: string;
/**
* Type of mark - ie value of `_type` in the case of annotations, or the name of the decorator otherwise - eg `em`, `italic`.
*/
markType: string;
/**
* Serialized HTML of child nodes of this mark
*/
children: string;
/**
* Function used to render any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans. You will rarely need
* to use this.
*/
renderNode: NodeRenderer;
}
/**
* Any node type that we can't identify - eg it has an `_type`,
* but we don't know anything about its other properties
*/
type UnknownNodeType = {
[key: string]: unknown;
_type: string;
} | TypedObject;
/**
* Function that renders any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans
*/
type NodeRenderer = <T extends TypedObject>(options: Serializable<T>) => string;
type NodeType = "block" | "mark" | "blockStyle" | "listStyle" | "listItemStyle";
type MissingComponentHandler = (message: string, options: {
type: string;
nodeType: NodeType;
}) => void;
interface Serializable<T> {
node: T;
index: number;
isInline: boolean;
renderNode: NodeRenderer;
}
interface SerializedBlock {
_key: string;
children: string;
index: number;
isInline: boolean;
node: PortableTextBlock | PortableTextListItemBlock;
}
/**
* A virtual "list" node for Portable Text - not strictly part of Portable Text,
* but generated by this library to ease the rendering of lists in HTML etc
*/
type HtmlPortableTextList = ToolkitPortableTextList;
/**
* A virtual "list item" node for Portable Text - not strictly any different from a
* regular Portable Text Block, but we can guarantee that it has a `listItem` property.
*/
type HtmlPortableTextListItem = ToolkitPortableTextListItem;
declare const defaultComponents: PortableTextHtmlComponents;
declare function mergeComponents(parent: PortableTextHtmlComponents, overrides: PortableTextComponents): PortableTextHtmlComponents;
declare function escapeHTML(str: string): string;
declare function uriLooksSafe(uri: string): boolean;
declare function toHTML<B extends TypedObject = PortableTextBlock | ArbitraryTypedObject>(value: B | B[], options?: PortableTextOptions): string;
export { HtmlPortableTextList, HtmlPortableTextListItem, MissingComponentHandler, NodeRenderer, NodeType, PortableTextBlockComponent, PortableTextComponent, PortableTextComponentOptions, PortableTextComponents, PortableTextHtmlComponents, PortableTextListComponent, PortableTextListItemComponent, PortableTextMarkComponent, PortableTextMarkComponentOptions, PortableTextOptions, PortableTextTypeComponent, PortableTextTypeComponentOptions, Serializable, SerializedBlock, UnknownNodeType, defaultComponents, escapeHTML, mergeComponents, toHTML, uriLooksSafe };
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","names":["defaultPortableTextBlockStyles: Record<\n PortableTextBlockStyle,\n PortableTextBlockComponent | undefined\n>","defaultComponents: PortableTextHtmlComponents"],"sources":["../src/types.ts","../src/components/defaults.ts","../src/components/merge.ts","../src/escape.ts","../src/to-html.ts"],"sourcesContent":[],"mappings":";;KAUK,YAFE,UAEF,MAAA,EAAkD,CAAA,CAAf,GAAA,MAAA,CAAA,MAAA,EAAe,CAAf,CAAA,GAAA,QAChC,CADgC,IAC3B,CAD2B,EAAA;;;;AAOvB,UAAA,mBAAA,CAAA;EAIM;;;EASA,UAAA,CAAA,EATR,OASQ,CATA,0BASA,CAAA;EAQvB;AAKA;AAKA;AAKA;AAOA;;;EACW,kBAAA,CAAA,EA/BY,uBA+BZ,GAAA,KAAA;;AAGX;;;;;AAQY,KAlCA,qBAkCA,CAAA,CAAA,CAAA,GAAA,CAAiC,OAAA,EAlCI,4BAkCZ,CAlCyC,CAkCzC,CAAA,EAAA,GAAA,MAAA;AAMrC;;;AAmBwB,KAtDZ,0BAAA,GAA6B,qBAsDjB,CAtDuC,iBAsDvC,CAAA;;;;AAWlB,KA5DM,yBAAA,GAA4B,qBA4DlC,CA5DwD,oBA4DxD,CAAA;;;;AAcA,KArEM,6BAAA,GAAgC,qBAqEtC,CArE4D,yBAqE5D,CAAA;;;;;;AA8BS,KA5FH,yBA4FG,CAAA,UA5FiC,WA4FjC,GAAA,GAAA,CAAA,GAAA,CAAA,OAAA,EA3FJ,gCA2FI,CA3F6B,CA2F7B,CAAA,EAAA,GAAA,MAAA;AAMsB,KA9FzB,yBA8FyB,CAAA,UA9FW,WA8FX,GAAA,GAAA,CAAA,GAAA,CAAA,OAAA,EA7F1B,gCA6F0B,CA7FO,CA6FP,CAAA,EAAA,GAAA,MAAA;;;;;AAYtB,KAlGH,sBAAA,GAAyB,OAkGtB,CAlG8B,0BAkG9B,CAAA;;;;AAcf;AAmCY,UA7IK,0BAAA,CA6IL;EAAwE;;;;AAOpF;;;;;;EAsCA,KAAY,EA/KH,MA+KG,CAAA,MAAA,EA/KY,yBA+KgD,GAAA,SAAA,CAAA;EAMxE;;;;;AAEA;EAEA,KAAY,EAjLH,MAiLG,CAAA,MAAA,EAjLY,yBAmLY,GAAA,SAAA,CAAA;EAGpC;AAOA;AAcA;AAMA;;ACnRA;;AC7BA;EACU,KAAA,EFyGJ,WEzGI,CFyGQ,sBEzGR,EFyGgC,0BEzGhC,GAAA,SAAA,CAAA,GF0GJ,0BE1GI;EACG;;;;ACKb;AAQA;;ACwBA;;;EAAmE,IAAA,EJiF7D,WIjF6D,CJiFjD,wBIjFiD,EJiFvB,yBIjFuB,GAAA,SAAA,CAAA,GJkF7D,yBIlF6D;EAC1D;;;;;;;;YJ4FH,YAAY,0BAA0B,6CACtC;;;;;;;;;;;;;;eAiBS;;;;;eAMA,sBAAsB;;;;;qBAMhB,sBAAsB;;;;;eAM5B,sBAAsB;;;;;mBAMlB,sBAAsB;;;;;;;UAQxB;;;;SAIR;;;;;;;;;;;;;;;;;;;cAuBK;;;;;;;KAQF,sCAAsC,KAAK,6BAA6B;;;;;;UAOnE,2CAA2C,cAAc;;;;UAIhE;;;;;;;;;;;;;;;;;;;;;;cA2BI;;;;;;KAOF,eAAA;;;IAA4D;;;;;KAM5D,YAAA,cAA0B,sBAAsB,aAAa;KAE7D,QAAA;KAEA,uBAAA;;YAEwB;;UAGnB;QACT;;;cAGM;;UAGG,eAAA;;;;;QAKT,oBAAoB;;;;;;KAShB,oBAAA,GAAuB;;;;;KAMvB,wBAAA,GAA2B;AAxSgB,cCqB1CC,iBDrB0C,ECqBvB,0BDrBuB;iBERvC,eAAA,SACN,uCACG,yBACV;iBCIa,UAAA;iBAQA,YAAA;iBCwBA,iBAAiB,cAAc,oBAAoB,6BAC1D,IAAI,eACF"}
import { buildMarksTree, isPortableTextBlock, isPortableTextListItemBlock, isPortableTextToolkitList, isPortableTextToolkitSpan, isPortableTextToolkitTextNode, nestLists, spanToPlainText } from "@portabletext/toolkit";
const allowedProtocols = [
"http",
"https",
"mailto",
"tel"
], charMap = {
"&": "amp",
"<": "lt",
">": "gt",
"\"": "quot",
"'": "#x27"
};
function escapeHTML(str) {
return replaceMultipleSpaces(str.replace(/[&<>"']/g, (s) => `&${charMap[s]};`));
}
function replaceMultipleSpaces(str) {
return str.replace(/ {2,}/g, (match) => `${"&nbsp;".repeat(match.length - 1)} `);
}
function uriLooksSafe(uri) {
let url = (uri || "").trim(), first = url.charAt(0);
if (first === "#" || first === "/") return !0;
let colonIndex = url.indexOf(":");
if (colonIndex === -1) return !0;
let proto = url.slice(0, colonIndex).toLowerCase();
if (allowedProtocols.indexOf(proto) !== -1) return !0;
let queryIndex = url.indexOf("?");
if (queryIndex !== -1 && colonIndex > queryIndex) return !0;
let hashIndex = url.indexOf("#");
return hashIndex !== -1 && colonIndex > hashIndex;
}
const defaultLists = {
number: ({ children }) => `<ol>${children}</ol>`,
bullet: ({ children }) => `<ul>${children}</ul>`
}, DefaultListItem = ({ children }) => `<li>${children}</li>`, link = ({ children, value }) => {
let href = value?.href || "";
return uriLooksSafe(href) ? `<a href="${escapeHTML(href)}">${children}</a>` : children;
}, defaultMarks = {
em: ({ children }) => `<em>${children}</em>`,
strong: ({ children }) => `<strong>${children}</strong>`,
code: ({ children }) => `<code>${children}</code>`,
underline: ({ children }) => `<span style="text-decoration:underline">${children}</span>`,
"strike-through": ({ children }) => `<del>${children}</del>`,
link
}, getTemplate = (type, prop) => `Unknown ${type}, specify a component for it in the \`components.${prop}\` option`, unknownTypeWarning = (typeName) => getTemplate(`block type "${typeName}"`, "types"), unknownMarkWarning = (markType) => getTemplate(`mark type "${markType}"`, "marks"), unknownBlockStyleWarning = (blockStyle) => getTemplate(`block style "${blockStyle}"`, "block"), unknownListStyleWarning = (listStyle) => getTemplate(`list style "${listStyle}"`, "list"), unknownListItemStyleWarning = (listStyle) => getTemplate(`list item style "${listStyle}"`, "listItem");
function printWarning(message) {
console.warn(message);
}
const defaultComponents = {
types: {},
block: {
normal: ({ children }) => `<p>${children}</p>`,
blockquote: ({ children }) => `<blockquote>${children}</blockquote>`,
h1: ({ children }) => `<h1>${children}</h1>`,
h2: ({ children }) => `<h2>${children}</h2>`,
h3: ({ children }) => `<h3>${children}</h3>`,
h4: ({ children }) => `<h4>${children}</h4>`,
h5: ({ children }) => `<h5>${children}</h5>`,
h6: ({ children }) => `<h6>${children}</h6>`
},
marks: defaultMarks,
list: defaultLists,
listItem: DefaultListItem,
hardBreak: () => "<br/>",
escapeHTML,
unknownType: ({ value, isInline }) => {
let warning = unknownTypeWarning(value._type);
return isInline ? `<span style="display:none">${warning}</span>` : `<div style="display:none">${warning}</div>`;
},
unknownMark: ({ markType, children }) => `<span class="unknown__pt__mark__${markType}">${children}</span>`,
unknownList: ({ children }) => `<ul>${children}</ul>`,
unknownListItem: ({ children }) => `<li>${children}</li>`,
unknownBlockStyle: ({ children }) => `<p>${children}</p>`
};
function mergeComponents(parent, overrides) {
let { block, list, listItem, marks, types, ...rest } = overrides;
return {
...parent,
block: mergeDeeply(parent, overrides, "block"),
list: mergeDeeply(parent, overrides, "list"),
listItem: mergeDeeply(parent, overrides, "listItem"),
marks: mergeDeeply(parent, overrides, "marks"),
types: mergeDeeply(parent, overrides, "types"),
...rest
};
}
function mergeDeeply(parent, overrides, key) {
let override = overrides[key], parentVal = parent[key];
return typeof override == "function" ? override : override ? typeof parentVal == "function" ? override : {
...parentVal,
...override
} : parentVal;
}
function toHTML(value, options = {}) {
let { components: componentOverrides, onMissingComponent: missingComponentHandler = printWarning } = options, handleMissingComponent = missingComponentHandler || noop, nested = nestLists(Array.isArray(value) ? value : [value], "html"), renderNode = getNodeRenderer(componentOverrides ? mergeComponents(defaultComponents, componentOverrides) : defaultComponents, handleMissingComponent);
return nested.map((node, index) => renderNode({
node,
index,
isInline: !1,
renderNode
})).join("");
}
const getNodeRenderer = (components, handleMissingComponent) => {
function renderNode(options) {
let { node, index, isInline } = options;
return isPortableTextToolkitList(node) ? renderList(node, index) : isPortableTextListItemBlock(node) ? renderListItem(node, index) : isPortableTextToolkitSpan(node) ? renderSpan(node) : isPortableTextBlock(node) ? renderBlock(node, index, isInline) : isPortableTextToolkitTextNode(node) ? renderText(node) : renderCustomBlock(node, index, isInline);
}
function renderListItem(node, index) {
let tree = serializeBlock({
node,
index,
isInline: !1,
renderNode
}), renderer = components.listItem, itemHandler = (typeof renderer == "function" ? renderer : renderer[node.listItem]) || components.unknownListItem;
if (itemHandler === components.unknownListItem) {
let style = node.listItem || "bullet";
handleMissingComponent(unknownListItemStyleWarning(style), {
type: style,
nodeType: "listItemStyle"
});
}
let children = tree.children;
if (node.style && node.style !== "normal") {
let { listItem, ...blockNode } = node;
children = renderNode({
node: blockNode,
index,
isInline: !1,
renderNode
});
}
return itemHandler({
value: node,
index,
isInline: !1,
renderNode,
children
});
}
function renderList(node, index) {
let children = node.children.map((child, childIndex) => renderNode({
node: child._key ? child : {
...child,
_key: `li-${index}-${childIndex}`
},
index: childIndex,
isInline: !1,
renderNode
})), component = components.list, list = (typeof component == "function" ? component : component[node.listItem]) || components.unknownList;
if (list === components.unknownList) {
let style = node.listItem || "bullet";
handleMissingComponent(unknownListStyleWarning(style), {
nodeType: "listStyle",
type: style
});
}
return list({
value: node,
index,
isInline: !1,
renderNode,
children: children.join("")
});
}
function renderSpan(node) {
let { markDef, markType, markKey } = node, span = components.marks[markType] || components.unknownMark, children = node.children.map((child, childIndex) => renderNode({
node: child,
index: childIndex,
isInline: !0,
renderNode
}));
return span === components.unknownMark && handleMissingComponent(unknownMarkWarning(markType), {
nodeType: "mark",
type: markType
}), span({
text: spanToPlainText(node),
value: markDef,
markType,
markKey,
renderNode,
children: children.join("")
});
}
function renderBlock(node, index, isInline) {
let { _key, ...props } = serializeBlock({
node,
index,
isInline,
renderNode
}), style = props.node.style || "normal", block = (typeof components.block == "function" ? components.block : components.block[style]) || components.unknownBlockStyle;
return block === components.unknownBlockStyle && handleMissingComponent(unknownBlockStyleWarning(style), {
nodeType: "blockStyle",
type: style
}), block({
...props,
value: props.node,
renderNode
});
}
function renderText(node) {
if (node.text === "\n") {
let hardBreak = components.hardBreak;
return hardBreak ? hardBreak() : "\n";
}
return components.escapeHTML(node.text);
}
function renderCustomBlock(value, index, isInline) {
let node = components.types[value._type];
return node || handleMissingComponent(unknownTypeWarning(value._type), {
nodeType: "block",
type: value._type
}), (node || components.unknownType)({
value,
isInline,
index,
renderNode
});
}
return renderNode;
};
function serializeBlock(options) {
let { node, index, isInline, renderNode } = options, children = buildMarksTree(node).map((child, i) => renderNode({
node: child,
isInline: !0,
index: i,
renderNode
}));
return {
_key: node._key || `block-${index}`,
children: children.join(""),
index,
isInline,
node
};
}
function noop() {}
export { defaultComponents, escapeHTML, mergeComponents, toHTML, uriLooksSafe };
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","names":["charMap: Record<string, string>","defaultLists: Record<'number' | 'bullet', PortableTextListComponent>","DefaultListItem: PortableTextListItemComponent","link: PortableTextMarkComponent<DefaultLink>","defaultMarks: Record<string, PortableTextMarkComponent | undefined>","DefaultUnknownType: PortableTextHtmlComponents['unknownType']","DefaultUnknownMark: PortableTextHtmlComponents['unknownMark']","DefaultUnknownBlockStyle: PortableTextHtmlComponents['unknownBlockStyle']","DefaultUnknownList: PortableTextHtmlComponents['unknownList']","DefaultUnknownListItem: PortableTextHtmlComponents['unknownListItem']","defaultPortableTextBlockStyles: Record<\n PortableTextBlockStyle,\n PortableTextBlockComponent | undefined\n>","defaultComponents: PortableTextHtmlComponents"],"sources":["../src/escape.ts","../src/components/list.ts","../src/components/marks.ts","../src/warnings.ts","../src/components/unknown.ts","../src/components/defaults.ts","../src/components/merge.ts","../src/to-html.ts"],"sourcesContent":["const allowedProtocols = ['http', 'https', 'mailto', 'tel']\nconst charMap: Record<string, string> = {\n '&': 'amp',\n '<': 'lt',\n '>': 'gt',\n '\"': 'quot',\n \"'\": '#x27',\n}\n\nexport function escapeHTML(str: string): string {\n return replaceMultipleSpaces(str.replace(/[&<>\"']/g, (s) => `&${charMap[s]};`))\n}\n\nexport function replaceMultipleSpaces(str: string): string {\n return str.replace(/ {2,}/g, (match: string) => `${'&nbsp;'.repeat(match.length - 1)} `)\n}\n\nexport function uriLooksSafe(uri: string): boolean {\n const url = (uri || '').trim()\n const first = url.charAt(0)\n\n // Allow hash-links, absolute paths and \"same-protocol\" (//foo.bar) URLs\n if (first === '#' || first === '/') {\n return true\n }\n\n // If the URL does not contain a `:`, allow it\n const colonIndex = url.indexOf(':')\n if (colonIndex === -1) {\n return true\n }\n\n // If the protocol is in the allowed list, treat it as OK\n const proto = url.slice(0, colonIndex).toLowerCase()\n if (allowedProtocols.indexOf(proto) !== -1) {\n return true\n }\n\n // If the URL is `site/search?query=author:espen`, allow it\n const queryIndex = url.indexOf('?')\n if (queryIndex !== -1 && colonIndex > queryIndex) {\n return true\n }\n\n // If the URL is `site/search#my:encoded:data`, allow it\n const hashIndex = url.indexOf('#')\n if (hashIndex !== -1 && colonIndex > hashIndex) {\n return true\n }\n\n return false\n}\n","import type {PortableTextListComponent, PortableTextListItemComponent} from '../types'\n\nexport const defaultLists: Record<'number' | 'bullet', PortableTextListComponent> = {\n number: ({children}) => `<ol>${children}</ol>`,\n bullet: ({children}) => `<ul>${children}</ul>`,\n}\n\nexport const DefaultListItem: PortableTextListItemComponent = ({children}) => `<li>${children}</li>`\n","import type {TypedObject} from '@portabletext/types'\n\nimport type {PortableTextMarkComponent} from '../types'\n\nimport {escapeHTML, uriLooksSafe} from '../escape'\n\ninterface DefaultLink extends TypedObject {\n _type: 'link'\n href: string\n}\n\nconst link: PortableTextMarkComponent<DefaultLink> = ({children, value}) => {\n const href = value?.href || ''\n const looksSafe = uriLooksSafe(href)\n return looksSafe ? `<a href=\"${escapeHTML(href)}\">${children}</a>` : children\n}\n\nexport const defaultMarks: Record<string, PortableTextMarkComponent | undefined> = {\n em: ({children}) => `<em>${children}</em>`,\n strong: ({children}) => `<strong>${children}</strong>`,\n code: ({children}) => `<code>${children}</code>`,\n underline: ({children}) => `<span style=\"text-decoration:underline\">${children}</span>`,\n 'strike-through': ({children}) => `<del>${children}</del>`,\n link,\n}\n","const getTemplate = (type: string, prop: string): string =>\n `Unknown ${type}, specify a component for it in the \\`components.${prop}\\` option`\n\nexport const unknownTypeWarning = (typeName: string): string =>\n getTemplate(`block type \"${typeName}\"`, 'types')\n\nexport const unknownMarkWarning = (markType: string): string =>\n getTemplate(`mark type \"${markType}\"`, 'marks')\n\nexport const unknownBlockStyleWarning = (blockStyle: string): string =>\n getTemplate(`block style \"${blockStyle}\"`, 'block')\n\nexport const unknownListStyleWarning = (listStyle: string): string =>\n getTemplate(`list style \"${listStyle}\"`, 'list')\n\nexport const unknownListItemStyleWarning = (listStyle: string): string =>\n getTemplate(`list item style \"${listStyle}\"`, 'listItem')\n\nexport function printWarning(message: string): void {\n // oxlint-disable-next-line no-console\n console.warn(message)\n}\n","import type {PortableTextHtmlComponents} from '../types'\n\nimport {unknownTypeWarning} from '../warnings'\n\nexport const DefaultUnknownType: PortableTextHtmlComponents['unknownType'] = ({\n value,\n isInline,\n}) => {\n const warning = unknownTypeWarning(value._type)\n return isInline\n ? `<span style=\"display:none\">${warning}</span>`\n : `<div style=\"display:none\">${warning}</div>`\n}\n\nexport const DefaultUnknownMark: PortableTextHtmlComponents['unknownMark'] = ({\n markType,\n children,\n}) => {\n return `<span class=\"unknown__pt__mark__${markType}\">${children}</span>`\n}\n\nexport const DefaultUnknownBlockStyle: PortableTextHtmlComponents['unknownBlockStyle'] = ({\n children,\n}) => {\n return `<p>${children}</p>`\n}\n\nexport const DefaultUnknownList: PortableTextHtmlComponents['unknownList'] = ({children}) => {\n return `<ul>${children}</ul>`\n}\n\nexport const DefaultUnknownListItem: PortableTextHtmlComponents['unknownListItem'] = ({\n children,\n}) => {\n return `<li>${children}</li>`\n}\n","import type {PortableTextBlockStyle} from '@portabletext/types'\n\nimport type {PortableTextBlockComponent, PortableTextHtmlComponents} from '../types'\n\nimport {escapeHTML} from '../escape'\nimport {DefaultListItem, defaultLists} from './list'\nimport {defaultMarks} from './marks'\nimport {\n DefaultUnknownBlockStyle,\n DefaultUnknownList,\n DefaultUnknownListItem,\n DefaultUnknownMark,\n DefaultUnknownType,\n} from './unknown'\n\nexport const DefaultHardBreak = (): string => '<br/>'\n\nexport const defaultPortableTextBlockStyles: Record<\n PortableTextBlockStyle,\n PortableTextBlockComponent | undefined\n> = {\n normal: ({children}) => `<p>${children}</p>`,\n blockquote: ({children}) => `<blockquote>${children}</blockquote>`,\n h1: ({children}) => `<h1>${children}</h1>`,\n h2: ({children}) => `<h2>${children}</h2>`,\n h3: ({children}) => `<h3>${children}</h3>`,\n h4: ({children}) => `<h4>${children}</h4>`,\n h5: ({children}) => `<h5>${children}</h5>`,\n h6: ({children}) => `<h6>${children}</h6>`,\n}\n\nexport const defaultComponents: PortableTextHtmlComponents = {\n types: {},\n\n block: defaultPortableTextBlockStyles,\n marks: defaultMarks,\n list: defaultLists,\n listItem: DefaultListItem,\n hardBreak: DefaultHardBreak,\n escapeHTML: escapeHTML,\n\n unknownType: DefaultUnknownType,\n unknownMark: DefaultUnknownMark,\n unknownList: DefaultUnknownList,\n unknownListItem: DefaultUnknownListItem,\n unknownBlockStyle: DefaultUnknownBlockStyle,\n}\n","import type {PortableTextComponents, PortableTextHtmlComponents} from '../types'\n\nexport function mergeComponents(\n parent: PortableTextHtmlComponents,\n overrides: PortableTextComponents,\n): PortableTextHtmlComponents {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {block, list, listItem, marks, types, ...rest} = overrides\n // @todo figure out how to not `as ...` these\n return {\n ...parent,\n block: mergeDeeply(parent, overrides, 'block') as PortableTextHtmlComponents['block'],\n list: mergeDeeply(parent, overrides, 'list') as PortableTextHtmlComponents['list'],\n listItem: mergeDeeply(parent, overrides, 'listItem') as PortableTextHtmlComponents['listItem'],\n marks: mergeDeeply(parent, overrides, 'marks') as PortableTextHtmlComponents['marks'],\n types: mergeDeeply(parent, overrides, 'types') as PortableTextHtmlComponents['types'],\n ...rest,\n }\n}\n\nfunction mergeDeeply(\n parent: PortableTextHtmlComponents,\n overrides: PortableTextComponents,\n key: 'block' | 'list' | 'listItem' | 'marks' | 'types',\n): PortableTextHtmlComponents[typeof key] {\n const override = overrides[key]\n const parentVal = parent[key]\n\n if (typeof override === 'function') {\n return override\n }\n\n if (override) {\n return typeof parentVal === 'function'\n ? override\n : ({...parentVal, ...override} as PortableTextHtmlComponents[typeof key])\n }\n\n return parentVal\n}\n","import type {\n ArbitraryTypedObject,\n PortableTextBlock,\n PortableTextListItemBlock,\n TypedObject,\n} from '@portabletext/types'\n\nimport {\n buildMarksTree,\n isPortableTextBlock,\n isPortableTextListItemBlock,\n isPortableTextToolkitList,\n isPortableTextToolkitSpan,\n isPortableTextToolkitTextNode,\n nestLists,\n spanToPlainText,\n type ToolkitNestedPortableTextSpan,\n type ToolkitTextNode,\n} from '@portabletext/toolkit'\n\nimport type {\n HtmlPortableTextList,\n MissingComponentHandler,\n NodeRenderer,\n PortableTextHtmlComponents,\n PortableTextOptions,\n Serializable,\n SerializedBlock,\n} from './types'\n\nimport {defaultComponents} from './components/defaults'\nimport {mergeComponents} from './components/merge'\nimport {\n printWarning,\n unknownBlockStyleWarning,\n unknownListItemStyleWarning,\n unknownListStyleWarning,\n unknownMarkWarning,\n unknownTypeWarning,\n} from './warnings'\n\nexport function toHTML<B extends TypedObject = PortableTextBlock | ArbitraryTypedObject>(\n value: B | B[],\n options: PortableTextOptions = {},\n): string {\n const {\n components: componentOverrides,\n onMissingComponent: missingComponentHandler = printWarning,\n } = options\n\n const handleMissingComponent = missingComponentHandler || noop\n const blocks = Array.isArray(value) ? value : [value]\n const nested = nestLists(blocks, 'html')\n const components = componentOverrides\n ? mergeComponents(defaultComponents, componentOverrides)\n : defaultComponents\n\n const renderNode = getNodeRenderer(components, handleMissingComponent)\n const rendered = nested.map((node, index) =>\n renderNode({node: node, index, isInline: false, renderNode}),\n )\n\n return rendered.join('')\n}\n\nconst getNodeRenderer = (\n components: PortableTextHtmlComponents,\n handleMissingComponent: MissingComponentHandler,\n): NodeRenderer => {\n function renderNode<N extends TypedObject>(options: Serializable<N>): string {\n const {node, index, isInline} = options\n\n if (isPortableTextToolkitList(node)) {\n return renderList(node, index)\n }\n\n if (isPortableTextListItemBlock(node)) {\n return renderListItem(node, index)\n }\n\n if (isPortableTextToolkitSpan(node)) {\n return renderSpan(node)\n }\n\n if (isPortableTextBlock(node)) {\n return renderBlock(node, index, isInline)\n }\n\n if (isPortableTextToolkitTextNode(node)) {\n return renderText(node)\n }\n\n return renderCustomBlock(node, index, isInline)\n }\n\n function renderListItem(node: PortableTextListItemBlock, index: number): string {\n const tree = serializeBlock({node, index, isInline: false, renderNode})\n const renderer = components.listItem\n const handler = typeof renderer === 'function' ? renderer : renderer[node.listItem]\n const itemHandler = handler || components.unknownListItem\n\n if (itemHandler === components.unknownListItem) {\n const style = node.listItem || 'bullet'\n handleMissingComponent(unknownListItemStyleWarning(style), {\n type: style,\n nodeType: 'listItemStyle',\n })\n }\n\n let children = tree.children\n if (node.style && node.style !== 'normal') {\n // Wrap any other style in whatever the block component says to use\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {listItem, ...blockNode} = node\n children = renderNode({node: blockNode, index, isInline: false, renderNode})\n }\n\n return itemHandler({value: node, index, isInline: false, renderNode, children})\n }\n\n function renderList(node: HtmlPortableTextList, index: number): string {\n const children = node.children.map((child, childIndex) =>\n renderNode({\n node: child._key ? child : {...child, _key: `li-${index}-${childIndex}`},\n index: childIndex,\n isInline: false,\n renderNode,\n }),\n )\n\n const component = components.list\n const handler = typeof component === 'function' ? component : component[node.listItem]\n const list = handler || components.unknownList\n\n if (list === components.unknownList) {\n const style = node.listItem || 'bullet'\n handleMissingComponent(unknownListStyleWarning(style), {nodeType: 'listStyle', type: style})\n }\n\n return list({value: node, index, isInline: false, renderNode, children: children.join('')})\n }\n\n function renderSpan(node: ToolkitNestedPortableTextSpan): string {\n const {markDef, markType, markKey} = node\n const span = components.marks[markType] || components.unknownMark\n const children = node.children.map((child, childIndex) =>\n renderNode({node: child, index: childIndex, isInline: true, renderNode}),\n )\n\n if (span === components.unknownMark) {\n handleMissingComponent(unknownMarkWarning(markType), {nodeType: 'mark', type: markType})\n }\n\n return span({\n text: spanToPlainText(node),\n value: markDef,\n markType,\n markKey,\n renderNode,\n children: children.join(''),\n })\n }\n\n function renderBlock(node: PortableTextBlock, index: number, isInline: boolean): string {\n const {_key, ...props} = serializeBlock({node, index, isInline, renderNode})\n const style = props.node.style || 'normal'\n const handler =\n typeof components.block === 'function' ? components.block : components.block[style]\n const block = handler || components.unknownBlockStyle\n\n if (block === components.unknownBlockStyle) {\n handleMissingComponent(unknownBlockStyleWarning(style), {\n nodeType: 'blockStyle',\n type: style,\n })\n }\n\n return block({...props, value: props.node, renderNode})\n }\n\n function renderText(node: ToolkitTextNode): string {\n if (node.text === '\\n') {\n const hardBreak = components.hardBreak\n return hardBreak ? hardBreak() : '\\n'\n }\n\n return components.escapeHTML(node.text)\n }\n\n function renderCustomBlock(value: TypedObject, index: number, isInline: boolean): string {\n const node = components.types[value._type]\n\n if (!node) {\n handleMissingComponent(unknownTypeWarning(value._type), {\n nodeType: 'block',\n type: value._type,\n })\n }\n\n const component = node || components.unknownType\n return component({\n value,\n isInline,\n index,\n renderNode,\n })\n }\n\n return renderNode\n}\n\nfunction serializeBlock(options: Serializable<PortableTextBlock>): SerializedBlock {\n const {node, index, isInline, renderNode} = options\n const tree = buildMarksTree(node)\n const children = tree.map((child, i) =>\n renderNode({node: child, isInline: true, index: i, renderNode}),\n )\n\n return {\n _key: node._key || `block-${index}`,\n children: children.join(''),\n index,\n isInline,\n node,\n }\n}\n\nfunction noop() {\n // Intentional noop\n}\n"],"mappings":"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"}
+1
-1
MIT License
Copyright (c) 2024 Sanity.io <hello@sanity.io>
Copyright (c) 2025 Sanity.io <hello@sanity.io>

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

{
"name": "@portabletext/to-html",
"version": "4.0.1",
"version": "5.0.0",
"description": "Render Portable Text to HTML",

@@ -21,58 +21,29 @@ "keywords": [

"exports": {
".": {
"source": "./src/index.ts",
"default": "./dist/pt-to-html.js"
},
".": "./dist/index.js",
"./package.json": "./package.json"
},
"main": "./dist/pt-to-html.js",
"types": "./dist/pt-to-html.d.ts",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"!dist/stats.html",
"src",
"README.md"
"!dist/stats.html"
],
"browserslist": "extends @sanity/browserslist-config",
"prettier": "@sanity/prettier-config",
"eslintConfig": {
"parserOptions": {
"ecmaFeatures": {
"modules": true
},
"ecmaVersion": 9,
"sourceType": "module"
},
"extends": [
"sanity",
"sanity/typescript",
"prettier"
],
"ignorePatterns": [
"dist/**/"
]
},
"dependencies": {
"@portabletext/toolkit": "^4.0.0",
"@portabletext/types": "^3.0.0"
"@portabletext/toolkit": "^5.0.0",
"@portabletext/types": "^4.0.0"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.29.7",
"@sanity/browserslist-config": "^1.0.5",
"@sanity/pkg-utils": "^9.0.1",
"@sanity/prettier-config": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitest/coverage-v8": "^4.0.6",
"esbuild": "^0.25.12",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.8",
"eslint-config-sanity": "^7.1.4",
"npm-run-all2": "^8.0.4",
"prettier": "^3.6.2",
"rimraf": "^4.4.1",
"typedoc": "^0.28.14",
"@changesets/changelog-github": "^0.5.2",
"@changesets/cli": "^2.29.8",
"@sanity/tsconfig": "^2.0.0",
"@sanity/tsdown-config": "^0.4.0",
"@vitest/coverage-v8": "^4.0.15",
"oxfmt": "^0.17.0",
"oxlint": "^1.32.0",
"oxlint-tsgolint": "^0.8.4",
"tsdown": "^0.17.2",
"typedoc": "^0.28.15",
"typescript": "5.9.3",
"vitest": "^4.0.6"
"vitest": "^4.0.15"
},

@@ -82,18 +53,11 @@ "engines": {

},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "run-s clean pkg:build pkg:check",
"clean": "rimraf dist coverage",
"build": "tsdown",
"coverage": "vitest run --coverage",
"docs:build": "typedoc",
"format": "prettier --write --cache --ignore-unknown .",
"lint": "eslint .",
"pkg:build": "pkg-utils build --strict",
"pkg:check": "pkg-utils --strict",
"format": "oxfmt .",
"lint": "oxlint --type-aware --type-check --deny-warnings --report-unused-disable-directives",
"release": "changeset publish",
"test": "vitest run",
"type-check": "tsc --noEmit"
"test": "vitest run"
}
}
import type {ArbitraryTypedObject} from '@portabletext/types'
import type {PortableTextBlock} from '@portabletext/types'
import type {PortableTextBlockStyle} from '@portabletext/types'
import type {PortableTextListItemBlock} from '@portabletext/types'
import type {PortableTextListItemType} from '@portabletext/types'
import type {ToolkitPortableTextList} from '@portabletext/toolkit'
import type {ToolkitPortableTextListItem} from '@portabletext/toolkit'
import type {TypedObject} from '@portabletext/types'
export declare const defaultComponents: PortableTextHtmlComponents
export declare function escapeHTML(str: string): string
/**
* A virtual "list" node for Portable Text - not strictly part of Portable Text,
* but generated by this library to ease the rendering of lists in HTML etc
*/
export declare type HtmlPortableTextList = ToolkitPortableTextList
/**
* A virtual "list item" node for Portable Text - not strictly any different from a
* regular Portable Text Block, but we can guarantee that it has a `listItem` property.
*/
export declare type HtmlPortableTextListItem = ToolkitPortableTextListItem
export declare function mergeComponents(
parent: PortableTextHtmlComponents,
overrides: PortableTextComponents,
): PortableTextHtmlComponents
export declare type MissingComponentHandler = (
message: string,
options: {
type: string
nodeType: NodeType
},
) => void
/**
* Function that renders any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans
*/
export declare type NodeRenderer = <T extends TypedObject>(options: Serializable<T>) => string
export declare type NodeType = 'block' | 'mark' | 'blockStyle' | 'listStyle' | 'listItemStyle'
/**
* Component function type for rendering portable text blocks (paragraphs, headings, blockquotes etc)
*/
export declare type PortableTextBlockComponent = PortableTextComponent<PortableTextBlock>
/**
* Generic type for portable text components that takes blocks/inline blocks
*
* @template N Node types we expect to be rendering (`PortableTextBlock` should usually be part of this)
*/
export declare type PortableTextComponent<N> = (options: PortableTextComponentOptions<N>) => string
/**
* Options received by most Portable Text components
*
* @template T Type of data this component will receive in its `value` property
*/
export declare interface PortableTextComponentOptions<T> {
/**
* Data associated with this portable text node, eg the raw JSON value of a block/type
*/
value: T
/**
* Index within its parent
*/
index: number
/**
* Whether or not this node is "inline" - ie as a child of a text block,
* alongside text spans, or a block in and of itself.
*/
isInline: boolean
/**
* Serialized HTML of child nodes of this block/type
*/
children?: string
/**
* Function used to render any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans. You will rarely need
* to use this.
*/
renderNode: NodeRenderer
}
/**
* Object defining the different component functions to use for rendering various aspects
* of Portable Text and user-provided types, where only the overrides needs to be provided.
*/
export declare type PortableTextComponents = Partial<PortableTextHtmlComponents>
/**
* Object definining the different component functions to use for rendering various aspects
* of Portable Text and user-provided types.
*/
export declare interface PortableTextHtmlComponents {
/**
* Object of component functions that renders different types of objects that might appear
* both as part of the blocks array, or as inline objects _inside_ of a block,
* alongside text spans.
*
* Use the `isInline` property to check whether or not this is an inline object or a block
*
* The object has the shape `{typeName: ComponentFn}`, where `typeName` is the value set
* in individual `_type` attributes.
*/
types: Record<string, PortableTextTypeComponent | undefined>
/**
* Object of component functions that renders different types of marks that might appear in spans.
*
* The object has the shape `{markName: ComponentFn}`, where `markName` is the value set
* in individual `_type` attributes, values being stored in the parent blocks `markDefs`.
*/
marks: Record<string, PortableTextMarkComponent | undefined>
/**
* Object of component functions that renders blocks with different `style` properties.
*
* The object has the shape `{styleName: ComponentFn}`, where `styleName` is the value set
* in individual `style` attributes on blocks.
*
* Can also be set to a single component function, which would handle block styles of _any_ type.
*/
block:
| Record<PortableTextBlockStyle, PortableTextBlockComponent | undefined>
| PortableTextBlockComponent
/**
* Object of component functions used to render lists of different types (bulleted vs numbered,
* for instance, which by default is `<ul>` and `<ol>`, respectively)
*
* There is no actual "list" node type in the Portable Text specification, but a series of
* list item blocks with the same `level` and `listItem` properties will be grouped into a
* virtual one inside of this library.
*
* Can also be set to a single component function, which would handle lists of _any_ type.
*/
list:
| Record<PortableTextListItemType, PortableTextListComponent | undefined>
| PortableTextListComponent
/**
* Object of component functions used to render different list item styles.
*
* The object has the shape `{listItemType: ComponentFn}`, where `listItemType` is the value
* set in individual `listItem` attributes on blocks.
*
* Can also be set to a single component function, which would handle list items of _any_ type.
*/
listItem:
| Record<PortableTextListItemType, PortableTextListItemComponent | undefined>
| PortableTextListItemComponent
/**
* Component to use for rendering "hard breaks", eg `\n` inside of text spans
* Will by default render a `<br />`. Pass `false` to render as-is (`\n`)
*/
hardBreak: (() => string) | false
/**
* Used when rendering text nodes to HTML
*/
escapeHTML: (html: string) => string
/**
* Component function used when encountering a mark type there is no registered component for
* in the `components.marks` prop.
*/
unknownMark: PortableTextMarkComponent
/**
* Component function used when encountering an object type there is no registered component for
* in the `components.types` prop.
*/
unknownType: PortableTextComponent<UnknownNodeType>
/**
* Component function used when encountering a block style there is no registered component for
* in the `components.block` prop. Only used if `components.block` is an object.
*/
unknownBlockStyle: PortableTextComponent<PortableTextBlock>
/**
* Component function used when encountering a list style there is no registered component for
* in the `components.list` prop. Only used if `components.list` is an object.
*/
unknownList: PortableTextComponent<HtmlPortableTextList>
/**
* Component function used when encountering a list item style there is no registered component for
* in the `components.listItem` prop. Only used if `components.listItem` is an object.
*/
unknownListItem: PortableTextComponent<PortableTextListItemBlock>
}
/**
* Component function type for rendering (virtual, not part of the spec) portable text lists
*/
export declare type PortableTextListComponent = PortableTextComponent<HtmlPortableTextList>
/**
* Component function type for rendering portable text list items
*/
export declare type PortableTextListItemComponent = PortableTextComponent<PortableTextListItemBlock>
/**
* Component function type for rendering portable text marks and/or decorators
*
* @template M The mark type we expect
*/
export declare type PortableTextMarkComponent<M extends TypedObject = any> = (
options: PortableTextMarkComponentOptions<M>,
) => string
/**
* Options received by Portable Text mark components
*
* @template M Shape describing the data associated with this mark, if it is an annotation
*/
export declare interface PortableTextMarkComponentOptions<
M extends TypedObject = ArbitraryTypedObject,
> {
/**
* Mark definition, eg the actual data of the annotation. If the mark is a simple decorator, this will be `undefined`
*/
value?: M
/**
* Text content of this mark
*/
text: string
/**
* Key for this mark. The same key can be used amongst multiple text spans within the same block, so don't rely on this to be unique.
*/
markKey?: string
/**
* Type of mark - ie value of `_type` in the case of annotations, or the name of the decorator otherwise - eg `em`, `italic`.
*/
markType: string
/**
* Serialized HTML of child nodes of this mark
*/
children: string
/**
* Function used to render any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans. You will rarely need
* to use this.
*/
renderNode: NodeRenderer
}
/**
* Options for the Portable Text to HTML function
*/
export declare interface PortableTextOptions {
/**
* Component functions to use for rendering
*/
components?: Partial<PortableTextHtmlComponents>
/**
* Function to call when encountering unknown unknown types, eg blocks, marks,
* block style, list styles without an associated component function.
*
* Will print a warning message to the console by default.
* Pass `false` to disable.
*/
onMissingComponent?: MissingComponentHandler | false
}
export declare type PortableTextTypeComponent<V extends TypedObject = any> = (
options: PortableTextTypeComponentOptions<V>,
) => string
/**
* Options received by any user-defined type in the input array that is not a text block
*
* @template T Type of data this component will receive in its `value` property
*/
export declare type PortableTextTypeComponentOptions<T> = Omit<
PortableTextComponentOptions<T>,
'children'
>
export declare interface Serializable<T> {
node: T
index: number
isInline: boolean
renderNode: NodeRenderer
}
export declare interface SerializedBlock {
_key: string
children: string
index: number
isInline: boolean
node: PortableTextBlock | PortableTextListItemBlock
}
export declare function toHTML<B extends TypedObject = PortableTextBlock | ArbitraryTypedObject>(
value: B | B[],
options?: PortableTextOptions,
): string
/**
* Any node type that we can't identify - eg it has an `_type`,
* but we don't know anything about its other properties
*/
export declare type UnknownNodeType =
| {
[key: string]: unknown
_type: string
}
| TypedObject
export declare function uriLooksSafe(uri: string): boolean
export {}
import { nestLists, isPortableTextToolkitList, isPortableTextListItemBlock, isPortableTextToolkitSpan, isPortableTextBlock, isPortableTextToolkitTextNode, spanToPlainText, buildMarksTree } from "@portabletext/toolkit";
const allowedProtocols = ["http", "https", "mailto", "tel"], charMap = {
"&": "amp",
"<": "lt",
">": "gt",
'"': "quot",
"'": "#x27"
};
function escapeHTML(str) {
return replaceMultipleSpaces(str.replace(/[&<>"']/g, (s) => `&${charMap[s]};`));
}
function replaceMultipleSpaces(str) {
return str.replace(/ {2,}/g, (match) => `${"&nbsp;".repeat(match.length - 1)} `);
}
function uriLooksSafe(uri) {
const url = (uri || "").trim(), first = url.charAt(0);
if (first === "#" || first === "/")
return !0;
const colonIndex = url.indexOf(":");
if (colonIndex === -1)
return !0;
const proto = url.slice(0, colonIndex).toLowerCase();
if (allowedProtocols.indexOf(proto) !== -1)
return !0;
const queryIndex = url.indexOf("?");
if (queryIndex !== -1 && colonIndex > queryIndex)
return !0;
const hashIndex = url.indexOf("#");
return hashIndex !== -1 && colonIndex > hashIndex;
}
const defaultLists = {
number: ({ children }) => `<ol>${children}</ol>`,
bullet: ({ children }) => `<ul>${children}</ul>`
}, DefaultListItem = ({ children }) => `<li>${children}</li>`, link = ({ children, value }) => {
const href = value?.href || "";
return uriLooksSafe(href) ? `<a href="${escapeHTML(href)}">${children}</a>` : children;
}, defaultMarks = {
em: ({ children }) => `<em>${children}</em>`,
strong: ({ children }) => `<strong>${children}</strong>`,
code: ({ children }) => `<code>${children}</code>`,
underline: ({ children }) => `<span style="text-decoration:underline">${children}</span>`,
"strike-through": ({ children }) => `<del>${children}</del>`,
link
}, getTemplate = (type, prop) => `Unknown ${type}, specify a component for it in the \`components.${prop}\` option`, unknownTypeWarning = (typeName) => getTemplate(`block type "${typeName}"`, "types"), unknownMarkWarning = (markType) => getTemplate(`mark type "${markType}"`, "marks"), unknownBlockStyleWarning = (blockStyle) => getTemplate(`block style "${blockStyle}"`, "block"), unknownListStyleWarning = (listStyle) => getTemplate(`list style "${listStyle}"`, "list"), unknownListItemStyleWarning = (listStyle) => getTemplate(`list item style "${listStyle}"`, "listItem");
function printWarning(message) {
console.warn(message);
}
const DefaultUnknownType = ({
value,
isInline
}) => {
const warning = unknownTypeWarning(value._type);
return isInline ? `<span style="display:none">${warning}</span>` : `<div style="display:none">${warning}</div>`;
}, DefaultUnknownMark = ({
markType,
children
}) => `<span class="unknown__pt__mark__${markType}">${children}</span>`, DefaultUnknownBlockStyle = ({
children
}) => `<p>${children}</p>`, DefaultUnknownList = ({ children }) => `<ul>${children}</ul>`, DefaultUnknownListItem = ({
children
}) => `<li>${children}</li>`, DefaultHardBreak = () => "<br/>", defaultPortableTextBlockStyles = {
normal: ({ children }) => `<p>${children}</p>`,
blockquote: ({ children }) => `<blockquote>${children}</blockquote>`,
h1: ({ children }) => `<h1>${children}</h1>`,
h2: ({ children }) => `<h2>${children}</h2>`,
h3: ({ children }) => `<h3>${children}</h3>`,
h4: ({ children }) => `<h4>${children}</h4>`,
h5: ({ children }) => `<h5>${children}</h5>`,
h6: ({ children }) => `<h6>${children}</h6>`
}, defaultComponents = {
types: {},
block: defaultPortableTextBlockStyles,
marks: defaultMarks,
list: defaultLists,
listItem: DefaultListItem,
hardBreak: DefaultHardBreak,
escapeHTML,
unknownType: DefaultUnknownType,
unknownMark: DefaultUnknownMark,
unknownList: DefaultUnknownList,
unknownListItem: DefaultUnknownListItem,
unknownBlockStyle: DefaultUnknownBlockStyle
};
function mergeComponents(parent, overrides) {
const { block, list, listItem, marks, types, ...rest } = overrides;
return {
...parent,
block: mergeDeeply(parent, overrides, "block"),
list: mergeDeeply(parent, overrides, "list"),
listItem: mergeDeeply(parent, overrides, "listItem"),
marks: mergeDeeply(parent, overrides, "marks"),
types: mergeDeeply(parent, overrides, "types"),
...rest
};
}
function mergeDeeply(parent, overrides, key) {
const override = overrides[key], parentVal = parent[key];
return typeof override == "function" || override && typeof parentVal == "function" ? override : override ? { ...parentVal, ...override } : parentVal;
}
function toHTML(value, options = {}) {
const {
components: componentOverrides,
onMissingComponent: missingComponentHandler = printWarning
} = options, handleMissingComponent = missingComponentHandler || noop, blocks = Array.isArray(value) ? value : [value], nested = nestLists(blocks, "html"), components = componentOverrides ? mergeComponents(defaultComponents, componentOverrides) : defaultComponents, renderNode = getNodeRenderer(components, handleMissingComponent);
return nested.map(
(node, index) => renderNode({ node, index, isInline: !1, renderNode })
).join("");
}
const getNodeRenderer = (components, handleMissingComponent) => {
function renderNode(options) {
const { node, index, isInline } = options;
return isPortableTextToolkitList(node) ? renderList(node, index) : isPortableTextListItemBlock(node) ? renderListItem(node, index) : isPortableTextToolkitSpan(node) ? renderSpan(node) : isPortableTextBlock(node) ? renderBlock(node, index, isInline) : isPortableTextToolkitTextNode(node) ? renderText(node) : renderCustomBlock(node, index, isInline);
}
function renderListItem(node, index) {
const tree = serializeBlock({ node, index, isInline: !1, renderNode }), renderer = components.listItem, itemHandler = (typeof renderer == "function" ? renderer : renderer[node.listItem]) || components.unknownListItem;
if (itemHandler === components.unknownListItem) {
const style = node.listItem || "bullet";
handleMissingComponent(unknownListItemStyleWarning(style), {
type: style,
nodeType: "listItemStyle"
});
}
let children = tree.children;
if (node.style && node.style !== "normal") {
const { listItem, ...blockNode } = node;
children = renderNode({ node: blockNode, index, isInline: !1 });
}
return itemHandler({ value: node, index, isInline: !1, renderNode, children });
}
function renderList(node, index) {
const children = node.children.map(
(child, childIndex) => renderNode({
node: child._key ? child : { ...child, _key: `li-${index}-${childIndex}` },
index: childIndex,
isInline: !1
})
), component = components.list, list = (typeof component == "function" ? component : component[node.listItem]) || components.unknownList;
if (list === components.unknownList) {
const style = node.listItem || "bullet";
handleMissingComponent(unknownListStyleWarning(style), { nodeType: "listStyle", type: style });
}
return list({ value: node, index, isInline: !1, renderNode, children: children.join("") });
}
function renderSpan(node) {
const { markDef, markType, markKey } = node, span = components.marks[markType] || components.unknownMark, children = node.children.map(
(child, childIndex) => renderNode({ node: child, index: childIndex, isInline: !0 })
);
return span === components.unknownMark && handleMissingComponent(unknownMarkWarning(markType), { nodeType: "mark", type: markType }), span({
text: spanToPlainText(node),
value: markDef,
markType,
markKey,
renderNode,
children: children.join("")
});
}
function renderBlock(node, index, isInline) {
const { _key, ...props } = serializeBlock({ node, index, isInline, renderNode }), style = props.node.style || "normal", block = (typeof components.block == "function" ? components.block : components.block[style]) || components.unknownBlockStyle;
return block === components.unknownBlockStyle && handleMissingComponent(unknownBlockStyleWarning(style), {
nodeType: "blockStyle",
type: style
}), block({ ...props, value: props.node, renderNode });
}
function renderText(node) {
if (node.text === `
`) {
const hardBreak = components.hardBreak;
return hardBreak ? hardBreak() : `
`;
}
return components.escapeHTML(node.text);
}
function renderCustomBlock(value, index, isInline) {
const node = components.types[value._type];
return node || handleMissingComponent(unknownTypeWarning(value._type), {
nodeType: "block",
type: value._type
}), (node || components.unknownType)({
value,
isInline,
index,
renderNode
});
}
return renderNode;
};
function serializeBlock(options) {
const { node, index, isInline, renderNode } = options, children = buildMarksTree(node).map(
(child, i) => renderNode({ node: child, isInline: !0, index: i, renderNode })
);
return {
_key: node._key || `block-${index}`,
children: children.join(""),
index,
isInline,
node
};
}
function noop() {
}
export {
defaultComponents,
escapeHTML,
mergeComponents,
toHTML,
uriLooksSafe
};
//# sourceMappingURL=pt-to-html.js.map
{"version":3,"file":"pt-to-html.js","sources":["../src/escape.ts","../src/components/list.ts","../src/components/marks.ts","../src/warnings.ts","../src/components/unknown.ts","../src/components/defaults.ts","../src/components/merge.ts","../src/to-html.ts"],"sourcesContent":["const allowedProtocols = ['http', 'https', 'mailto', 'tel']\nconst charMap: Record<string, string> = {\n '&': 'amp',\n '<': 'lt',\n '>': 'gt',\n '\"': 'quot',\n \"'\": '#x27',\n}\n\nexport function escapeHTML(str: string): string {\n return replaceMultipleSpaces(str.replace(/[&<>\"']/g, (s) => `&${charMap[s]};`))\n}\n\nexport function replaceMultipleSpaces(str: string): string {\n return str.replace(/ {2,}/g, (match: string) => `${'&nbsp;'.repeat(match.length - 1)} `)\n}\n\nexport function uriLooksSafe(uri: string): boolean {\n const url = (uri || '').trim()\n const first = url.charAt(0)\n\n // Allow hash-links, absolute paths and \"same-protocol\" (//foo.bar) URLs\n if (first === '#' || first === '/') {\n return true\n }\n\n // If the URL does not contain a `:`, allow it\n const colonIndex = url.indexOf(':')\n if (colonIndex === -1) {\n return true\n }\n\n // If the protocol is in the allowed list, treat it as OK\n const proto = url.slice(0, colonIndex).toLowerCase()\n if (allowedProtocols.indexOf(proto) !== -1) {\n return true\n }\n\n // If the URL is `site/search?query=author:espen`, allow it\n const queryIndex = url.indexOf('?')\n if (queryIndex !== -1 && colonIndex > queryIndex) {\n return true\n }\n\n // If the URL is `site/search#my:encoded:data`, allow it\n const hashIndex = url.indexOf('#')\n if (hashIndex !== -1 && colonIndex > hashIndex) {\n return true\n }\n\n return false\n}\n","import type {PortableTextListComponent, PortableTextListItemComponent} from '../types'\n\nexport const defaultLists: Record<'number' | 'bullet', PortableTextListComponent> = {\n number: ({children}) => `<ol>${children}</ol>`,\n bullet: ({children}) => `<ul>${children}</ul>`,\n}\n\nexport const DefaultListItem: PortableTextListItemComponent = ({children}) => `<li>${children}</li>`\n","import type {TypedObject} from '@portabletext/types'\n\nimport {escapeHTML, uriLooksSafe} from '../escape'\nimport type {PortableTextMarkComponent} from '../types'\n\ninterface DefaultLink extends TypedObject {\n _type: 'link'\n href: string\n}\n\nconst link: PortableTextMarkComponent<DefaultLink> = ({children, value}) => {\n const href = value?.href || ''\n const looksSafe = uriLooksSafe(href)\n return looksSafe ? `<a href=\"${escapeHTML(href)}\">${children}</a>` : children\n}\n\nexport const defaultMarks: Record<string, PortableTextMarkComponent | undefined> = {\n 'em': ({children}) => `<em>${children}</em>`,\n 'strong': ({children}) => `<strong>${children}</strong>`,\n 'code': ({children}) => `<code>${children}</code>`,\n 'underline': ({children}) => `<span style=\"text-decoration:underline\">${children}</span>`,\n 'strike-through': ({children}) => `<del>${children}</del>`,\n link,\n}\n","const getTemplate = (type: string, prop: string): string =>\n `Unknown ${type}, specify a component for it in the \\`components.${prop}\\` option`\n\nexport const unknownTypeWarning = (typeName: string): string =>\n getTemplate(`block type \"${typeName}\"`, 'types')\n\nexport const unknownMarkWarning = (markType: string): string =>\n getTemplate(`mark type \"${markType}\"`, 'marks')\n\nexport const unknownBlockStyleWarning = (blockStyle: string): string =>\n getTemplate(`block style \"${blockStyle}\"`, 'block')\n\nexport const unknownListStyleWarning = (listStyle: string): string =>\n getTemplate(`list style \"${listStyle}\"`, 'list')\n\nexport const unknownListItemStyleWarning = (listStyle: string): string =>\n getTemplate(`list item style \"${listStyle}\"`, 'listItem')\n\nexport function printWarning(message: string): void {\n console.warn(message)\n}\n","import type {PortableTextHtmlComponents} from '../types'\nimport {unknownTypeWarning} from '../warnings'\n\nexport const DefaultUnknownType: PortableTextHtmlComponents['unknownType'] = ({\n value,\n isInline,\n}) => {\n const warning = unknownTypeWarning(value._type)\n return isInline\n ? `<span style=\"display:none\">${warning}</span>`\n : `<div style=\"display:none\">${warning}</div>`\n}\n\nexport const DefaultUnknownMark: PortableTextHtmlComponents['unknownMark'] = ({\n markType,\n children,\n}) => {\n return `<span class=\"unknown__pt__mark__${markType}\">${children}</span>`\n}\n\nexport const DefaultUnknownBlockStyle: PortableTextHtmlComponents['unknownBlockStyle'] = ({\n children,\n}) => {\n return `<p>${children}</p>`\n}\n\nexport const DefaultUnknownList: PortableTextHtmlComponents['unknownList'] = ({children}) => {\n return `<ul>${children}</ul>`\n}\n\nexport const DefaultUnknownListItem: PortableTextHtmlComponents['unknownListItem'] = ({\n children,\n}) => {\n return `<li>${children}</li>`\n}\n","import type {PortableTextBlockStyle} from '@portabletext/types'\n\nimport {escapeHTML} from '../escape'\nimport type {PortableTextBlockComponent, PortableTextHtmlComponents} from '../types'\nimport {DefaultListItem, defaultLists} from './list'\nimport {defaultMarks} from './marks'\nimport {\n DefaultUnknownBlockStyle,\n DefaultUnknownList,\n DefaultUnknownListItem,\n DefaultUnknownMark,\n DefaultUnknownType,\n} from './unknown'\n\nexport const DefaultHardBreak = (): string => '<br/>'\n\nexport const defaultPortableTextBlockStyles: Record<\n PortableTextBlockStyle,\n PortableTextBlockComponent | undefined\n> = {\n normal: ({children}) => `<p>${children}</p>`,\n blockquote: ({children}) => `<blockquote>${children}</blockquote>`,\n h1: ({children}) => `<h1>${children}</h1>`,\n h2: ({children}) => `<h2>${children}</h2>`,\n h3: ({children}) => `<h3>${children}</h3>`,\n h4: ({children}) => `<h4>${children}</h4>`,\n h5: ({children}) => `<h5>${children}</h5>`,\n h6: ({children}) => `<h6>${children}</h6>`,\n}\n\nexport const defaultComponents: PortableTextHtmlComponents = {\n types: {},\n\n block: defaultPortableTextBlockStyles,\n marks: defaultMarks,\n list: defaultLists,\n listItem: DefaultListItem,\n hardBreak: DefaultHardBreak,\n escapeHTML: escapeHTML,\n\n unknownType: DefaultUnknownType,\n unknownMark: DefaultUnknownMark,\n unknownList: DefaultUnknownList,\n unknownListItem: DefaultUnknownListItem,\n unknownBlockStyle: DefaultUnknownBlockStyle,\n}\n","import type {PortableTextComponents, PortableTextHtmlComponents} from '../types'\n\nexport function mergeComponents(\n parent: PortableTextHtmlComponents,\n overrides: PortableTextComponents,\n): PortableTextHtmlComponents {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {block, list, listItem, marks, types, ...rest} = overrides\n // @todo figure out how to not `as ...` these\n return {\n ...parent,\n block: mergeDeeply(parent, overrides, 'block') as PortableTextHtmlComponents['block'],\n list: mergeDeeply(parent, overrides, 'list') as PortableTextHtmlComponents['list'],\n listItem: mergeDeeply(parent, overrides, 'listItem') as PortableTextHtmlComponents['listItem'],\n marks: mergeDeeply(parent, overrides, 'marks') as PortableTextHtmlComponents['marks'],\n types: mergeDeeply(parent, overrides, 'types') as PortableTextHtmlComponents['types'],\n ...rest,\n }\n}\n\nfunction mergeDeeply(\n parent: PortableTextHtmlComponents,\n overrides: PortableTextComponents,\n key: 'block' | 'list' | 'listItem' | 'marks' | 'types',\n): PortableTextHtmlComponents[typeof key] {\n const override = overrides[key]\n const parentVal = parent[key]\n\n if (typeof override === 'function') {\n return override\n }\n\n if (override && typeof parentVal === 'function') {\n return override\n }\n\n if (override) {\n return {...parentVal, ...override} as PortableTextHtmlComponents[typeof key]\n }\n\n return parentVal\n}\n","import {\n buildMarksTree,\n isPortableTextBlock,\n isPortableTextListItemBlock,\n isPortableTextToolkitList,\n isPortableTextToolkitSpan,\n isPortableTextToolkitTextNode,\n nestLists,\n spanToPlainText,\n type ToolkitNestedPortableTextSpan,\n type ToolkitTextNode,\n} from '@portabletext/toolkit'\nimport type {\n ArbitraryTypedObject,\n PortableTextBlock,\n PortableTextListItemBlock,\n PortableTextMarkDefinition,\n PortableTextSpan,\n TypedObject,\n} from '@portabletext/types'\n\nimport {defaultComponents} from './components/defaults'\nimport {mergeComponents} from './components/merge'\nimport type {\n HtmlPortableTextList,\n MissingComponentHandler,\n NodeRenderer,\n PortableTextHtmlComponents,\n PortableTextOptions,\n Serializable,\n SerializedBlock,\n} from './types'\nimport {\n printWarning,\n unknownBlockStyleWarning,\n unknownListItemStyleWarning,\n unknownListStyleWarning,\n unknownMarkWarning,\n unknownTypeWarning,\n} from './warnings'\n\nexport function toHTML<B extends TypedObject = PortableTextBlock | ArbitraryTypedObject>(\n value: B | B[],\n options: PortableTextOptions = {},\n): string {\n const {\n components: componentOverrides,\n onMissingComponent: missingComponentHandler = printWarning,\n } = options\n\n const handleMissingComponent = missingComponentHandler || noop\n const blocks = Array.isArray(value) ? value : [value]\n const nested = nestLists(blocks, 'html')\n const components = componentOverrides\n ? mergeComponents(defaultComponents, componentOverrides)\n : defaultComponents\n\n const renderNode = getNodeRenderer(components, handleMissingComponent)\n const rendered = nested.map((node, index) =>\n renderNode({node: node, index, isInline: false, renderNode}),\n )\n\n return rendered.join('')\n}\n\nconst getNodeRenderer = (\n components: PortableTextHtmlComponents,\n handleMissingComponent: MissingComponentHandler,\n): NodeRenderer => {\n function renderNode<N extends TypedObject>(options: Serializable<N>): string {\n const {node, index, isInline} = options\n\n if (isPortableTextToolkitList(node)) {\n return renderList(node, index)\n }\n\n if (isPortableTextListItemBlock(node)) {\n return renderListItem(node, index)\n }\n\n if (isPortableTextToolkitSpan(node)) {\n return renderSpan(node)\n }\n\n if (isPortableTextBlock(node)) {\n return renderBlock(node, index, isInline)\n }\n\n if (isPortableTextToolkitTextNode(node)) {\n return renderText(node)\n }\n\n return renderCustomBlock(node, index, isInline)\n }\n\n function renderListItem(\n node: PortableTextListItemBlock<PortableTextMarkDefinition, PortableTextSpan>,\n index: number,\n ): string {\n const tree = serializeBlock({node, index, isInline: false, renderNode})\n const renderer = components.listItem\n const handler = typeof renderer === 'function' ? renderer : renderer[node.listItem]\n const itemHandler = handler || components.unknownListItem\n\n if (itemHandler === components.unknownListItem) {\n const style = node.listItem || 'bullet'\n handleMissingComponent(unknownListItemStyleWarning(style), {\n type: style,\n nodeType: 'listItemStyle',\n })\n }\n\n let children = tree.children\n if (node.style && node.style !== 'normal') {\n // Wrap any other style in whatever the block component says to use\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {listItem, ...blockNode} = node\n children = renderNode({node: blockNode, index, isInline: false, renderNode})\n }\n\n return itemHandler({value: node, index, isInline: false, renderNode, children})\n }\n\n function renderList(node: HtmlPortableTextList, index: number): string {\n const children = node.children.map((child, childIndex) =>\n renderNode({\n node: child._key ? child : {...child, _key: `li-${index}-${childIndex}`},\n index: childIndex,\n isInline: false,\n renderNode,\n }),\n )\n\n const component = components.list\n const handler = typeof component === 'function' ? component : component[node.listItem]\n const list = handler || components.unknownList\n\n if (list === components.unknownList) {\n const style = node.listItem || 'bullet'\n handleMissingComponent(unknownListStyleWarning(style), {nodeType: 'listStyle', type: style})\n }\n\n return list({value: node, index, isInline: false, renderNode, children: children.join('')})\n }\n\n function renderSpan(node: ToolkitNestedPortableTextSpan): string {\n const {markDef, markType, markKey} = node\n const span = components.marks[markType] || components.unknownMark\n const children = node.children.map((child, childIndex) =>\n renderNode({node: child, index: childIndex, isInline: true, renderNode}),\n )\n\n if (span === components.unknownMark) {\n handleMissingComponent(unknownMarkWarning(markType), {nodeType: 'mark', type: markType})\n }\n\n return span({\n text: spanToPlainText(node),\n value: markDef,\n markType,\n markKey,\n renderNode,\n children: children.join(''),\n })\n }\n\n function renderBlock(node: PortableTextBlock, index: number, isInline: boolean): string {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {_key, ...props} = serializeBlock({node, index, isInline, renderNode})\n const style = props.node.style || 'normal'\n const handler =\n typeof components.block === 'function' ? components.block : components.block[style]\n const block = handler || components.unknownBlockStyle\n\n if (block === components.unknownBlockStyle) {\n handleMissingComponent(unknownBlockStyleWarning(style), {\n nodeType: 'blockStyle',\n type: style,\n })\n }\n\n return block({...props, value: props.node, renderNode})\n }\n\n function renderText(node: ToolkitTextNode): string {\n if (node.text === '\\n') {\n const hardBreak = components.hardBreak\n return hardBreak ? hardBreak() : '\\n'\n }\n\n return components.escapeHTML(node.text)\n }\n\n function renderCustomBlock(value: TypedObject, index: number, isInline: boolean): string {\n const node = components.types[value._type]\n\n if (!node) {\n handleMissingComponent(unknownTypeWarning(value._type), {\n nodeType: 'block',\n type: value._type,\n })\n }\n\n const component = node || components.unknownType\n return component({\n value,\n isInline,\n index,\n renderNode,\n })\n }\n\n return renderNode\n}\n\nfunction serializeBlock(options: Serializable<PortableTextBlock>): SerializedBlock {\n const {node, index, isInline, renderNode} = options\n const tree = buildMarksTree(node)\n const children = tree.map((child, i) =>\n renderNode({node: child, isInline: true, index: i, renderNode}),\n )\n\n return {\n _key: node._key || `block-${index}`,\n children: children.join(''),\n index,\n isInline,\n node,\n }\n}\n\nfunction noop() {\n // Intentional noop\n}\n"],"names":[],"mappings":";AAAA,MAAM,mBAAmB,CAAC,QAAQ,SAAS,UAAU,KAAK,GACpD,UAAkC;AAAA,EACtC,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAEO,SAAS,WAAW,KAAqB;AAC9C,SAAO,sBAAsB,IAAI,QAAQ,YAAY,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,GAAG,CAAC;AAChF;AAEO,SAAS,sBAAsB,KAAqB;AACzD,SAAO,IAAI,QAAQ,UAAU,CAAC,UAAkB,GAAG,SAAS,OAAO,MAAM,SAAS,CAAC,CAAC,GAAG;AACzF;AAEO,SAAS,aAAa,KAAsB;AACjD,QAAM,OAAO,OAAO,IAAI,KAAA,GAClB,QAAQ,IAAI,OAAO,CAAC;AAG1B,MAAI,UAAU,OAAO,UAAU;AAC7B,WAAO;AAIT,QAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,MAAI,eAAe;AACjB,WAAO;AAIT,QAAM,QAAQ,IAAI,MAAM,GAAG,UAAU,EAAE,YAAA;AACvC,MAAI,iBAAiB,QAAQ,KAAK,MAAM;AACtC,WAAO;AAIT,QAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,MAAI,eAAe,MAAM,aAAa;AACpC,WAAO;AAIT,QAAM,YAAY,IAAI,QAAQ,GAAG;AACjC,SAAI,cAAc,MAAM,aAAa;AAKvC;ACjDO,MAAM,eAAuE;AAAA,EAClF,QAAQ,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACvC,QAAQ,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AACzC,GAEa,kBAAiD,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ,SCGvF,OAA+C,CAAC,EAAC,UAAU,YAAW;AAC1E,QAAM,OAAO,OAAO,QAAQ;AAE5B,SADkB,aAAa,IAAI,IAChB,YAAY,WAAW,IAAI,CAAC,KAAK,QAAQ,SAAS;AACvE,GAEa,eAAsE;AAAA,EACjF,IAAM,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACrC,QAAU,CAAC,EAAC,SAAA,MAAc,WAAW,QAAQ;AAAA,EAC7C,MAAQ,CAAC,EAAC,SAAA,MAAc,SAAS,QAAQ;AAAA,EACzC,WAAa,CAAC,EAAC,SAAA,MAAc,2CAA2C,QAAQ;AAAA,EAChF,kBAAkB,CAAC,EAAC,SAAA,MAAc,QAAQ,QAAQ;AAAA,EAClD;AACF,GCvBM,cAAc,CAAC,MAAc,SACjC,WAAW,IAAI,oDAAoD,IAAI,aAE5D,qBAAqB,CAAC,aACjC,YAAY,eAAe,QAAQ,KAAK,OAAO,GAEpC,qBAAqB,CAAC,aACjC,YAAY,cAAc,QAAQ,KAAK,OAAO,GAEnC,2BAA2B,CAAC,eACvC,YAAY,gBAAgB,UAAU,KAAK,OAAO,GAEvC,0BAA0B,CAAC,cACtC,YAAY,eAAe,SAAS,KAAK,MAAM,GAEpC,8BAA8B,CAAC,cAC1C,YAAY,oBAAoB,SAAS,KAAK,UAAU;AAEnD,SAAS,aAAa,SAAuB;AAClD,UAAQ,KAAK,OAAO;AACtB;ACjBO,MAAM,qBAAgE,CAAC;AAAA,EAC5E;AAAA,EACA;AACF,MAAM;AACJ,QAAM,UAAU,mBAAmB,MAAM,KAAK;AAC9C,SAAO,WACH,8BAA8B,OAAO,YACrC,6BAA6B,OAAO;AAC1C,GAEa,qBAAgE,CAAC;AAAA,EAC5E;AAAA,EACA;AACF,MACS,mCAAmC,QAAQ,KAAK,QAAQ,WAGpD,2BAA4E,CAAC;AAAA,EACxF;AACF,MACS,MAAM,QAAQ,QAGV,qBAAgE,CAAC,EAAC,SAAA,MACtE,OAAO,QAAQ,SAGX,yBAAwE,CAAC;AAAA,EACpF;AACF,MACS,OAAO,QAAQ,SCnBX,mBAAmB,MAAc,SAEjC,iCAGT;AAAA,EACF,QAAQ,CAAC,EAAC,SAAA,MAAc,MAAM,QAAQ;AAAA,EACtC,YAAY,CAAC,EAAC,SAAA,MAAc,eAAe,QAAQ;AAAA,EACnD,IAAI,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACnC,IAAI,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACnC,IAAI,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACnC,IAAI,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACnC,IAAI,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AAAA,EACnC,IAAI,CAAC,EAAC,SAAA,MAAc,OAAO,QAAQ;AACrC,GAEa,oBAAgD;AAAA,EAC3D,OAAO,CAAA;AAAA,EAEP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX;AAAA,EAEA,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,mBAAmB;AACrB;AC3CO,SAAS,gBACd,QACA,WAC4B;AAE5B,QAAM,EAAC,OAAO,MAAM,UAAU,OAAO,OAAO,GAAG,SAAQ;AAEvD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO,YAAY,QAAQ,WAAW,OAAO;AAAA,IAC7C,MAAM,YAAY,QAAQ,WAAW,MAAM;AAAA,IAC3C,UAAU,YAAY,QAAQ,WAAW,UAAU;AAAA,IACnD,OAAO,YAAY,QAAQ,WAAW,OAAO;AAAA,IAC7C,OAAO,YAAY,QAAQ,WAAW,OAAO;AAAA,IAC7C,GAAG;AAAA,EAAA;AAEP;AAEA,SAAS,YACP,QACA,WACA,KACwC;AACxC,QAAM,WAAW,UAAU,GAAG,GACxB,YAAY,OAAO,GAAG;AAM5B,SAJI,OAAO,YAAa,cAIpB,YAAY,OAAO,aAAc,aAC5B,WAGL,WACK,EAAC,GAAG,WAAW,GAAG,aAGpB;AACT;ACAO,SAAS,OACd,OACA,UAA+B,IACvB;AACR,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ,oBAAoB,0BAA0B;AAAA,EAAA,IAC5C,SAEE,yBAAyB,2BAA2B,MACpD,SAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GAC9C,SAAS,UAAU,QAAQ,MAAM,GACjC,aAAa,qBACf,gBAAgB,mBAAmB,kBAAkB,IACrD,mBAEE,aAAa,gBAAgB,YAAY,sBAAsB;AAKrE,SAJiB,OAAO;AAAA,IAAI,CAAC,MAAM,UACjC,WAAW,EAAC,MAAY,OAAO,UAAU,IAAO,WAAA,CAAW;AAAA,EAAA,EAG7C,KAAK,EAAE;AACzB;AAEA,MAAM,kBAAkB,CACtB,YACA,2BACiB;AACjB,WAAS,WAAkC,SAAkC;AAC3E,UAAM,EAAC,MAAM,OAAO,SAAA,IAAY;AAEhC,WAAI,0BAA0B,IAAI,IACzB,WAAW,MAAM,KAAK,IAG3B,4BAA4B,IAAI,IAC3B,eAAe,MAAM,KAAK,IAG/B,0BAA0B,IAAI,IACzB,WAAW,IAAI,IAGpB,oBAAoB,IAAI,IACnB,YAAY,MAAM,OAAO,QAAQ,IAGtC,8BAA8B,IAAI,IAC7B,WAAW,IAAI,IAGjB,kBAAkB,MAAM,OAAO,QAAQ;AAAA,EAChD;AAEA,WAAS,eACP,MACA,OACQ;AACR,UAAM,OAAO,eAAe,EAAC,MAAM,OAAO,UAAU,IAAO,YAAW,GAChE,WAAW,WAAW,UAEtB,eADU,OAAO,YAAa,aAAa,WAAW,SAAS,KAAK,QAAQ,MACnD,WAAW;AAE1C,QAAI,gBAAgB,WAAW,iBAAiB;AAC9C,YAAM,QAAQ,KAAK,YAAY;AAC/B,6BAAuB,4BAA4B,KAAK,GAAG;AAAA,QACzD,MAAM;AAAA,QACN,UAAU;AAAA,MAAA,CACX;AAAA,IACH;AAEA,QAAI,WAAW,KAAK;AACpB,QAAI,KAAK,SAAS,KAAK,UAAU,UAAU;AAGzC,YAAM,EAAC,UAAU,GAAG,UAAA,IAAa;AACjC,iBAAW,WAAW,EAAC,MAAM,WAAW,OAAO,UAAU,GAAiB,CAAC;AAAA,IAC7E;AAEA,WAAO,YAAY,EAAC,OAAO,MAAM,OAAO,UAAU,IAAO,YAAY,UAAS;AAAA,EAChF;AAEA,WAAS,WAAW,MAA4B,OAAuB;AACrE,UAAM,WAAW,KAAK,SAAS;AAAA,MAAI,CAAC,OAAO,eACzC,WAAW;AAAA,QACT,MAAM,MAAM,OAAO,QAAQ,EAAC,GAAG,OAAO,MAAM,MAAM,KAAK,IAAI,UAAU,GAAA;AAAA,QACrE,OAAO;AAAA,QACP,UAAU;AAAA,MAEZ,CAAC;AAAA,IAAA,GAGG,YAAY,WAAW,MAEvB,QADU,OAAO,aAAc,aAAa,YAAY,UAAU,KAAK,QAAQ,MAC7D,WAAW;AAEnC,QAAI,SAAS,WAAW,aAAa;AACnC,YAAM,QAAQ,KAAK,YAAY;AAC/B,6BAAuB,wBAAwB,KAAK,GAAG,EAAC,UAAU,aAAa,MAAM,OAAM;AAAA,IAC7F;AAEA,WAAO,KAAK,EAAC,OAAO,MAAM,OAAO,UAAU,IAAO,YAAY,UAAU,SAAS,KAAK,EAAE,GAAE;AAAA,EAC5F;AAEA,WAAS,WAAW,MAA6C;AAC/D,UAAM,EAAC,SAAS,UAAU,QAAA,IAAW,MAC/B,OAAO,WAAW,MAAM,QAAQ,KAAK,WAAW,aAChD,WAAW,KAAK,SAAS;AAAA,MAAI,CAAC,OAAO,eACzC,WAAW,EAAC,MAAM,OAAO,OAAO,YAAY,UAAU,GAAgB,CAAC;AAAA,IAAA;AAGzE,WAAI,SAAS,WAAW,eACtB,uBAAuB,mBAAmB,QAAQ,GAAG,EAAC,UAAU,QAAQ,MAAM,SAAA,CAAS,GAGlF,KAAK;AAAA,MACV,MAAM,gBAAgB,IAAI;AAAA,MAC1B,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,SAAS,KAAK,EAAE;AAAA,IAAA,CAC3B;AAAA,EACH;AAEA,WAAS,YAAY,MAAyB,OAAe,UAA2B;AAEtF,UAAM,EAAC,MAAM,GAAG,MAAA,IAAS,eAAe,EAAC,MAAM,OAAO,UAAU,WAAA,CAAW,GACrE,QAAQ,MAAM,KAAK,SAAS,UAG5B,SADJ,OAAO,WAAW,SAAU,aAAa,WAAW,QAAQ,WAAW,MAAM,KAAK,MAC3D,WAAW;AAEpC,WAAI,UAAU,WAAW,qBACvB,uBAAuB,yBAAyB,KAAK,GAAG;AAAA,MACtD,UAAU;AAAA,MACV,MAAM;AAAA,IAAA,CACP,GAGI,MAAM,EAAC,GAAG,OAAO,OAAO,MAAM,MAAM,YAAW;AAAA,EACxD;AAEA,WAAS,WAAW,MAA+B;AACjD,QAAI,KAAK,SAAS;AAAA,GAAM;AACtB,YAAM,YAAY,WAAW;AAC7B,aAAO,YAAY,cAAc;AAAA;AAAA,IACnC;AAEA,WAAO,WAAW,WAAW,KAAK,IAAI;AAAA,EACxC;AAEA,WAAS,kBAAkB,OAAoB,OAAe,UAA2B;AACvF,UAAM,OAAO,WAAW,MAAM,MAAM,KAAK;AAEzC,WAAK,QACH,uBAAuB,mBAAmB,MAAM,KAAK,GAAG;AAAA,MACtD,UAAU;AAAA,MACV,MAAM,MAAM;AAAA,IAAA,CACb,IAGe,QAAQ,WAAW,aACpB;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,SAA2D;AACjF,QAAM,EAAC,MAAM,OAAO,UAAU,WAAA,IAAc,SAEtC,WADO,eAAe,IAAI,EACV;AAAA,IAAI,CAAC,OAAO,MAChC,WAAW,EAAC,MAAM,OAAO,UAAU,IAAM,OAAO,GAAG,WAAA,CAAW;AAAA,EAAA;AAGhE,SAAO;AAAA,IACL,MAAM,KAAK,QAAQ,SAAS,KAAK;AAAA,IACjC,UAAU,SAAS,KAAK,EAAE;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,SAAS,OAAO;AAEhB;"}
import type {PortableTextBlockStyle} from '@portabletext/types'
import {escapeHTML} from '../escape'
import type {PortableTextBlockComponent, PortableTextHtmlComponents} from '../types'
import {DefaultListItem, defaultLists} from './list'
import {defaultMarks} from './marks'
import {
DefaultUnknownBlockStyle,
DefaultUnknownList,
DefaultUnknownListItem,
DefaultUnknownMark,
DefaultUnknownType,
} from './unknown'
export const DefaultHardBreak = (): string => '<br/>'
export const defaultPortableTextBlockStyles: Record<
PortableTextBlockStyle,
PortableTextBlockComponent | undefined
> = {
normal: ({children}) => `<p>${children}</p>`,
blockquote: ({children}) => `<blockquote>${children}</blockquote>`,
h1: ({children}) => `<h1>${children}</h1>`,
h2: ({children}) => `<h2>${children}</h2>`,
h3: ({children}) => `<h3>${children}</h3>`,
h4: ({children}) => `<h4>${children}</h4>`,
h5: ({children}) => `<h5>${children}</h5>`,
h6: ({children}) => `<h6>${children}</h6>`,
}
export const defaultComponents: PortableTextHtmlComponents = {
types: {},
block: defaultPortableTextBlockStyles,
marks: defaultMarks,
list: defaultLists,
listItem: DefaultListItem,
hardBreak: DefaultHardBreak,
escapeHTML: escapeHTML,
unknownType: DefaultUnknownType,
unknownMark: DefaultUnknownMark,
unknownList: DefaultUnknownList,
unknownListItem: DefaultUnknownListItem,
unknownBlockStyle: DefaultUnknownBlockStyle,
}
import type {PortableTextListComponent, PortableTextListItemComponent} from '../types'
export const defaultLists: Record<'number' | 'bullet', PortableTextListComponent> = {
number: ({children}) => `<ol>${children}</ol>`,
bullet: ({children}) => `<ul>${children}</ul>`,
}
export const DefaultListItem: PortableTextListItemComponent = ({children}) => `<li>${children}</li>`
import type {TypedObject} from '@portabletext/types'
import {escapeHTML, uriLooksSafe} from '../escape'
import type {PortableTextMarkComponent} from '../types'
interface DefaultLink extends TypedObject {
_type: 'link'
href: string
}
const link: PortableTextMarkComponent<DefaultLink> = ({children, value}) => {
const href = value?.href || ''
const looksSafe = uriLooksSafe(href)
return looksSafe ? `<a href="${escapeHTML(href)}">${children}</a>` : children
}
export const defaultMarks: Record<string, PortableTextMarkComponent | undefined> = {
'em': ({children}) => `<em>${children}</em>`,
'strong': ({children}) => `<strong>${children}</strong>`,
'code': ({children}) => `<code>${children}</code>`,
'underline': ({children}) => `<span style="text-decoration:underline">${children}</span>`,
'strike-through': ({children}) => `<del>${children}</del>`,
link,
}
import type {PortableTextComponents, PortableTextHtmlComponents} from '../types'
export function mergeComponents(
parent: PortableTextHtmlComponents,
overrides: PortableTextComponents,
): PortableTextHtmlComponents {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {block, list, listItem, marks, types, ...rest} = overrides
// @todo figure out how to not `as ...` these
return {
...parent,
block: mergeDeeply(parent, overrides, 'block') as PortableTextHtmlComponents['block'],
list: mergeDeeply(parent, overrides, 'list') as PortableTextHtmlComponents['list'],
listItem: mergeDeeply(parent, overrides, 'listItem') as PortableTextHtmlComponents['listItem'],
marks: mergeDeeply(parent, overrides, 'marks') as PortableTextHtmlComponents['marks'],
types: mergeDeeply(parent, overrides, 'types') as PortableTextHtmlComponents['types'],
...rest,
}
}
function mergeDeeply(
parent: PortableTextHtmlComponents,
overrides: PortableTextComponents,
key: 'block' | 'list' | 'listItem' | 'marks' | 'types',
): PortableTextHtmlComponents[typeof key] {
const override = overrides[key]
const parentVal = parent[key]
if (typeof override === 'function') {
return override
}
if (override && typeof parentVal === 'function') {
return override
}
if (override) {
return {...parentVal, ...override} as PortableTextHtmlComponents[typeof key]
}
return parentVal
}
import type {PortableTextHtmlComponents} from '../types'
import {unknownTypeWarning} from '../warnings'
export const DefaultUnknownType: PortableTextHtmlComponents['unknownType'] = ({
value,
isInline,
}) => {
const warning = unknownTypeWarning(value._type)
return isInline
? `<span style="display:none">${warning}</span>`
: `<div style="display:none">${warning}</div>`
}
export const DefaultUnknownMark: PortableTextHtmlComponents['unknownMark'] = ({
markType,
children,
}) => {
return `<span class="unknown__pt__mark__${markType}">${children}</span>`
}
export const DefaultUnknownBlockStyle: PortableTextHtmlComponents['unknownBlockStyle'] = ({
children,
}) => {
return `<p>${children}</p>`
}
export const DefaultUnknownList: PortableTextHtmlComponents['unknownList'] = ({children}) => {
return `<ul>${children}</ul>`
}
export const DefaultUnknownListItem: PortableTextHtmlComponents['unknownListItem'] = ({
children,
}) => {
return `<li>${children}</li>`
}
const allowedProtocols = ['http', 'https', 'mailto', 'tel']
const charMap: Record<string, string> = {
'&': 'amp',
'<': 'lt',
'>': 'gt',
'"': 'quot',
"'": '#x27',
}
export function escapeHTML(str: string): string {
return replaceMultipleSpaces(str.replace(/[&<>"']/g, (s) => `&${charMap[s]};`))
}
export function replaceMultipleSpaces(str: string): string {
return str.replace(/ {2,}/g, (match: string) => `${'&nbsp;'.repeat(match.length - 1)} `)
}
export function uriLooksSafe(uri: string): boolean {
const url = (uri || '').trim()
const first = url.charAt(0)
// Allow hash-links, absolute paths and "same-protocol" (//foo.bar) URLs
if (first === '#' || first === '/') {
return true
}
// If the URL does not contain a `:`, allow it
const colonIndex = url.indexOf(':')
if (colonIndex === -1) {
return true
}
// If the protocol is in the allowed list, treat it as OK
const proto = url.slice(0, colonIndex).toLowerCase()
if (allowedProtocols.indexOf(proto) !== -1) {
return true
}
// If the URL is `site/search?query=author:espen`, allow it
const queryIndex = url.indexOf('?')
if (queryIndex !== -1 && colonIndex > queryIndex) {
return true
}
// If the URL is `site/search#my:encoded:data`, allow it
const hashIndex = url.indexOf('#')
if (hashIndex !== -1 && colonIndex > hashIndex) {
return true
}
return false
}
export {defaultComponents} from './components/defaults'
export {mergeComponents} from './components/merge'
export {escapeHTML, uriLooksSafe} from './escape'
export {toHTML} from './to-html'
export * from './types'
import {
buildMarksTree,
isPortableTextBlock,
isPortableTextListItemBlock,
isPortableTextToolkitList,
isPortableTextToolkitSpan,
isPortableTextToolkitTextNode,
nestLists,
spanToPlainText,
type ToolkitNestedPortableTextSpan,
type ToolkitTextNode,
} from '@portabletext/toolkit'
import type {
ArbitraryTypedObject,
PortableTextBlock,
PortableTextListItemBlock,
PortableTextMarkDefinition,
PortableTextSpan,
TypedObject,
} from '@portabletext/types'
import {defaultComponents} from './components/defaults'
import {mergeComponents} from './components/merge'
import type {
HtmlPortableTextList,
MissingComponentHandler,
NodeRenderer,
PortableTextHtmlComponents,
PortableTextOptions,
Serializable,
SerializedBlock,
} from './types'
import {
printWarning,
unknownBlockStyleWarning,
unknownListItemStyleWarning,
unknownListStyleWarning,
unknownMarkWarning,
unknownTypeWarning,
} from './warnings'
export function toHTML<B extends TypedObject = PortableTextBlock | ArbitraryTypedObject>(
value: B | B[],
options: PortableTextOptions = {},
): string {
const {
components: componentOverrides,
onMissingComponent: missingComponentHandler = printWarning,
} = options
const handleMissingComponent = missingComponentHandler || noop
const blocks = Array.isArray(value) ? value : [value]
const nested = nestLists(blocks, 'html')
const components = componentOverrides
? mergeComponents(defaultComponents, componentOverrides)
: defaultComponents
const renderNode = getNodeRenderer(components, handleMissingComponent)
const rendered = nested.map((node, index) =>
renderNode({node: node, index, isInline: false, renderNode}),
)
return rendered.join('')
}
const getNodeRenderer = (
components: PortableTextHtmlComponents,
handleMissingComponent: MissingComponentHandler,
): NodeRenderer => {
function renderNode<N extends TypedObject>(options: Serializable<N>): string {
const {node, index, isInline} = options
if (isPortableTextToolkitList(node)) {
return renderList(node, index)
}
if (isPortableTextListItemBlock(node)) {
return renderListItem(node, index)
}
if (isPortableTextToolkitSpan(node)) {
return renderSpan(node)
}
if (isPortableTextBlock(node)) {
return renderBlock(node, index, isInline)
}
if (isPortableTextToolkitTextNode(node)) {
return renderText(node)
}
return renderCustomBlock(node, index, isInline)
}
function renderListItem(
node: PortableTextListItemBlock<PortableTextMarkDefinition, PortableTextSpan>,
index: number,
): string {
const tree = serializeBlock({node, index, isInline: false, renderNode})
const renderer = components.listItem
const handler = typeof renderer === 'function' ? renderer : renderer[node.listItem]
const itemHandler = handler || components.unknownListItem
if (itemHandler === components.unknownListItem) {
const style = node.listItem || 'bullet'
handleMissingComponent(unknownListItemStyleWarning(style), {
type: style,
nodeType: 'listItemStyle',
})
}
let children = tree.children
if (node.style && node.style !== 'normal') {
// Wrap any other style in whatever the block component says to use
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {listItem, ...blockNode} = node
children = renderNode({node: blockNode, index, isInline: false, renderNode})
}
return itemHandler({value: node, index, isInline: false, renderNode, children})
}
function renderList(node: HtmlPortableTextList, index: number): string {
const children = node.children.map((child, childIndex) =>
renderNode({
node: child._key ? child : {...child, _key: `li-${index}-${childIndex}`},
index: childIndex,
isInline: false,
renderNode,
}),
)
const component = components.list
const handler = typeof component === 'function' ? component : component[node.listItem]
const list = handler || components.unknownList
if (list === components.unknownList) {
const style = node.listItem || 'bullet'
handleMissingComponent(unknownListStyleWarning(style), {nodeType: 'listStyle', type: style})
}
return list({value: node, index, isInline: false, renderNode, children: children.join('')})
}
function renderSpan(node: ToolkitNestedPortableTextSpan): string {
const {markDef, markType, markKey} = node
const span = components.marks[markType] || components.unknownMark
const children = node.children.map((child, childIndex) =>
renderNode({node: child, index: childIndex, isInline: true, renderNode}),
)
if (span === components.unknownMark) {
handleMissingComponent(unknownMarkWarning(markType), {nodeType: 'mark', type: markType})
}
return span({
text: spanToPlainText(node),
value: markDef,
markType,
markKey,
renderNode,
children: children.join(''),
})
}
function renderBlock(node: PortableTextBlock, index: number, isInline: boolean): string {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {_key, ...props} = serializeBlock({node, index, isInline, renderNode})
const style = props.node.style || 'normal'
const handler =
typeof components.block === 'function' ? components.block : components.block[style]
const block = handler || components.unknownBlockStyle
if (block === components.unknownBlockStyle) {
handleMissingComponent(unknownBlockStyleWarning(style), {
nodeType: 'blockStyle',
type: style,
})
}
return block({...props, value: props.node, renderNode})
}
function renderText(node: ToolkitTextNode): string {
if (node.text === '\n') {
const hardBreak = components.hardBreak
return hardBreak ? hardBreak() : '\n'
}
return components.escapeHTML(node.text)
}
function renderCustomBlock(value: TypedObject, index: number, isInline: boolean): string {
const node = components.types[value._type]
if (!node) {
handleMissingComponent(unknownTypeWarning(value._type), {
nodeType: 'block',
type: value._type,
})
}
const component = node || components.unknownType
return component({
value,
isInline,
index,
renderNode,
})
}
return renderNode
}
function serializeBlock(options: Serializable<PortableTextBlock>): SerializedBlock {
const {node, index, isInline, renderNode} = options
const tree = buildMarksTree(node)
const children = tree.map((child, i) =>
renderNode({node: child, isInline: true, index: i, renderNode}),
)
return {
_key: node._key || `block-${index}`,
children: children.join(''),
index,
isInline,
node,
}
}
function noop() {
// Intentional noop
}
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {ToolkitPortableTextList, ToolkitPortableTextListItem} from '@portabletext/toolkit'
import type {
ArbitraryTypedObject,
PortableTextBlock,
PortableTextBlockStyle,
PortableTextListItemBlock,
PortableTextListItemType,
TypedObject,
} from '@portabletext/types'
/**
* Options for the Portable Text to HTML function
*/
export interface PortableTextOptions {
/**
* Component functions to use for rendering
*/
components?: Partial<PortableTextHtmlComponents>
/**
* Function to call when encountering unknown unknown types, eg blocks, marks,
* block style, list styles without an associated component function.
*
* Will print a warning message to the console by default.
* Pass `false` to disable.
*/
onMissingComponent?: MissingComponentHandler | false
}
/**
* Generic type for portable text components that takes blocks/inline blocks
*
* @template N Node types we expect to be rendering (`PortableTextBlock` should usually be part of this)
*/
export type PortableTextComponent<N> = (options: PortableTextComponentOptions<N>) => string
/**
* Component function type for rendering portable text blocks (paragraphs, headings, blockquotes etc)
*/
export type PortableTextBlockComponent = PortableTextComponent<PortableTextBlock>
/**
* Component function type for rendering (virtual, not part of the spec) portable text lists
*/
export type PortableTextListComponent = PortableTextComponent<HtmlPortableTextList>
/**
* Component function type for rendering portable text list items
*/
export type PortableTextListItemComponent = PortableTextComponent<PortableTextListItemBlock>
/**
* Component function type for rendering portable text marks and/or decorators
*
* @template M The mark type we expect
*/
export type PortableTextMarkComponent<M extends TypedObject = any> = (
options: PortableTextMarkComponentOptions<M>,
) => string
export type PortableTextTypeComponent<V extends TypedObject = any> = (
options: PortableTextTypeComponentOptions<V>,
) => string
/**
* Object defining the different component functions to use for rendering various aspects
* of Portable Text and user-provided types, where only the overrides needs to be provided.
*/
export type PortableTextComponents = Partial<PortableTextHtmlComponents>
/**
* Object definining the different component functions to use for rendering various aspects
* of Portable Text and user-provided types.
*/
export interface PortableTextHtmlComponents {
/**
* Object of component functions that renders different types of objects that might appear
* both as part of the blocks array, or as inline objects _inside_ of a block,
* alongside text spans.
*
* Use the `isInline` property to check whether or not this is an inline object or a block
*
* The object has the shape `{typeName: ComponentFn}`, where `typeName` is the value set
* in individual `_type` attributes.
*/
types: Record<string, PortableTextTypeComponent | undefined>
/**
* Object of component functions that renders different types of marks that might appear in spans.
*
* The object has the shape `{markName: ComponentFn}`, where `markName` is the value set
* in individual `_type` attributes, values being stored in the parent blocks `markDefs`.
*/
marks: Record<string, PortableTextMarkComponent | undefined>
/**
* Object of component functions that renders blocks with different `style` properties.
*
* The object has the shape `{styleName: ComponentFn}`, where `styleName` is the value set
* in individual `style` attributes on blocks.
*
* Can also be set to a single component function, which would handle block styles of _any_ type.
*/
block:
| Record<PortableTextBlockStyle, PortableTextBlockComponent | undefined>
| PortableTextBlockComponent
/**
* Object of component functions used to render lists of different types (bulleted vs numbered,
* for instance, which by default is `<ul>` and `<ol>`, respectively)
*
* There is no actual "list" node type in the Portable Text specification, but a series of
* list item blocks with the same `level` and `listItem` properties will be grouped into a
* virtual one inside of this library.
*
* Can also be set to a single component function, which would handle lists of _any_ type.
*/
list:
| Record<PortableTextListItemType, PortableTextListComponent | undefined>
| PortableTextListComponent
/**
* Object of component functions used to render different list item styles.
*
* The object has the shape `{listItemType: ComponentFn}`, where `listItemType` is the value
* set in individual `listItem` attributes on blocks.
*
* Can also be set to a single component function, which would handle list items of _any_ type.
*/
listItem:
| Record<PortableTextListItemType, PortableTextListItemComponent | undefined>
| PortableTextListItemComponent
/**
* Component to use for rendering "hard breaks", eg `\n` inside of text spans
* Will by default render a `<br />`. Pass `false` to render as-is (`\n`)
*/
hardBreak: (() => string) | false
/**
* Used when rendering text nodes to HTML
*/
escapeHTML: (html: string) => string
/**
* Component function used when encountering a mark type there is no registered component for
* in the `components.marks` prop.
*/
unknownMark: PortableTextMarkComponent
/**
* Component function used when encountering an object type there is no registered component for
* in the `components.types` prop.
*/
unknownType: PortableTextComponent<UnknownNodeType>
/**
* Component function used when encountering a block style there is no registered component for
* in the `components.block` prop. Only used if `components.block` is an object.
*/
unknownBlockStyle: PortableTextComponent<PortableTextBlock>
/**
* Component function used when encountering a list style there is no registered component for
* in the `components.list` prop. Only used if `components.list` is an object.
*/
unknownList: PortableTextComponent<HtmlPortableTextList>
/**
* Component function used when encountering a list item style there is no registered component for
* in the `components.listItem` prop. Only used if `components.listItem` is an object.
*/
unknownListItem: PortableTextComponent<PortableTextListItemBlock>
}
/**
* Options received by most Portable Text components
*
* @template T Type of data this component will receive in its `value` property
*/
export interface PortableTextComponentOptions<T> {
/**
* Data associated with this portable text node, eg the raw JSON value of a block/type
*/
value: T
/**
* Index within its parent
*/
index: number
/**
* Whether or not this node is "inline" - ie as a child of a text block,
* alongside text spans, or a block in and of itself.
*/
isInline: boolean
/**
* Serialized HTML of child nodes of this block/type
*/
children?: string
/**
* Function used to render any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans. You will rarely need
* to use this.
*/
renderNode: NodeRenderer
}
/**
* Options received by any user-defined type in the input array that is not a text block
*
* @template T Type of data this component will receive in its `value` property
*/
export type PortableTextTypeComponentOptions<T> = Omit<PortableTextComponentOptions<T>, 'children'>
/**
* Options received by Portable Text mark components
*
* @template M Shape describing the data associated with this mark, if it is an annotation
*/
export interface PortableTextMarkComponentOptions<M extends TypedObject = ArbitraryTypedObject> {
/**
* Mark definition, eg the actual data of the annotation. If the mark is a simple decorator, this will be `undefined`
*/
value?: M
/**
* Text content of this mark
*/
text: string
/**
* Key for this mark. The same key can be used amongst multiple text spans within the same block, so don't rely on this to be unique.
*/
markKey?: string
/**
* Type of mark - ie value of `_type` in the case of annotations, or the name of the decorator otherwise - eg `em`, `italic`.
*/
markType: string
/**
* Serialized HTML of child nodes of this mark
*/
children: string
/**
* Function used to render any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans. You will rarely need
* to use this.
*/
renderNode: NodeRenderer
}
/**
* Any node type that we can't identify - eg it has an `_type`,
* but we don't know anything about its other properties
*/
export type UnknownNodeType = {[key: string]: unknown; _type: string} | TypedObject
/**
* Function that renders any node that might appear in a portable text array or block,
* including virtual "toolkit"-nodes like lists and nested spans
*/
export type NodeRenderer = <T extends TypedObject>(options: Serializable<T>) => string
export type NodeType = 'block' | 'mark' | 'blockStyle' | 'listStyle' | 'listItemStyle'
export type MissingComponentHandler = (
message: string,
options: {type: string; nodeType: NodeType},
) => void
export interface Serializable<T> {
node: T
index: number
isInline: boolean
renderNode: NodeRenderer
}
export interface SerializedBlock {
_key: string
children: string
index: number
isInline: boolean
node: PortableTextBlock | PortableTextListItemBlock
}
// Re-exporting these as we don't want to refer to "toolkit" outside of this module
/**
* A virtual "list" node for Portable Text - not strictly part of Portable Text,
* but generated by this library to ease the rendering of lists in HTML etc
*/
export type HtmlPortableTextList = ToolkitPortableTextList
/**
* A virtual "list item" node for Portable Text - not strictly any different from a
* regular Portable Text Block, but we can guarantee that it has a `listItem` property.
*/
export type HtmlPortableTextListItem = ToolkitPortableTextListItem
const getTemplate = (type: string, prop: string): string =>
`Unknown ${type}, specify a component for it in the \`components.${prop}\` option`
export const unknownTypeWarning = (typeName: string): string =>
getTemplate(`block type "${typeName}"`, 'types')
export const unknownMarkWarning = (markType: string): string =>
getTemplate(`mark type "${markType}"`, 'marks')
export const unknownBlockStyleWarning = (blockStyle: string): string =>
getTemplate(`block style "${blockStyle}"`, 'block')
export const unknownListStyleWarning = (listStyle: string): string =>
getTemplate(`list style "${listStyle}"`, 'list')
export const unknownListItemStyleWarning = (listStyle: string): string =>
getTemplate(`list item style "${listStyle}"`, 'listItem')
export function printWarning(message: string): void {
console.warn(message)
}