@orkestrel/markdown 0.0.6 → 0.0.7
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/README.md +49 -64
- package/dist/src/core/index.cjs +2722 -1734
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +489 -126
- package/dist/src/core/index.d.ts +489 -126
- package/dist/src/core/index.js +2706 -1730
- package/dist/src/core/index.js.map +1 -1
- package/package.json +7 -6
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { BooleanShape } from '@orkestrel/contract';
|
|
2
|
+
import { CommentNode } from '@orkestrel/html';
|
|
2
3
|
import { ContractInterface } from '@orkestrel/contract';
|
|
4
|
+
import { DoctypeNode } from '@orkestrel/html';
|
|
5
|
+
import { ElementNode } from '@orkestrel/html';
|
|
3
6
|
import { Guard } from '@orkestrel/contract';
|
|
7
|
+
import { HTMLDocument } from '@orkestrel/html';
|
|
8
|
+
import { HTMLNode } from '@orkestrel/html';
|
|
4
9
|
import { LiteralShape } from '@orkestrel/contract';
|
|
5
10
|
import { NumberShape } from '@orkestrel/contract';
|
|
6
11
|
import { ObjectShape } from '@orkestrel/contract';
|
|
7
12
|
import { OptionalShape } from '@orkestrel/contract';
|
|
8
13
|
import { StringShape } from '@orkestrel/contract';
|
|
14
|
+
import { TextNode as TextNode_2 } from '@orkestrel/html';
|
|
9
15
|
|
|
10
16
|
/** A node that can appear at the block level of a document (or inside a list item / blockquote). */
|
|
11
17
|
export declare type BlockNode = HeadingNode | ParagraphNode | ListNode | TableNode | CodeBlockNode | BlockquoteNode | ThematicBreakNode;
|
|
@@ -131,6 +137,20 @@ export declare function collectTable(lines: readonly string[], start: number): {
|
|
|
131
137
|
readonly next: number;
|
|
132
138
|
};
|
|
133
139
|
|
|
140
|
+
/**
|
|
141
|
+
* The count of leading space / tab characters on `line` (a tab counts as one) - the
|
|
142
|
+
* indent that decides whether a list item's continuation belongs to the item.
|
|
143
|
+
*
|
|
144
|
+
* @param line - The line to measure
|
|
145
|
+
* @returns The number of leading space / tab characters
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* countIndent(' text') // 2
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare function countIndent(line: string): number;
|
|
153
|
+
|
|
134
154
|
/**
|
|
135
155
|
* Compile the {@link codeBlockShape} into a {@link ContractInterface} for
|
|
136
156
|
* {@link CodeBlockNode} - a guard, coercing parser, JSON Schema, and seeded
|
|
@@ -165,6 +185,21 @@ export declare function createCodeBlockContract(): ContractInterface<CodeBlockNo
|
|
|
165
185
|
*/
|
|
166
186
|
export declare function createCodeSpanContract(): ContractInterface<CodeSpanNode>;
|
|
167
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Compile the {@link lineBreakShape} into a {@link ContractInterface} for
|
|
190
|
+
* {@link LineBreakNode}.
|
|
191
|
+
*
|
|
192
|
+
* @returns A `LineBreakNode` contract bundling `schema` / `is` / `parse` / `generate`
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* import { createLineBreakContract } from '@src/core'
|
|
197
|
+
*
|
|
198
|
+
* createLineBreakContract().is({ element: 'break' }) // true
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export declare function createLineBreakContract(): ContractInterface<LineBreakNode>;
|
|
202
|
+
|
|
168
203
|
/**
|
|
169
204
|
* Create a stateful markdown handle from a markdown string or an already-parsed
|
|
170
205
|
* {@link MarkdownDocument} - a typed AST plus the query, rewrite, and fold operations
|
|
@@ -173,7 +208,8 @@ export declare function createCodeSpanContract(): ContractInterface<CodeSpanNode
|
|
|
173
208
|
* @remarks
|
|
174
209
|
* Given a `string`, runs a block phase (headings / paragraphs / lists / GFM tables /
|
|
175
210
|
* fenced code / blockquotes / thematic breaks) then an inline phase (emphasis /
|
|
176
|
-
* inline code / links) to build a render-agnostic
|
|
211
|
+
* inline code / links / images / hard breaks) to build a render-agnostic
|
|
212
|
+
* {@link MarkdownDocument}. Given a
|
|
177
213
|
* {@link MarkdownDocument}, adopts it AS-IS without re-validation - gate an untrusted
|
|
178
214
|
* value with `isMarkdownDocument` first. Pure + total parse (malformed markdown
|
|
179
215
|
* degrades to text, never throws) and zero-dependency - a hand-written scanner, no
|
|
@@ -192,6 +228,28 @@ export declare function createCodeSpanContract(): ContractInterface<CodeSpanNode
|
|
|
192
228
|
*/
|
|
193
229
|
export declare function createMarkdown(input: string | MarkdownDocument): MarkdownInterface;
|
|
194
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Create an HTML-to-markdown projection with absent fields defaulted from
|
|
233
|
+
* {@link EMPTY_PROJECTION} and the block/inline exclusivity invariant enforced.
|
|
234
|
+
*
|
|
235
|
+
* @remarks
|
|
236
|
+
* A block-bearing projection cannot also expose inline content. Callers may provide
|
|
237
|
+
* both views, but `inlines` is flushed whenever `blocks` is non-empty.
|
|
238
|
+
*
|
|
239
|
+
* @param parts - The projection fields to provide
|
|
240
|
+
* @returns A complete invariant-preserving projection
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* createProjection({
|
|
245
|
+
* blocks: [{ element: 'thematicBreak' }],
|
|
246
|
+
* inlines: [{ element: 'text', value: 'discarded' }],
|
|
247
|
+
* })
|
|
248
|
+
* // { blocks: [{ element: 'thematicBreak' }], inlines: [], text: '', cells: [], rows: [] }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export declare function createProjection(parts?: Partial<MarkdownProjection>): MarkdownProjection;
|
|
252
|
+
|
|
195
253
|
/**
|
|
196
254
|
* Compile the {@link textShape} into a {@link ContractInterface} for
|
|
197
255
|
* {@link TextNode} - a guard, coercing parser, JSON Schema, and seeded
|
|
@@ -226,6 +284,21 @@ export declare function createTextContract(): ContractInterface<TextNode>;
|
|
|
226
284
|
*/
|
|
227
285
|
export declare function createThematicBreakContract(): ContractInterface<ThematicBreakNode>;
|
|
228
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Derive the per-column {@link TableAlign} list from a GFM delimiter row - `:---`
|
|
289
|
+
* left, `---:` right, `:---:` center, and `---` as the explicit no-alignment
|
|
290
|
+
* marker represented by `null`.
|
|
291
|
+
*
|
|
292
|
+
* @param delimiter - The table's delimiter row
|
|
293
|
+
* @returns One alignment per column, in column order
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* delimiterToAlignments('| :--- | ---: |') // ['left', 'right']
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
export declare function delimiterToAlignments(delimiter: string): readonly (TableAlign | null)[];
|
|
301
|
+
|
|
229
302
|
/**
|
|
230
303
|
* Emphasized inline content - `*italic*` / `_italic_` (`strong: false`) or
|
|
231
304
|
* `**bold**` / `__bold__` (`strong: true`). `children` are the nested inline nodes,
|
|
@@ -241,19 +314,16 @@ export declare interface EmphasisNode {
|
|
|
241
314
|
}
|
|
242
315
|
|
|
243
316
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* text run, code body, and (escaped further) attribute value.
|
|
247
|
-
*
|
|
248
|
-
* @param text - The raw text
|
|
249
|
-
* @returns The HTML-escaped text
|
|
317
|
+
* The frozen empty HTML-to-markdown projection from which projection factories
|
|
318
|
+
* default every absent field.
|
|
250
319
|
*
|
|
251
320
|
* @example
|
|
252
321
|
* ```ts
|
|
253
|
-
*
|
|
322
|
+
* EMPTY_PROJECTION.blocks // []
|
|
323
|
+
* Object.isFrozen(EMPTY_PROJECTION) // true
|
|
254
324
|
* ```
|
|
255
325
|
*/
|
|
256
|
-
export declare
|
|
326
|
+
export declare const EMPTY_PROJECTION: MarkdownProjection;
|
|
257
327
|
|
|
258
328
|
/**
|
|
259
329
|
* Extract a fenced-code opening line (```` ``` ```` or `~~~`, optionally with an info
|
|
@@ -295,7 +365,7 @@ export declare function extractHeading(line: string): {
|
|
|
295
365
|
|
|
296
366
|
/**
|
|
297
367
|
* Extract a list-item line (`-` / `*` / `+` bullet, or `1.` / `1)` ordinal, followed by
|
|
298
|
-
* a space) into its {@link
|
|
368
|
+
* a space) into its {@link ListItemMatch}, or `undefined` when `line` is not a list
|
|
299
369
|
* item. `content` is the text after the marker; `marker` is the full marker-plus-space
|
|
300
370
|
* width (for measuring a continuation's indent).
|
|
301
371
|
*
|
|
@@ -307,12 +377,13 @@ export declare function extractHeading(line: string): {
|
|
|
307
377
|
* extractListItem('- item') // { ordered: false, start: 1, content: 'item', indent: 0, marker: 2 }
|
|
308
378
|
* ```
|
|
309
379
|
*/
|
|
310
|
-
export declare function extractListItem(line: string):
|
|
380
|
+
export declare function extractListItem(line: string): ListItemMatch | undefined;
|
|
311
381
|
|
|
312
382
|
/**
|
|
313
383
|
* Concatenate the `value` / `code` content of every descendant text / code-span /
|
|
314
|
-
* code-block node under `node`, in walk order -
|
|
315
|
-
* (search indexing, word counts, a text-only
|
|
384
|
+
* code-block node under `node`, including image alternative content, in walk order -
|
|
385
|
+
* the plain-text projection of an AST (search indexing, word counts, a text-only
|
|
386
|
+
* preview).
|
|
316
387
|
*
|
|
317
388
|
* @remarks
|
|
318
389
|
* Total: never throws. Descent stops at {@link MAX_DEPTH} (contributes `''` past the
|
|
@@ -377,8 +448,66 @@ export declare interface HeadingNode {
|
|
|
377
448
|
readonly children: readonly InlineNode[];
|
|
378
449
|
}
|
|
379
450
|
|
|
451
|
+
/**
|
|
452
|
+
* Project an `@orkestrel/html` {@link HTMLNode} into a {@link MarkdownDocument} - the
|
|
453
|
+
* HTML→markdown direction, and the inverse of {@link markdownToHTML}.
|
|
454
|
+
*
|
|
455
|
+
* @remarks
|
|
456
|
+
* **Engine.** One total handler table - {@link projectHTMLNode} for the containers,
|
|
457
|
+
* {@link projectHTMLLeaf} for the leaves - folded by `@orkestrel/html`'s own `foldNode`, so
|
|
458
|
+
* depth capping, cycle safety, and bottom-up ordering are inherited rather than
|
|
459
|
+
* rebuilt. Total: hostile, cyclic, and pathologically deep input degrades instead of
|
|
460
|
+
* throwing.
|
|
461
|
+
*
|
|
462
|
+
* **Composed depth.** Both packages cap recursion at 64, and html's cap is reached
|
|
463
|
+
* first: a document nested past it projects to a chain bounded by THAT cap, with the
|
|
464
|
+
* content below it truncated before markdown ever sees it. Since the projected chain
|
|
465
|
+
* can be a level or two deeper than {@link MAX_DEPTH}, the serializer's own cap can
|
|
466
|
+
* then truncate again - so the anchor law below is a law within the depth budget, and
|
|
467
|
+
* beyond it only totality is promised.
|
|
468
|
+
*
|
|
469
|
+
* **Safety.** Every `href` and `src` is re-sanitized through
|
|
470
|
+
* `sanitizeURL(value, SAFE_URL_SCHEMES)` whether or not the AST was ever sanitized,
|
|
471
|
+
* because a hand-built one never was. A refused destination empties to `''` and the
|
|
472
|
+
* link or image is KEPT - `[text]()` - since a bad URL is no reason to lose the words
|
|
473
|
+
* around it. An `UNSAFE_ELEMENTS` subtree contributes nothing at all, text included, so
|
|
474
|
+
* a `script` body can never resurface as prose.
|
|
475
|
+
*
|
|
476
|
+
* **The anchor law.** HTML→markdown is lossy, so the fixpoint that matters is the
|
|
477
|
+
* PROJECTED AST, not the input bytes:
|
|
478
|
+
* `parseDocument(renderMarkdown(htmlToMarkdown(x)))` deep-equals `htmlToMarkdown(x)`.
|
|
479
|
+
* The projection therefore emits canonical markdown shapes rather than literal
|
|
480
|
+
* translations - whitespace collapsed, edges trimmed, a blank paragraph dropped, a hard
|
|
481
|
+
* break only where a line can end - because a shape markdown cannot write back is a
|
|
482
|
+
* shape this projection has no business producing.
|
|
483
|
+
*
|
|
484
|
+
* @param node - The HTML document or bare node to project
|
|
485
|
+
* @returns The projected markdown document
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```ts
|
|
489
|
+
* import { parseDocument } from '@orkestrel/html'
|
|
490
|
+
*
|
|
491
|
+
* htmlToMarkdown(parseDocument('<h1>Title</h1>'))
|
|
492
|
+
* // { element: 'document', children: [{ element: 'heading', level: 1, children: [...] }] }
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
export declare function htmlToMarkdown(node: HTMLNode): MarkdownDocument;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* An inline image - ``. `children` are the inline nodes of the
|
|
499
|
+
* alternative content and `src` is the image destination.
|
|
500
|
+
*/
|
|
501
|
+
export declare interface ImageNode {
|
|
502
|
+
readonly element: 'image';
|
|
503
|
+
/** The image destination. */
|
|
504
|
+
readonly src: string;
|
|
505
|
+
/** The inline alternative content. */
|
|
506
|
+
readonly children: readonly InlineNode[];
|
|
507
|
+
}
|
|
508
|
+
|
|
380
509
|
/** A node that can appear inside inline content (a heading / paragraph / cell / list item / link text). */
|
|
381
|
-
export declare type InlineNode = TextNode | EmphasisNode | CodeSpanNode | LinkNode;
|
|
510
|
+
export declare type InlineNode = TextNode | EmphasisNode | CodeSpanNode | LineBreakNode | LinkNode | ImageNode;
|
|
382
511
|
|
|
383
512
|
/**
|
|
384
513
|
* Whether `line` is blank - empty, or containing only whitespace - the markdown
|
|
@@ -512,9 +641,19 @@ export declare function isFenceWhitespace(character: string | undefined): boolea
|
|
|
512
641
|
/** Determine whether a node is a heading block. */
|
|
513
642
|
export declare function isHeadingNode(node: MarkdownNode): node is HeadingNode;
|
|
514
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Determine whether a node is an image.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```ts
|
|
649
|
+
* isImageNode({ element: 'image', src: 'x.png', children: [] }) // true
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
export declare function isImageNode(node: MarkdownNode): node is ImageNode;
|
|
653
|
+
|
|
515
654
|
/**
|
|
516
655
|
* Determine whether an arbitrary value is a valid {@link InlineNode} - a text
|
|
517
|
-
* run, emphasis, code span,
|
|
656
|
+
* run, emphasis, code span, hard break, link, or image, recursively validated.
|
|
518
657
|
*
|
|
519
658
|
* @remarks
|
|
520
659
|
* Total: never throws, even on cyclic or pathologically deep input - every
|
|
@@ -534,6 +673,16 @@ export declare function isHeadingNode(node: MarkdownNode): node is HeadingNode;
|
|
|
534
673
|
*/
|
|
535
674
|
export declare const isInlineNode: Guard<InlineNode>;
|
|
536
675
|
|
|
676
|
+
/**
|
|
677
|
+
* Determine whether a node is a GFM hard line break.
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```ts
|
|
681
|
+
* isLineBreakNode({ element: 'break' }) // true
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
684
|
+
export declare function isLineBreakNode(node: MarkdownNode): node is LineBreakNode;
|
|
685
|
+
|
|
537
686
|
/** Determine whether a node is a link. */
|
|
538
687
|
export declare function isLinkNode(node: MarkdownNode): node is LinkNode;
|
|
539
688
|
|
|
@@ -688,24 +837,31 @@ export declare function isThematicBreakNode(node: MarkdownNode): node is Themati
|
|
|
688
837
|
*/
|
|
689
838
|
export declare function isWhitespace(character: string): boolean;
|
|
690
839
|
|
|
840
|
+
/** A GFM hard line break - two or more trailing spaces before a newline. */
|
|
841
|
+
export declare interface LineBreakNode {
|
|
842
|
+
readonly element: 'break';
|
|
843
|
+
}
|
|
844
|
+
|
|
691
845
|
/**
|
|
692
|
-
* The
|
|
693
|
-
* indent that decides whether a list item's continuation belongs to the item.
|
|
694
|
-
*
|
|
695
|
-
* @param line - The line to measure
|
|
696
|
-
* @returns The number of leading space / tab characters
|
|
846
|
+
* The shape of a {@link LineBreakNode} - a GFM hard line-break leaf.
|
|
697
847
|
*
|
|
698
848
|
* @example
|
|
699
849
|
* ```ts
|
|
700
|
-
*
|
|
850
|
+
* import { createContract } from '@orkestrel/contract'
|
|
851
|
+
* import { lineBreakShape } from '@src/core'
|
|
852
|
+
*
|
|
853
|
+
* const lineBreak = createContract(lineBreakShape)
|
|
854
|
+
* lineBreak.is({ element: 'break' }) // true
|
|
701
855
|
* ```
|
|
702
856
|
*/
|
|
703
|
-
export declare
|
|
857
|
+
export declare const lineBreakShape: ObjectShape<{
|
|
858
|
+
element: LiteralShape<readonly ["break"]>;
|
|
859
|
+
}, false>;
|
|
704
860
|
|
|
705
861
|
/**
|
|
706
|
-
* An inline link - `[text](href)`. `children` are the inline nodes of the link text
|
|
707
|
-
*
|
|
708
|
-
*
|
|
862
|
+
* An inline link - `[text](href)`. `children` are the inline nodes of the link text.
|
|
863
|
+
* At render, html's floor removes a refused `href` attribute and the link keeps its
|
|
864
|
+
* text; {@link htmlToMarkdown} instead stores a refused destination as `''`.
|
|
709
865
|
*/
|
|
710
866
|
export declare interface LinkNode {
|
|
711
867
|
readonly element: 'link';
|
|
@@ -715,18 +871,11 @@ export declare interface LinkNode {
|
|
|
715
871
|
readonly children: readonly InlineNode[];
|
|
716
872
|
}
|
|
717
873
|
|
|
718
|
-
/** One item of a {@link ListNode} - `children` the block content of the item (typically one paragraph, plus any nested list). */
|
|
719
|
-
export declare interface ListItemNode {
|
|
720
|
-
readonly element: 'listItem';
|
|
721
|
-
/** The block content of the list item (its text as a paragraph, plus any nested list). */
|
|
722
|
-
readonly children: readonly BlockNode[];
|
|
723
|
-
}
|
|
724
|
-
|
|
725
874
|
/**
|
|
726
875
|
* The parsed parts of a single list-item line - the value the block phase's
|
|
727
876
|
* list detector returns for a `-` / `*` / `+` bullet or a `1.` / `1)` ordinal line.
|
|
728
877
|
*/
|
|
729
|
-
export declare interface
|
|
878
|
+
export declare interface ListItemMatch {
|
|
730
879
|
/** `true` for an ordered (`1.` / `1)`) item, `false` for a bullet (`-` / `*` / `+`). */
|
|
731
880
|
readonly ordered: boolean;
|
|
732
881
|
/** The ordinal of an ordered item (its number); `1` for a bullet. */
|
|
@@ -740,20 +889,20 @@ export declare interface ListItemParts {
|
|
|
740
889
|
}
|
|
741
890
|
|
|
742
891
|
/**
|
|
743
|
-
* The shape of {@link
|
|
892
|
+
* The shape of {@link ListItemMatch} - the parsed parts of a single list-item
|
|
744
893
|
* line the block phase's list detector returns. Fully non-recursive (no
|
|
745
894
|
* nested node fields), so every field shapes directly.
|
|
746
895
|
*
|
|
747
896
|
* @example
|
|
748
897
|
* ```ts
|
|
749
898
|
* import { createContract } from '@orkestrel/contract'
|
|
750
|
-
* import {
|
|
899
|
+
* import { listItemMatchShape } from '@src/core'
|
|
751
900
|
*
|
|
752
|
-
* const listItemParts = createContract(
|
|
901
|
+
* const listItemParts = createContract(listItemMatchShape)
|
|
753
902
|
* listItemParts.is({ ordered: false, start: 1, content: 'hi', indent: 0, marker: 2 }) // true
|
|
754
903
|
* ```
|
|
755
904
|
*/
|
|
756
|
-
export declare const
|
|
905
|
+
export declare const listItemMatchShape: ObjectShape<{
|
|
757
906
|
ordered: BooleanShape;
|
|
758
907
|
start: NumberShape;
|
|
759
908
|
content: StringShape;
|
|
@@ -761,6 +910,13 @@ export declare const listItemPartsShape: ObjectShape<{
|
|
|
761
910
|
marker: NumberShape;
|
|
762
911
|
}, false>;
|
|
763
912
|
|
|
913
|
+
/** One item of a {@link ListNode} - `children` the block content of the item (typically one paragraph, plus any nested list). */
|
|
914
|
+
export declare interface ListItemNode {
|
|
915
|
+
readonly element: 'listItem';
|
|
916
|
+
/** The block content of the list item (its text as a paragraph, plus any nested list). */
|
|
917
|
+
readonly children: readonly BlockNode[];
|
|
918
|
+
}
|
|
919
|
+
|
|
764
920
|
/**
|
|
765
921
|
* A list - bulleted (`-` / `*` / `+`, `ordered: false`) or numbered (`1.` / `1)`,
|
|
766
922
|
* `ordered: true`). `start` is the first ordinal of an ordered list (usually `1`).
|
|
@@ -863,6 +1019,14 @@ export declare class Markdown implements MarkdownInterface {
|
|
|
863
1019
|
stream(): ReadableStream<BlockNode>;
|
|
864
1020
|
}
|
|
865
1021
|
|
|
1022
|
+
/** One projected table cell - the inline content and alignment of a `th` / `td`. */
|
|
1023
|
+
export declare interface MarkdownCell {
|
|
1024
|
+
/** The alignment the cell's `align` attribute declared; `undefined` when it declared none. */
|
|
1025
|
+
readonly align: TableAlign | undefined;
|
|
1026
|
+
/** The cell's inline content - a table cell is inline-only, so block content flattens to text. */
|
|
1027
|
+
readonly inlines: readonly InlineNode[];
|
|
1028
|
+
}
|
|
1029
|
+
|
|
866
1030
|
/**
|
|
867
1031
|
* The root of a parsed markdown AST - the ordered block children of the whole
|
|
868
1032
|
* document. The value {@link MarkdownInterface.document} holds.
|
|
@@ -916,8 +1080,12 @@ export declare interface MarkdownHandlers<T> {
|
|
|
916
1080
|
readonly emphasis: MarkdownHandler<EmphasisNode, T>;
|
|
917
1081
|
/** Folds a {@link CodeSpanNode} (leaf - always called with an empty children list). */
|
|
918
1082
|
readonly codeSpan: MarkdownHandler<CodeSpanNode, T>;
|
|
1083
|
+
/** Folds a {@link LineBreakNode} (leaf - always called with an empty children list). */
|
|
1084
|
+
readonly break: MarkdownHandler<LineBreakNode, T>;
|
|
919
1085
|
/** Folds a {@link LinkNode} from its already-folded inline children. */
|
|
920
1086
|
readonly link: MarkdownHandler<LinkNode, T>;
|
|
1087
|
+
/** Folds an {@link ImageNode} from its already-folded alternative content. */
|
|
1088
|
+
readonly image: MarkdownHandler<ImageNode, T>;
|
|
921
1089
|
}
|
|
922
1090
|
|
|
923
1091
|
/**
|
|
@@ -979,11 +1147,46 @@ export declare interface MarkdownInterface {
|
|
|
979
1147
|
|
|
980
1148
|
/**
|
|
981
1149
|
* Any node in a markdown AST - the {@link MarkdownDocument} root, a {@link BlockNode},
|
|
982
|
-
* a {@link ListItemNode}, or an {@link InlineNode}. The exhaustive set
|
|
983
|
-
* `switch` covers.
|
|
1150
|
+
* a {@link ListItemNode}, or an {@link InlineNode}. The exhaustive set every
|
|
1151
|
+
* projection's `switch` covers.
|
|
984
1152
|
*/
|
|
985
1153
|
export declare type MarkdownNode = MarkdownDocument | BlockNode | ListItemNode | InlineNode;
|
|
986
1154
|
|
|
1155
|
+
/**
|
|
1156
|
+
* What one HTML node projects to on the way to markdown - the fold value
|
|
1157
|
+
* `htmlToMarkdown` carries up the AST.
|
|
1158
|
+
*
|
|
1159
|
+
* @remarks
|
|
1160
|
+
* A node projects to several things at once because markdown decides late what a
|
|
1161
|
+
* given HTML subtree becomes: a `td`'s content is inline in a table and a paragraph
|
|
1162
|
+
* outside one, and a `code` body is a code span in prose and a verbatim code block
|
|
1163
|
+
* under a `pre`. Rather than guess, each node reports every view its ancestors could
|
|
1164
|
+
* need, and the ancestor that knows the context takes the one it wants.
|
|
1165
|
+
*
|
|
1166
|
+
* - `blocks` / `inlines` - the block and inline views. They are exclusive by
|
|
1167
|
+
* construction: as soon as a node contributes a block, the inline runs around it
|
|
1168
|
+
* are wrapped into paragraphs, so `blocks` being non-empty means `inlines` is
|
|
1169
|
+
* empty and no interleaving is ever lost.
|
|
1170
|
+
* - `text` - the raw, uncollapsed, unescaped subtree text a code span and a
|
|
1171
|
+
* `pre > code` body need verbatim. An `UNSAFE_ELEMENTS` subtree contributes none
|
|
1172
|
+
* of it, so a script body can never resurface as prose.
|
|
1173
|
+
* - `cells` / `rows` - table structure in flight. A cell travels up to its `tr` and a
|
|
1174
|
+
* row up to its `table`, passing through the `thead` / `tbody` wrappers between
|
|
1175
|
+
* them untouched; whatever never reaches a table degrades to paragraphs.
|
|
1176
|
+
*/
|
|
1177
|
+
export declare interface MarkdownProjection {
|
|
1178
|
+
/** The node's block content, with any surrounding inline runs already wrapped into paragraphs. */
|
|
1179
|
+
readonly blocks: readonly BlockNode[];
|
|
1180
|
+
/** The node's inline content; empty whenever `blocks` is not. */
|
|
1181
|
+
readonly inlines: readonly InlineNode[];
|
|
1182
|
+
/** The raw subtree text, whitespace uncollapsed and escapes unresolved. */
|
|
1183
|
+
readonly text: string;
|
|
1184
|
+
/** The cells this node contributes to an enclosing row. */
|
|
1185
|
+
readonly cells: readonly MarkdownCell[];
|
|
1186
|
+
/** The rows this node contributes to an enclosing table - each its cells, in column order. */
|
|
1187
|
+
readonly rows: readonly (readonly MarkdownCell[])[];
|
|
1188
|
+
}
|
|
1189
|
+
|
|
987
1190
|
/**
|
|
988
1191
|
* A copy-on-write node rewrite applied bottom-up by {@link MarkdownInterface.map} -
|
|
989
1192
|
* receives one node (its own children already rewritten) and returns its
|
|
@@ -991,17 +1194,92 @@ export declare type MarkdownNode = MarkdownDocument | BlockNode | ListItemNode |
|
|
|
991
1194
|
*/
|
|
992
1195
|
export declare type MarkdownRewriteHandler = (node: MarkdownNode) => MarkdownNode;
|
|
993
1196
|
|
|
1197
|
+
/**
|
|
1198
|
+
* Project a {@link MarkdownNode} into an unsanitized {@link HTMLDocument}.
|
|
1199
|
+
*
|
|
1200
|
+
* @remarks
|
|
1201
|
+
* The projection is pure and iterative. Text and attribute values remain literal for
|
|
1202
|
+
* `@orkestrel/html` to encode, and URL values remain unsanitized so callers can choose
|
|
1203
|
+
* their own HTML policy. Projected HTML element depth, including generated `pre > code`
|
|
1204
|
+
* and table scaffolding, never exceeds {@link MAX_DEPTH}. At the cap a node carrying a
|
|
1205
|
+
* string `value` degrades to a text node and a structural node contributes nothing.
|
|
1206
|
+
*
|
|
1207
|
+
* @param node - The markdown document or bare node to project
|
|
1208
|
+
* @returns An unsanitized HTML document wrapping the projected node or nodes
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```ts
|
|
1212
|
+
* markdownToHTML({ element: 'text', value: 'a & b' })
|
|
1213
|
+
* // { category: 'document', children: [{ category: 'text', value: 'a & b' }] }
|
|
1214
|
+
* ```
|
|
1215
|
+
*/
|
|
1216
|
+
export declare function markdownToHTML(node: MarkdownNode): HTMLDocument;
|
|
1217
|
+
|
|
994
1218
|
/**
|
|
995
1219
|
* The maximum recursion depth the parse pipeline (`parseDocument` and its
|
|
996
|
-
* `parsers.ts` helpers) and the `helpers.ts` traversal /
|
|
997
|
-
* (`renderHTML`, `renderMarkdown`, `walkNodes`, `foldNode
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1220
|
+
* `parsers.ts` helpers) and the `helpers.ts` traversal / projection functions
|
|
1221
|
+
* (`markdownToHTML`, `renderHTML`, `renderMarkdown`, `walkNodes`, `foldNode`,
|
|
1222
|
+
* `rewriteDocument`) honor before degrading. It bounds blockquote nesting, inline
|
|
1223
|
+
* nesting (emphasis / links), and traversal / projection recursion so pathological
|
|
1224
|
+
* or hostile input cannot exhaust the call stack. {@link htmlToMarkdown} is the
|
|
1225
|
+
* inherited exception: its fold and depth cap belong to `@orkestrel/html`.
|
|
1002
1226
|
*/
|
|
1003
1227
|
export declare const MAX_DEPTH = 64;
|
|
1004
1228
|
|
|
1229
|
+
/**
|
|
1230
|
+
* Combine the projections of one node's children into the projection of that node -
|
|
1231
|
+
* the single place inline runs become paragraphs, so no ancestor has to decide it
|
|
1232
|
+
* twice.
|
|
1233
|
+
*
|
|
1234
|
+
* @remarks
|
|
1235
|
+
* A child is either inline or block, never both, so merging preserves source order
|
|
1236
|
+
* exactly: an inline run is held pending until a block arrives, then written out as a
|
|
1237
|
+
* paragraph BEFORE it. That is what keeps `<div>lead<p>a</p></div>` two paragraphs in
|
|
1238
|
+
* the order they were written rather than two lists that lost their interleaving. A
|
|
1239
|
+
* pending run carrying no text is dropped rather than becoming a blank paragraph.
|
|
1240
|
+
* Direct cells become one row before a later row, while cells/rows before a block
|
|
1241
|
+
* materialize as paragraphs at that exact source position.
|
|
1242
|
+
*
|
|
1243
|
+
* @param children - The children's projections, in source order
|
|
1244
|
+
* @returns Their combined projection
|
|
1245
|
+
*
|
|
1246
|
+
* @example
|
|
1247
|
+
* ```ts
|
|
1248
|
+
* mergeProjections([
|
|
1249
|
+
* createProjection({ inlines: [{ element: 'text', value: 'a' }], text: 'a' }),
|
|
1250
|
+
* createProjection({ blocks: [{ element: 'thematicBreak' }] }),
|
|
1251
|
+
* ]).blocks
|
|
1252
|
+
* // [{ element: 'paragraph', children: [...] }, { element: 'thematicBreak' }]
|
|
1253
|
+
* ```
|
|
1254
|
+
*/
|
|
1255
|
+
export declare function mergeProjections(children: readonly MarkdownProjection[]): MarkdownProjection;
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Reduce an inline run to the shape markdown can actually write back: adjacent text
|
|
1259
|
+
* coalesced, empty text dropped, and every hard break either kept as a real line
|
|
1260
|
+
* ending or spent as a space.
|
|
1261
|
+
*
|
|
1262
|
+
* @remarks
|
|
1263
|
+
* A hard break is ` \n` in markdown source, so it survives a re-parse only BETWEEN
|
|
1264
|
+
* two lines of content and only with no whitespace touching it: a leading or trailing
|
|
1265
|
+
* break has no line to end, a run of breaks reads as one blank line (which would end
|
|
1266
|
+
* the paragraph), and a space beside one is eaten by the parser's line trimming. Where
|
|
1267
|
+
* a break cannot be written at all - a heading and a table cell are one line each - it
|
|
1268
|
+
* becomes the space it stood for.
|
|
1269
|
+
*
|
|
1270
|
+
* @param nodes - The inline run to normalize
|
|
1271
|
+
* @param breaks - Whether the target context can carry a hard break at all; `false` for
|
|
1272
|
+
* a heading or a table cell, where every break becomes a space
|
|
1273
|
+
* @returns The normalized run
|
|
1274
|
+
*
|
|
1275
|
+
* @example
|
|
1276
|
+
* ```ts
|
|
1277
|
+
* normalizeInlines([{ element: 'break' }, { element: 'text', value: 'a' }], true)
|
|
1278
|
+
* // [{ element: 'text', value: 'a' }] - a leading break has no line to end
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
export declare function normalizeInlines(nodes: readonly InlineNode[], breaks: boolean): readonly InlineNode[];
|
|
1282
|
+
|
|
1005
1283
|
/** A paragraph - a run of non-blank lines that is not another block; `children` its inline content. */
|
|
1006
1284
|
export declare interface ParagraphNode {
|
|
1007
1285
|
readonly element: 'paragraph';
|
|
@@ -1034,8 +1312,8 @@ export declare function parseBlocks(lines: readonly string[], depth: number): re
|
|
|
1034
1312
|
export declare function parseDocument(markdown: string): MarkdownDocument;
|
|
1035
1313
|
|
|
1036
1314
|
/**
|
|
1037
|
-
* Parses inline markdown text (emphasis, code spans, links
|
|
1038
|
-
* nodes, coalescing adjacent text runs.
|
|
1315
|
+
* Parses inline markdown text (emphasis, code spans, links, images, and hard
|
|
1316
|
+
* breaks) into inline AST nodes, coalescing adjacent text runs.
|
|
1039
1317
|
*
|
|
1040
1318
|
* @param text - The inline markdown text to parse.
|
|
1041
1319
|
* @returns The parsed inline nodes.
|
|
@@ -1043,25 +1321,122 @@ export declare function parseDocument(markdown: string): MarkdownDocument;
|
|
|
1043
1321
|
export declare function parseInline(text: string): readonly InlineNode[];
|
|
1044
1322
|
|
|
1045
1323
|
/**
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
* fenced code, blockquotes, links, emphasis, inline code), escaping every text run and
|
|
1049
|
-
* sanitizing every link `href`.
|
|
1324
|
+
* Project one HTML leaf - a text node, a comment, or a doctype - to its
|
|
1325
|
+
* {@link MarkdownProjection}.
|
|
1050
1326
|
*
|
|
1051
1327
|
* @remarks
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1328
|
+
* Text collapses each whitespace run to one space, which is both what HTML means by it
|
|
1329
|
+
* and all markdown can write back; the raw value travels on in `text` for the two
|
|
1330
|
+
* places that need it verbatim, a code span and a `pre > code` body. A comment and a
|
|
1331
|
+
* doctype carry nothing into markdown and project to nothing.
|
|
1055
1332
|
*
|
|
1056
|
-
* @param
|
|
1057
|
-
* @returns
|
|
1333
|
+
* @param leaf - The leaf node to project
|
|
1334
|
+
* @returns Its projection
|
|
1058
1335
|
*
|
|
1059
1336
|
* @example
|
|
1060
1337
|
* ```ts
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
|
|
1338
|
+
* projectHTMLLeaf({ category: 'text', value: 'a\n b' }).inlines
|
|
1339
|
+
* // [{ element: 'text', value: 'a b' }]
|
|
1340
|
+
* ```
|
|
1341
|
+
*/
|
|
1342
|
+
export declare function projectHTMLLeaf(leaf: CommentNode | DoctypeNode | TextNode_2): MarkdownProjection;
|
|
1343
|
+
|
|
1344
|
+
/**
|
|
1345
|
+
* Project one HTML container - the document root or an element - from its children's
|
|
1346
|
+
* already-computed projections. THE element mapping, and the only place that decides
|
|
1347
|
+
* what an HTML tag becomes in markdown.
|
|
1348
|
+
*
|
|
1349
|
+
* @remarks
|
|
1350
|
+
* `h1`-`h6` become headings; `p` a paragraph; `strong` / `b` and `em` / `i` emphasis;
|
|
1351
|
+
* `code` a code span; `pre` a code block, verbatim through a first `code` element child
|
|
1352
|
+
* (its `language-` class naming the language) and through `renderText` otherwise; `a`
|
|
1353
|
+
* and `img` a link and an image, each destination re-sanitized; `br` and `hr` a hard
|
|
1354
|
+
* break and a thematic break; `blockquote` and `li` their block content, with bare
|
|
1355
|
+
* inline runs wrapped in paragraphs; `ul` / `ol` a list, ordered from the tag and
|
|
1356
|
+
* numbered from `start`; `th` / `td`, `tr`, and `table` a GFM table whose column
|
|
1357
|
+
* alignment comes from each header-position cell's `align` attribute. Every
|
|
1358
|
+
* `UNSAFE_ELEMENTS` subtree contributes nothing at all, text included. Every OTHER
|
|
1359
|
+
* element unwraps to its children, so wrapper soup melts while its content keeps its
|
|
1360
|
+
* shape - `<div><p>a</p><p>b</p></div>` stays two paragraphs.
|
|
1361
|
+
*
|
|
1362
|
+
* Three mappings read their own node rather than only their children's projections,
|
|
1363
|
+
* because HTML puts the fact in a position rather than in a value: a `pre` takes its
|
|
1364
|
+
* body from its `code` child's raw text, and a list takes one item per `li` child - so
|
|
1365
|
+
* an empty `<li>` is still an item, while the whitespace between two of them is not.
|
|
1366
|
+
* A `tr` accepts only its own direct cells, and a table derives the first `th`-bearing
|
|
1367
|
+
* row from its own source structure.
|
|
1368
|
+
*
|
|
1369
|
+
* @param node - The document root or element to project
|
|
1370
|
+
* @param children - Its children's projections, in source order
|
|
1371
|
+
* @returns Its projection
|
|
1372
|
+
*
|
|
1373
|
+
* @example
|
|
1374
|
+
* ```ts
|
|
1375
|
+
* projectHTMLNode({ category: 'element', name: 'hr', attributes: [], children: [] }, []).blocks
|
|
1376
|
+
* // [{ element: 'thematicBreak' }]
|
|
1377
|
+
* ```
|
|
1378
|
+
*/
|
|
1379
|
+
export declare function projectHTMLNode(node: ElementNode | HTMLDocument, children: readonly MarkdownProjection[]): MarkdownProjection;
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Read a projection as BLOCK content - the view a document, a blockquote, and a list
|
|
1383
|
+
* item each need.
|
|
1384
|
+
*
|
|
1385
|
+
* @remarks
|
|
1386
|
+
* A bare inline run becomes one paragraph, and a run carrying no text becomes nothing
|
|
1387
|
+
* at all, because a blank paragraph is unwritable in markdown. A cell or a row that
|
|
1388
|
+
* never reached a table is unwrapped here rather than dropped: a stray `<td>` is still
|
|
1389
|
+
* someone's content.
|
|
1390
|
+
*
|
|
1391
|
+
* @param projection - The projection to read
|
|
1392
|
+
* @returns Its block content
|
|
1393
|
+
*
|
|
1394
|
+
* @example
|
|
1395
|
+
* ```ts
|
|
1396
|
+
* projectionToBlocks(createProjection({ inlines: [{ element: 'text', value: 'a' }], text: 'a' }))
|
|
1397
|
+
* // [{ element: 'paragraph', children: [{ element: 'text', value: 'a' }] }]
|
|
1398
|
+
* ```
|
|
1399
|
+
*/
|
|
1400
|
+
export declare function projectionToBlocks(projection: MarkdownProjection): readonly BlockNode[];
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* Read a projection as INLINE content - the view a link, an emphasis, and a table cell
|
|
1404
|
+
* each need.
|
|
1405
|
+
*
|
|
1406
|
+
* @remarks
|
|
1407
|
+
* Inline content passes through as itself. Block content cannot: markdown has no way to
|
|
1408
|
+
* put a paragraph inside a table cell, so it flattens to one text node of its own words,
|
|
1409
|
+
* joined and whitespace-collapsed. Content that carries no text flattens to nothing
|
|
1410
|
+
* rather than to an empty text node, which is a shape the parser never produces.
|
|
1411
|
+
*
|
|
1412
|
+
* @param projection - The projection to read
|
|
1413
|
+
* @returns Its inline content
|
|
1414
|
+
*
|
|
1415
|
+
* @example
|
|
1416
|
+
* ```ts
|
|
1417
|
+
* projectionToInlines(createProjection({ inlines: [{ element: 'break' }] }))
|
|
1418
|
+
* // [{ element: 'break' }]
|
|
1419
|
+
* ```
|
|
1420
|
+
*/
|
|
1421
|
+
export declare function projectionToInlines(projection: MarkdownProjection): readonly InlineNode[];
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* Render a {@link MarkdownNode} to sanitized canonical HTML.
|
|
1425
|
+
*
|
|
1426
|
+
* @remarks
|
|
1427
|
+
* Markdown widens `@orkestrel/html`'s attribute floor by exactly `src`, because image
|
|
1428
|
+
* syntax is meaningless without its source. `src` is still a URL attribute, so the
|
|
1429
|
+
* floor refuses `javascript:`, `data:`, `vbscript:`, and `file:` values. A stricter
|
|
1430
|
+
* consumer can compose {@link markdownToHTML} with `@orkestrel/html`'s `HTML` class
|
|
1431
|
+
* directly.
|
|
1432
|
+
*
|
|
1433
|
+
* @param node - The markdown document or bare node to render
|
|
1434
|
+
* @returns Sanitized canonical HTML
|
|
1435
|
+
*
|
|
1436
|
+
* @example
|
|
1437
|
+
* ```ts
|
|
1438
|
+
* renderHTML({ element: 'paragraph', children: [{ element: 'text', value: 'a & b' }] })
|
|
1439
|
+
* // '<p>a & b</p>'
|
|
1065
1440
|
* ```
|
|
1066
1441
|
*/
|
|
1067
1442
|
export declare function renderHTML(node: MarkdownNode): string;
|
|
@@ -1069,13 +1444,15 @@ export declare function renderHTML(node: MarkdownNode): string;
|
|
|
1069
1444
|
/**
|
|
1070
1445
|
* Render a {@link MarkdownNode} to its CANONICAL markdown source - the inverse
|
|
1071
1446
|
* projection of `renderHTML`, and the serializer a `parse(renderMarkdown(doc))`
|
|
1072
|
-
* round-trip is built on. Canonical forms:
|
|
1073
|
-
*
|
|
1074
|
-
* `start`), `---` thematic breaks, fenced code blocks
|
|
1075
|
-
* 3+ backtick run inside the body), ATX headings,
|
|
1076
|
-
* tables (1-space-padded cells, `\|`-escaped
|
|
1077
|
-
* `[text](href)` links
|
|
1078
|
-
*
|
|
1447
|
+
* round-trip is built on. Canonical forms: `*` / `**` emphasis at even emphasis
|
|
1448
|
+
* nesting depths and `_` / `__` at odd depths, `- ` bullets, `N. ` sequential
|
|
1449
|
+
* ordinals (from the list's `start`), `---` thematic breaks, fenced code blocks
|
|
1450
|
+
* (backtick run widened past any 3+ backtick run inside the body), ATX headings,
|
|
1451
|
+
* `> `-prefixed blockquote lines, GFM tables (1-space-padded cells, `\|`-escaped
|
|
1452
|
+
* pipes, an alignment delimiter row), `[text](href)` links, `` images,
|
|
1453
|
+
* and two-space hard breaks. A `text` node's literal content is backslash-escaped
|
|
1454
|
+
* wherever it would otherwise re-parse as markup (AGENTS §14 parse↔render
|
|
1455
|
+
* soundness).
|
|
1079
1456
|
*
|
|
1080
1457
|
* @remarks
|
|
1081
1458
|
* Total: never throws. At {@link MAX_DEPTH} a value-bearing node degrades to its
|
|
@@ -1129,36 +1506,6 @@ export declare function renderMarkdown(node: MarkdownNode): string;
|
|
|
1129
1506
|
*/
|
|
1130
1507
|
export declare function rewriteDocument(document: MarkdownDocument, rewrite: MarkdownRewriteHandler): MarkdownDocument;
|
|
1131
1508
|
|
|
1132
|
-
/**
|
|
1133
|
-
* The URL schemes `renderHTML` permits on a link `href` - anything else (notably
|
|
1134
|
-
* `javascript:`, `data:`, `vbscript:`, `file:`) is dropped to an empty `href` so a
|
|
1135
|
-
* hostile link can never execute. Frozen, lower-case; a relative / anchor /
|
|
1136
|
-
* scheme-less `href` (no `scheme:` prefix) is always allowed.
|
|
1137
|
-
*/
|
|
1138
|
-
export declare const SAFE_URL_SCHEMES: ReadonlySet<string>;
|
|
1139
|
-
|
|
1140
|
-
/**
|
|
1141
|
-
* Sanitize + HTML-attribute-escape a link `href` - a destination whose scheme is not
|
|
1142
|
-
* in {@link SAFE_URL_SCHEMES} (notably `javascript:` / `data:` / `vbscript:`), or that
|
|
1143
|
-
* is protocol-relative (`//host/path`, or a backslash variant a browser normalizes to
|
|
1144
|
-
* the same effect - `\\host`, `/\host`, `\/host` - inherits whatever scheme the
|
|
1145
|
-
* embedding page is served over, including an unsafe one), is dropped to an empty
|
|
1146
|
-
* string; a relative / anchor / scheme-less (and non-protocol-relative) destination
|
|
1147
|
-
* (including a SINGLE leading `/` or `\`) is kept;
|
|
1148
|
-
* the surviving value is then HTML-escaped. Defence-in-depth against an XSS `href`,
|
|
1149
|
-
* even though the input is trusted.
|
|
1150
|
-
*
|
|
1151
|
-
* @param href - The raw link destination
|
|
1152
|
-
* @returns A safe, escaped `href` (empty when the scheme is unsafe or protocol-relative)
|
|
1153
|
-
*
|
|
1154
|
-
* @example
|
|
1155
|
-
* ```ts
|
|
1156
|
-
* sanitizeUrl('javascript:alert(1)') // ''
|
|
1157
|
-
* sanitizeUrl('/path') // '/path'
|
|
1158
|
-
* ```
|
|
1159
|
-
*/
|
|
1160
|
-
export declare function sanitizeUrl(href: string): string;
|
|
1161
|
-
|
|
1162
1509
|
/**
|
|
1163
1510
|
* Scan an inline code span at `start` (a `` ` ``-run … a matching `` ` ``-run of the
|
|
1164
1511
|
* SAME length, the CommonMark rule that lets a span contain backticks). Returns the
|
|
@@ -1182,10 +1529,11 @@ export declare function scanCode(source: string, start: number, to: number): {
|
|
|
1182
1529
|
|
|
1183
1530
|
/**
|
|
1184
1531
|
* Scan an emphasis run at `start` (`*` / `_`, doubled for strong) - finds the nearest
|
|
1185
|
-
* matching closing run of the same marker + width
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
*
|
|
1532
|
+
* matching closing run of the same marker + width while skipping complete nested
|
|
1533
|
+
* runs from the other marker family, and requires non-space immediately inside both
|
|
1534
|
+
* delimiters (the CommonMark flanking simplification that blocks `* x *`). Returns
|
|
1535
|
+
* the emphasis node, or `undefined` when no valid closer exists (it then degrades to
|
|
1536
|
+
* a literal marker).
|
|
1189
1537
|
*
|
|
1190
1538
|
* @param source - The inline source text
|
|
1191
1539
|
* @param start - The index of the opening marker
|
|
@@ -1208,7 +1556,8 @@ export declare function scanEmphasis(source: string, start: number, to: number,
|
|
|
1208
1556
|
|
|
1209
1557
|
/**
|
|
1210
1558
|
* Scan the window `[from, to)` of `source` into inline nodes - the single recursive
|
|
1211
|
-
* engine the inline phase runs on (emphasis
|
|
1559
|
+
* engine the inline phase runs on (emphasis, link text, and image alternative
|
|
1560
|
+
* content recurse through it). Linear:
|
|
1212
1561
|
* each character is consumed once; a failed construct emits its opening character as
|
|
1213
1562
|
* text and advances by one, so there is no re-scan (no ReDoS).
|
|
1214
1563
|
*
|
|
@@ -1319,24 +1668,12 @@ export declare function stripQuote(line: string): string;
|
|
|
1319
1668
|
|
|
1320
1669
|
/**
|
|
1321
1670
|
* The horizontal alignment of a GFM table column, as declared by its delimiter row
|
|
1322
|
-
* (`:---` left, `---:` right, `:---:` center)
|
|
1323
|
-
*
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
/**
|
|
1328
|
-
* Derive the per-column {@link TableAlign} list from a GFM delimiter row - `:---`
|
|
1329
|
-
* left, `---:` right, `:---:` center, `---` none.
|
|
1330
|
-
*
|
|
1331
|
-
* @param delimiter - The table's delimiter row
|
|
1332
|
-
* @returns One alignment per column, in column order
|
|
1333
|
-
*
|
|
1334
|
-
* @example
|
|
1335
|
-
* ```ts
|
|
1336
|
-
* tableAlignments('| :--- | ---: |') // ['left', 'right']
|
|
1337
|
-
* ```
|
|
1671
|
+
* (`:---` left, `---:` right, `:---:` center). A bare `---` delimiter is represented
|
|
1672
|
+
* by `null` in {@link TableNode.align}: the positional array requires one entry per
|
|
1673
|
+
* column, JSON cannot carry `undefined` in an array, and the bare delimiter is an
|
|
1674
|
+
* explicit no-alignment marker rather than an omitted value.
|
|
1338
1675
|
*/
|
|
1339
|
-
export declare
|
|
1676
|
+
export declare type TableAlign = 'left' | 'right' | 'center';
|
|
1340
1677
|
|
|
1341
1678
|
/**
|
|
1342
1679
|
* The shape of a {@link TableAlign} - the per-column GFM table alignment
|
|
@@ -1353,7 +1690,7 @@ export declare function tableAlignments(delimiter: string): readonly TableAlign[
|
|
|
1353
1690
|
* tableAlign.is('top') // false
|
|
1354
1691
|
* ```
|
|
1355
1692
|
*/
|
|
1356
|
-
export declare const tableAlignShape: LiteralShape<readonly ["
|
|
1693
|
+
export declare const tableAlignShape: LiteralShape<readonly ["left", "right", "center"]>;
|
|
1357
1694
|
|
|
1358
1695
|
/**
|
|
1359
1696
|
* A GFM table - `header` the inline content of each header cell, `rows` the body
|
|
@@ -1367,14 +1704,20 @@ export declare interface TableNode {
|
|
|
1367
1704
|
readonly header: readonly (readonly InlineNode[])[];
|
|
1368
1705
|
/** The body rows - each a list of cells, each cell inline content. */
|
|
1369
1706
|
readonly rows: readonly (readonly (readonly InlineNode[])[])[];
|
|
1370
|
-
/**
|
|
1371
|
-
|
|
1707
|
+
/**
|
|
1708
|
+
* The per-column alignment from the delimiter row, in column order. `null`
|
|
1709
|
+
* represents a bare `---` delimiter because this positional array requires one
|
|
1710
|
+
* entry per column, JSON cannot carry `undefined` in an array, and the delimiter
|
|
1711
|
+
* is an explicit no-alignment marker rather than an omitted value.
|
|
1712
|
+
*/
|
|
1713
|
+
readonly align: readonly (TableAlign | null)[];
|
|
1372
1714
|
}
|
|
1373
1715
|
|
|
1374
1716
|
/**
|
|
1375
1717
|
* A run of plain text - the leaf inline node. `value` is the decoded text with
|
|
1376
|
-
* markdown escapes (`\*`, `\_`, …) already resolved to their literal characters;
|
|
1377
|
-
*
|
|
1718
|
+
* markdown escapes (`\*`, `\_`, …) already resolved to their literal characters;
|
|
1719
|
+
* html's text encoder escapes `&`, `<`, `>` on the way out; `"` and `'` stay literal
|
|
1720
|
+
* in character data.
|
|
1378
1721
|
*/
|
|
1379
1722
|
export declare interface TextNode {
|
|
1380
1723
|
readonly element: 'text';
|
|
@@ -1421,6 +1764,26 @@ export declare const thematicBreakShape: ObjectShape<{
|
|
|
1421
1764
|
element: LiteralShape<readonly ["thematicBreak"]>;
|
|
1422
1765
|
}, false>;
|
|
1423
1766
|
|
|
1767
|
+
/**
|
|
1768
|
+
* Trim the whitespace at the two ends of an inline run - the leading whitespace of a
|
|
1769
|
+
* leading text node and the trailing whitespace of a trailing one - dropping either
|
|
1770
|
+
* node when nothing survives.
|
|
1771
|
+
*
|
|
1772
|
+
* @remarks
|
|
1773
|
+
* Markdown trims every line of a paragraph, a heading's text, and a table cell, so an
|
|
1774
|
+
* untrimmed run would come back from a re-parse a different AST. Expects a coalesced
|
|
1775
|
+
* run (see {@link coalesceText}): only the outermost node on each side is examined.
|
|
1776
|
+
*
|
|
1777
|
+
* @param nodes - The inline run to trim
|
|
1778
|
+
* @returns The run with its edge whitespace removed
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```ts
|
|
1782
|
+
* trimInlines([{ element: 'text', value: ' a ' }]) // [{ element: 'text', value: 'a' }]
|
|
1783
|
+
* ```
|
|
1784
|
+
*/
|
|
1785
|
+
export declare function trimInlines(nodes: readonly InlineNode[]): readonly InlineNode[];
|
|
1786
|
+
|
|
1424
1787
|
/**
|
|
1425
1788
|
* Resolve backslash escapes in a raw string to their literal characters - used for a
|
|
1426
1789
|
* link `href` (which is not otherwise inline-parsed) and any plain text run.
|
|
@@ -1437,8 +1800,8 @@ export declare function unescapeText(text: string): string;
|
|
|
1437
1800
|
|
|
1438
1801
|
/**
|
|
1439
1802
|
* Depth-first, pre-order, root-inclusive traversal of a {@link MarkdownNode} - yields
|
|
1440
|
-
* the node itself, then recurses into its children (block children, list items,
|
|
1441
|
-
* header/row cells' inline nodes) in walk order.
|
|
1803
|
+
* the node itself, then recurses into its children (block children, list items,
|
|
1804
|
+
* image/link inline children, table header/row cells' inline nodes) in walk order.
|
|
1442
1805
|
*
|
|
1443
1806
|
* @remarks
|
|
1444
1807
|
* Total: never throws. Descent stops at {@link MAX_DEPTH} (the node at the cap is
|