@x12i/editor 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,184 +1,220 @@
1
- import { ChartSpec } from '@x12i/charts';
2
- import { SavedView } from '@x12i/data';
3
- import { DiagramDocument } from '@x12i/diagrams';
1
+ import { E as EditorDocument, B as Block, I as Inline } from './outline-Drc3E0C1.js';
2
+ export { A as AiCommand, a as AiProvider, b as BLOCK_ID_NODES, c as BLOCK_TYPES, d as BlockBase, e as BlockType, C as CALLOUT_VARIANTS, f as CalloutVariant, g as CollaborationProvider, h as ConversionWarning, i as ConversionWarningCode, j as ConvertOptions, D as DOCUMENT_VERSION, k as InlineNode, L as ListItem, M as MARK_TYPES, l as Mark, O as OutlineItem, m as OutlineNode, n as OutlineOptions, T as TIPTAP_MARKS, o as TIPTAP_NODES, p as TableCell, q as TaskItem, r as TiptapDoc, s as TiptapMark, t as TiptapNode, u as TiptapNodeName, v as buildOutlineTree, w as createEmptyDocument, x as createParagraph, y as documentToTiptap, z as getOutline, F as isKnownBlockType, G as normalizeDocument, H as normalizeInline, J as slugifyHeading, K as tiptapToDocument } from './outline-Drc3E0C1.js';
3
+ import '@x12i/charts';
4
+ import '@x12i/data';
5
+ import '@x12i/diagrams';
4
6
 
5
7
  /**
6
- * The stored document (spec section 11): versioned JSON owned by the kit, with
7
- * converters to and from the engine's format planned. That keeps the editor
8
- * engine (Tiptap by default) replaceable.
8
+ * Block ids. Every block, list item and task item carries a stable id so a
9
+ * diff, a comment anchor or a collaboration layer can refer to it.
9
10
  */
10
11
 
