@portabletext/to-html
Advanced tools
+254
| 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"} |
+239
| 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) => `${" ".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) => `${' '.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 |
+23
-59
| { | ||
| "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) => `${" ".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) => `${' '.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":"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"} |
| 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) => `${' '.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' |
-234
| 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 | ||
| } |
-304
| /* 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) | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
12
-33.33%0
-100%54611
-30.96%7
-56.25%496
-56.94%1
Infinity%+ Added
+ Added
- Removed
- Removed
Updated
Updated