@effected/markdown 0.18.0 → 0.20.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/Frontmatter.js +1 -2
- package/FrontmatterResolver.js +1 -2
- package/FrontmatterSource.js +1 -2
- package/JsonFrontmatter.js +1 -2
- package/Markdown.js +25 -14
- package/MarkdownDiagnostic.js +1 -2
- package/MarkdownDocument.js +5 -39
- package/MarkdownEdit.js +1 -2
- package/MarkdownFormat.js +1 -2
- package/MarkdownNode.js +1 -2
- package/MarkdownTransform.js +166 -0
- package/MarkdownVisitor.js +1 -2
- package/Mdast.js +50 -2
- package/TomlFrontmatter.js +1 -2
- package/YamlFrontmatter.js +1 -2
- package/index.d.ts +310 -39
- package/index.js +2 -1
- package/internal/blockParser.js +1 -2
- package/internal/blockRegistry.js +1 -2
- package/internal/blockTypes.js +1 -2
- package/internal/blocks/atxHeading.js +1 -2
- package/internal/blocks/blockquote.js +1 -2
- package/internal/blocks/code.js +1 -2
- package/internal/blocks/document.js +1 -2
- package/internal/blocks/fencedCode.js +1 -2
- package/internal/blocks/footnoteDefinition.js +1 -2
- package/internal/blocks/frontmatter.js +1 -2
- package/internal/blocks/htmlBlock.js +1 -2
- package/internal/blocks/indentedCode.js +1 -2
- package/internal/blocks/linkReferenceDefinition.js +1 -2
- package/internal/blocks/list.js +1 -2
- package/internal/blocks/paragraph.js +1 -2
- package/internal/blocks/setextHeading.js +1 -2
- package/internal/blocks/table.js +1 -2
- package/internal/blocks/taskListItem.js +1 -2
- package/internal/blocks/thematicBreak.js +1 -2
- package/internal/carriers.js +1 -2
- package/internal/entities.js +1 -2
- package/internal/entityMap.js +1 -2
- package/internal/htmlTags.js +1 -2
- package/internal/inlineNode.js +1 -2
- package/internal/inlineParser.js +1 -2
- package/internal/inlineRegistry.js +1 -2
- package/internal/inlines/autolink.js +1 -2
- package/internal/inlines/autolinkLiteral.js +1 -2
- package/internal/inlines/codeSpan.js +1 -2
- package/internal/inlines/emphasis.js +1 -2
- package/internal/inlines/entity.js +1 -2
- package/internal/inlines/escape.js +1 -2
- package/internal/inlines/footnoteReference.js +1 -2
- package/internal/inlines/lineBreak.js +1 -2
- package/internal/inlines/link.js +1 -2
- package/internal/inlines/rawHtml.js +1 -2
- package/internal/inlines/strikethrough.js +1 -2
- package/internal/inlines/text.js +1 -2
- package/internal/lineIndex.js +1 -2
- package/internal/patterns.js +1 -2
- package/internal/phrasing.js +1 -2
- package/internal/preprocess.js +1 -2
- package/internal/rawInline.js +1 -2
- package/internal/references.js +1 -2
- package/internal/sectionBounds.js +43 -0
- package/internal/segments.js +1 -2
- package/internal/stringify.js +8 -11
- package/internal/unescape.js +1 -2
- package/package.json +4 -4
package/Frontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { MarkdownEdit } from "./MarkdownEdit.js";
|
|
|
2
2
|
import { FrontmatterFormat } from "./MarkdownNode.js";
|
|
3
3
|
import { Effect, Schema } from "effect";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/Frontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* Indicates that a frontmatter codec was handed a capture of a different
|
|
8
8
|
* format — a yaml codec applied to a `+++` toml capture, for example.
|
|
@@ -303,5 +303,4 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
|
|
|
303
303
|
}
|
|
304
304
|
};
|
|
305
305
|
|
|
306
|
-
//#endregion
|
|
307
306
|
export { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterValidationError, MarkdownFrontmatter };
|
package/FrontmatterResolver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Result, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/FrontmatterResolver.ts
|
|
4
4
|
/**
|
|
5
5
|
* A `$schema` declaration referencing a schema by URL — any string containing
|
|
6
6
|
* `://`.
|
|
@@ -267,5 +267,4 @@ var SchemaResolver = class SchemaResolver {
|
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
|
|
270
|
-
//#endregion
|
|
271
270
|
export { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError };
|
package/FrontmatterSource.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterFormat } from "./MarkdownNode.js";
|
|
|
2
2
|
import { scanRawFrontmatter } from "./internal/blocks/frontmatter.js";
|
|
3
3
|
import { Effect, Schema } from "effect";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/FrontmatterSource.ts
|
|
6
6
|
/**
|
|
7
7
|
* The three line-terminator spellings a fence line may end with. Recorded by
|
|
8
8
|
* {@link FrontmatterSource.split} as fidelity — what the source's fence
|
|
@@ -169,5 +169,4 @@ var FrontmatterSource = class {
|
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
-
//#endregion
|
|
173
172
|
export { FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit };
|
package/JsonFrontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismat
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Jsonc } from "@effected/jsonc";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/JsonFrontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* The json frontmatter codec, over `@effected/jsonc`.
|
|
8
8
|
*
|
|
@@ -42,5 +42,4 @@ const JsonFrontmatter = {
|
|
|
42
42
|
})))
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
//#endregion
|
|
46
45
|
export { JsonFrontmatter };
|
package/Markdown.js
CHANGED
|
@@ -2,11 +2,11 @@ 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
|
|
|
9
|
-
|
|
9
|
+
// src/Markdown.ts
|
|
10
10
|
/**
|
|
11
11
|
* The markdown dialects the parser can be pointed at. `"gfm"` — CommonMark
|
|
12
12
|
* 0.31.2 plus the GitHub extensions (tables, strikethrough, autolink
|
|
@@ -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
|
|
@@ -414,5 +426,4 @@ var Markdown = class Markdown {
|
|
|
414
426
|
static MarkdownFromString = Markdown.fromString();
|
|
415
427
|
};
|
|
416
428
|
|
|
417
|
-
//#endregion
|
|
418
429
|
export { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError, parsePassResult };
|
package/MarkdownDiagnostic.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MARKDOWN_PARSE_ERROR_CODES } from "./internal/carriers.js";
|
|
2
2
|
import { Schema } from "effect";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/MarkdownDiagnostic.ts
|
|
5
5
|
/**
|
|
6
6
|
* Error codes `Markdown.parse`/`MarkdownDocument.parse` can fail with.
|
|
7
7
|
* Currently exactly one, `"NestingDepthExceeded"`, the hardening-guard trip;
|
|
@@ -84,5 +84,4 @@ function lineChar(text, offset) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
//#endregion
|
|
88
87
|
export { MarkdownDiagnostic, MarkdownParseErrorCode };
|
package/MarkdownDocument.js
CHANGED
|
@@ -5,9 +5,10 @@ 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
|
+
// src/MarkdownDocument.ts
|
|
11
12
|
/**
|
|
12
13
|
* A heading-delimited span from {@link MarkdownDocument.sections}: the
|
|
13
14
|
* heading, its depth, the source `range` the section occupies and the
|
|
@@ -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,
|
|
@@ -424,5 +391,4 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
424
391
|
static parse = Effect.fn("MarkdownDocument.parse")((text, options) => Effect.fromResult(MarkdownDocument.parseResult(text, options)));
|
|
425
392
|
};
|
|
426
393
|
|
|
427
|
-
//#endregion
|
|
428
394
|
export { DocumentSection, MarkdownDocument };
|
package/MarkdownEdit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/MarkdownEdit.ts
|
|
4
4
|
/**
|
|
5
5
|
* A range within a markdown document, expressed as a zero-based character
|
|
6
6
|
* `offset` and a `length` in UTF-16 code units. Pass to `MarkdownFormat.format`
|
|
@@ -52,5 +52,4 @@ var MarkdownEdit = class extends Schema.Class("MarkdownEdit")({
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
//#endregion
|
|
56
55
|
export { MarkdownEdit, MarkdownRange };
|
package/MarkdownFormat.js
CHANGED
|
@@ -3,7 +3,7 @@ import { BulletChar, EmphasisChar, FenceChar, HeadingStyle, Paragraph, Root, Tex
|
|
|
3
3
|
import { Markdown, MarkdownDialect, MarkdownParseOptions } from "./Markdown.js";
|
|
4
4
|
import { Effect, Result, Schema } from "effect";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// src/MarkdownFormat.ts
|
|
7
7
|
/**
|
|
8
8
|
* The two ways CommonMark spells a code block: `fenced` (a backtick or tilde
|
|
9
9
|
* fence) and `indented` (four-space indentation).
|
|
@@ -511,5 +511,4 @@ var MarkdownFormat = class MarkdownFormat {
|
|
|
511
511
|
});
|
|
512
512
|
};
|
|
513
513
|
|
|
514
|
-
//#endregion
|
|
515
514
|
export { CodeBlockStyle, MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode };
|
package/MarkdownNode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/MarkdownNode.ts
|
|
4
4
|
/**
|
|
5
5
|
* A single point in a source document: 1-based `line` and `column`, 0-based
|
|
6
6
|
* `offset`.
|
|
@@ -854,5 +854,4 @@ const MarkdownNode = Schema.suspend(() => Schema.Union([
|
|
|
854
854
|
TableContent
|
|
855
855
|
]));
|
|
856
856
|
|
|
857
|
-
//#endregion
|
|
858
857
|
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterFormat, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, MarkdownNode, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
2
|
+
|
|
3
|
+
// 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
|
+
export { MarkdownTransform };
|
package/MarkdownVisitor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
2
2
|
import { Data, Stream } from "effect";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/MarkdownVisitor.ts
|
|
5
5
|
/**
|
|
6
6
|
* Constructors and matchers for the `MarkdownVisitorEvent` union (e.g.
|
|
7
7
|
* `MarkdownVisitorEvent.Enter({ node, path, depth })`,
|
|
@@ -105,5 +105,4 @@ function* walkNode(node, path, depth) {
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
//#endregion
|
|
109
108
|
export { MarkdownVisitor, MarkdownVisitorEvent };
|
package/Mdast.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Root } from "./MarkdownNode.js";
|
|
|
2
2
|
import { unescapeString } from "./internal/unescape.js";
|
|
3
3
|
import { Effect, Result, Schema } from "effect";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/Mdast.ts
|
|
6
6
|
/**
|
|
7
7
|
* Indicates that foreign mdast input failed to decode into the package's
|
|
8
8
|
* node classes.
|
|
@@ -469,7 +469,55 @@ 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
|
-
//#endregion
|
|
475
523
|
export { Mdast, MdastDecodeError };
|
package/TomlFrontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismat
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Toml } from "@effected/toml";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/TomlFrontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* The toml frontmatter codec, over `@effected/toml`.
|
|
8
8
|
*
|
|
@@ -40,5 +40,4 @@ const TomlFrontmatter = {
|
|
|
40
40
|
})))
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
//#endregion
|
|
44
43
|
export { TomlFrontmatter };
|
package/YamlFrontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismat
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Yaml } from "@effected/yaml";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/YamlFrontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* The yaml frontmatter codec, over `@effected/yaml`.
|
|
8
8
|
*
|
|
@@ -41,5 +41,4 @@ const YamlFrontmatter = {
|
|
|
41
41
|
})))
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
//#endregion
|
|
45
44
|
export { YamlFrontmatter };
|