11
- declare const DOCUMENT_VERSION = 1;
12
- type Mark = {
13
- type: "bold";
14
- } | {
15
- type: "italic";
16
- } | {
17
- type: "underline";
18
- } | {
19
- type: "strike";
20
- } | {
21
- type: "code";
22
- } | {
23
- type: "link";
24
- href: string;
25
- } | {
26
- type: "color";
27
- token: string;
28
- } | {
29
- type: "highlight";
30
- token: string;
31
- };
32
- type InlineNode = {
33
- type: "text";
34
- text: string;
35
- marks?: Mark[];
36
- } | {
37
- type: "mention";
38
- kind: "person" | "page" | "date";
39
- id: string;
40
- label: string;
41
- } | {
42
- type: "emoji";
43
- shortcode: string;
44
- };
45
- type Inline = InlineNode[];
46
- interface BlockBase {
47
- id: string;
12
+ /**
13
+ * A new random block id: 12 lowercase base-36 characters. Uses the Web Crypto
14
+ * API when it exists (browsers, Node 20+, edge runtimes) and falls back to
15
+ * `Math.random`, so it works on the server too.
16
+ */
17
+ declare function generateBlockId(): string;
18
+ /** A small seeded PRNG (mulberry32). Deterministic across platforms. */
19
+ declare function createSeededRandom(seed: number): () => number;
20
+ /**
21
+ * A deterministic id generator for tests and fixtures: `b1`, `b2`, ... with
22
+ * your own prefix. Never use it for real documents.
23
+ */
24
+ declare function createSequentialIdGenerator(prefix?: string, start?: number): () => string;
25
+ /** Calls `visit` for every block, including blocks nested in toggles, columns and AI results. */
26
+ declare function walkBlocks(blocks: readonly Block[], visit: (block: Block, parent: Block | null) => void, parent?: Block | null): void;
27
+ /** Every id in the document: blocks (nested too), list items and task items, in document order. */
28
+ declare function collectIds(doc: EditorDocument): string[];
29
+ /** Ids that occur more than once. A healthy document returns an empty array. */
30
+ declare function findDuplicateIds(doc: EditorDocument): string[];
31
+
32
+ /**
33
+ * URL safety. Links and media in a stored document come from users, so every
34
+ * renderer runs them through here: http, https, mailto, tel and relative URLs
35
+ * pass; `javascript:`, `vbscript:`, `file:` and `data:` do not, except
36
+ * `data:image/*` for image sources.
37
+ */
38
+ interface SanitizeUrlOptions {
39
+ /** Allow `data:image/*` URLs. Only set this for image sources, never for links. */
40
+ allowDataImage?: boolean;
41
+ /** Schemes (without the colon) that pass, in lower case. Defaults to http, https, mailto, tel. */
42
+ allowedProtocols?: readonly string[];
48
43
  }
49
- type CalloutVariant = "info" | "success" | "warning" | "danger" | "neutral";
50
- interface TaskItem {
51
- id: string;
52
- content: Inline;
53
- checked: boolean;
54
- children?: TaskItem[];
44
+ /**
45
+ * Returns the trimmed URL when it is safe to render, or `null` when it is not.
46
+ * Relative URLs (`/a`, `./a`, `../a`, `a/b`, `#id`, `?q=1`) are safe.
47
+ */
48
+ declare function sanitizeUrl(input: unknown, options?: SanitizeUrlOptions): string | null;
49
+ /** True when `sanitizeUrl` accepts the value. */
50
+ declare function isSafeUrl(input: unknown, options?: SanitizeUrlOptions): boolean;
51
+ /** Sanitizer for image, video and audio sources: also lets `data:image/*` through. */
52
+ declare function sanitizeMediaUrl(input: unknown): string | null;
53
+ /**
54
+ * Turns what a person typed in a link field into a URL: bare domains get
55
+ * `https://`, bare email addresses get `mailto:`. Anything else is returned
56
+ * trimmed. Run the result through `sanitizeUrl` before storing it.
57
+ */
58
+ declare function normalizeUrlInput(input: string): string;
59
+
60
+ /** Text colors. Foreground tokens are chosen to keep 4.5:1 on the surface in every theme. */
61
+ declare const TEXT_COLOR_TOKENS: readonly ["color.text.secondary", "color.text.link", "color.accent.default", "color.status.success.foreground", "color.status.warning.foreground", "color.status.danger.foreground", "color.status.info.foreground", "color.chart.categorical.4"];
62
+ /** Highlight (background) colors. Text keeps the primary text color on top of them. */
63
+ declare const HIGHLIGHT_TOKENS: readonly ["color.highlight", "color.accent.subtle", "color.status.success.background", "color.status.warning.background", "color.status.danger.background", "color.status.info.background"];
64
+ type TextColorToken = (typeof TEXT_COLOR_TOKENS)[number];
65
+ type HighlightToken = (typeof HIGHLIGHT_TOKENS)[number];
66
+ declare function isTextColorToken(token: unknown): token is TextColorToken;
67
+ declare function isHighlightToken(token: unknown): token is HighlightToken;
68
+ /** The CSS custom property for a token path, for example `--kit-color-text-link`. */
69
+ declare function tokenToCssVarName(token: string): string;
70
+ /** `var(--kit-...)` for an allowed text color token, or `null` when the token is not on the list. */
71
+ declare function textColorToCss(token: unknown): string | null;
72
+ /** `var(--kit-...)` for an allowed highlight token, or `null` when the token is not on the list. */
73
+ declare function highlightToCss(token: unknown): string | null;
74
+
75
+ /** Plain text of an inline run: marks dropped, mentions as `@label`, emoji as characters. */
76
+ declare function inlineToPlainText(inline: Inline | undefined): string;
77
+ interface PlainTextOptions {
78
+ /** Text between blocks. Defaults to a blank line. */
79
+ blockSeparator?: string;
80
+ /** Prefix list items with `-`, `1.` and `[x]`. Defaults to true. Counting turns it off. */
81
+ markers?: boolean;
55
82
  }
56
- interface ListItem {
57
- id: string;
58
- content: Inline;
59
- children?: ListItem[];
83
+ /**
84
+ * The document as plain text. Blocks are separated by a blank line, lists keep
85
+ * their markers and tables become tab-separated rows. Blocks with no text
86
+ * (dividers, page breaks) are skipped. Columns read in order.
87
+ */
88
+ declare function documentToPlainText(doc: EditorDocument, options?: PlainTextOptions): string;
89
+ /**
90
+ * Counts words. Uses `Intl.Segmenter` when available, so Hebrew, Arabic and
91
+ * CJK text count correctly; otherwise it splits on whitespace.
92
+ */
93
+ declare function countWords(text: string, locale?: string): number;
94
+ /** Counts characters as the reader sees them (grapheme clusters), not UTF-16 units. */
95
+ declare function countCharacters(text: string, options?: {
96
+ includeSpaces?: boolean;
97
+ }): number;
98
+ interface DocumentStats {
99
+ words: number;
100
+ characters: number;
101
+ charactersNoSpaces: number;
102
+ /** Top-level blocks. */
103
+ blocks: number;
104
+ /** Reading time at 200 words per minute, rounded up, at least 1 when there is any text. */
105
+ readingMinutes: number;
60
106
  }
61
- interface TableCell {
62
- content: Inline;
63
- colSpan?: number;
64
- rowSpan?: number;
65
- header?: boolean;
107
+ /** Word, character and block counts for a document. */
108
+ declare function getDocumentStats(doc: EditorDocument, options?: {
109
+ locale?: string;
110
+ }): DocumentStats;
111
+
112
+ /** English labels for the few strings an export needs. Pass `labels` to translate them. */
113
+ interface ExportLabels {
114
+ tableOfContents: string;
115
+ chart: string;
116
+ diagram: string;
117
+ dataView: string;
118
+ ai: string;
119
+ file: string;
66
120
  }
67
- type Block = BlockBase & ({
68
- type: "paragraph";
69
- content: Inline;
70
- } | {
71
- type: "heading";
72
- level: 1 | 2 | 3 | 4;
73
- content: Inline;
74
- } | {
75
- type: "quote";
76
- content: Inline;
77
- } | {
78
- type: "callout";
79
- variant: CalloutVariant;
80
- content: Inline;
81
- } | {
82
- type: "toggle";
83
- summary: Inline;
84
- children: Block[];
85
- } | {
86
- type: "divider";
87
- } | {
88
- type: "bulleted-list";
89
- items: ListItem[];
90
- } | {
91
- type: "numbered-list";
92
- items: ListItem[];
93
- } | {
94
- type: "task-list";
95
- items: TaskItem[];
96
- } | {
97
- type: "code";
98
- language?: string;
99
- code: string;
100
- } | {
101
- type: "equation";
102
- latex: string;
103
- } | {
104
- type: "table";
105
- headerRow?: boolean;
106
- headerColumn?: boolean;
107
- rows: TableCell[][];
108
- } | {
109
- type: "image";
110
- src: string;
111
- alt: string;
112
- caption?: Inline;
113
- align?: "start" | "center" | "end";
114
- width?: number;
115
- } | {
116
- type: "video" | "audio";
117
- src: string;
118
- caption?: Inline;
119
- } | {
120
- type: "file";
121
- src: string;
122
- name: string;
123
- size?: number;
124
- } | {
125
- type: "bookmark";
126
- url: string;
127
- title?: string;
128
- description?: string;
129
- } | {
130
- type: "columns";
131
- columns: Block[][];
132
- } | {
133
- type: "page-break";
134
- } | {
135
- type: "table-of-contents";
136
- } | {
137
- type: "chart";
138
- spec: ChartSpec;
139
- } | {
140
- type: "diagram";
141
- engine: "mermaid" | "canvas" | "excalidraw";
142
- diagram?: DiagramDocument;
143
- source?: string;
144
- } | {
145
- type: "data-view";
146
- dataset: string;
147
- view: SavedView;
148
- } | {
149
- type: "ai";
150
- prompt: string;
151
- result?: Block[];
152
- });
153
- type BlockType = Block["type"];
154
- interface EditorDocument {
155
- version: typeof DOCUMENT_VERSION;
121
+ declare const defaultExportLabels: ExportLabels;
122
+ interface HtmlOptions {
123
+ /** Prefix for generated element ids (headings, task labels). Defaults to `kit-`. */
124
+ idPrefix?: string;
125
+ /** Wrap the output in `<div data-kit="editor-view">`. Defaults to false. */
126
+ wrap?: boolean;
127
+ labels?: Partial<ExportLabels>;
128
+ /** Locale for number formatting (file sizes). */
129
+ locale?: string;
130
+ }
131
+ /** Escapes text for use in HTML content or a quoted attribute. */
132
+ declare function escapeHtml(value: string): string;
133
+ /**
134
+ * Renders the document as safe HTML (see the note at the top of this file).
135
+ * Block ids become `data-block-id`; headings get an `id` so a table of
136
+ * contents block can link to them.
137
+ */
138
+ declare function documentToHtml(doc: EditorDocument, options?: HtmlOptions): string;
139
+
140
+ interface MarkdownOptions {
141
+ /** Keep marks Markdown has no syntax for (underline, color, highlight) as inline HTML. Defaults to true. */
142
+ inlineHtml?: boolean;
143
+ }
144
+ /** Escapes characters that would start Markdown syntax inside running text. */
145
+ declare function escapeMarkdown(text: string): string;
146
+ /**
147
+ * Renders the document as Markdown. A level 1 `title` is not added; the
148
+ * document's `title` field is metadata, not content.
149
+ */
150
+ declare function documentToMarkdown(doc: EditorDocument, options?: MarkdownOptions): string;
151
+
152
+ interface MarkdownImportOptions {
153
+ /** Creates block ids. Defaults to `generateBlockId`. */
154
+ generateId?: () => string;
155
+ /** Document title to set. */
156
156
  title?: string;
157
- blocks: Block[];
158
157
  }
159
- /** An empty document with one empty paragraph. */
160
- declare function createEmptyDocument(id?: string): EditorDocument;
161
- /** Shared document model behind a provider interface (Yjs and Hocuspocus planned). */
162
- interface CollaborationProvider {
163
- connect(documentId: string): Promise<void>;
164
- disconnect(): void;
165
- onPresence(listener: (users: Array<{
166
- id: string;
167
- name: string;
168
- color: string;
169
- }>) => void): () => void;
158
+ /** Parses inline Markdown into schema inline content (canonical form). */
159
+ declare function markdownToInline(markdown: string): Inline;
160
+ /**
161
+ * Parses Markdown into a document. Block ids are generated; Markdown has no
162
+ * ids. Line endings of any kind are accepted.
163
+ */
164
+ declare function markdownToDocument(markdown: string, options?: MarkdownImportOptions): EditorDocument;
165
+
166
+ interface DocumentDiff {
167
+ /** Ids present only in the newer document. */
168
+ added: string[];
169
+ /** Ids present only in the older document. */
170
+ removed: string[];
171
+ /** Ids in both whose own content differs (children of containers are compared by their own ids). */
172
+ changed: string[];
173
+ /** Ids in both, with the same content, that sit in a different order or place. */
174
+ moved: string[];
175
+ /** Ids in both with no difference. */
176
+ unchanged: string[];
177
+ }
178
+ /** Compares two documents by block id (nested blocks included). */
179
+ declare function diffDocuments(previous: EditorDocument, next: EditorDocument): DocumentDiff;
180
+
181
+ type ValidationCode = "invalid-document" | "unsupported-version" | "invalid-block" | "unknown-block-type" | "missing-id" | "duplicate-id" | "invalid-field" | "invalid-inline" | "unsafe-url" | "unknown-token";
182
+ interface ValidationIssue {
183
+ code: ValidationCode;
184
+ /** `error` means the document cannot be rendered as is; `warning` means a renderer will neutralize it. */
185
+ severity: "error" | "warning";
186
+ /** A path such as `blocks[2].items[0].content[1]`. */
187
+ path: string;
188
+ message: string;
170
189
  }
171
- type AiCommand = "continue" | "summarize" | "rewrite" | "fix-grammar";
172
- /** Provider-agnostic hook. The kit calls no model by itself. */
173
- interface AiProvider {
174
- run(input: {
175
- command: AiCommand;
176
- selection: string;
177
- /** For `rewrite`, for example "formal". */
178
- tone?: string;
179
- signal?: AbortSignal;
180
- }): AsyncIterable<string>;
190
+ interface ValidationResult {
191
+ /** True when there are no errors. Warnings do not make a document invalid. */
192
+ valid: boolean;
193
+ issues: ValidationIssue[];
181
194
  }
195
+ /** Checks an unknown value against the document schema and reports every problem found. */
196
+ declare function validateDocument(value: unknown): ValidationResult;
197
+ /** Type guard: the value has no structural errors (warnings are allowed). */
198
+ declare function isEditorDocument(value: unknown): value is EditorDocument;
199
+ /**
200
+ * Returns a copy of the document that is safe to render: links with unsafe
201
+ * URLs lose the link mark, media with unsafe sources get an empty source,
202
+ * and color marks outside the token allow-list are dropped. Structure and
203
+ * ids are kept. Renderers already neutralize these values; sanitize when
204
+ * you store or forward a document you do not control.
205
+ */
206
+ declare function sanitizeDocument(doc: EditorDocument): EditorDocument;
207
+
208
+ /**
209
+ * A small shortcode table for the `emoji` inline node, so exports and the
210
+ * editor can show a character without an emoji dependency. Unknown shortcodes
211
+ * render as `:shortcode:`, which is also what Markdown uses.
212
+ */
213
+ declare const EMOJI_SHORTCODES: Readonly<Record<string, string>>;
214
+ /** The character for a shortcode (with or without colons), or `null` when it is not in the table. */
215
+ declare function emojiForShortcode(shortcode: string): string | null;
216
+ /** The character when known, otherwise `:shortcode:`. */
217
+ declare function emojiToText(shortcode: string): string;
182
218
 
183
- export { DOCUMENT_VERSION, createEmptyDocument };
184
- export type { AiCommand, AiProvider, Block, BlockBase, BlockType, CalloutVariant, CollaborationProvider, EditorDocument, Inline, InlineNode, ListItem, Mark, TableCell, TaskItem };
219
+ export { Block, EMOJI_SHORTCODES, EditorDocument, HIGHLIGHT_TOKENS, Inline, TEXT_COLOR_TOKENS, collectIds, countCharacters, countWords, createSeededRandom, createSequentialIdGenerator, defaultExportLabels, diffDocuments, documentToHtml, documentToMarkdown, documentToPlainText, emojiForShortcode, emojiToText, escapeHtml, escapeMarkdown, findDuplicateIds, generateBlockId, getDocumentStats, highlightToCss, inlineToPlainText, isEditorDocument, isHighlightToken, isSafeUrl, isTextColorToken, markdownToDocument, markdownToInline, normalizeUrlInput, sanitizeDocument, sanitizeMediaUrl, sanitizeUrl, textColorToCss, tokenToCssVarName, validateDocument, walkBlocks };
220
+ export type { DocumentDiff, DocumentStats, ExportLabels, HighlightToken, HtmlOptions, MarkdownImportOptions, MarkdownOptions, PlainTextOptions, SanitizeUrlOptions, TextColorToken, ValidationCode, ValidationIssue, ValidationResult };