@portabletext/toolkit
Version:
Toolkit of handy utility functions for dealing with Portable Text
373 lines (348 loc) • 12.6 kB
text/typescript
import type {ArbitraryTypedObject} from '@portabletext/types'
import type {PortableTextBlock} from '@portabletext/types'
import type {PortableTextListItemBlock} from '@portabletext/types'
import type {PortableTextMarkDefinition} from '@portabletext/types'
import type {PortableTextSpan} from '@portabletext/types'
import type {TypedObject} from '@portabletext/types'
/**
* Takes a Portable Text block and returns a nested tree of nodes optimized for rendering
* in HTML-like environments where you want marks/annotations to be nested inside of eachother.
* For instance, a naive span-by-span rendering might yield:
*
* ```html
* <strong>This block contains </strong>
* <strong><a href="https://some.url/">a link</a></strong>
* <strong> and some bolded and </strong>
* <em><strong>italicized text</strong></em>
* ```
*
* ...whereas an optimal order would be:
*
* ```html
* <strong>
* This block contains <a href="https://some.url/">a link</a>
* and some bolded and <em>italicized text</em>
* </strong>
* ```
*
* Note that since "native" Portable Text spans cannot be nested,
* this function returns an array of "toolkit specific" types:
* {@link ToolkitTextNode | `@text`} and {@link ToolkitNestedPortableTextSpan | `@span` }.
*
* The toolkit-specific type can hold both types, as well as any arbitrary inline objects,
* creating an actual tree.
*
* @param block - The Portable Text block to create a tree of nodes from
* @returns Array of (potentially) nested spans, text nodes and/or arbitrary inline objects
*/
export declare function buildMarksTree<
M extends PortableTextMarkDefinition = PortableTextMarkDefinition,
>(
block: PortableTextBlock<M>,
): (ToolkitNestedPortableTextSpan<M> | ToolkitTextNode | ArbitraryTypedObject)[]
/**
* Strict check to determine if node is a correctly formatted Portable Text block.
*
* @param node - Node to check
* @returns True if valid Portable Text block, otherwise false
*/
export declare function isPortableTextBlock(
node: PortableTextBlock | TypedObject,
): node is PortableTextBlock
/**
* Strict check to determine if node is a correctly formatted portable list item block.
*
* @param block - Block to check
* @returns True if valid Portable Text list item block, otherwise false
*/
export declare function isPortableTextListItemBlock(
block: PortableTextBlock | TypedObject,
): block is PortableTextListItemBlock
/**
* Strict check to determine if node is a correctly formatted Portable Text span.
*
* @param node - Node to check
* @returns True if valid Portable Text span, otherwise false
*/
export declare function isPortableTextSpan(
node: ArbitraryTypedObject | PortableTextSpan,
): node is PortableTextSpan
/**
* Loose check to determine if block is a toolkit list node.
* Only checks `_type`, assumes correct structure.
*
* @param block - Block to check
* @returns True if toolkit list, otherwise false
*/
export declare function isPortableTextToolkitList(
block: TypedObject | ToolkitPortableTextList,
): block is ToolkitPortableTextList
/**
* Loose check to determine if span is a toolkit span node.
* Only checks `_type`, assumes correct structure.
*
* @param span - Span to check
* @returns True if toolkit span, otherwise false
*/
export declare function isPortableTextToolkitSpan(
span: TypedObject | ToolkitNestedPortableTextSpan,
): span is ToolkitNestedPortableTextSpan
/**
* Loose check to determine if node is a toolkit text node.
* Only checks `_type`, assumes correct structure.
*
* @param node - Node to check
* @returns True if toolkit text node, otherwise false
*/
export declare function isPortableTextToolkitTextNode(
node: TypedObject | ToolkitTextNode,
): node is ToolkitTextNode
/**
* List nesting mode for direct, nested lists, see the {@link nestLists | `nestLists()` function}
*/
export declare const LIST_NEST_MODE_DIRECT = 'direct'
/**
* List nesting mode for HTML, see the {@link nestLists | `nestLists()` function}
*/
export declare const LIST_NEST_MODE_HTML = 'html'
/**
* Takes an array of blocks and returns an array of nodes optimized for rendering in HTML-like
* environment, where lists are nested inside of eachother instead of appearing "flat" as in
* native Portable Text data structures.
*
* Note that the list node is not a native Portable Text node type, and thus is represented
* using the {@link ToolkitPortableTextList | `@list`} type name (`{_type: '@list'}`).
*
* The nesting can be configured in two modes:
*
* - `direct`: deeper list nodes will appear as a direct child of the parent list
* - `html`, deeper list nodes will appear as a child of the last _list item_ in the parent list
*
* When using `direct`, all list nodes will be of type {@link ToolkitPortableTextDirectList},
* while with `html` they will be of type {@link ToolkitPortableTextHtmlList}
*
* These modes are available as {@link LIST_NEST_MODE_HTML} and {@link LIST_NEST_MODE_DIRECT}.
*
* @param blocks - Array of Portable Text blocks and other arbitrary types
* @param mode - Mode to use for nesting, `direct` or `html`
* @returns Array of potentially nested nodes optimized for rendering
*/
export declare function nestLists<T extends TypedObject = PortableTextBlock | TypedObject>(
blocks: T[],
mode: 'direct',
): (T | ToolkitPortableTextDirectList)[]
export declare function nestLists<T extends TypedObject = PortableTextBlock | TypedObject>(
blocks: T[],
mode: 'html',
): (T | ToolkitPortableTextHtmlList)[]
export declare function nestLists<T extends TypedObject = PortableTextBlock | TypedObject>(
blocks: T[],
mode: 'direct' | 'html',
): (T | ToolkitPortableTextHtmlList | ToolkitPortableTextDirectList)[]
/**
* Figures out the optimal order of marks, in order to minimize the amount of
* nesting/repeated elements in environments such as HTML. For instance, a naive
* implementation might render something like:
*
* ```html
* <strong>This block contains </strong>
* <strong><a href="https://some.url/">a link</a></strong>
* <strong> and some bolded text</strong>
* ```
*
* ...whereas an optimal order would be:
*
* ```html
* <strong>
* This block contains <a href="https://some.url/">a link</a> and some bolded text
* </strong>
* ```
*
* This is particularly necessary for cases like links, where you don't want multiple
* individual links for different segments of the link text, even if parts of it are
* bolded/italicized.
*
* This function is meant to be used like: `block.children.map(sortMarksByOccurences)`,
* and is used internally in {@link buildMarksTree | `buildMarksTree()`}.
*
* The marks are sorted in the following order:
*
* 1. Marks that are shared amongst the most adjacent siblings
* 2. Non-default marks (links, custom metadata)
* 3. Decorators (bold, emphasis, code etc), in a predefined, preferred order
*
* @param span - The current span to sort
* @param index - The index of the current span within the block
* @param blockChildren - All children of the block being sorted
* @returns Array of decorators and annotations, sorted by "most adjacent siblings"
*/
export declare function sortMarksByOccurences(
span: PortableTextSpan | TypedObject,
index: number,
blockChildren: (PortableTextSpan | TypedObject)[],
): string[]
/**
* Returns the plain-text representation of a
* {@link ToolkitNestedPortableTextSpan | toolkit-specific Portable Text span}.
*
* Useful if you have a subset of nested nodes and want the text from just those,
* instead of for the entire Portable Text block.
*
* @param span - Span node to get text from (Portable Text toolkit specific type)
* @returns The plain-text version of the span
*/
export declare function spanToPlainText(span: ToolkitNestedPortableTextSpan): string
/**
* List nesting mode, see the {@link nestLists | `nestLists()` function}
*/
export declare type ToolkitListNestMode = 'html' | 'direct'
/**
* Toolkit-specific type representing a portable text span that can hold other spans.
* In this type, each span only has a single mark, instead of an array of them.
*/
export declare interface ToolkitNestedPortableTextSpan<
M extends PortableTextMarkDefinition = PortableTextMarkDefinition,
> {
/**
* Type name, prefixed with `@` to signal that this is a toolkit-specific node.
*/
_type: '@span'
/**
* Unique key for this span
*/
_key?: string
/**
* Holds the value (definition) of the mark in the case of annotations.
* `undefined` if the mark is a decorator (strong, em or similar).
*/
markDef?: M
/**
* The key of the mark definition (in the case of annotations).
* `undefined` if the mark is a decorator (strong, em or similar).
*/
markKey?: string
/**
* Type of the mark. For annotations, this is the `_type` property of the value.
* For decorators, it will hold the name of the decorator (strong, em or similar).
*/
markType: string
/**
* Child nodes of this span. Can be toolkit-specific text nodes, nested spans
* or any inline object type.
*/
children: (
| ToolkitTextNode
| ToolkitNestedPortableTextSpan<PortableTextMarkDefinition>
| ArbitraryTypedObject
)[]
}
export declare type ToolkitNestListsOutputNode<T> =
| T
| ToolkitPortableTextHtmlList
| ToolkitPortableTextDirectList
/**
* Toolkit-specific type representing a nested list in "direct" mode, where deeper lists are nested
* inside of the lists children, alongside other blocks.
*/
export declare interface ToolkitPortableTextDirectList {
/**
* Type name, prefixed with `@` to signal that this is a toolkit-specific node.
*/
_type: '@list'
/**
* Unique key for this list (within its parent)
*/
_key: string
/**
* List mode, signaling that list nodes can appear as direct children
*/
mode: 'direct'
/**
* Level/depth of this list node (starts at `1`)
*/
level: number
/**
* Style of this list item (`bullet`, `number` are common values, but can be customized)
*/
listItem: string
/**
* Child nodes of this list - either portable text list items, or another, deeper list
*/
children: (PortableTextListItemBlock | ToolkitPortableTextDirectList)[]
}
/**
* Toolkit-specific type representing a nested list in HTML mode, where deeper lists are nested
* inside of the _list items_, eg `<ul><li>Some text<ul><li>Deeper</li></ul></li></ul>`
*/
export declare interface ToolkitPortableTextHtmlList {
/**
* Type name, prefixed with `@` to signal that this is a toolkit-specific node.
*/
_type: '@list'
/**
* Unique key for this list (within its parent)
*/
_key: string
/**
* List mode, signaling that list nodes will appear as children of the _list items_
*/
mode: 'html'
/**
* Level/depth of this list node (starts at `1`)
*/
level: number
/**
* Style of this list item (`bullet`, `number` are common values, but can be customized)
*/
listItem: string
/**
* Child nodes of this list - toolkit-specific list items which can themselves hold deeper lists
*/
children: ToolkitPortableTextListItem[]
}
/**
* Toolkit-specific type representing a nested list
*
* See the `nestLists()` function for more info
*/
export declare type ToolkitPortableTextList =
| ToolkitPortableTextHtmlList
| ToolkitPortableTextDirectList
/**
* Toolkit-specific type representing a list item block, but where the children can be another list
*/
export declare interface ToolkitPortableTextListItem
extends PortableTextListItemBlock<
PortableTextMarkDefinition,
PortableTextSpan | ToolkitPortableTextList
> {}
/**
* Toolkit-specific type representing a text node, used when nesting spans.
*
* See the {@link buildMarksTree | `buildMarksTree()` function}
*/
export declare interface ToolkitTextNode {
/**
* Type name, prefixed with `@` to signal that this is a toolkit-specific node.
*/
_type: '@text'
/**
* The actual string value of the text node
*/
text: string
}
/**
* Takes a Portable Text block (or an array of them) and returns the text value
* of all the Portable Text span nodes. Adds whitespace when encountering inline,
* non-span nodes to ensure text flow is optimal.
*
* Note that this only accounts for regular Portable Text blocks - any text inside
* custom content types are not included in the output.
*
* @param block - Single block or an array of blocks to extract text from
* @returns The plain-text content of the blocks
*/
export declare function toPlainText(
block: PortableTextBlock | ArbitraryTypedObject[] | PortableTextBlock[],
): string
export {}