UNPKG

@ryhrm-gz/xincodo-lib

Version:

Utilities for working with Xincodo body documents.

441 lines (440 loc) 18.5 kB
import * as v from "valibot"; //#region src/constants.d.ts declare const BODY_VERSION = 1; declare const TEXT_COLORS: readonly ["default", "gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red"]; declare const BODY_BLOCK_TYPES: readonly ["text", "heading_1", "heading_2", "heading_3", "heading_4", "bulleted_list", "numbered_list", "toggle_list", "callout", "quote", "table", "divider", "page_link", "gallery", "image"]; declare const HEADING_LEVELS: readonly [1, 2, 3, 4]; type HeadingLevel = (typeof HEADING_LEVELS)[number]; //#endregion //#region src/types.d.ts type Body = { version: 1; content: BodyBlock[]; }; type BodyBlock = TextBlock | HeadingBlock | BulletedListBlock | NumberedListBlock | ToggleListBlock | CalloutBlock | QuoteBlock | TableBlock | DividerBlock | PageLinkBlock | GalleryBlock | ImageBlock; type HeadingBlock = Heading1Block | Heading2Block | Heading3Block | Heading4Block; type ListBlock = BulletedListBlock | NumberedListBlock | ToggleListBlock; type BodyBlockBase<Type extends BodyBlockType> = { type: Type; id?: string; }; type BodyBlockChildren = { children?: BodyBlock[]; }; type RichTextBlock<Type extends BodyBlockType> = BodyBlockBase<Type> & { richText: BodyRichText[]; }; type TextBlock = RichTextBlock<"text"> & BodyBlockChildren; type Heading1Block = RichTextBlock<"heading_1">; type Heading2Block = RichTextBlock<"heading_2">; type Heading3Block = RichTextBlock<"heading_3">; type Heading4Block = RichTextBlock<"heading_4">; type BulletedListBlock = BodyBlockBase<"bulleted_list"> & { items: ListItem[]; }; type NumberedListBlock = BodyBlockBase<"numbered_list"> & { start?: number; items: ListItem[]; }; type ToggleListBlock = BodyBlockBase<"toggle_list"> & { items: ToggleListItem[]; }; type ListItem = { richText: BodyRichText[]; children?: BodyBlock[]; }; type ToggleListItem = ListItem & { expanded?: boolean; }; type CalloutBlock = RichTextBlock<"callout"> & BodyBlockChildren & { icon?: CalloutIcon; color?: TextColor; backgroundColor?: TextColor; }; type QuoteBlock = RichTextBlock<"quote"> & BodyBlockChildren; type TableBlock = BodyBlockBase<"table"> & { hasColumnHeader?: boolean; hasRowHeader?: boolean; rows: TableRow[]; }; type TableRow = { cells: TableCell[]; }; type TableCell = { richText: BodyRichText[]; }; type DividerBlock = BodyBlockBase<"divider">; type PageLinkBlock = BodyBlockBase<"page_link"> & { page: PageReference; title?: BodyRichText[]; }; type GalleryBlock = BodyBlockBase<"gallery"> & { images: GalleryImage[]; caption?: BodyRichText[]; }; type GalleryImage = { id?: string; source: ImageSource; caption?: BodyRichText[]; alt?: string; }; type ImageBlock = BodyBlockBase<"image"> & { source: ImageSource; caption?: BodyRichText[]; alt?: string; }; type BodyRichText = TextInline | LineBreakInline; type BodyInline = BodyRichText; type TextInline = { type: "text"; text: string; annotations?: TextAnnotations; link?: TextLink; }; type LineBreakInline = { type: "line_break"; }; type TextAnnotations = { color?: TextColor; backgroundColor?: TextColor; bold?: boolean; italic?: boolean; underline?: boolean; strikethrough?: boolean; }; type TextLink = { href: string; title?: string; }; type TextColor = "default" | "gray" | "brown" | "orange" | "yellow" | "green" | "blue" | "purple" | "pink" | "red"; type CalloutIcon = { type: "emoji"; emoji: string; } | { type: "image"; source: ImageSource; }; type PageReference = { type: "page_id"; pageId: string; } | { type: "url"; url: string; }; type ImageSource = { type: "external"; url: string; } | { type: "file"; url: string; expiryTime?: string; }; type BodyBlockType = "text" | "heading_1" | "heading_2" | "heading_3" | "heading_4" | "bulleted_list" | "numbered_list" | "toggle_list" | "callout" | "quote" | "table" | "divider" | "page_link" | "gallery" | "image"; //#endregion //#region src/builders.d.ts type RichTextInput = string | BodyRichText; type RichTextOptions = { annotations?: TextAnnotations; link?: TextLink; }; type BlockBuilderOptions = { id?: string; }; type TextBlockOptions = BlockBuilderOptions & { children?: BodyBlock[]; }; type ListItemOptions = { children?: BodyBlock[]; }; type ToggleListItemOptions = ListItemOptions & { expanded?: boolean; }; type CalloutOptions = TextBlockOptions & { icon?: CalloutIcon; color?: CalloutBlock["color"]; backgroundColor?: CalloutBlock["backgroundColor"]; }; type TableOptions = BlockBuilderOptions & { hasColumnHeader?: boolean; hasRowHeader?: boolean; }; type ImageOptions = BlockBuilderOptions & { caption?: BodyRichText[]; alt?: string; }; type GalleryImageOptions = { id?: string; caption?: BodyRichText[]; alt?: string; }; type GalleryOptions = BlockBuilderOptions & { caption?: BodyRichText[]; }; type PageLinkOptions = BlockBuilderOptions & { title?: BodyRichText[]; }; declare function createBody(content: BodyBlock[]): Body; declare function text(value: string, options?: RichTextOptions): TextInline; declare function lineBreak(): LineBreakInline; declare function richText(...content: RichTextInput[]): BodyRichText[]; declare function paragraph(...content: RichTextInput[]): TextBlock; declare function paragraph(...contentAndOptions: [...RichTextInput[], TextBlockOptions]): TextBlock; declare function heading(level: HeadingLevel, ...content: RichTextInput[]): HeadingBlock; declare function heading(level: HeadingLevel, ...contentAndOptions: [...RichTextInput[], BlockBuilderOptions]): HeadingBlock; declare function listItem(...content: RichTextInput[]): ListItem; declare function listItem(...contentAndOptions: [...RichTextInput[], ListItemOptions]): ListItem; declare function toggleListItem(...content: RichTextInput[]): ToggleListItem; declare function toggleListItem(...contentAndOptions: [...RichTextInput[], ToggleListItemOptions]): ToggleListItem; declare function bulletedList(items: ListItem[], options?: BlockBuilderOptions): BulletedListBlock; declare function numberedList(items: ListItem[], options?: BlockBuilderOptions & { start?: number; }): NumberedListBlock; declare function toggleList(items: ToggleListItem[], options?: BlockBuilderOptions): ToggleListBlock; declare function callout(...content: RichTextInput[]): CalloutBlock; declare function callout(...contentAndOptions: [...RichTextInput[], CalloutOptions]): CalloutBlock; declare function quote(...content: RichTextInput[]): QuoteBlock; declare function quote(...contentAndOptions: [...RichTextInput[], TextBlockOptions]): QuoteBlock; declare function tableCell(...content: RichTextInput[]): TableCell; declare function tableRow(cells: TableCell[]): TableRow; declare function table(rows: TableRow[], options?: TableOptions): TableBlock; declare function divider(options?: BlockBuilderOptions): DividerBlock; declare function pageLink(page: PageReference, options?: PageLinkOptions): PageLinkBlock; declare function galleryImage(source: ImageSource, options?: GalleryImageOptions): GalleryImage; declare function gallery(images: GalleryImage[], options?: GalleryOptions): GalleryBlock; declare function image(source: ImageSource, options?: ImageOptions): ImageBlock; //#endregion //#region src/traversal.d.ts type BodyTraversalInput = Body | BodyBlock | readonly BodyBlock[]; type TraversalPathSegment = { key: "content" | "children" | "items" | "rows" | "cells" | "images" | "richText" | "caption" | "title"; index?: number; }; type TraversalPath = readonly TraversalPathSegment[]; type BodyBlockVisitorContext = { path: TraversalPath; parent?: Body | BodyBlock | ListItem | ToggleListItem; index?: number; depth: number; }; type RichTextVisitorContext = { path: TraversalPath; parent: BodyBlock | ListItem | ToggleListItem | TableCell | GalleryImage; field: "richText" | "caption" | "title"; depth: number; }; type InlineVisitorContext = RichTextVisitorContext & { index: number; }; type ListItemVisitorContext = { path: TraversalPath; parent: BodyBlock; index: number; depth: number; }; type TableCellVisitorContext = { path: TraversalPath; parent: BodyBlock; rowIndex: number; cellIndex: number; depth: number; }; type GalleryImageVisitorContext = { path: TraversalPath; parent: BodyBlock; index: number; depth: number; }; type BodyVisitor = { block?: (block: BodyBlock, context: BodyBlockVisitorContext) => false | void; richText?: (richText: readonly BodyRichText[], context: RichTextVisitorContext) => false | void; inline?: (inline: BodyRichText, context: InlineVisitorContext) => void; listItem?: (item: ListItem | ToggleListItem, context: ListItemVisitorContext) => false | void; tableCell?: (cell: TableCell, context: TableCellVisitorContext) => false | void; galleryImage?: (image: GalleryImage, context: GalleryImageVisitorContext) => false | void; }; type BodyBlockPredicate = (block: BodyBlock, context: BodyBlockVisitorContext) => boolean; type BodyBlockMapper = (block: BodyBlock, context: BodyBlockVisitorContext) => BodyBlock; declare function walkBody(input: BodyTraversalInput, visitor: BodyVisitor): void; declare function findBlock(input: BodyTraversalInput, predicate: BodyBlockPredicate): BodyBlock | undefined; declare function filterBlocks(input: BodyTraversalInput, predicate: BodyBlockPredicate): BodyBlock[]; declare function mapBody(body: Body, mapper: BodyBlockMapper): Body; declare function mapBody(block: BodyBlock, mapper: BodyBlockMapper): BodyBlock; declare function mapBody(blocks: readonly BodyBlock[], mapper: BodyBlockMapper): BodyBlock[]; //#endregion //#region src/editing.d.ts type BlockInsertPosition = "before" | "after"; type BlockInsertOptions = { position?: BlockInsertPosition; }; type RootBlockInsertOptions = { index?: number; }; type BodyBlockUpdater = (block: BodyBlock) => BodyBlock; type RichTextUpdater = (richText: BodyRichText[]) => BodyRichText[]; declare class BodyEditError extends Error { constructor(message: string); } declare function findBlockPathById(input: BodyTraversalInput, id: string): TraversalPath | undefined; declare function getBlockAtPath(input: BodyTraversalInput, path: TraversalPath): BodyBlock | undefined; declare function updateBlockAtPath(body: Body, path: TraversalPath, updater: BodyBlockUpdater): Body; declare function replaceBlockAtPath(body: Body, path: TraversalPath, block: BodyBlock): Body; declare function removeBlockAtPath(body: Body, path: TraversalPath): Body; declare function insertBlockAtRoot(body: Body, block: BodyBlock, options?: RootBlockInsertOptions): Body; declare function insertBlockAtPath(body: Body, path: TraversalPath, block: BodyBlock, options?: BlockInsertOptions): Body; declare function moveBlock(body: Body, fromPath: TraversalPath, toPath: TraversalPath, options?: BlockInsertOptions): Body; declare function updateRichTextAtPath(body: Body, path: TraversalPath, updater: RichTextUpdater): Body; //#endregion //#region src/lint.d.ts type BodyLintSeverity = "error" | "warning" | "info"; type BodyLintCode = "duplicate_id" | "empty_gallery" | "empty_list" | "empty_page_reference" | "empty_rich_text" | "empty_table" | "heading_level_jump" | "inconsistent_table_columns" | "invalid_numbered_list_start" | "invalid_url" | "max_depth_exceeded" | "missing_image_alt"; type BodyLintIssue = { severity: BodyLintSeverity; code: BodyLintCode; message: string; path: TraversalPath; }; type BodyLintReport = { valid: boolean; issues: BodyLintIssue[]; errors: BodyLintIssue[]; warnings: BodyLintIssue[]; infos: BodyLintIssue[]; }; type BodyLintOptions = { requireImageAlt?: boolean; validateUrls?: boolean; maxDepth?: number; }; declare function lintBody(input: BodyTraversalInput, options?: BodyLintOptions): BodyLintReport; //#endregion //#region src/schemas.d.ts declare const textColorSchema: v.GenericSchema<TextColor>; declare const textAnnotationsSchema: v.GenericSchema<TextAnnotations>; declare const textLinkSchema: v.GenericSchema<TextLink>; declare const bodyRichTextSchema: v.GenericSchema<BodyRichText>; declare const imageSourceSchema: v.GenericSchema<ImageSource>; declare const calloutIconSchema: v.GenericSchema<CalloutIcon>; declare const pageReferenceSchema: v.GenericSchema<PageReference>; declare const tableCellSchema: v.GenericSchema<TableCell>; declare const tableRowSchema: v.GenericSchema<TableRow>; declare const galleryImageSchema: v.GenericSchema<GalleryImage>; declare const listItemSchema: v.GenericSchema<ListItem>; declare const toggleListItemSchema: v.GenericSchema<ToggleListItem>; declare const bodyBlockSchema: v.GenericSchema<BodyBlock>; declare const bodySchema: v.GenericSchema<Body>; type BodyParseResult = v.SafeParseResult<typeof bodySchema>; declare function parseBody(input: unknown): BodyParseResult; //#endregion //#region src/migration.d.ts type BodyParseFailure = Extract<BodyParseResult, { success: false; }>; type BodyMigrationIssueCode = "invalid_body" | "unsupported_version"; type BodyMigrationIssue = { code: BodyMigrationIssueCode; message: string; version?: unknown; parseIssues?: BodyParseFailure["issues"]; }; type BodyMigrationSuccess = { success: true; output: Body; migrated: boolean; fromVersion: number; toVersion: typeof BODY_VERSION; }; type BodyMigrationFailure = { success: false; issue: BodyMigrationIssue; }; type BodyMigrationResult = BodyMigrationSuccess | BodyMigrationFailure; declare class BodyMigrationError extends Error { readonly issue: BodyMigrationIssue; constructor(issue: BodyMigrationIssue); } declare function safeMigrateBody(input: unknown): BodyMigrationResult; declare function migrateBody(input: unknown): Body; //#endregion //#region src/rendering.d.ts type ExtractedHeading = { id?: string; level: HeadingLevel; text: string; block: HeadingBlock; path: TraversalPath; }; type ExtractedHeadingAnchor = ExtractedHeading & { anchorId: string; href: `#${string}`; slug: string; }; type HeadingSlugOptions = { preserveExistingIds?: boolean; fallbackPrefix?: string; duplicateSeparator?: string; slugify?: (heading: ExtractedHeading) => string; }; type ExcerptOptions = { maxLength?: number; omission?: string; preserveWords?: boolean; }; type ReadingTimeOptions = { wordsPerMinute?: number; charactersPerMinute?: number; minimumMinutes?: number; }; type ReadingTimeEstimate = { minutes: number; words: number; characters: number; }; type CollectedImageSource = { kind: "image"; source: ImageSource; alt?: string; caption?: BodyRichText[]; block: ImageBlock; path: TraversalPath; } | { kind: "gallery_image"; source: ImageSource; alt?: string; caption?: BodyRichText[]; imageId?: string; block: GalleryBlock; path: TraversalPath; } | { kind: "callout_icon"; source: ImageSource; block: CalloutBlock; path: TraversalPath; }; type CollectedLink = { kind: "text_link"; href: string; title?: string; text: string; link: TextLink; path: TraversalPath; } | { kind: "page_link"; page: PageReference; text: string; block: PageLinkBlock; path: TraversalPath; }; declare function extractHeadings(input: BodyTraversalInput): ExtractedHeading[]; declare function extractHeadingAnchors(input: BodyTraversalInput, options?: HeadingSlugOptions): ExtractedHeadingAnchor[]; declare function slugifyHeading(value: string): string; declare function createExcerpt(input: BodyTraversalInput, options?: ExcerptOptions): string; declare function estimateReadingTime(input: BodyTraversalInput, options?: ReadingTimeOptions): ReadingTimeEstimate; declare function collectImageSources(input: BodyTraversalInput): CollectedImageSource[]; declare function collectLinks(input: BodyTraversalInput): CollectedLink[]; //#endregion //#region src/utils.d.ts type PlainTextInput = Body | BodyBlock | readonly BodyBlock[] | BodyRichText | readonly BodyRichText[]; declare function toPlainText(input: PlainTextInput): string; declare function richTextToPlainText(richText: readonly BodyRichText[]): string; //#endregion export { BODY_BLOCK_TYPES, BODY_VERSION, BlockBuilderOptions, BlockInsertOptions, BlockInsertPosition, Body, BodyBlock, BodyBlockBase, BodyBlockChildren, BodyBlockMapper, BodyBlockPredicate, BodyBlockType, BodyBlockUpdater, BodyBlockVisitorContext, BodyEditError, BodyInline, BodyLintCode, BodyLintIssue, BodyLintOptions, BodyLintReport, BodyLintSeverity, BodyMigrationError, BodyMigrationFailure, BodyMigrationIssue, BodyMigrationIssueCode, BodyMigrationResult, BodyMigrationSuccess, BodyParseResult, BodyRichText, BodyTraversalInput, BodyVisitor, BulletedListBlock, CalloutBlock, CalloutIcon, CalloutOptions, CollectedImageSource, CollectedLink, DividerBlock, ExcerptOptions, ExtractedHeading, ExtractedHeadingAnchor, GalleryBlock, GalleryImage, GalleryImageOptions, GalleryImageVisitorContext, GalleryOptions, HEADING_LEVELS, Heading1Block, Heading2Block, Heading3Block, Heading4Block, HeadingBlock, HeadingLevel, HeadingSlugOptions, ImageBlock, ImageOptions, ImageSource, InlineVisitorContext, LineBreakInline, ListBlock, ListItem, ListItemOptions, ListItemVisitorContext, NumberedListBlock, PageLinkBlock, PageLinkOptions, PageReference, PlainTextInput, QuoteBlock, ReadingTimeEstimate, ReadingTimeOptions, RichTextBlock, RichTextInput, RichTextOptions, RichTextUpdater, RichTextVisitorContext, RootBlockInsertOptions, TEXT_COLORS, TableBlock, TableCell, TableCellVisitorContext, TableOptions, TableRow, TextAnnotations, TextBlock, TextBlockOptions, TextColor, TextInline, TextLink, ToggleListBlock, ToggleListItem, ToggleListItemOptions, TraversalPath, TraversalPathSegment, bodyBlockSchema, bodyRichTextSchema, bodySchema, bulletedList, callout, calloutIconSchema, collectImageSources, collectLinks, createBody, createExcerpt, divider, estimateReadingTime, extractHeadingAnchors, extractHeadings, filterBlocks, findBlock, findBlockPathById, gallery, galleryImage, galleryImageSchema, getBlockAtPath, heading, image, imageSourceSchema, insertBlockAtPath, insertBlockAtRoot, lineBreak, lintBody, listItem, listItemSchema, mapBody, migrateBody, moveBlock, numberedList, pageLink, pageReferenceSchema, paragraph, parseBody, quote, removeBlockAtPath, replaceBlockAtPath, richText, richTextToPlainText, safeMigrateBody, slugifyHeading, table, tableCell, tableCellSchema, tableRow, tableRowSchema, text, textAnnotationsSchema, textColorSchema, textLinkSchema, toPlainText, toggleList, toggleListItem, toggleListItemSchema, updateBlockAtPath, updateRichTextAtPath, walkBody };