@ryhrm-gz/xincodo-lib
Version:
Utilities for working with Xincodo body documents.
441 lines (440 loc) • 18.5 kB
text/typescript
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 };