UNPKG

@storyblok/richtext

Version:
470 lines (469 loc) 16 kB
import { Attributes, Extension, Mark, Node } from "@tiptap/core"; import * as _tiptap_extension_list0 from "@tiptap/extension-list"; import * as _tiptap_extension_details0 from "@tiptap/extension-details"; import * as _tiptap_extension_table0 from "@tiptap/extension-table"; import * as _tiptap_extension_blockquote0 from "@tiptap/extension-blockquote"; import * as _tiptap_extension_code_block0 from "@tiptap/extension-code-block"; import * as _tiptap_extension_emoji0 from "@tiptap/extension-emoji"; import * as _tiptap_extension_hard_break0 from "@tiptap/extension-hard-break"; import * as _tiptap_extension_heading0 from "@tiptap/extension-heading"; import * as _tiptap_extension_horizontal_rule0 from "@tiptap/extension-horizontal-rule"; import * as _tiptap_extension_paragraph0 from "@tiptap/extension-paragraph"; import * as _tiptap_extension_text_align0 from "@tiptap/extension-text-align"; import * as _tiptap_extension_bold0 from "@tiptap/extension-bold"; import * as _tiptap_extension_code0 from "@tiptap/extension-code"; import * as _tiptap_extension_highlight0 from "@tiptap/extension-highlight"; import * as _tiptap_extension_italic0 from "@tiptap/extension-italic"; import * as _tiptap_extension_link0 from "@tiptap/extension-link"; import * as _tiptap_extension_strike0 from "@tiptap/extension-strike"; import * as _tiptap_extension_subscript0 from "@tiptap/extension-subscript"; import * as _tiptap_extension_superscript0 from "@tiptap/extension-superscript"; import * as _tiptap_extension_underline0 from "@tiptap/extension-underline"; import { ISbComponentType } from "storyblok-js-client"; //#region src/types/index.d.ts declare enum BlockTypes { DOCUMENT = "doc", HEADING = "heading", PARAGRAPH = "paragraph", QUOTE = "blockquote", OL_LIST = "ordered_list", UL_LIST = "bullet_list", LIST_ITEM = "list_item", CODE_BLOCK = "code_block", HR = "horizontal_rule", BR = "hard_break", IMAGE = "image", EMOJI = "emoji", COMPONENT = "blok", TABLE = "table", TABLE_ROW = "tableRow", TABLE_CELL = "tableCell", TABLE_HEADER = "tableHeader" } declare enum MarkTypes { BOLD = "bold", STRONG = "strong", STRIKE = "strike", UNDERLINE = "underline", ITALIC = "italic", CODE = "code", LINK = "link", ANCHOR = "anchor", STYLED = "styled", SUPERSCRIPT = "superscript", SUBSCRIPT = "subscript", TEXT_STYLE = "textStyle", HIGHLIGHT = "highlight" } declare enum TextTypes { TEXT = "text" } declare enum LinkTargets { SELF = "_self", BLANK = "_blank" } declare enum LinkTypes { URL = "url", STORY = "story", ASSET = "asset", EMAIL = "email" } /** * Represents text alignment attributes that can be applied to block-level elements. */ interface TextAlignmentAttrs { textAlign?: 'left' | 'center' | 'right' | 'justify'; } /** * Represents common attributes that can be applied to block-level elements. */ interface BlockAttributes extends TextAlignmentAttrs { class?: string; id?: string; [key: string]: any; } interface StoryblokRichTextDocumentNode { type: string; content?: StoryblokRichTextDocumentNode[]; attrs?: BlockAttributes; text?: string; marks?: StoryblokRichTextDocumentNode[]; } type StoryblokRichTextNodeTypes = BlockTypes | MarkTypes | TextTypes; interface StoryblokRichTextNode<T = string> { type: StoryblokRichTextNodeTypes; content: StoryblokRichTextNode<T>[]; children?: T; attrs?: BlockAttributes; text?: string; } interface LinkNode<T = string> extends StoryblokRichTextNode<T> { type: MarkTypes.LINK | MarkTypes.ANCHOR; linktype: LinkTypes; attrs: BlockAttributes; } interface MarkNode<T = string> extends StoryblokRichTextNode<T> { type: MarkTypes.BOLD | MarkTypes.ITALIC | MarkTypes.UNDERLINE | MarkTypes.STRIKE | MarkTypes.CODE | MarkTypes.LINK | MarkTypes.ANCHOR | MarkTypes.STYLED | MarkTypes.SUPERSCRIPT | MarkTypes.SUBSCRIPT | MarkTypes.TEXT_STYLE | MarkTypes.HIGHLIGHT; attrs?: BlockAttributes; } interface TextNode<T = string> extends StoryblokRichTextNode<T> { type: TextTypes.TEXT; text: string; marks?: MarkNode<T>[]; } /** * Represents the configuration options for optimizing images in rich text content. */ interface StoryblokRichTextImageOptimizationOptions { /** * CSS class to be applied to the image. */ class: string; /** * Width of the image in pixels. */ width: number; /** * Height of the image in pixels. */ height: number; /** * Loading strategy for the image. 'lazy' loads the image when it enters the viewport. 'eager' loads the image immediately. */ loading: 'lazy' | 'eager'; /** * Optional filters that can be applied to the image to adjust its appearance. * * @example * * ```typescript * const filters: Partial<StoryblokRichTextImageOptimizationOptions['filters']> = { * blur: 5, * brightness: 150, * grayscale: true * } * ``` */ filters: Partial<{ blur: number; brightness: number; fill: 'transparent'; format: 'webp' | 'png' | 'jpg'; grayscale: boolean; quality: number; rotate: 0 | 90 | 180 | 270; }>; /** * Defines a set of source set values that tell the browser different image sizes to load based on screen conditions. * The entries can be just the width in pixels or a tuple of width and pixel density. * * @example * * ```typescript * const srcset: (number | [number, number])[] = [ * 320, * [640, 2] * ] * ``` */ srcset: (number | [number, number])[]; /** * A list of sizes that correspond to different viewport widths, instructing the browser on which srcset source to use. * * @example * * ```typescript * const sizes: string[] = [ * '(max-width: 320px) 280px', * '(max-width: 480px) 440px', * '800px' * ] * ``` */ sizes: string[]; } /** * Represents the options for rendering rich text. */ interface StoryblokRichTextOptions<T = string, S = (tag: string, attrs: BlockAttributes, children?: T) => T> { /** * Defines the function that will be used to render the final HTML string (vanilla) or Framework component (React, Vue). * * @example * * ```typescript * const renderFn = (tag: string, attrs: Record<string, any>, text?: string) => { * return `<${tag} ${Object.keys(attrs).map(key => `${key}="${attrs[key]}"`).join(' ')}>${text}</${tag}>` * } * * const options: StoryblokRichTextOptions = { * renderFn * } * ``` */ renderFn?: S; /** * Defines the function that will be used to render HTML text. * * @example * * ```typescript * import { h, createTextVNode } from 'vue' * * const options: StoryblokRichTextOptions = { * renderFn: h, * textFn: createTextVNode * } * ``` */ textFn?: (text: string, attrs?: BlockAttributes) => T; /** * Defines opt-out image optimization options. * * @example * * ```typescript * const options: StoryblokRichTextOptions = { * optimizeImages: true * } * ``` * * @example * * ```typescript * const options: StoryblokRichTextOptions = { * optimizeImages: { * class: 'my-image', * width: 800, * height: 600, * loading: 'lazy', * } * ``` */ optimizeImages?: boolean | Partial<StoryblokRichTextImageOptimizationOptions>; /** * Defines whether to use the key attribute in the resolvers for framework use cases. * @default false * @example * * ```typescript * * const options: StoryblokRichTextOptions = { * renderFn: h, * keyedResolvers: true * } * ``` */ keyedResolvers?: boolean; /** * Custom tiptap extensions to override or add node/mark rendering. * Extensions are merged with the built-in defaults, overriding by key. */ tiptapExtensions?: Record<string, any>; } //#endregion //#region src/extensions/nodes.d.ts declare const ComponentBlok: Node<{ renderComponent: ((blok: Record<string, unknown>, id?: string) => unknown) | null; }, any>; //#endregion //#region src/extensions/marks.d.ts interface StyledOptions { allowedStyles?: string[]; } //#endregion //#region src/extensions/index.d.ts interface StyleOption { name: string; value: string; } interface StoryblokExtensionOptions { optimizeImages?: boolean | Partial<StoryblokRichTextImageOptimizationOptions>; allowCustomAttributes?: boolean; styleOptions?: StyleOption[]; } declare function getStoryblokExtensions(options?: StoryblokExtensionOptions): { image: Node<{ optimizeImages: boolean | Partial<StoryblokRichTextImageOptimizationOptions>; }, any>; link: Mark<_tiptap_extension_link0.LinkOptions, any>; styled: Mark<StyledOptions, any>; reporter: Mark<any, any>; document: Node<any, any>; text: Node<any, any>; paragraph: Node<_tiptap_extension_paragraph0.ParagraphOptions, any>; blockquote: Node<_tiptap_extension_blockquote0.BlockquoteOptions, any>; heading: Node<_tiptap_extension_heading0.HeadingOptions, any>; bulletList: Node<_tiptap_extension_list0.BulletListOptions, any>; orderedList: Node<_tiptap_extension_list0.OrderedListOptions, any>; listItem: Node<_tiptap_extension_list0.ListItemOptions, any>; codeBlock: Node<_tiptap_extension_code_block0.CodeBlockOptions, any>; hardBreak: Node<_tiptap_extension_hard_break0.HardBreakOptions, any>; horizontalRule: Node<_tiptap_extension_horizontal_rule0.HorizontalRuleOptions, any>; emoji: Node<_tiptap_extension_emoji0.EmojiOptions, _tiptap_extension_emoji0.EmojiStorage>; table: Node<_tiptap_extension_table0.TableOptions, any>; tableRow: Node<_tiptap_extension_table0.TableRowOptions, any>; tableCell: Node<_tiptap_extension_table0.TableCellOptions, any>; tableHeader: Node<_tiptap_extension_table0.TableHeaderOptions, any>; blok: Node<{ renderComponent: ((blok: Record<string, unknown>, id?: string) => unknown) | null; }, any>; details: Node<_tiptap_extension_details0.DetailsOptions, any>; detailsContent: Node<_tiptap_extension_details0.DetailsContentOptions, any>; detailsSummary: Node<_tiptap_extension_details0.DetailsSummaryOptions, any>; bold: Mark<_tiptap_extension_bold0.BoldOptions, any>; italic: Mark<_tiptap_extension_italic0.ItalicOptions, any>; strike: Mark<_tiptap_extension_strike0.StrikeOptions, any>; underline: Mark<_tiptap_extension_underline0.UnderlineOptions, any>; code: Mark<_tiptap_extension_code0.CodeOptions, any>; superscript: Mark<_tiptap_extension_superscript0.SuperscriptExtensionOptions, any>; subscript: Mark<_tiptap_extension_subscript0.SubscriptExtensionOptions, any>; highlight: Mark<_tiptap_extension_highlight0.HighlightOptions, any>; textStyle: Mark<any, any>; anchor: Mark<any, any>; textAlign: Extension<_tiptap_extension_text_align0.TextAlignOptions, any>; }; //#endregion //#region src/richtext-segment.d.ts /** * Segment Types */ interface TextSegment { kind: 'text'; text: string; } interface NodeSegment { kind: 'node'; type: string; tag: string | null; attrs: Attributes; content: SBRichTextSegment[]; } interface MarkSegment { kind: 'mark'; type: string; tag: string | null; attrs: Attributes; content: SBRichTextSegment[]; } interface ComponentSegment { kind: 'component'; type: string; props: Record<string, unknown>; } type SBRichTextSegment = TextSegment | NodeSegment | MarkSegment | ComponentSegment; type StoryblokExtensions = ReturnType<typeof getStoryblokExtensions>; type StoryblokSegmentType = keyof StoryblokExtensions; /** * Renderer Options */ interface StoryblokRichTextOptionsNew { optimizeImages?: boolean; /** * Called when a node has no extension renderer */ onUnknownNode?: (node: StoryblokRichTextNode) => SBRichTextSegment[]; /** * Called when a mark has no extension renderer */ onUnknownMark?: (mark: any) => SBRichTextSegment[]; } declare function getRichTextSegments(richText: StoryblokRichTextNode, options?: StoryblokRichTextOptionsNew): SBRichTextSegment[]; declare function isVoidElement(tag: string): boolean; /** * Parses an inline CSS style string into an object. * * Example: * parseStyleString("width: 1.25em; height: 1.25em; vertical-align: text-top") * -> { width: "1.25em", height: "1.25em", "vertical-align": "text-top" } */ declare function parseStyleString(style: string): Record<string, string>; //#endregion //#region src/render-segments.d.ts interface RendererAdapter<T = unknown> { createElement: (tag: string, attrs?: Record<string, unknown>, children?: T[]) => T; createText: (text: string) => T; createComponent?: (type: StoryblokSegmentType, props: Record<string, unknown>) => T; } declare function renderSegments<T>(segments: SBRichTextSegment[], adapter: RendererAdapter<T>, customComponents: StoryblokSegmentType[]): T[]; //#endregion //#region src/richtext.d.ts /** * Creates a rich text resolver with the given options. */ declare function richTextResolver<T>(options?: StoryblokRichTextOptions<T>): { render: (node: StoryblokRichTextNode<T> | StoryblokRichTextDocumentNode) => T; }; //#endregion //#region src/utils/segment-richtext.d.ts type SbBlokKeyDataTypes = string | number | object | boolean | undefined; interface SbBlokData extends ISbComponentType<string> { [index: string]: SbBlokKeyDataTypes; } interface RichTextHtmlSegment { type: 'html'; content: string; } interface RichTextBlokSegment { type: 'blok'; blok: SbBlokData; } type RichTextSegment = RichTextHtmlSegment | RichTextBlokSegment; /** * Converts a Storyblok Rich Text document into a linear list of segments. * * The returned segments preserve the original content order and consist of: * - HTML segments for regular rich text content * - Blok segments for embedded Storyblok components * * This allows consumers to render HTML normally while handling Storyblok * components separately using framework-specific logic. * * @param doc - The Storyblok Rich Text document to process * @param options - Optional rich text resolver options * @returns An ordered array of rich text segments (HTML and bloks) * * @example * ```ts * const segments = segmentStoryblokRichText(richTextDoc); * * for (const segment of segments) { * if (segment.type === 'html') { * renderHtml(segment.content); * } * * if (segment.type === 'blok') { * renderBlokComponent(segment.blok); * } * } * ``` */ declare function segmentStoryblokRichText(doc: StoryblokRichTextNode<string>, options?: StoryblokRichTextOptions<string>): RichTextSegment[]; //#endregion //#region src/index.d.ts /** * Wraps a framework component (React, Vue, etc.) for use as a tag * in Tiptap's `renderHTML` DOMOutputSpec. * * Tiptap's `DOMOutputSpec` type only accepts strings at position 0, * but the Storyblok richtext resolver also handles component references. * Use this helper to satisfy TypeScript without a manual `as unknown as string` type assertion. * * @example * ```typescript * import { Mark } from '@tiptap/core'; * import { asTag } from '@storyblok/vue'; // or @storyblok/react * import { RouterLink } from 'vue-router'; * * const CustomLink = Mark.create({ * name: 'link', * renderHTML({ HTMLAttributes }) { * return [asTag(RouterLink), { to: HTMLAttributes.href }, 0]; * }, * }); * ``` */ declare function asTag(component: unknown): string; //#endregion export { BlockAttributes, BlockTypes, ComponentBlok, LinkNode, LinkTargets, LinkTypes, MarkNode, MarkSegment, MarkTypes, NodeSegment, RendererAdapter, RichTextBlokSegment, RichTextHtmlSegment, RichTextSegment, SBRichTextSegment, SbBlokData, SbBlokKeyDataTypes, StoryblokExtensions, StoryblokRichTextDocumentNode, StoryblokRichTextImageOptimizationOptions, StoryblokRichTextNode, StoryblokRichTextNodeTypes, StoryblokRichTextOptions, StoryblokRichTextOptionsNew, StoryblokSegmentType, TextAlignmentAttrs, TextNode, TextSegment, TextTypes, asTag, getRichTextSegments, isVoidElement, parseStyleString, renderSegments, richTextResolver, segmentStoryblokRichText }; //# sourceMappingURL=index.d.mts.map