You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/words/engine/document.ts

206 lines
5.8 KiB

export const WORDS_DOCUMENT_VERSION = 1;
export type WordsMark = 'bold' | 'italic' | 'underline' | 'strike' | 'code';
export type WordsBlockType = 'paragraph' | 'heading' | 'quote' | 'list';
export type WordsListKind = 'ordered' | 'unordered' | 'check';
export type WordsHeadingLevel = 1 | 2 | 3;
export type WordsTextAlign = 'left' | 'center' | 'right' | 'justify';
export interface WordsDocument {
readonly version: typeof WORDS_DOCUMENT_VERSION;
readonly children: readonly WordsBlock[];
}
export type WordsBlock = WordsParagraphBlock | WordsHeadingBlock | WordsQuoteBlock | WordsListBlock;
export interface WordsParagraphBlock {
readonly type: 'paragraph';
readonly children: readonly WordsInline[];
readonly textAlign?: WordsTextAlign;
}
export interface WordsHeadingBlock {
readonly type: 'heading';
readonly level: WordsHeadingLevel;
readonly children: readonly WordsInline[];
readonly textAlign?: WordsTextAlign;
}
export interface WordsQuoteBlock {
readonly type: 'quote';
readonly children: readonly WordsInline[];
readonly textAlign?: WordsTextAlign;
}
export interface WordsListBlock {
readonly type: 'list';
readonly kind: WordsListKind;
readonly children: readonly WordsListItem[];
}
export interface WordsListItem {
readonly type: 'list-item';
readonly checked?: boolean;
readonly indent?: number;
readonly children: readonly WordsInline[];
}
export type WordsInline = WordsText | WordsLink;
export interface WordsText {
readonly type: 'text';
readonly text: string;
readonly marks?: readonly WordsMark[];
}
export interface WordsLink {
readonly type: 'link';
readonly href: string;
readonly title?: string;
readonly children: readonly WordsInline[];
}
export const WORDS_MARKS = [
'bold',
'italic',
'underline',
'strike',
'code'
] as const satisfies readonly WordsMark[];
export const WORDS_LIST_KINDS = [
'ordered',
'unordered',
'check'
] as const satisfies readonly WordsListKind[];
export const WORDS_HEADING_LEVELS = [1, 2, 3] as const satisfies readonly WordsHeadingLevel[];
export const WORDS_TEXT_ALIGNS = ['left', 'center', 'right', 'justify'] as const satisfies readonly WordsTextAlign[];
export function createEmptyWordsDocument(): WordsDocument {
return {
version: WORDS_DOCUMENT_VERSION,
children: [createParagraph()]
};
}
export function createParagraph(
children: readonly WordsInline[] = [createText('')],
textAlign?: WordsTextAlign
): WordsParagraphBlock {
return { type: 'paragraph', children, ...(textAlign ? { textAlign } : {}) };
}
export function createHeading(
level: WordsHeadingLevel,
children: readonly WordsInline[] = [createText('')],
textAlign?: WordsTextAlign
): WordsHeadingBlock {
return { type: 'heading', level, children, ...(textAlign ? { textAlign } : {}) };
}
export function createQuote(
children: readonly WordsInline[] = [createText('')],
textAlign?: WordsTextAlign
): WordsQuoteBlock {
return { type: 'quote', children, ...(textAlign ? { textAlign } : {}) };
}
export function createList(
kind: WordsListKind,
children: readonly WordsListItem[] = [createListItem()]
): WordsListBlock {
return { type: 'list', kind, children };
}
export function createListItem(
children: readonly WordsInline[] = [createText('')],
checked = false,
indent?: number
): WordsListItem {
return {
type: 'list-item',
...(checked ? { checked } : {}),
...(indent ? { indent } : {}),
children
};
}
export function createText(text: string, marks: readonly WordsMark[] = []): WordsText {
const normalizedMarks = normalizeMarks(marks);
return {
type: 'text',
text,
...(normalizedMarks.length ? { marks: normalizedMarks } : {})
};
}
export function createLink(
href: string,
children: readonly WordsInline[],
title?: string
): WordsLink {
return {
type: 'link',
href,
...(title ? { title } : {}),
children
};
}
export function normalizeMarks(marks: readonly WordsMark[] | undefined): readonly WordsMark[] {
if (!marks?.length) return [];
const order = new Map<WordsMark, number>(WORDS_MARKS.map((mark, index) => [mark, index]));
return Array.from(new Set(marks)).sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));
}
export function sameMarks(
a: readonly WordsMark[] | undefined,
b: readonly WordsMark[] | undefined
): boolean {
const left = normalizeMarks(a);
const right = normalizeMarks(b);
if (left.length !== right.length) return false;
return left.every((mark, index) => mark === right[index]);
}
export function isWordsMark(value: unknown): value is WordsMark {
return typeof value === 'string' && (WORDS_MARKS as readonly string[]).includes(value);
}
export function isWordsHeadingLevel(value: unknown): value is WordsHeadingLevel {
return typeof value === 'number' && (WORDS_HEADING_LEVELS as readonly number[]).includes(value);
}
export function isWordsListKind(value: unknown): value is WordsListKind {
return typeof value === 'string' && (WORDS_LIST_KINDS as readonly string[]).includes(value);
}
export function isWordsTextAlign(value: unknown): value is WordsTextAlign {
return typeof value === 'string' && (WORDS_TEXT_ALIGNS as readonly string[]).includes(value);
}
export function isTextInline(inline: WordsInline): inline is WordsText {
return inline.type === 'text';
}
export function isLinkInline(inline: WordsInline): inline is WordsLink {
return inline.type === 'link';
}
export function getBlockText(block: WordsBlock): string {
if (block.type === 'list') {
return block.children.map((item) => item.children.map(getInlineText).join('')).join('\n');
}
return block.children.map(getInlineText).join('');
}
export function getInlineText(inline: WordsInline): string {
return getInlineTextSafe(inline, new WeakSet<object>());
}
function getInlineTextSafe(inline: WordsInline, seen: WeakSet<object>): string {
if (seen.has(inline)) return '';
seen.add(inline);
if (inline.type === 'text') return inline.text;
return inline.children.map((child) => getInlineTextSafe(child, seen)).join('');
}

Powered by TurnKey Linux.