@effected/markdown 0.17.0 → 0.19.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/Markdown.js +24 -12
- package/MarkdownDocument.js +4 -37
- package/MarkdownTransform.js +167 -0
- package/Mdast.js +49 -0
- package/index.d.ts +312 -11
- package/index.js +2 -1
- package/internal/sectionBounds.js +44 -0
- package/internal/stringify.js +7 -9
- package/package.json +4 -4
package/Markdown.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Root } from "./MarkdownNode.js";
|
|
|
2
2
|
import { isGuardExceeded, isRawMarkdownError } from "./internal/carriers.js";
|
|
3
3
|
import { parseBlocks } from "./internal/blockParser.js";
|
|
4
4
|
import { parsePhrasingText } from "./internal/phrasing.js";
|
|
5
|
-
import {
|
|
5
|
+
import { stringifyBlocks } from "./internal/stringify.js";
|
|
6
6
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
7
7
|
import { Effect, Result, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
8
8
|
|
|
@@ -256,11 +256,20 @@ var Markdown = class Markdown {
|
|
|
256
256
|
*/
|
|
257
257
|
static parsePhrasing = Effect.fn("Markdown.parsePhrasing")((text, options) => Effect.fromResult(Markdown.parsePhrasingResult(text, options)));
|
|
258
258
|
/**
|
|
259
|
-
* Serialize a {@link Root} tree
|
|
260
|
-
* `Result`. The pure primitive twin
|
|
261
|
-
*
|
|
259
|
+
* Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
|
|
260
|
+
* canonical markdown, synchronously, as a `Result`. The pure primitive twin
|
|
261
|
+
* of {@link Markdown.stringify}, on the same terms as
|
|
262
|
+
* {@link Markdown.parseResult}.
|
|
262
263
|
*
|
|
263
264
|
* @remarks
|
|
265
|
+
* **A single flow node is defined as its one-child document**: passing
|
|
266
|
+
* `node` is exactly `stringifyResult(Root.make({ children: [node] }))` —
|
|
267
|
+
* the same code path, the same trailing `\n`, the same canonical choices.
|
|
268
|
+
* This lets a caller serialize one block (a table, a section's paragraph,
|
|
269
|
+
* an MDX flow element) without building the wrapper by hand.
|
|
270
|
+
* `Frontmatter` and `MdxjsEsm` are deliberately not admitted alone — each
|
|
271
|
+
* is meaningful only at a document's root — so wrap them in a `Root`.
|
|
272
|
+
*
|
|
264
273
|
* Canonical serialization: fidelity fields (marker characters, fence
|
|
265
274
|
* style, heading spelling) win when present; documented canonical
|
|
266
275
|
* defaults apply when absent. One default surprises: a language-less
|
|
@@ -343,13 +352,13 @@ var Markdown = class Markdown {
|
|
|
343
352
|
* trip on a tree nesting past the depth cap fails, and only synthesized
|
|
344
353
|
* or decoded trees can nest that far.
|
|
345
354
|
*
|
|
346
|
-
* @param
|
|
355
|
+
* @param node - The document tree, or a single flow node, to serialize.
|
|
347
356
|
* @returns A `Result` succeeding with markdown source, or failing with
|
|
348
357
|
* {@link MarkdownStringifyError}.
|
|
349
358
|
*/
|
|
350
|
-
static stringifyResult(
|
|
359
|
+
static stringifyResult(node) {
|
|
351
360
|
try {
|
|
352
|
-
return Result.succeed(
|
|
361
|
+
return Result.succeed(stringifyBlocks(node.type === "root" ? node.children : [node]));
|
|
353
362
|
} catch (caught) {
|
|
354
363
|
if (isGuardExceeded(caught)) return Result.fail(new MarkdownStringifyError({ diagnostic: MarkdownDiagnostic.make({
|
|
355
364
|
code: caught.reason,
|
|
@@ -363,9 +372,9 @@ var Markdown = class Markdown {
|
|
|
363
372
|
}
|
|
364
373
|
}
|
|
365
374
|
/**
|
|
366
|
-
* Serialize a {@link Root} tree
|
|
367
|
-
* {@link Markdown.stringifyResult}
|
|
368
|
-
* variant directly.
|
|
375
|
+
* Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
|
|
376
|
+
* canonical markdown. Defined in terms of {@link Markdown.stringifyResult}
|
|
377
|
+
* — synchronous callers can use that variant directly.
|
|
369
378
|
*
|
|
370
379
|
* @remarks
|
|
371
380
|
* The canonical form is a **stability commitment** and safe to assert
|
|
@@ -381,11 +390,14 @@ var Markdown = class Markdown {
|
|
|
381
390
|
* `escapeStyle`, an emitter instruction rather than a source spelling,
|
|
382
391
|
* which the boundary admits.
|
|
383
392
|
*
|
|
384
|
-
*
|
|
393
|
+
* A single flow node serializes exactly as its one-child document does;
|
|
394
|
+
* see {@link Markdown.stringifyResult}.
|
|
395
|
+
*
|
|
396
|
+
* @param node - The document tree, or a single flow node, to serialize.
|
|
385
397
|
* @returns An `Effect` that succeeds with markdown source, or fails with
|
|
386
398
|
* {@link MarkdownStringifyError}.
|
|
387
399
|
*/
|
|
388
|
-
static stringify = Effect.fn("Markdown.stringify")((
|
|
400
|
+
static stringify = Effect.fn("Markdown.stringify")((node) => Effect.fromResult(Markdown.stringifyResult(node)));
|
|
389
401
|
/**
|
|
390
402
|
* A `Schema<Root, string>` decoding markdown source into a {@link Root}
|
|
391
403
|
* tree and encoding a tree back to canonical markdown via
|
package/MarkdownDocument.js
CHANGED
|
@@ -5,6 +5,7 @@ import { preprocessLines } from "./internal/preprocess.js";
|
|
|
5
5
|
import { normalizeLabelText } from "./internal/references.js";
|
|
6
6
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
7
7
|
import { parsePassResult } from "./Markdown.js";
|
|
8
|
+
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
8
9
|
import { Effect, Result, Schema } from "effect";
|
|
9
10
|
|
|
10
11
|
//#region src/MarkdownDocument.ts
|
|
@@ -239,43 +240,9 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
239
240
|
get sections() {
|
|
240
241
|
const blocks = this.root.children;
|
|
241
242
|
const sections = [];
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
const block = blocks[index];
|
|
245
|
-
if (block !== void 0 && block.type === "heading") headings.push({
|
|
246
|
-
index,
|
|
247
|
-
node: block
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
const nextByDepth = [
|
|
251
|
-
void 0,
|
|
252
|
-
void 0,
|
|
253
|
-
void 0,
|
|
254
|
-
void 0,
|
|
255
|
-
void 0,
|
|
256
|
-
void 0,
|
|
257
|
-
void 0
|
|
258
|
-
];
|
|
259
|
-
const boundaryByHeading = new Array(headings.length);
|
|
260
|
-
for (let index = headings.length - 1; index >= 0; index -= 1) {
|
|
261
|
-
const current = headings[index]?.node;
|
|
262
|
-
if (current === void 0) continue;
|
|
263
|
-
let boundary;
|
|
264
|
-
for (let depth = 1; depth <= current.depth; depth += 1) {
|
|
265
|
-
const candidate = nextByDepth[depth];
|
|
266
|
-
if (candidate !== void 0 && (boundary === void 0 || candidate < boundary)) boundary = candidate;
|
|
267
|
-
}
|
|
268
|
-
boundaryByHeading[index] = boundary;
|
|
269
|
-
nextByDepth[current.depth] = index;
|
|
270
|
-
}
|
|
271
|
-
for (let index = 0; index < headings.length; index += 1) {
|
|
272
|
-
const current = headings[index];
|
|
273
|
-
if (current === void 0) continue;
|
|
274
|
-
const block = current.node;
|
|
275
|
-
const boundaryHeading = boundaryByHeading[index];
|
|
276
|
-
const boundaryNode = boundaryHeading === void 0 ? void 0 : headings[boundaryHeading]?.node;
|
|
243
|
+
for (const { heading: block, index, end } of rootHeadingBounds(blocks)) {
|
|
244
|
+
const boundaryNode = blocks[end];
|
|
277
245
|
const boundary = boundaryNode === void 0 ? this.source.length : boundaryNode.position.start.offset;
|
|
278
|
-
const end = boundaryHeading === void 0 ? blocks.length : headings[boundaryHeading]?.index ?? blocks.length;
|
|
279
246
|
const start = block.position.start.offset;
|
|
280
247
|
const bodyStart = block.position.end.offset;
|
|
281
248
|
sections.push(new DocumentSection({
|
|
@@ -285,7 +252,7 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
285
252
|
offset: start,
|
|
286
253
|
length: boundary - start
|
|
287
254
|
}),
|
|
288
|
-
children: blocks.slice(
|
|
255
|
+
children: blocks.slice(index + 1, end).filter((child) => child.type !== "frontmatter" && child.type !== "mdxjsEsm"),
|
|
289
256
|
text: phrasingText(block.children),
|
|
290
257
|
bodyRange: MarkdownRange.make({
|
|
291
258
|
offset: bodyStart,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
2
|
+
|
|
3
|
+
//#region src/MarkdownTransform.ts
|
|
4
|
+
/**
|
|
5
|
+
* Pure, total tree-to-tree transforms over the markdown node classes. Not
|
|
6
|
+
* instantiable.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Every method returns a plain value — none has an error channel. They work
|
|
10
|
+
* on a tree, never on source text, so they compose with
|
|
11
|
+
* {@link Markdown.stringify} for output and with each other; offset-splice
|
|
12
|
+
* editing stays the job of `MarkdownFormat` and `MarkdownEdit`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { LinkReference, Markdown, MarkdownTransform } from "@effected/markdown";
|
|
17
|
+
* import { Result } from "effect";
|
|
18
|
+
*
|
|
19
|
+
* const parsed = Markdown.parseResult("# Title\n\nSee [docs](https://example.com).\n");
|
|
20
|
+
* if (Result.isSuccess(parsed)) {
|
|
21
|
+
* const shifted = MarkdownTransform.shiftHeadings(parsed.success, 1);
|
|
22
|
+
* const rewritten = MarkdownTransform.map(shifted, {
|
|
23
|
+
* link: (link) =>
|
|
24
|
+
* LinkReference.make({ identifier: "docs", referenceType: "full", children: link.children }),
|
|
25
|
+
* });
|
|
26
|
+
* Markdown.stringifyResult(rewritten); // "## Title\n\nSee [docs][docs].\n"
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
var MarkdownTransform = class {
|
|
33
|
+
constructor() {}
|
|
34
|
+
static map(node, handlers) {
|
|
35
|
+
return mapNode(node, handlers);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Shift every heading's depth by `by`, clamped into `[min, max]`.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* The new depth is `clamp(depth + by, min ?? 1, max ?? 6)`. A heading
|
|
42
|
+
* whose new depth exceeds 2 loses `headingStyle: "setext"` (the key is
|
|
43
|
+
* omitted), since setext spells depths 1 and 2 only; it then serializes
|
|
44
|
+
* as ATX. A heading whose depth does not change, and needs no setext
|
|
45
|
+
* drop, keeps its identity; structural sharing follows
|
|
46
|
+
* {@link MarkdownTransform.(map:1)}, on which this is built. Accepts any node,
|
|
47
|
+
* including a lone {@link Heading}.
|
|
48
|
+
*
|
|
49
|
+
* Built on `map`, it recurses the same way: a hand-built tree nested far
|
|
50
|
+
* deeper than anything `Markdown.parse` or `Mdast.fromMdast` produces can
|
|
51
|
+
* overflow the call stack.
|
|
52
|
+
*
|
|
53
|
+
* @param node - The node whose headings to shift.
|
|
54
|
+
* @param by - The signed depth offset; must be an integer.
|
|
55
|
+
* @param options - Optional {@link ShiftHeadingsOptions} clamp bounds.
|
|
56
|
+
* @returns The node with headings shifted.
|
|
57
|
+
* @throws `RangeError` when `by` is not an integer, or `min` / `max` is not
|
|
58
|
+
* a heading depth or `min > max` — a wiring error, not a data error.
|
|
59
|
+
*/
|
|
60
|
+
static shiftHeadings(node, by, options) {
|
|
61
|
+
if (!Number.isInteger(by)) throw new RangeError(`MarkdownTransform.shiftHeadings: by must be an integer, got ${by}`);
|
|
62
|
+
const min = options?.min ?? 1;
|
|
63
|
+
const max = options?.max ?? 6;
|
|
64
|
+
if (!isHeadingDepth(min) || !isHeadingDepth(max) || min > max) throw new RangeError(`MarkdownTransform.shiftHeadings: invalid depth bounds min=${min} max=${max}`);
|
|
65
|
+
return mapNode(node, { heading: (heading) => {
|
|
66
|
+
const depth = Math.min(max, Math.max(min, heading.depth + by));
|
|
67
|
+
const dropSetext = depth > 2 && heading.headingStyle === "setext";
|
|
68
|
+
if (depth === heading.depth && !dropSetext) return heading;
|
|
69
|
+
const { headingStyle, ...rest } = heading;
|
|
70
|
+
const ctor = heading.constructor;
|
|
71
|
+
return new ctor({
|
|
72
|
+
...rest,
|
|
73
|
+
depth,
|
|
74
|
+
...headingStyle !== void 0 && !dropSetext ? { headingStyle } : {}
|
|
75
|
+
}, { disableChecks: true });
|
|
76
|
+
} });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Locate the root-level sections of one heading depth by child index —
|
|
80
|
+
* no source text needed, so it works on a tree a
|
|
81
|
+
* `MarkdownTransform.map` has reshaped.
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* Returns the root-level headings of exactly `depth` whose index lies in
|
|
85
|
+
* `[start, end)`, in document order. Each section ends at the next
|
|
86
|
+
* root-level heading of equal or shallower depth, or at `end` — the same
|
|
87
|
+
* boundary rule as `MarkdownDocument.sections`, from one shared
|
|
88
|
+
* implementation. The window is what lets a caller nest: find the depth-2
|
|
89
|
+
* blocks, then the depth-3 sections inside one block's
|
|
90
|
+
* `[headingIndex + 1, end)`.
|
|
91
|
+
*
|
|
92
|
+
* **A shallower heading ends a section too.** Querying depth 2 on
|
|
93
|
+
* `## Features`, a list, a stray `# Top` and another list yields one span
|
|
94
|
+
* whose `end` is the `# Top` heading's index, so neither `# Top` nor the
|
|
95
|
+
* list after it belongs to the section. That is the outline reading. A
|
|
96
|
+
* caller that wants each section to run to the next heading of the SAME
|
|
97
|
+
* depth (slicing a changeset or changelog by entry, where a stray H1 is
|
|
98
|
+
* content) computes each body's end from the next span's `headingIndex`
|
|
99
|
+
* instead, or the window's end for the last span, as in the example.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* Bodies that run to the next same-depth heading:
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { Markdown, MarkdownTransform } from "@effected/markdown";
|
|
106
|
+
* import { Result } from "effect";
|
|
107
|
+
*
|
|
108
|
+
* const root = Result.getOrThrow(Markdown.parseResult("## Features\n\n- a\n\n# Top\n\n- b\n"));
|
|
109
|
+
* const spans = MarkdownTransform.sections(root, { depth: 2 });
|
|
110
|
+
* // spans[0].end === 2: the outline section stops at `# Top`
|
|
111
|
+
* const bodies = spans.map((span, index) =>
|
|
112
|
+
* root.children.slice(span.headingIndex + 1, spans[index + 1]?.headingIndex ?? root.children.length),
|
|
113
|
+
* );
|
|
114
|
+
* // bodies[0] holds the first list, `# Top` and the second list
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @param root - The tree whose root-level headings delimit sections.
|
|
118
|
+
* @param options - The {@link SectionSpanOptions} depth and window.
|
|
119
|
+
* @returns The matching {@link SectionSpan}s, in document order.
|
|
120
|
+
* @throws `RangeError` when `depth` is not an integer from 1 to 6, when
|
|
121
|
+
* `start` or `end` is not an integer in `[0, root.children.length]`, or
|
|
122
|
+
* when `start > end`.
|
|
123
|
+
*/
|
|
124
|
+
static sections(root, options) {
|
|
125
|
+
if (!isHeadingDepth(options.depth)) throw new RangeError(`MarkdownTransform.sections: depth must be a heading depth 1-6, got ${options.depth}`);
|
|
126
|
+
const length = root.children.length;
|
|
127
|
+
const start = options.start ?? 0;
|
|
128
|
+
const end = options.end ?? length;
|
|
129
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > length || start > end) throw new RangeError(`MarkdownTransform.sections: invalid window start=${start} end=${end} for ${length} children`);
|
|
130
|
+
const spans = [];
|
|
131
|
+
for (const bound of rootHeadingBounds(root.children)) {
|
|
132
|
+
if (bound.index < start || bound.index >= end || bound.heading.depth !== options.depth) continue;
|
|
133
|
+
spans.push({
|
|
134
|
+
heading: bound.heading,
|
|
135
|
+
headingIndex: bound.index,
|
|
136
|
+
end: Math.min(bound.end, end)
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return spans;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const isHeadingDepth = (value) => Number.isInteger(value) && value >= 1 && value <= 6;
|
|
143
|
+
function mapNode(node, handlers) {
|
|
144
|
+
let current = node;
|
|
145
|
+
if ("children" in node) {
|
|
146
|
+
const children = node.children;
|
|
147
|
+
let next;
|
|
148
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
149
|
+
const child = children[index];
|
|
150
|
+
const mapped = mapNode(child, handlers);
|
|
151
|
+
if (next === void 0 && mapped !== child) next = children.slice(0, index);
|
|
152
|
+
next?.push(mapped);
|
|
153
|
+
}
|
|
154
|
+
if (next !== void 0) {
|
|
155
|
+
const ctor = node.constructor;
|
|
156
|
+
current = new ctor({
|
|
157
|
+
...node,
|
|
158
|
+
children: next
|
|
159
|
+
}, { disableChecks: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const handler = handlers[current.type];
|
|
163
|
+
return handler === void 0 ? current : handler(current);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
export { MarkdownTransform };
|
package/Mdast.js
CHANGED
|
@@ -469,7 +469,56 @@ var Mdast = class Mdast {
|
|
|
469
469
|
* fails with {@link MdastDecodeError}.
|
|
470
470
|
*/
|
|
471
471
|
static fromMdast = Effect.fn("Mdast.fromMdast")((input) => Effect.fromResult(Mdast.fromMdastResult(input)));
|
|
472
|
+
/**
|
|
473
|
+
* The plain-text content of a node, defined as `mdast-util-to-string`
|
|
474
|
+
* applied to the node's {@link Mdast.toMdast} projection.
|
|
475
|
+
*
|
|
476
|
+
* @remarks
|
|
477
|
+
* A node carrying a string `value` yields it (an `html` node only when
|
|
478
|
+
* `includeHtml`); an `image` or `imageReference` yields its non-empty
|
|
479
|
+
* `alt` when `includeImageAlt`; a node with children yields the
|
|
480
|
+
* concatenation of its children's text; anything else — a `break`, a
|
|
481
|
+
* `thematicBreak`, a `definition` — yields `""`. Both options default to
|
|
482
|
+
* `true`. Pure and total.
|
|
483
|
+
*
|
|
484
|
+
* **Values are read as the projection spells them.** The one value the
|
|
485
|
+
* projection changes is a `code` node's: this package keeps the final line
|
|
486
|
+
* ending of a code block's content, plain mdast does not, so `toText`
|
|
487
|
+
* strips it exactly as {@link Mdast.toMdast} does — `"let x"`, not
|
|
488
|
+
* `"let x\n"`, for a fenced block holding one line.
|
|
489
|
+
*
|
|
490
|
+
* This is the remark-ecosystem derivation, and it deliberately differs
|
|
491
|
+
* from the display derivation behind `DocumentHeading.text` and
|
|
492
|
+
* `DocumentSection.text`, which render a hard break as a space and drop
|
|
493
|
+
* raw HTML. Reach for `toText` when matching what remark tooling computes
|
|
494
|
+
* (slugs, search indexes); reach for the document accessors for display.
|
|
495
|
+
*
|
|
496
|
+
* The walk recurses once per nesting level. Every tree `Markdown.parse` or
|
|
497
|
+
* `Mdast.fromMdast` produces stays within the package's nesting cap, but a
|
|
498
|
+
* hand-built tree nested far deeper than that can overflow the call stack.
|
|
499
|
+
*
|
|
500
|
+
* @param node - The node to flatten.
|
|
501
|
+
* @param options - Optional {@link MdastToTextOptions}.
|
|
502
|
+
* @returns The node's text content.
|
|
503
|
+
*/
|
|
504
|
+
static toText(node, options) {
|
|
505
|
+
return nodeText(node, options?.includeImageAlt ?? true, options?.includeHtml ?? true);
|
|
506
|
+
}
|
|
472
507
|
};
|
|
508
|
+
/** `mdast-util-to-string`'s `one`, over the node classes as `toMdast` projects them. */
|
|
509
|
+
function nodeText(node, includeImageAlt, includeHtml) {
|
|
510
|
+
if ("value" in node) {
|
|
511
|
+
if (node.type === "html") return includeHtml ? node.value : "";
|
|
512
|
+
return node.type === "code" ? stripFinalLineEnding(node.value) : node.value;
|
|
513
|
+
}
|
|
514
|
+
if (includeImageAlt && "alt" in node && node.alt) return node.alt;
|
|
515
|
+
if ("children" in node) {
|
|
516
|
+
let text = "";
|
|
517
|
+
for (const child of node.children) text += nodeText(child, includeImageAlt, includeHtml);
|
|
518
|
+
return text;
|
|
519
|
+
}
|
|
520
|
+
return "";
|
|
521
|
+
}
|
|
473
522
|
|
|
474
523
|
//#endregion
|
|
475
524
|
export { Mdast, MdastDecodeError };
|
package/index.d.ts
CHANGED
|
@@ -1204,11 +1204,20 @@ export declare class Markdown {
|
|
|
1204
1204
|
*/
|
|
1205
1205
|
static readonly parsePhrasing: (text: string, options?: MarkdownParseOptions | undefined) => Effect.Effect<readonly PhrasingContent[], MarkdownParseError, never>;
|
|
1206
1206
|
/**
|
|
1207
|
-
* Serialize a {@link Root} tree
|
|
1208
|
-
* `Result`. The pure primitive twin
|
|
1209
|
-
*
|
|
1207
|
+
* Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
|
|
1208
|
+
* canonical markdown, synchronously, as a `Result`. The pure primitive twin
|
|
1209
|
+
* of {@link Markdown.stringify}, on the same terms as
|
|
1210
|
+
* {@link Markdown.parseResult}.
|
|
1210
1211
|
*
|
|
1211
1212
|
* @remarks
|
|
1213
|
+
* **A single flow node is defined as its one-child document**: passing
|
|
1214
|
+
* `node` is exactly `stringifyResult(Root.make({ children: [node] }))` —
|
|
1215
|
+
* the same code path, the same trailing `\n`, the same canonical choices.
|
|
1216
|
+
* This lets a caller serialize one block (a table, a section's paragraph,
|
|
1217
|
+
* an MDX flow element) without building the wrapper by hand.
|
|
1218
|
+
* `Frontmatter` and `MdxjsEsm` are deliberately not admitted alone — each
|
|
1219
|
+
* is meaningful only at a document's root — so wrap them in a `Root`.
|
|
1220
|
+
*
|
|
1212
1221
|
* Canonical serialization: fidelity fields (marker characters, fence
|
|
1213
1222
|
* style, heading spelling) win when present; documented canonical
|
|
1214
1223
|
* defaults apply when absent. One default surprises: a language-less
|
|
@@ -1291,15 +1300,15 @@ export declare class Markdown {
|
|
|
1291
1300
|
* trip on a tree nesting past the depth cap fails, and only synthesized
|
|
1292
1301
|
* or decoded trees can nest that far.
|
|
1293
1302
|
*
|
|
1294
|
-
* @param
|
|
1303
|
+
* @param node - The document tree, or a single flow node, to serialize.
|
|
1295
1304
|
* @returns A `Result` succeeding with markdown source, or failing with
|
|
1296
1305
|
* {@link MarkdownStringifyError}.
|
|
1297
1306
|
*/
|
|
1298
|
-
static stringifyResult(
|
|
1307
|
+
static stringifyResult(node: Root | FlowContent): Result.Result<string, MarkdownStringifyError>;
|
|
1299
1308
|
/**
|
|
1300
|
-
* Serialize a {@link Root} tree
|
|
1301
|
-
* {@link Markdown.stringifyResult}
|
|
1302
|
-
* variant directly.
|
|
1309
|
+
* Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
|
|
1310
|
+
* canonical markdown. Defined in terms of {@link Markdown.stringifyResult}
|
|
1311
|
+
* — synchronous callers can use that variant directly.
|
|
1303
1312
|
*
|
|
1304
1313
|
* @remarks
|
|
1305
1314
|
* The canonical form is a **stability commitment** and safe to assert
|
|
@@ -1315,11 +1324,14 @@ export declare class Markdown {
|
|
|
1315
1324
|
* `escapeStyle`, an emitter instruction rather than a source spelling,
|
|
1316
1325
|
* which the boundary admits.
|
|
1317
1326
|
*
|
|
1318
|
-
*
|
|
1327
|
+
* A single flow node serializes exactly as its one-child document does;
|
|
1328
|
+
* see {@link Markdown.stringifyResult}.
|
|
1329
|
+
*
|
|
1330
|
+
* @param node - The document tree, or a single flow node, to serialize.
|
|
1319
1331
|
* @returns An `Effect` that succeeds with markdown source, or fails with
|
|
1320
1332
|
* {@link MarkdownStringifyError}.
|
|
1321
1333
|
*/
|
|
1322
|
-
static readonly stringify: (
|
|
1334
|
+
static readonly stringify: (node: Root | FlowContent) => Effect.Effect<string, MarkdownStringifyError, never>;
|
|
1323
1335
|
/**
|
|
1324
1336
|
* A `Schema<Root, string>` decoding markdown source into a {@link Root}
|
|
1325
1337
|
* tree and encoding a tree back to canonical markdown via
|
|
@@ -2661,6 +2673,251 @@ export declare class MarkdownFormat {
|
|
|
2661
2673
|
static readonly modifyToString: (document: MarkdownDocument, target: MarkdownNode, replacement: string | MarkdownNode) => Effect.Effect<string, MarkdownModificationError, never>;
|
|
2662
2674
|
}
|
|
2663
2675
|
//#endregion
|
|
2676
|
+
//#region src/MarkdownTransform.d.ts
|
|
2677
|
+
/**
|
|
2678
|
+
* The node `type` tags whose handler may change the node's type — the
|
|
2679
|
+
* phrasing-only types, i.e. every member of `PhrasingContent` that is
|
|
2680
|
+
* not also flow content.
|
|
2681
|
+
*
|
|
2682
|
+
* @remarks
|
|
2683
|
+
* `html` is excluded: an `html` node is both phrasing and flow content, so it
|
|
2684
|
+
* can sit in a `Root`, `Blockquote` or `ListItem` child list, where only flow
|
|
2685
|
+
* content is valid. Its handler must therefore return an `Html`.
|
|
2686
|
+
*
|
|
2687
|
+
* @public
|
|
2688
|
+
*/
|
|
2689
|
+
type MarkdownPhrasingOnlyType = Exclude<PhrasingContent, FlowContent>["type"];
|
|
2690
|
+
/**
|
|
2691
|
+
* The handler table for `MarkdownTransform.map`: at most one function
|
|
2692
|
+
* per node `type` tag, each receiving the node narrowed to its class.
|
|
2693
|
+
*
|
|
2694
|
+
* @remarks
|
|
2695
|
+
* A handler for a phrasing-only type (see {@link MarkdownPhrasingOnlyType})
|
|
2696
|
+
* may return any {@link (PhrasingContent:type)} — turning a `link` into a
|
|
2697
|
+
* `linkReference`, say. That is sound because every parent of a phrasing
|
|
2698
|
+
* node holds `PhrasingContent` children, so any phrasing node is valid in its
|
|
2699
|
+
* place. Every other handler must return a node of its own type, which keeps
|
|
2700
|
+
* `map` total: no handler can put a node where its parent forbids it.
|
|
2701
|
+
*
|
|
2702
|
+
* @public
|
|
2703
|
+
*/
|
|
2704
|
+
type MarkdownTransformHandlers = { readonly [T in MarkdownNodeType]?: (node: MarkdownNodeOfType<T>) => T extends MarkdownPhrasingOnlyType ? PhrasingContent : MarkdownNodeOfType<T>; };
|
|
2705
|
+
/**
|
|
2706
|
+
* What `MarkdownTransform.map` returns for an input of type `N`: any
|
|
2707
|
+
* `PhrasingContent` when `N` is phrasing-only (its handler may change
|
|
2708
|
+
* its type), otherwise `N` itself.
|
|
2709
|
+
*
|
|
2710
|
+
* @public
|
|
2711
|
+
*/
|
|
2712
|
+
type MarkdownTransformResult<N extends MarkdownNode> = N extends Exclude<PhrasingContent, FlowContent> ? PhrasingContent : N;
|
|
2713
|
+
/**
|
|
2714
|
+
* Options for {@link MarkdownTransform.shiftHeadings}: the inclusive depth
|
|
2715
|
+
* range shifted headings are clamped into.
|
|
2716
|
+
*
|
|
2717
|
+
* @public
|
|
2718
|
+
*/
|
|
2719
|
+
interface ShiftHeadingsOptions {
|
|
2720
|
+
/** The shallowest depth a shifted heading may take. Defaults to `1`. */
|
|
2721
|
+
readonly min?: HeadingDepth;
|
|
2722
|
+
/** The deepest depth a shifted heading may take. Defaults to `6`. */
|
|
2723
|
+
readonly max?: HeadingDepth;
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Options for {@link MarkdownTransform.sections}: the heading depth to match
|
|
2727
|
+
* and the window of `root.children` indexes to search.
|
|
2728
|
+
*
|
|
2729
|
+
* @public
|
|
2730
|
+
*/
|
|
2731
|
+
interface SectionSpanOptions {
|
|
2732
|
+
/** The exact heading depth to match. */
|
|
2733
|
+
readonly depth: HeadingDepth;
|
|
2734
|
+
/** The first child index searched, inclusive. Defaults to `0`. */
|
|
2735
|
+
readonly start?: number;
|
|
2736
|
+
/** The child index the search stops at, exclusive. Defaults to `root.children.length`. */
|
|
2737
|
+
readonly end?: number;
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* A root-level section located by index: the heading, its index in
|
|
2741
|
+
* `root.children`, and the exclusive index its section ends at. The body is
|
|
2742
|
+
* the children at `[headingIndex + 1, end)`.
|
|
2743
|
+
*
|
|
2744
|
+
* @public
|
|
2745
|
+
*/
|
|
2746
|
+
interface SectionSpan {
|
|
2747
|
+
readonly heading: Heading;
|
|
2748
|
+
readonly headingIndex: number;
|
|
2749
|
+
readonly end: number;
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Pure, total tree-to-tree transforms over the markdown node classes. Not
|
|
2753
|
+
* instantiable.
|
|
2754
|
+
*
|
|
2755
|
+
* @remarks
|
|
2756
|
+
* Every method returns a plain value — none has an error channel. They work
|
|
2757
|
+
* on a tree, never on source text, so they compose with
|
|
2758
|
+
* {@link Markdown.stringify} for output and with each other; offset-splice
|
|
2759
|
+
* editing stays the job of `MarkdownFormat` and `MarkdownEdit`.
|
|
2760
|
+
*
|
|
2761
|
+
* @example
|
|
2762
|
+
* ```ts
|
|
2763
|
+
* import { LinkReference, Markdown, MarkdownTransform } from "@effected/markdown";
|
|
2764
|
+
* import { Result } from "effect";
|
|
2765
|
+
*
|
|
2766
|
+
* const parsed = Markdown.parseResult("# Title\n\nSee [docs](https://example.com).\n");
|
|
2767
|
+
* if (Result.isSuccess(parsed)) {
|
|
2768
|
+
* const shifted = MarkdownTransform.shiftHeadings(parsed.success, 1);
|
|
2769
|
+
* const rewritten = MarkdownTransform.map(shifted, {
|
|
2770
|
+
* link: (link) =>
|
|
2771
|
+
* LinkReference.make({ identifier: "docs", referenceType: "full", children: link.children }),
|
|
2772
|
+
* });
|
|
2773
|
+
* Markdown.stringifyResult(rewritten); // "## Title\n\nSee [docs][docs].\n"
|
|
2774
|
+
* }
|
|
2775
|
+
* ```
|
|
2776
|
+
*
|
|
2777
|
+
* @public
|
|
2778
|
+
*/
|
|
2779
|
+
export declare class MarkdownTransform {
|
|
2780
|
+
private constructor();
|
|
2781
|
+
/**
|
|
2782
|
+
* Rebuild a tree by applying `handlers` to its nodes, bottom-up.
|
|
2783
|
+
*
|
|
2784
|
+
* @remarks
|
|
2785
|
+
* **Walk order is a contract:** post-order, children left to right. A
|
|
2786
|
+
* handler sees its node with its children ALREADY transformed, and
|
|
2787
|
+
* handlers are called in document order of the nodes' closing positions,
|
|
2788
|
+
* so a stateful handler can rely on it — deduplicating while keeping the
|
|
2789
|
+
* first occurrence, or remembering the last paragraph seen. The input
|
|
2790
|
+
* node itself is visited last.
|
|
2791
|
+
*
|
|
2792
|
+
* **Structural sharing:** a subtree no handler changed is returned as the
|
|
2793
|
+
* same instance (`===`), so `map(node, {})` returns `node`. A parent whose
|
|
2794
|
+
* children changed is rebuilt with only `children` swapped — every other
|
|
2795
|
+
* field, including `position`, kept as it was. A rebuilt parent therefore
|
|
2796
|
+
* carries its ORIGINAL `position`: offsets into the pre-transform source,
|
|
2797
|
+
* which no longer describe the transformed tree.
|
|
2798
|
+
*
|
|
2799
|
+
* **Handler typing keeps the map total.** A handler for a phrasing-only
|
|
2800
|
+
* type ({@link MarkdownPhrasingOnlyType}) may return any phrasing node —
|
|
2801
|
+
* a `link` may become a `linkReference` — because every parent of a
|
|
2802
|
+
* phrasing node holds phrasing children. Every other handler must return a
|
|
2803
|
+
* node of its own type. `html` is the one phrasing type excluded from the
|
|
2804
|
+
* type-changing set: it is a member of BOTH the phrasing and the flow
|
|
2805
|
+
* unions, so an `html` node can be a child of a `Root`, `Blockquote` or
|
|
2806
|
+
* `ListItem`, where only flow content is valid — its handler must return
|
|
2807
|
+
* an `Html`, and `map` of an `Html` returns an `Html`. See
|
|
2808
|
+
* {@link MarkdownTransformHandlers}. A handler that throws propagates the
|
|
2809
|
+
* exception unchanged; `map` has no error channel of its own.
|
|
2810
|
+
*
|
|
2811
|
+
* The walk recurses once per nesting level. Every tree `Markdown.parse` or
|
|
2812
|
+
* `Mdast.fromMdast` produces stays within the package's nesting cap, but a
|
|
2813
|
+
* hand-built tree nested far deeper than that can overflow the call stack.
|
|
2814
|
+
*
|
|
2815
|
+
* **Two signatures.** This one covers every node whose handler cannot
|
|
2816
|
+
* change its type — a `Root`, any flow node, a list item, a table row or
|
|
2817
|
+
* cell, an `html` node — and returns `N` itself, so a generic caller
|
|
2818
|
+
* constrained to, say, `Root | FlowContent` gets its own type parameter
|
|
2819
|
+
* back. A lone phrasing-only node resolves to the second signature, whose
|
|
2820
|
+
* result widens to `PhrasingContent`.
|
|
2821
|
+
*
|
|
2822
|
+
* @param node - The node to transform — typically a {@link Root}.
|
|
2823
|
+
* @param handlers - At most one function per node `type` tag.
|
|
2824
|
+
* @returns The transformed node, of the input's own type: `node` itself
|
|
2825
|
+
* when nothing changed.
|
|
2826
|
+
*/
|
|
2827
|
+
static map<N extends Exclude<MarkdownNode, Exclude<PhrasingContent, FlowContent>>>(node: N, handlers: MarkdownTransformHandlers): N;
|
|
2828
|
+
/**
|
|
2829
|
+
* Rebuild a tree by applying `handlers` to its nodes, bottom-up — the
|
|
2830
|
+
* signature for any node, including a lone phrasing-only node whose own
|
|
2831
|
+
* handler may change its type.
|
|
2832
|
+
*
|
|
2833
|
+
* @remarks
|
|
2834
|
+
* Walk order, structural sharing, handler typing and the recursion limit
|
|
2835
|
+
* are exactly as documented on the first signature. The difference is the
|
|
2836
|
+
* result type, {@link MarkdownTransformResult}: for a phrasing-only node
|
|
2837
|
+
* (a `link`, `text`, `emphasis`, ...) it is any `PhrasingContent`, since
|
|
2838
|
+
* that node's handler may return, say, a `linkReference` for a `link`. For
|
|
2839
|
+
* every other node it is `N`, but a caller holding a node of a known
|
|
2840
|
+
* non-phrasing type resolves to the first signature, which returns `N`
|
|
2841
|
+
* without the conditional type.
|
|
2842
|
+
*
|
|
2843
|
+
* @param node - Any node.
|
|
2844
|
+
* @param handlers - At most one function per node `type` tag.
|
|
2845
|
+
* @returns The transformed node: `node` itself when nothing changed.
|
|
2846
|
+
*/
|
|
2847
|
+
static map<N extends MarkdownNode>(node: N, handlers: MarkdownTransformHandlers): MarkdownTransformResult<N>;
|
|
2848
|
+
/**
|
|
2849
|
+
* Shift every heading's depth by `by`, clamped into `[min, max]`.
|
|
2850
|
+
*
|
|
2851
|
+
* @remarks
|
|
2852
|
+
* The new depth is `clamp(depth + by, min ?? 1, max ?? 6)`. A heading
|
|
2853
|
+
* whose new depth exceeds 2 loses `headingStyle: "setext"` (the key is
|
|
2854
|
+
* omitted), since setext spells depths 1 and 2 only; it then serializes
|
|
2855
|
+
* as ATX. A heading whose depth does not change, and needs no setext
|
|
2856
|
+
* drop, keeps its identity; structural sharing follows
|
|
2857
|
+
* {@link MarkdownTransform.(map:1)}, on which this is built. Accepts any node,
|
|
2858
|
+
* including a lone {@link Heading}.
|
|
2859
|
+
*
|
|
2860
|
+
* Built on `map`, it recurses the same way: a hand-built tree nested far
|
|
2861
|
+
* deeper than anything `Markdown.parse` or `Mdast.fromMdast` produces can
|
|
2862
|
+
* overflow the call stack.
|
|
2863
|
+
*
|
|
2864
|
+
* @param node - The node whose headings to shift.
|
|
2865
|
+
* @param by - The signed depth offset; must be an integer.
|
|
2866
|
+
* @param options - Optional {@link ShiftHeadingsOptions} clamp bounds.
|
|
2867
|
+
* @returns The node with headings shifted.
|
|
2868
|
+
* @throws `RangeError` when `by` is not an integer, or `min` / `max` is not
|
|
2869
|
+
* a heading depth or `min > max` — a wiring error, not a data error.
|
|
2870
|
+
*/
|
|
2871
|
+
static shiftHeadings<N extends MarkdownNode>(node: N, by: number, options?: ShiftHeadingsOptions): N;
|
|
2872
|
+
/**
|
|
2873
|
+
* Locate the root-level sections of one heading depth by child index —
|
|
2874
|
+
* no source text needed, so it works on a tree a
|
|
2875
|
+
* `MarkdownTransform.map` has reshaped.
|
|
2876
|
+
*
|
|
2877
|
+
* @remarks
|
|
2878
|
+
* Returns the root-level headings of exactly `depth` whose index lies in
|
|
2879
|
+
* `[start, end)`, in document order. Each section ends at the next
|
|
2880
|
+
* root-level heading of equal or shallower depth, or at `end` — the same
|
|
2881
|
+
* boundary rule as `MarkdownDocument.sections`, from one shared
|
|
2882
|
+
* implementation. The window is what lets a caller nest: find the depth-2
|
|
2883
|
+
* blocks, then the depth-3 sections inside one block's
|
|
2884
|
+
* `[headingIndex + 1, end)`.
|
|
2885
|
+
*
|
|
2886
|
+
* **A shallower heading ends a section too.** Querying depth 2 on
|
|
2887
|
+
* `## Features`, a list, a stray `# Top` and another list yields one span
|
|
2888
|
+
* whose `end` is the `# Top` heading's index, so neither `# Top` nor the
|
|
2889
|
+
* list after it belongs to the section. That is the outline reading. A
|
|
2890
|
+
* caller that wants each section to run to the next heading of the SAME
|
|
2891
|
+
* depth (slicing a changeset or changelog by entry, where a stray H1 is
|
|
2892
|
+
* content) computes each body's end from the next span's `headingIndex`
|
|
2893
|
+
* instead, or the window's end for the last span, as in the example.
|
|
2894
|
+
*
|
|
2895
|
+
* @example
|
|
2896
|
+
* Bodies that run to the next same-depth heading:
|
|
2897
|
+
*
|
|
2898
|
+
* ```ts
|
|
2899
|
+
* import { Markdown, MarkdownTransform } from "@effected/markdown";
|
|
2900
|
+
* import { Result } from "effect";
|
|
2901
|
+
*
|
|
2902
|
+
* const root = Result.getOrThrow(Markdown.parseResult("## Features\n\n- a\n\n# Top\n\n- b\n"));
|
|
2903
|
+
* const spans = MarkdownTransform.sections(root, { depth: 2 });
|
|
2904
|
+
* // spans[0].end === 2: the outline section stops at `# Top`
|
|
2905
|
+
* const bodies = spans.map((span, index) =>
|
|
2906
|
+
* root.children.slice(span.headingIndex + 1, spans[index + 1]?.headingIndex ?? root.children.length),
|
|
2907
|
+
* );
|
|
2908
|
+
* // bodies[0] holds the first list, `# Top` and the second list
|
|
2909
|
+
* ```
|
|
2910
|
+
*
|
|
2911
|
+
* @param root - The tree whose root-level headings delimit sections.
|
|
2912
|
+
* @param options - The {@link SectionSpanOptions} depth and window.
|
|
2913
|
+
* @returns The matching {@link SectionSpan}s, in document order.
|
|
2914
|
+
* @throws `RangeError` when `depth` is not an integer from 1 to 6, when
|
|
2915
|
+
* `start` or `end` is not an integer in `[0, root.children.length]`, or
|
|
2916
|
+
* when `start > end`.
|
|
2917
|
+
*/
|
|
2918
|
+
static sections(root: Root, options: SectionSpanOptions): ReadonlyArray<SectionSpan>;
|
|
2919
|
+
}
|
|
2920
|
+
//#endregion
|
|
2664
2921
|
//#region src/MarkdownVisitor.d.ts
|
|
2665
2922
|
/**
|
|
2666
2923
|
* The discriminated union of markdown visitor events, in document pre-order.
|
|
@@ -2872,6 +3129,17 @@ interface MdastNode {
|
|
|
2872
3129
|
readonly type: string;
|
|
2873
3130
|
readonly [key: string]: unknown;
|
|
2874
3131
|
}
|
|
3132
|
+
/**
|
|
3133
|
+
* Options for {@link Mdast.toText}, matching `mdast-util-to-string`'s.
|
|
3134
|
+
*
|
|
3135
|
+
* @public
|
|
3136
|
+
*/
|
|
3137
|
+
interface MdastToTextOptions {
|
|
3138
|
+
/** Whether an `image` or `imageReference` contributes its `alt` text. Defaults to `true`. */
|
|
3139
|
+
readonly includeImageAlt?: boolean;
|
|
3140
|
+
/** Whether an `html` node contributes its raw value. Defaults to `true`. */
|
|
3141
|
+
readonly includeHtml?: boolean;
|
|
3142
|
+
}
|
|
2875
3143
|
declare const MdastDecodeError_base: Schema.Class<MdastDecodeError, Schema.TaggedStruct<"MdastDecodeError", {
|
|
2876
3144
|
/** The structured schema issue. Never a string. */
|
|
2877
3145
|
readonly issue: Schema.Defect;
|
|
@@ -2993,6 +3261,39 @@ export declare class Mdast {
|
|
|
2993
3261
|
* fails with {@link MdastDecodeError}.
|
|
2994
3262
|
*/
|
|
2995
3263
|
static readonly fromMdast: (input: unknown) => Effect.Effect<Root, MdastDecodeError, never>;
|
|
3264
|
+
/**
|
|
3265
|
+
* The plain-text content of a node, defined as `mdast-util-to-string`
|
|
3266
|
+
* applied to the node's {@link Mdast.toMdast} projection.
|
|
3267
|
+
*
|
|
3268
|
+
* @remarks
|
|
3269
|
+
* A node carrying a string `value` yields it (an `html` node only when
|
|
3270
|
+
* `includeHtml`); an `image` or `imageReference` yields its non-empty
|
|
3271
|
+
* `alt` when `includeImageAlt`; a node with children yields the
|
|
3272
|
+
* concatenation of its children's text; anything else — a `break`, a
|
|
3273
|
+
* `thematicBreak`, a `definition` — yields `""`. Both options default to
|
|
3274
|
+
* `true`. Pure and total.
|
|
3275
|
+
*
|
|
3276
|
+
* **Values are read as the projection spells them.** The one value the
|
|
3277
|
+
* projection changes is a `code` node's: this package keeps the final line
|
|
3278
|
+
* ending of a code block's content, plain mdast does not, so `toText`
|
|
3279
|
+
* strips it exactly as {@link Mdast.toMdast} does — `"let x"`, not
|
|
3280
|
+
* `"let x\n"`, for a fenced block holding one line.
|
|
3281
|
+
*
|
|
3282
|
+
* This is the remark-ecosystem derivation, and it deliberately differs
|
|
3283
|
+
* from the display derivation behind `DocumentHeading.text` and
|
|
3284
|
+
* `DocumentSection.text`, which render a hard break as a space and drop
|
|
3285
|
+
* raw HTML. Reach for `toText` when matching what remark tooling computes
|
|
3286
|
+
* (slugs, search indexes); reach for the document accessors for display.
|
|
3287
|
+
*
|
|
3288
|
+
* The walk recurses once per nesting level. Every tree `Markdown.parse` or
|
|
3289
|
+
* `Mdast.fromMdast` produces stays within the package's nesting cap, but a
|
|
3290
|
+
* hand-built tree nested far deeper than that can overflow the call stack.
|
|
3291
|
+
*
|
|
3292
|
+
* @param node - The node to flatten.
|
|
3293
|
+
* @param options - Optional {@link MdastToTextOptions}.
|
|
3294
|
+
* @returns The node's text content.
|
|
3295
|
+
*/
|
|
3296
|
+
static toText(node: MarkdownNode, options?: MdastToTextOptions): string;
|
|
2996
3297
|
}
|
|
2997
3298
|
//#endregion
|
|
2998
3299
|
//#region src/TomlFrontmatter.d.ts
|
|
@@ -3046,5 +3347,5 @@ export declare const TomlFrontmatter: FrontmatterCodec;
|
|
|
3046
3347
|
*/
|
|
3047
3348
|
export declare const YamlFrontmatter: FrontmatterCodec;
|
|
3048
3349
|
//#endregion
|
|
3049
|
-
export type { DocumentHeading, DocumentLink, FrontmatterCodec, FrontmatterResolveError, FrontmatterSchemaError, FrontmatterSchemaResolver, FrontmatterWriteError, LinkBearingNode, MarkdownNodeOfType, MarkdownNodeType, MarkdownPath, MarkdownRangeLike, MarkdownSegment, MdastNode, SectionHeadingMatch, SectionQueryOptions };
|
|
3350
|
+
export type { DocumentHeading, DocumentLink, FrontmatterCodec, FrontmatterResolveError, FrontmatterSchemaError, FrontmatterSchemaResolver, FrontmatterWriteError, LinkBearingNode, MarkdownNodeOfType, MarkdownNodeType, MarkdownPath, MarkdownPhrasingOnlyType, MarkdownRangeLike, MarkdownSegment, MarkdownTransformHandlers, MarkdownTransformResult, MdastNode, MdastToTextOptions, SectionHeadingMatch, SectionQueryOptions, SectionSpan, SectionSpanOptions, ShiftHeadingsOptions };
|
|
3050
3351
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -8,9 +8,10 @@ import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic
|
|
|
8
8
|
import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
|
|
9
9
|
import { DocumentSection, MarkdownDocument } from "./MarkdownDocument.js";
|
|
10
10
|
import { CodeBlockStyle, MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode } from "./MarkdownFormat.js";
|
|
11
|
+
import { MarkdownTransform } from "./MarkdownTransform.js";
|
|
11
12
|
import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
|
|
12
13
|
import { Mdast, MdastDecodeError } from "./Mdast.js";
|
|
13
14
|
import { TomlFrontmatter } from "./TomlFrontmatter.js";
|
|
14
15
|
import { YamlFrontmatter } from "./YamlFrontmatter.js";
|
|
15
16
|
|
|
16
|
-
export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
|
17
|
+
export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownTransform, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/internal/sectionBounds.ts
|
|
2
|
+
/**
|
|
3
|
+
* Every root-level heading of `children`, in document order, paired with the
|
|
4
|
+
* child index its section ends at.
|
|
5
|
+
*/
|
|
6
|
+
const rootHeadingBounds = (children) => {
|
|
7
|
+
const headings = [];
|
|
8
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
9
|
+
const block = children[index];
|
|
10
|
+
if (block !== void 0 && block.type === "heading") headings.push({
|
|
11
|
+
index,
|
|
12
|
+
node: block
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const nextByDepth = [
|
|
16
|
+
void 0,
|
|
17
|
+
void 0,
|
|
18
|
+
void 0,
|
|
19
|
+
void 0,
|
|
20
|
+
void 0,
|
|
21
|
+
void 0,
|
|
22
|
+
void 0
|
|
23
|
+
];
|
|
24
|
+
const bounds = new Array(headings.length);
|
|
25
|
+
for (let index = headings.length - 1; index >= 0; index -= 1) {
|
|
26
|
+
const current = headings[index];
|
|
27
|
+
if (current === void 0) continue;
|
|
28
|
+
let boundary;
|
|
29
|
+
for (let depth = 1; depth <= current.node.depth; depth += 1) {
|
|
30
|
+
const candidate = nextByDepth[depth];
|
|
31
|
+
if (candidate !== void 0 && (boundary === void 0 || candidate < boundary)) boundary = candidate;
|
|
32
|
+
}
|
|
33
|
+
bounds[index] = {
|
|
34
|
+
heading: current.node,
|
|
35
|
+
index: current.index,
|
|
36
|
+
end: boundary === void 0 ? children.length : headings[boundary]?.index ?? children.length
|
|
37
|
+
};
|
|
38
|
+
nextByDepth[current.node.depth] = index;
|
|
39
|
+
}
|
|
40
|
+
return bounds;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { rootHeadingBounds };
|
package/internal/stringify.js
CHANGED
|
@@ -824,21 +824,19 @@ const serializeBlocks = (children, state, tight = false) => {
|
|
|
824
824
|
return out;
|
|
825
825
|
};
|
|
826
826
|
/**
|
|
827
|
-
* Serialize a
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
* `MAX_NESTING_DEPTH`; the facade materializes the typed error. Output is
|
|
831
|
-
* empty for an empty root and ends with exactly one newline otherwise.
|
|
827
|
+
* Serialize a root-level block list — the one path behind both a whole
|
|
828
|
+
* `Root` and a lone flow node (`[node]`), so the two cannot diverge. Builds
|
|
829
|
+
* no wrapper node, so nothing here can fail a schema constructor.
|
|
832
830
|
*/
|
|
833
|
-
const
|
|
831
|
+
const stringifyBlocks = (children) => {
|
|
834
832
|
const state = {
|
|
835
833
|
depth: 0,
|
|
836
834
|
jsxDepth: 0,
|
|
837
|
-
mdx: treeContainsMdx(
|
|
835
|
+
mdx: children.some((child) => treeContainsMdx(child))
|
|
838
836
|
};
|
|
839
|
-
const body = serializeBlocks(
|
|
837
|
+
const body = serializeBlocks(children, state);
|
|
840
838
|
return body === "" ? "" : `${body}\n`;
|
|
841
839
|
};
|
|
842
840
|
|
|
843
841
|
//#endregion
|
|
844
|
-
export {
|
|
842
|
+
export { stringifyBlocks };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
|
|
6
6
|
"keywords": [
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@effected/jsonc": "^0.
|
|
43
|
-
"@effected/toml": "^0.
|
|
44
|
-
"@effected/yaml": "^0.
|
|
42
|
+
"@effected/jsonc": "^0.18.0",
|
|
43
|
+
"@effected/toml": "^0.14.0",
|
|
44
|
+
"@effected/yaml": "^0.22.0",
|
|
45
45
|
"effect": "^4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|