markdown-codec 6.4.2 → 6.5.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.
Files changed (54) hide show
  1. package/README.md +20 -7
  2. package/dist/block/block.cjs +1 -1
  3. package/dist/block/block.d.cts +1 -1
  4. package/dist/block/block.d.ts +1 -1
  5. package/dist/block/block.js +1 -1
  6. package/dist/block/definitions.d.cts +1 -1
  7. package/dist/block/definitions.d.ts +1 -1
  8. package/dist/codec.d.cts +8 -8
  9. package/dist/codec.d.ts +8 -8
  10. package/dist/diagnostics/diagnostics.cjs +2 -1
  11. package/dist/diagnostics/diagnostics.d.cts +1 -1
  12. package/dist/diagnostics/diagnostics.d.ts +1 -1
  13. package/dist/diagnostics/diagnostics.js +2 -1
  14. package/dist/{diagnostics-eL5c8OWm.d.cts → diagnostics-DLum7kY6.d.cts} +1 -0
  15. package/dist/{diagnostics-eL5c8OWm.d.ts → diagnostics-DLum7kY6.d.ts} +1 -0
  16. package/dist/emit/emit.cjs +28 -5
  17. package/dist/emit/emit.js +28 -5
  18. package/dist/emit/front-matter.cjs +22 -5
  19. package/dist/emit/front-matter.js +22 -5
  20. package/dist/emit/html-table.cjs +104 -0
  21. package/dist/emit/html-table.d.cts +7 -0
  22. package/dist/emit/html-table.d.ts +7 -0
  23. package/dist/emit/html-table.js +102 -0
  24. package/dist/emit/inline.cjs +30 -6
  25. package/dist/emit/inline.d.cts +1 -13
  26. package/dist/emit/inline.d.ts +1 -13
  27. package/dist/emit/inline.js +30 -6
  28. package/dist/emit/table.cjs +10 -13
  29. package/dist/emit/table.d.cts +2 -2
  30. package/dist/emit/table.d.ts +2 -2
  31. package/dist/emit/table.js +10 -13
  32. package/dist/html/html-table.cjs +243 -0
  33. package/dist/html/html-table.d.cts +5 -0
  34. package/dist/html/html-table.d.ts +5 -0
  35. package/dist/html/html-table.js +242 -0
  36. package/dist/index.cjs +1 -1
  37. package/dist/index.d.cts +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/inline-2-lUb0vo.d.ts +14 -0
  41. package/dist/inline-514HW3Pi.d.cts +14 -0
  42. package/dist/lower/front-matter.cjs +64 -20
  43. package/dist/lower/front-matter.d.cts +1 -1
  44. package/dist/lower/front-matter.d.ts +1 -1
  45. package/dist/lower/front-matter.js +64 -20
  46. package/dist/lower/inline.d.cts +1 -1
  47. package/dist/lower/inline.d.ts +1 -1
  48. package/dist/lower/lower.cjs +16 -5
  49. package/dist/lower/lower.js +16 -5
  50. package/dist/options/options.d.cts +1 -1
  51. package/dist/options/options.d.ts +1 -1
  52. package/dist/read.d.cts +1 -1
  53. package/dist/read.d.ts +1 -1
  54. package/package.json +18 -18
package/README.md CHANGED
@@ -142,11 +142,11 @@ Modelled on `pdf-codec`'s own layering, aimed at CommonMark+GFM instead of PDF:
142
142
  - **`src/scan/`** — CommonMark line/character scanner, plus `entity-table.ts` (generated from `assets/html-entities/entities.json`).
143
143
  - **`src/block/`** — CommonMark block-structure algorithm (open-block stack, continuation matching): paragraphs, headings, code blocks, block quotes, lists (incl. GFM task-list-item), thematic breaks, link references, footnote definitions, GFM tables.
144
144
  - **`src/inline/`** — emphasis, code spans, links, autolinks, raw HTML, GFM strikethrough, footnote references, line breaks. `link.ts` and `footnote.ts` hold the label grammars the block phase shares.
145
- - **`src/html/`** — raw HTML recognition (bounded rules, not a general parser) plus `render.ts` (conformance oracle; internal only).
145
+ - **`src/html/`** — raw HTML recognition (bounded rules, not a general parser) plus `render.ts` (conformance oracle; internal only). `html-table.ts` is the one exception to "never parsed as markup": a block-level HTML that is, in full, one well-formed `<table>` recognises straight to a real `ContentTable` instead of opaque text — see [HTML-table fallback](#html-table-fallback).
146
146
  - **`src/image/`** — PNG/JPEG dimension reader and base64 codec, shared by `src/lower/` and `src/emit/`.
147
147
  - **`src/shared/`** — string-shape conventions `src/lower`/`src/emit` agree on (`style-constants.ts`, `list-id.ts`'s opaque `numId`). Re-exported so `documents.js`'s `MarkdownEditor` reuses the identical grammar.
148
148
  - **`src/lower/`** — AST → `ContentDocument` lowering (thin adapter, not a second parser); top-of-file table maps each construct to its diagnostic gap.
149
- - **`src/emit/`** — `ContentDocument` → markdown text emission, the structural inverse of `src/lower`.
149
+ - **`src/emit/`** — `ContentDocument` → markdown text emission, the structural inverse of `src/lower`. `html-table.ts` is `src/html/html-table.ts`'s own write-side counterpart.
150
150
  - **`src/read.ts`** / **`src/write.ts`** / **`src/codec.ts`** — the public entry points at both levels: `readMarkdown`/`writeMarkdown`/`markdownCodec` over `DocumentTree`, and `readMarkdownContent`/`writeMarkdownContent`/`markdownContentCodec` over `ContentDocument`. The tree-form functions are thin compositions of `document-schema.js`'s `assembleTree`/`flattenTree` onto the flat ones; no conversion logic of their own lives here.
151
151
 
152
152
  ## Vendored assets
@@ -195,7 +195,7 @@ Every construct `src/lower`/`src/emit` cannot represent losslessly is a document
195
195
  - **`md/math-inline-preserved-as-text`** — inline `\( \)` math stays a Cambria-Math-marked raw-LaTeX run; display `$$` math is a real embedded formula carrying the presentation layer.
196
196
  - **`md/image-unresolved`** — no resolver, `undefined` return, or non-PNG/JPEG bytes degrades to alt-text run.
197
197
  - **`md/raw-html-preserved-as-text` / `md/raw-html-dropped`** — raw HTML kept as literal text (default) or dropped; never interpreted. The preserved text's verbatim original quarantines as markdown residue on its node, and this package's own writer re-emits that residue as-is.
198
- - **`md/front-matter-key-unmapped`** — no YAML/TOML engine; only five known `LayoutMetadata` keys recognised. The verbatim original block rides the package-level residue table (`readMarkdown`'s `documentPackage.source.frontmatter`), which `writeMarkdown` re-emits as-is.
198
+ - **`md/front-matter-key-unmapped`** — no YAML/TOML engine; only `LayoutMetadata`'s own string/array/enum fields are recognised (every one of them except `producer`, which is PDF-writer-only), each as a flat `key: value` (or `keywords: [a, b]`) line. The verbatim original block rides the package-level residue table (`readMarkdown`'s `documentPackage.source.frontmatter`), which `writeMarkdown` re-emits as-is.
199
199
  - **`md/heading-level-clamped`** — styleId beyond `Heading6` (from another format) clamps to level 6 via document-schema.js's shared `clampHeadingLevel()`.
200
200
  - **`md/heading-line-break-collapsed`** — a hard or soft break embedded in a heading's own runs (most commonly a setext heading read with more than one physical line) is promoted to setext output whenever the level admits one (1 or 2) and the break's own placement is safe to promote, the only markdown grammar that can hold a genuine line break inside heading text; a level 3-6 heading with the same shape has no such fallback and always collapses the break to a single space instead, as does a level 1/2 heading whose break placement is unsafe to promote (see `md/heading-line-break-unsafe-for-setext` below).
201
201
  - **`md/heading-line-break-unsafe-for-setext`** — a level 1/2 heading's own embedded break sits where promoting to setext would violate one of the three clauses packed into setext's own grammar sentence (spec 0.31.2, "Setext headings"): "one or more lines of text, not interrupted by a blank line, of which the first line does not have more than 3 spaces of indentation, followed by a setext heading underline. The lines of text must be such that, were they not followed by the setext heading underline, they would be interpreted as a paragraph: they cannot be interpretable as a code fence, ATX heading, block quote, thematic break, list item, or HTML block." Reported when promoting would leave a blank line immediately before the underline (a trailing break) or in the middle of the heading's own text (an interior break between two embedded breaks, or a break immediately followed by incidental whitespace-only content); when the heading's own first content line opens with 4 or more columns of space/tab indentation — CommonMark's own tab-stop rule counts a tab by the same 4-column stop, so a single leading tab is exactly as unsafe as 4 leading spaces — since that line would read back as an indented code block rather than heading text; or when a line AFTER the first would itself be read as one of the spec's six interrupting constructs (a code fence, ATX heading, block quote, thematic break, list item, or HTML block) instead of ordinary paragraph continuation text — reachable from a bold or strikethrough run that becomes empty immediately after a break, whose bare pair of emphasis markers spells as a thematic break (`____`/`****`) or a code-fence opener (`~~~~`). ATX collapses the break to a single space instead of promoting into any of these corrupt reparses. A break at the very START of the heading's own text stays eligible for setext, but that exemption covers only a single leading blank line, and only the blank line itself: it sits before the text-and-underline run even begins, so it reparses as ordinary inter-block whitespace ahead of the heading rather than corrupting anything. A second consecutive leading blank line is not exempt — CommonMark's own list-item grammar allows at most one, so a second closes a list item as empty and spills the heading out with its list membership lost, even though it would be harmless at top level or inside a blockquote — and the indentation clause above still applies in full to the line that follows the leading blank line, the heading's own first real content: a leading break immediately followed by 4 or more columns of indentation is refused exactly as an unbroken heading in the same shape would be, since promoting would read that following line back as an indented code block regardless of the leading break ahead of it.
@@ -204,9 +204,10 @@ Every construct `src/lower`/`src/emit` cannot represent losslessly is a document
204
204
  - **`md/paragraph-indent-dropped`** — `indentLeftPt` without a recognised styleId; indent dropped, paragraph renders.
205
205
  - **`md/list-numid-fallback`** — a foreign or absent `numId` (depth-only `ContentListMembership`) falls back to a plain bullet list.
206
206
  - **`md/raw-html-preserved-as-text`** — see the raw-HTML entry above.
207
- - **`md/table-cell-formatting-dropped`** — colSpan/rowSpan/background, or a non-paragraph, non-image block (a nested table, say), have no GFM cell representation at all; the cell still renders, unmerged/unstyled, with that content dropped.
208
- - **`md/table-cell-multi-paragraph-joined`** — a cell carrying more than one block joins their rendered text with a literal `<br>`, real inline HTML every GFM table renderer treats as a genuine line break.
209
- - **`md/table-cell-image-degraded`** — an `image`-kind cell block emits inline (`![alt](data:...)`) rather than being dropped; on read-back it degrades to a run carrying the alt text with the image's own data URI as that run's hyperlink, the same "nested image" shape an image inside emphasis/a link already degrades to elsewhere in this package.
207
+ - **`md/table-html-fallback`** — a cell's own colSpan/rowSpan/background, or a block a plain GFM cell cannot represent at all (most commonly a nested table), sends the WHOLE table through the HTML-table fallback instead of plain pipe syntax — see [HTML-table fallback](#html-table-fallback). Fires once per table that falls back.
208
+ - **`md/table-cell-formatting-dropped`** — the residual gap even inside the HTML-table fallback: a cell's own pattern background fill (only a solid fill maps onto CSS `background-color`), a nested table mixed with sibling content in the same cell (a cell's own nested table must be its entire content), or any other block kind (`embeddedObject`, `pageBreak`) — none of these have a representation this bounded recogniser attempts; the cell still renders, unmerged/unstyled, with that content dropped.
209
+ - **`md/table-cell-multi-paragraph-joined`** — a cell carrying more than one block joins their rendered content with a literal `<br>`, real inline HTML every GFM table renderer treats as a genuine line break — markdown syntax in the plain pipe-syntax writer, a real `<br>` HTML tag in the HTML-table fallback.
210
+ - **`md/table-cell-image-degraded`** — plain pipe-syntax writer only: an `image`-kind cell block emits inline (`![alt](data:...)`) rather than being dropped; on read-back it degrades to a run carrying the alt text with the image's own data URI as that run's hyperlink, the same "nested image" shape an image inside emphasis/a link already degrades to elsewhere in this package. The HTML-table fallback represents an image losslessly instead, as a real `<img>` tag.
210
211
  - **`md/duplicate-footnote-definition`** — two definitions share a label; every reference resolves to the first, both are kept as written.
211
212
  - **`md/footnote-body-heading-flattened`** — a heading inside a definition body is carried as literal ATX text, since a construct extent may not open or close a heading scope.
212
213
  - **`md/construct-unrepresented`** — a construct kind markdown has no syntax for renders transparently: its extent still appears, the construct itself does not.
@@ -221,10 +222,22 @@ The two halves of a footnote map onto the **same `anchor` construct at two diffe
221
222
  - **A definition becomes an `anchor` construct.** Lowering emits document-schema.js's construct boundary markers — a `constructStart` carrying `{ kind: 'anchor', anchorType: 'footnote', name }`, the definition's own lowered body blocks, and a `constructEnd` — which is what `readMarkdownContent` returns in its block flow, and what `decompose` promotes to a construct group of its own in the `DocumentTree` `readMarkdown` returns (the descriptor rides the group's `node`, the body blocks its `children`). The body rides the construct's extent rather than `AnchorDescriptor.definition`, which names a key in a package-level definitions table: `DocumentTree` does carry that table as a root (unlike the flat `ContentDocument`), but a table entry there is a flat descriptor record, not a container for block content, so a body that is genuinely several paragraphs, a code block, or a list still has nowhere to live as a table value either way — the construct's own bracketed extent is the one shape in this schema built to hold real block content. A bodyless `[^1]:` lowers to the point anchor the same descriptor describes: a pair with nothing between it.
222
223
  - **A reference site becomes a point run-level `anchor` extent.** A reference sits between two runs inside a paragraph, so no block-level boundary marker can bracket it without splitting the paragraph in two — but a run-level construct extent (`RunConstructExtent` on `ContentParagraph.constructs`, document-schema.js 4.5.0) names exactly that shape. Lowering emits an ordinary text run keeping the reference's own `[^label]` spelling (the materialised rendering, so a consumer that ignores constructs still shows `[^1]`) plus a point extent — `{ kind: 'anchor', anchorType: 'footnote', name }` at `startRun === endRun` naming that run, the same wiring ooxml.js's docx reader mints for a `w:footnoteReference` — carried through `decompose`/`flattenTree` verbatim, exactly the way a table cell's own markers are. The writer spells `[^label]` back out from the covering extent rather than from anything about the run's text (which is what still distinguishes a genuine reference from a deliberately-escaped literal `\[^1\]`), gated by the same label grammar as the definition marker: a foreign name that grammar cannot spell degrades to the run's own escaped text plus `md/construct-unrepresented`.
223
224
 
224
- Definitions are recognised only at the document's own top level. Inside a block quote or a list item, the pair's extent would sit inside a scope the enclosing container had already opened, which the marker contract forbids a producer from emitting — so the text stays an ordinary paragraph there. A heading inside a definition body is flattened to literal ATX text for the same reason.
225
+ Definitions are recognised at the document's own top level, directly inside a block quote, or directly inside a list item (ExaDev/markdown-codec#957) — a blockquote's own division construct threads its enclosing container's context straight through the definition's anchor pair the same way it already does for a nested blockquote, and a list item whose own first block is the anchor pair picks up the same placeholder-paragraph carry a nested blockquote already relies on. Not recognised inside another definition's own body: a footnote-in-a-footnote has no cross-format shape, so a `[^2]:` line there stays ordinary body text. A heading inside a definition body is still flattened to literal ATX text, and a blockquote containing a heading anywhere in its own subtree still degrades to indent-only structure — both because a construct boundary marker's extent may not open or close a heading-group scope, a document-schema.js-level limitation this package cannot route around from its own side.
225
226
 
226
227
  Emission is the inverse and validates first: a section's markers must pair as balanced brackets (checked through document-schema.js's own `findConstructMarkerImbalance`, the shared definition every codec and `decompose` agree on) or `writeMarkdownContent` throws `MarkdownUnbalancedConstructMarkersError`. A tree already satisfies that balance by construction — `decompose` refuses to build one from an unbalanced stream — so `writeMarkdown` reaches this check only on a hand-built package flattened back to an unbalanced flow. A construct kind with no markdown syntax — a bookmark, a division, a tracked change — renders transparently: its extent still appears in place, only the construct's own identity is lost.
227
228
 
229
+ ## HTML-table fallback
230
+
231
+ GFM's own pipe-table syntax (github.github.com/gfm, "Tables (extension)") holds inline content only — no cell can carry `colSpan`/`rowSpan`/a background, and no cell can hold a block that isn't inlinable (a nested table, most notably). `writeMarkdownContent` detects this per table (`tableNeedsHtmlFallback`, `src/emit/html-table.ts`): the moment ANY cell needs one of these, the WHOLE table renders as a raw HTML `<table>` block instead of the plain `| a | b |` writer — GFM's own cell grammar cannot mix an HTML sub-block into one cell of an otherwise pipe-syntax table, so a partial, per-cell fallback is not an option. A raw HTML `<table>` block is legal CommonMark regardless (spec 0.31.2, "HTML blocks" start condition 6 names `table` directly — <https://spec.commonmark.org/0.31.2/#html-blocks>). A table with none of these needs still emits the existing, human-readable pipe syntax, unaffected.
232
+
233
+ `readMarkdownContent`/`readMarkdown` recognise this same shape on the way back in (`parseHtmlTable`, `src/html/html-table.ts`) — the one exception to this package's own "raw HTML is never parsed as markup" rule (see the raw-HTML gotcha above and `src/html/html.ts`'s own top comment): a block-level HTML that is, in full, one well-formed `<table>` lowers straight to a real `ContentTable`, so a table written through this fallback reads back as an equal (or equivalent) `ContentTable` rather than degrading to opaque preserved text. This recognition is gated on GFM's table extension being enabled (`gfmTables`, default `true`) exactly the way plain pipe-table promotion already is — with it disabled, a `<table>` block stays exactly the opaque raw HTML it always was, since a `ContentTable` is a GFM-table-extension construct either way it is spelled in the source.
234
+
235
+ Both halves are **bounded recognisers, not a general HTML/DOM parser** — matching `src/html/html.ts`'s own deliberate scope. They recognise `<table>`/`<tr>`/`<td>`/`<th>` and, on each cell, `colspan`/`rowspan` (positive integers) and a `style="background-color:#rrggbb"` declaration (a `ContentCellFill`'s `solid` variant only — a `pattern` fill has no plain-CSS equivalent this writer attempts, and degrades with `TABLE_CELL_FORMATTING_DROPPED`), plus `style="text-align:..."` for the cell's own paragraph alignment — read per cell rather than only from the header row, a strictly richer fidelity than plain GFM's single per-column delimiter marker can ever express, so a table already falling back for another reason never silently loses alignment plain GFM would have kept. Attribute values must be double-quoted, matching exactly what the writer itself emits; a single-quoted or unquoted value is a real HTML shape neither side attempts.
236
+
237
+ A cell's own inline formatting renders as real HTML tags — `<strong>`/`<em>`/`<del>`/`<code>`/`<a href>`, the identical bold/italic/strike/hyperlink/Courier-New-as-code-span vocabulary the plain writer already spells in markdown syntax — because raw HTML block content is never reprocessed as markdown, so writing markdown punctuation inside this block would render as its own literal characters rather than formatting. The reader also recognises the common `<b>`/`<i>`/`<s>`/`<strike>` synonyms real hand-authored HTML tends to use, in any nesting order or combination.
238
+
239
+ A cell's own nested-block content is bounded to two shapes: several paragraph/image blocks join with a literal `<br>`, exactly as the plain writer already joins a multi-paragraph cell (`TABLE_CELL_MULTI_PARAGRAPH_JOINED`); or, when a cell's ENTIRE content is one nested table and nothing else, that table recurses through the identical fallback, unconditionally, since a table nested inside another table's cell can never itself be pipe syntax. A nested table mixed with sibling content in the same cell, or any other block kind (`embeddedObject`, `pageBreak`), has no representation either side attempts and is dropped with `TABLE_CELL_FORMATTING_DROPPED`, exactly as it always was before this fallback existed.
240
+
228
241
  ## Fidelity
229
242
 
230
243
  **Markdown → `ContentDocument` is dominated by target-schema limits, not parsing gaps.** The parser recognises every construct CommonMark and GFM define; the limiting factor is what `ContentDocument` can hold — a cross-format pivot shaped around docx/pptx/odt/odp/ods/odg, not markdown's richer model. Each gap is a permanent structural mismatch.
@@ -312,7 +312,7 @@ var BlockParser = class {
312
312
  footnoteDefinitionMayOpenIn(container) {
313
313
  let node = container;
314
314
  while (node?.kind === "list") node = node.parent;
315
- return node?.kind === "document";
315
+ return node?.kind === "document" || node?.kind === "blockquote" || node?.kind === "listItem";
316
316
  }
317
317
  tryHtmlBlockStart(container) {
318
318
  if (this.line.indented || this.line.peekNextNonspace() !== "<") return "none";
@@ -1,6 +1,6 @@
1
1
  import { s as MarkdownDocumentNode } from "../ast-CNfTgS0N.cjs";
2
2
  import { r as FootnoteLabelSet } from "../footnote-CKk4JbLk.cjs";
3
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.cjs";
3
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-DLum7kY6.cjs";
4
4
  import { LinkReferenceMap } from "../inline/link.cjs";
5
5
  import { t as InlineParseOptions } from "../inline-CnO86zMw.cjs";
6
6
  //#region src/block/block.d.ts
@@ -1,6 +1,6 @@
1
1
  import { s as MarkdownDocumentNode } from "../ast-CNfTgS0N.js";
2
2
  import { r as FootnoteLabelSet } from "../footnote-CKk4JbLk.js";
3
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.js";
3
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-DLum7kY6.js";
4
4
  import { LinkReferenceMap } from "../inline/link.js";
5
5
  import { t as InlineParseOptions } from "../inline-DItZ-VVw.js";
6
6
  //#region src/block/block.d.ts
@@ -311,7 +311,7 @@ var BlockParser = class {
311
311
  footnoteDefinitionMayOpenIn(container) {
312
312
  let node = container;
313
313
  while (node?.kind === "list") node = node.parent;
314
- return node?.kind === "document";
314
+ return node?.kind === "document" || node?.kind === "blockquote" || node?.kind === "listItem";
315
315
  }
316
316
  tryHtmlBlockStart(container) {
317
317
  if (this.line.indented || this.line.peekNextNonspace() !== "<") return "none";
@@ -1,4 +1,4 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.cjs";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-DLum7kY6.cjs";
2
2
  import { LinkReferenceDefinition } from "../inline/link.cjs";
3
3
  //#region src/block/definitions.d.ts
4
4
  declare function extractDefinitions(content: string, references: Map<string, LinkReferenceDefinition>, sink?: MarkdownDiagnosticSink, startLine?: number): string;
@@ -1,4 +1,4 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.js";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-DLum7kY6.js";
2
2
  import { LinkReferenceDefinition } from "../inline/link.js";
3
3
  //#region src/block/definitions.d.ts
4
4
  declare function extractDefinitions(content: string, references: Map<string, LinkReferenceDefinition>, sink?: MarkdownDiagnosticSink, startLine?: number): string;
package/dist/codec.d.cts CHANGED
@@ -11,8 +11,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
11
11
  paragraph: z.ZodOptional<z.ZodObject<{
12
12
  alignment: z.ZodOptional<z.ZodEnum<{
13
13
  left: "left";
14
- center: "center";
15
14
  right: "right";
15
+ center: "center";
16
16
  justify: "justify";
17
17
  }>>;
18
18
  list: z.ZodOptional<z.ZodObject<{
@@ -159,8 +159,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
159
159
  paragraph: z.ZodOptional<z.ZodObject<{
160
160
  alignment: z.ZodOptional<z.ZodEnum<{
161
161
  left: "left";
162
- center: "center";
163
162
  right: "right";
163
+ center: "center";
164
164
  justify: "justify";
165
165
  }>>;
166
166
  list: z.ZodOptional<z.ZodObject<{
@@ -307,8 +307,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
307
307
  paragraph: z.ZodOptional<z.ZodObject<{
308
308
  alignment: z.ZodOptional<z.ZodEnum<{
309
309
  left: "left";
310
- center: "center";
311
310
  right: "right";
311
+ center: "center";
312
312
  justify: "justify";
313
313
  }>>;
314
314
  list: z.ZodOptional<z.ZodObject<{
@@ -455,8 +455,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
455
455
  paragraph: z.ZodOptional<z.ZodObject<{
456
456
  alignment: z.ZodOptional<z.ZodEnum<{
457
457
  left: "left";
458
- center: "center";
459
458
  right: "right";
459
+ center: "center";
460
460
  justify: "justify";
461
461
  }>>;
462
462
  list: z.ZodOptional<z.ZodObject<{
@@ -634,8 +634,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
634
634
  paragraph: z.ZodOptional<z.ZodObject<{
635
635
  alignment: z.ZodOptional<z.ZodEnum<{
636
636
  left: "left";
637
- center: "center";
638
637
  right: "right";
638
+ center: "center";
639
639
  justify: "justify";
640
640
  }>>;
641
641
  list: z.ZodOptional<z.ZodObject<{
@@ -1275,8 +1275,8 @@ declare const markdownContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffe
1275
1275
  }, z.core.$strip>>;
1276
1276
  alignment: z.ZodOptional<z.ZodEnum<{
1277
1277
  left: "left";
1278
- center: "center";
1279
1278
  right: "right";
1279
+ center: "center";
1280
1280
  justify: "justify";
1281
1281
  }>>;
1282
1282
  verticalAlignment: z.ZodOptional<z.ZodEnum<{
@@ -1379,10 +1379,10 @@ declare const markdownContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffe
1379
1379
  }>;
1380
1380
  align: z.ZodEnum<{
1381
1381
  left: "left";
1382
- center: "center";
1383
1382
  right: "right";
1384
1383
  top: "top";
1385
1384
  bottom: "bottom";
1385
+ center: "center";
1386
1386
  inside: "inside";
1387
1387
  outside: "outside";
1388
1388
  }>;
@@ -1422,10 +1422,10 @@ declare const markdownContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffe
1422
1422
  }>;
1423
1423
  align: z.ZodEnum<{
1424
1424
  left: "left";
1425
- center: "center";
1426
1425
  right: "right";
1427
1426
  top: "top";
1428
1427
  bottom: "bottom";
1428
+ center: "center";
1429
1429
  inside: "inside";
1430
1430
  outside: "outside";
1431
1431
  }>;
package/dist/codec.d.ts CHANGED
@@ -11,8 +11,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
11
11
  paragraph: z.ZodOptional<z.ZodObject<{
12
12
  alignment: z.ZodOptional<z.ZodEnum<{
13
13
  left: "left";
14
- center: "center";
15
14
  right: "right";
15
+ center: "center";
16
16
  justify: "justify";
17
17
  }>>;
18
18
  list: z.ZodOptional<z.ZodObject<{
@@ -159,8 +159,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
159
159
  paragraph: z.ZodOptional<z.ZodObject<{
160
160
  alignment: z.ZodOptional<z.ZodEnum<{
161
161
  left: "left";
162
- center: "center";
163
162
  right: "right";
163
+ center: "center";
164
164
  justify: "justify";
165
165
  }>>;
166
166
  list: z.ZodOptional<z.ZodObject<{
@@ -307,8 +307,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
307
307
  paragraph: z.ZodOptional<z.ZodObject<{
308
308
  alignment: z.ZodOptional<z.ZodEnum<{
309
309
  left: "left";
310
- center: "center";
311
310
  right: "right";
311
+ center: "center";
312
312
  justify: "justify";
313
313
  }>>;
314
314
  list: z.ZodOptional<z.ZodObject<{
@@ -455,8 +455,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
455
455
  paragraph: z.ZodOptional<z.ZodObject<{
456
456
  alignment: z.ZodOptional<z.ZodEnum<{
457
457
  left: "left";
458
- center: "center";
459
458
  right: "right";
459
+ center: "center";
460
460
  justify: "justify";
461
461
  }>>;
462
462
  list: z.ZodOptional<z.ZodObject<{
@@ -634,8 +634,8 @@ declare const markdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uin
634
634
  paragraph: z.ZodOptional<z.ZodObject<{
635
635
  alignment: z.ZodOptional<z.ZodEnum<{
636
636
  left: "left";
637
- center: "center";
638
637
  right: "right";
638
+ center: "center";
639
639
  justify: "justify";
640
640
  }>>;
641
641
  list: z.ZodOptional<z.ZodObject<{
@@ -1275,8 +1275,8 @@ declare const markdownContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffe
1275
1275
  }, z.core.$strip>>;
1276
1276
  alignment: z.ZodOptional<z.ZodEnum<{
1277
1277
  left: "left";
1278
- center: "center";
1279
1278
  right: "right";
1279
+ center: "center";
1280
1280
  justify: "justify";
1281
1281
  }>>;
1282
1282
  verticalAlignment: z.ZodOptional<z.ZodEnum<{
@@ -1379,10 +1379,10 @@ declare const markdownContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffe
1379
1379
  }>;
1380
1380
  align: z.ZodEnum<{
1381
1381
  left: "left";
1382
- center: "center";
1383
1382
  right: "right";
1384
1383
  top: "top";
1385
1384
  bottom: "bottom";
1385
+ center: "center";
1386
1386
  inside: "inside";
1387
1387
  outside: "outside";
1388
1388
  }>;
@@ -1422,10 +1422,10 @@ declare const markdownContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffe
1422
1422
  }>;
1423
1423
  align: z.ZodEnum<{
1424
1424
  left: "left";
1425
- center: "center";
1426
1425
  right: "right";
1427
1426
  top: "top";
1428
1427
  bottom: "bottom";
1428
+ center: "center";
1429
1429
  inside: "inside";
1430
1430
  outside: "outside";
1431
1431
  }>;
@@ -32,7 +32,8 @@ const MarkdownDiagnosticCodes = {
32
32
  LIST_NUMID_FALLBACK: "md/list-numid-fallback",
33
33
  TABLE_CELL_FORMATTING_DROPPED: "md/table-cell-formatting-dropped",
34
34
  TABLE_CELL_MULTI_PARAGRAPH_JOINED: "md/table-cell-multi-paragraph-joined",
35
- TABLE_CELL_IMAGE_DEGRADED: "md/table-cell-image-degraded"
35
+ TABLE_CELL_IMAGE_DEGRADED: "md/table-cell-image-degraded",
36
+ TABLE_HTML_FALLBACK: "md/table-html-fallback"
36
37
  };
37
38
  var MarkdownParseError = class extends Error {
38
39
  code;
@@ -1,2 +1,2 @@
1
- import { a as MarkdownInputTooLargeError, c as MarkdownNestingLimitExceededError, d as MarkdownUnbalancedConstructMarkersError, f as MarkdownUnsupportedDocumentKindError, i as MarkdownDiagnosticSink, l as MarkdownPackageFlattenError, m as NOOP_MARKDOWN_DIAGNOSTIC_SINK, n as MarkdownDiagnosticCodes, o as MarkdownInvalidRunConstructExtentError, p as MarkdownWriteError, r as MarkdownDiagnosticSeverity, s as MarkdownInvalidUtf8Error, t as MarkdownDiagnostic, u as MarkdownParseError } from "../diagnostics-eL5c8OWm.cjs";
1
+ import { a as MarkdownInputTooLargeError, c as MarkdownNestingLimitExceededError, d as MarkdownUnbalancedConstructMarkersError, f as MarkdownUnsupportedDocumentKindError, i as MarkdownDiagnosticSink, l as MarkdownPackageFlattenError, m as NOOP_MARKDOWN_DIAGNOSTIC_SINK, n as MarkdownDiagnosticCodes, o as MarkdownInvalidRunConstructExtentError, p as MarkdownWriteError, r as MarkdownDiagnosticSeverity, s as MarkdownInvalidUtf8Error, t as MarkdownDiagnostic, u as MarkdownParseError } from "../diagnostics-DLum7kY6.cjs";
2
2
  export { MarkdownDiagnostic, MarkdownDiagnosticCodes, MarkdownDiagnosticSeverity, MarkdownDiagnosticSink, MarkdownInputTooLargeError, MarkdownInvalidRunConstructExtentError, MarkdownInvalidUtf8Error, MarkdownNestingLimitExceededError, MarkdownPackageFlattenError, MarkdownParseError, MarkdownUnbalancedConstructMarkersError, MarkdownUnsupportedDocumentKindError, MarkdownWriteError, NOOP_MARKDOWN_DIAGNOSTIC_SINK };
@@ -1,2 +1,2 @@
1
- import { a as MarkdownInputTooLargeError, c as MarkdownNestingLimitExceededError, d as MarkdownUnbalancedConstructMarkersError, f as MarkdownUnsupportedDocumentKindError, i as MarkdownDiagnosticSink, l as MarkdownPackageFlattenError, m as NOOP_MARKDOWN_DIAGNOSTIC_SINK, n as MarkdownDiagnosticCodes, o as MarkdownInvalidRunConstructExtentError, p as MarkdownWriteError, r as MarkdownDiagnosticSeverity, s as MarkdownInvalidUtf8Error, t as MarkdownDiagnostic, u as MarkdownParseError } from "../diagnostics-eL5c8OWm.js";
1
+ import { a as MarkdownInputTooLargeError, c as MarkdownNestingLimitExceededError, d as MarkdownUnbalancedConstructMarkersError, f as MarkdownUnsupportedDocumentKindError, i as MarkdownDiagnosticSink, l as MarkdownPackageFlattenError, m as NOOP_MARKDOWN_DIAGNOSTIC_SINK, n as MarkdownDiagnosticCodes, o as MarkdownInvalidRunConstructExtentError, p as MarkdownWriteError, r as MarkdownDiagnosticSeverity, s as MarkdownInvalidUtf8Error, t as MarkdownDiagnostic, u as MarkdownParseError } from "../diagnostics-DLum7kY6.js";
2
2
  export { MarkdownDiagnostic, MarkdownDiagnosticCodes, MarkdownDiagnosticSeverity, MarkdownDiagnosticSink, MarkdownInputTooLargeError, MarkdownInvalidRunConstructExtentError, MarkdownInvalidUtf8Error, MarkdownNestingLimitExceededError, MarkdownPackageFlattenError, MarkdownParseError, MarkdownUnbalancedConstructMarkersError, MarkdownUnsupportedDocumentKindError, MarkdownWriteError, NOOP_MARKDOWN_DIAGNOSTIC_SINK };
@@ -31,7 +31,8 @@ const MarkdownDiagnosticCodes = {
31
31
  LIST_NUMID_FALLBACK: "md/list-numid-fallback",
32
32
  TABLE_CELL_FORMATTING_DROPPED: "md/table-cell-formatting-dropped",
33
33
  TABLE_CELL_MULTI_PARAGRAPH_JOINED: "md/table-cell-multi-paragraph-joined",
34
- TABLE_CELL_IMAGE_DEGRADED: "md/table-cell-image-degraded"
34
+ TABLE_CELL_IMAGE_DEGRADED: "md/table-cell-image-degraded",
35
+ TABLE_HTML_FALLBACK: "md/table-html-fallback"
35
36
  };
36
37
  var MarkdownParseError = class extends Error {
37
38
  code;
@@ -40,6 +40,7 @@ declare const MarkdownDiagnosticCodes: {
40
40
  readonly TABLE_CELL_FORMATTING_DROPPED: "md/table-cell-formatting-dropped";
41
41
  readonly TABLE_CELL_MULTI_PARAGRAPH_JOINED: "md/table-cell-multi-paragraph-joined";
42
42
  readonly TABLE_CELL_IMAGE_DEGRADED: "md/table-cell-image-degraded";
43
+ readonly TABLE_HTML_FALLBACK: "md/table-html-fallback";
43
44
  };
44
45
  declare class MarkdownParseError extends Error {
45
46
  readonly code: string;
@@ -40,6 +40,7 @@ declare const MarkdownDiagnosticCodes: {
40
40
  readonly TABLE_CELL_FORMATTING_DROPPED: "md/table-cell-formatting-dropped";
41
41
  readonly TABLE_CELL_MULTI_PARAGRAPH_JOINED: "md/table-cell-multi-paragraph-joined";
42
42
  readonly TABLE_CELL_IMAGE_DEGRADED: "md/table-cell-image-degraded";
43
+ readonly TABLE_HTML_FALLBACK: "md/table-html-fallback";
43
44
  };
44
45
  declare class MarkdownParseError extends Error {
45
46
  readonly code: string;
@@ -9,8 +9,8 @@ const require_block_line = require("../block/line.cjs");
9
9
  const require_block_list = require("../block/list.cjs");
10
10
  const require_block_table = require("../block/table.cjs");
11
11
  const require_block_block = require("../block/block.cjs");
12
- const require_shared_list_id = require("../shared/list-id.cjs");
13
12
  const require_shared_style_constants = require("../shared/style-constants.cjs");
13
+ const require_shared_list_id = require("../shared/list-id.cjs");
14
14
  const require_emit_front_matter = require("./front-matter.cjs");
15
15
  const require_emit_inline = require("./inline.cjs");
16
16
  const require_emit_image = require("./image.cjs");
@@ -284,22 +284,36 @@ function firstBlockCheckbox(first, taskNumId) {
284
284
  stripGlyph: false
285
285
  };
286
286
  }
287
- function renderListItemMarker(numId, info, checkboxText, context) {
287
+ function renderListItemMarker(numId, info, glyph, checkboxText, context) {
288
288
  if (info?.type === "ordered" && numId !== void 0) {
289
289
  const next = context.orderedCounters.get(numId) ?? info.start ?? 1;
290
290
  context.orderedCounters.set(numId, next + 1);
291
- const bare = `${String(next)}${context.orderedDelimiter} `;
291
+ const bare = `${String(next)}${glyph} `;
292
292
  return {
293
293
  full: `${bare}${checkboxText}`,
294
294
  bareLength: bare.length
295
295
  };
296
296
  }
297
- const bare = `${context.bulletMarker} `;
297
+ const bare = `${glyph} `;
298
298
  return {
299
299
  full: `${bare}${checkboxText}`,
300
300
  bareLength: bare.length
301
301
  };
302
302
  }
303
+ const BULLET_GLYPH_CANDIDATES = [
304
+ "-",
305
+ "+",
306
+ "*"
307
+ ];
308
+ const ORDERED_GLYPH_CANDIDATES = [".", ")"];
309
+ function resolveListGlyph(numId, type, previousSibling, context) {
310
+ const cached = context.resolvedListGlyphs.get(numId);
311
+ if (cached !== void 0) return cached;
312
+ const defaultGlyph = type === "ordered" ? context.orderedDelimiter : context.bulletMarker;
313
+ const glyph = previousSibling?.type === type && previousSibling.glyph === defaultGlyph ? (type === "ordered" ? ORDERED_GLYPH_CANDIDATES : BULLET_GLYPH_CANDIDATES).find((candidate) => candidate !== defaultGlyph) ?? defaultGlyph : defaultGlyph;
314
+ context.resolvedListGlyphs.set(numId, glyph);
315
+ return glyph;
316
+ }
303
317
  function consumeSameItemRun(items, from, level, itemId) {
304
318
  let end = from;
305
319
  while (end < items.length) {
@@ -379,17 +393,25 @@ function requiresBlankLineBefore(next, previousStyleId, context) {
379
393
  function renderListRegion(items, context) {
380
394
  const parts = [];
381
395
  let index = 0;
396
+ let previousSibling;
382
397
  while (index < items.length) {
383
398
  const item = items[index];
384
399
  if (item === void 0) break;
385
400
  const { numId, level, itemId } = item.list;
386
401
  const info = listInfoFor(numId, context);
387
402
  const loose = info?.loose === true;
403
+ const type = info?.type ?? "bullet";
404
+ const glyph = numId === void 0 ? type === "ordered" ? context.orderedDelimiter : context.bulletMarker : resolveListGlyph(numId, type, previousSibling, context);
405
+ if (numId !== void 0) previousSibling = {
406
+ numId,
407
+ type,
408
+ glyph
409
+ };
388
410
  const { segments, next } = collectListItem(items, index, level, itemId);
389
411
  const first = segments[0]?.blocks[0];
390
412
  if (first === void 0) break;
391
413
  const { checkboxText, stripGlyph } = firstBlockCheckbox(first, info?.task === true);
392
- const marker = renderListItemMarker(numId, info, checkboxText, context);
414
+ const marker = renderListItemMarker(numId, info, glyph, checkboxText, context);
393
415
  const indent = " ".repeat(marker.bareLength);
394
416
  let text = "";
395
417
  let renderedFirstLine = false;
@@ -609,6 +631,7 @@ function emitMarkdown(document, options = {}) {
609
631
  headingStyle: options.headingStyle ?? "atx",
610
632
  embedImages: options.images ?? true,
611
633
  orderedCounters: /* @__PURE__ */ new Map(),
634
+ resolvedListGlyphs: /* @__PURE__ */ new Map(),
612
635
  reportedFallbackNumIds: /* @__PURE__ */ new Set(),
613
636
  reportedAbsentNumIdFallback: false,
614
637
  divisionDepth: 0,
package/dist/emit/emit.js CHANGED
@@ -8,8 +8,8 @@ import { LineCursor } from "../block/line.js";
8
8
  import { parseListMarker } from "../block/list.js";
9
9
  import { parseTableDelimiterRow, splitTableRow } from "../block/table.js";
10
10
  import { ATX_MARKER_PATTERN, CODE_FENCE_PATTERN, MATH_BLOCK_MARKER_PATTERN, THEMATIC_BREAK_PATTERN } from "../block/block.js";
11
- import { parseListNumId } from "../shared/list-id.js";
12
11
  import { CODE_BLOCK_STYLE_ID, HORIZONTAL_RULE_STYLE_ID, HTML_PREFORMATTED_STYLE_ID, MATH_BLOCK_STYLE_ID, QUOTE_STYLE_ID, parseHeadingStyleId } from "../shared/style-constants.js";
12
+ import { parseListNumId } from "../shared/list-id.js";
13
13
  import { emitFrontMatter } from "./front-matter.js";
14
14
  import { emitRuns, escapeLinkDestination, escapeMarkdownText, renderLinkTitle } from "./inline.js";
15
15
  import { emitImage } from "./image.js";
@@ -283,22 +283,36 @@ function firstBlockCheckbox(first, taskNumId) {
283
283
  stripGlyph: false
284
284
  };
285
285
  }
286
- function renderListItemMarker(numId, info, checkboxText, context) {
286
+ function renderListItemMarker(numId, info, glyph, checkboxText, context) {
287
287
  if (info?.type === "ordered" && numId !== void 0) {
288
288
  const next = context.orderedCounters.get(numId) ?? info.start ?? 1;
289
289
  context.orderedCounters.set(numId, next + 1);
290
- const bare = `${String(next)}${context.orderedDelimiter} `;
290
+ const bare = `${String(next)}${glyph} `;
291
291
  return {
292
292
  full: `${bare}${checkboxText}`,
293
293
  bareLength: bare.length
294
294
  };
295
295
  }
296
- const bare = `${context.bulletMarker} `;
296
+ const bare = `${glyph} `;
297
297
  return {
298
298
  full: `${bare}${checkboxText}`,
299
299
  bareLength: bare.length
300
300
  };
301
301
  }
302
+ const BULLET_GLYPH_CANDIDATES = [
303
+ "-",
304
+ "+",
305
+ "*"
306
+ ];
307
+ const ORDERED_GLYPH_CANDIDATES = [".", ")"];
308
+ function resolveListGlyph(numId, type, previousSibling, context) {
309
+ const cached = context.resolvedListGlyphs.get(numId);
310
+ if (cached !== void 0) return cached;
311
+ const defaultGlyph = type === "ordered" ? context.orderedDelimiter : context.bulletMarker;
312
+ const glyph = previousSibling?.type === type && previousSibling.glyph === defaultGlyph ? (type === "ordered" ? ORDERED_GLYPH_CANDIDATES : BULLET_GLYPH_CANDIDATES).find((candidate) => candidate !== defaultGlyph) ?? defaultGlyph : defaultGlyph;
313
+ context.resolvedListGlyphs.set(numId, glyph);
314
+ return glyph;
315
+ }
302
316
  function consumeSameItemRun(items, from, level, itemId) {
303
317
  let end = from;
304
318
  while (end < items.length) {
@@ -378,17 +392,25 @@ function requiresBlankLineBefore(next, previousStyleId, context) {
378
392
  function renderListRegion(items, context) {
379
393
  const parts = [];
380
394
  let index = 0;
395
+ let previousSibling;
381
396
  while (index < items.length) {
382
397
  const item = items[index];
383
398
  if (item === void 0) break;
384
399
  const { numId, level, itemId } = item.list;
385
400
  const info = listInfoFor(numId, context);
386
401
  const loose = info?.loose === true;
402
+ const type = info?.type ?? "bullet";
403
+ const glyph = numId === void 0 ? type === "ordered" ? context.orderedDelimiter : context.bulletMarker : resolveListGlyph(numId, type, previousSibling, context);
404
+ if (numId !== void 0) previousSibling = {
405
+ numId,
406
+ type,
407
+ glyph
408
+ };
387
409
  const { segments, next } = collectListItem(items, index, level, itemId);
388
410
  const first = segments[0]?.blocks[0];
389
411
  if (first === void 0) break;
390
412
  const { checkboxText, stripGlyph } = firstBlockCheckbox(first, info?.task === true);
391
- const marker = renderListItemMarker(numId, info, checkboxText, context);
413
+ const marker = renderListItemMarker(numId, info, glyph, checkboxText, context);
392
414
  const indent = " ".repeat(marker.bareLength);
393
415
  let text = "";
394
416
  let renderedFirstLine = false;
@@ -608,6 +630,7 @@ function emitMarkdown(document, options = {}) {
608
630
  headingStyle: options.headingStyle ?? "atx",
609
631
  embedImages: options.images ?? true,
610
632
  orderedCounters: /* @__PURE__ */ new Map(),
633
+ resolvedListGlyphs: /* @__PURE__ */ new Map(),
611
634
  reportedFallbackNumIds: /* @__PURE__ */ new Set(),
612
635
  reportedAbsentNumIdFallback: false,
613
636
  divisionDepth: 0,
@@ -5,13 +5,30 @@ function emitScalar(value) {
5
5
  if (!NEEDS_QUOTING_PATTERN.test(value) && value.trim() === value && value.length > 0) return value;
6
6
  return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
7
7
  }
8
+ const STRING_FIELD_ENTRIES = [
9
+ ["title", (metadata) => metadata.title],
10
+ ["author", (metadata) => metadata.author],
11
+ ["subject", (metadata) => metadata.subject],
12
+ ["creator", (metadata) => metadata.creator],
13
+ ["date", (metadata) => metadata.createdIso],
14
+ ["modified", (metadata) => metadata.modifiedIso],
15
+ ["lastPrinted", (metadata) => metadata.lastPrintedIso],
16
+ ["language", (metadata) => metadata.language],
17
+ ["publisher", (metadata) => metadata.publisher],
18
+ ["contributor", (metadata) => metadata.contributor],
19
+ ["rights", (metadata) => metadata.rights],
20
+ ["identifier", (metadata) => metadata.identifier],
21
+ ["comments", (metadata) => metadata.comments],
22
+ ["company", (metadata) => metadata.company],
23
+ ["manager", (metadata) => metadata.manager]
24
+ ];
8
25
  function emitFrontMatter(metadata) {
9
26
  const lines = [];
10
- if (metadata.title !== void 0) lines.push(`title: ${emitScalar(metadata.title)}`);
11
- if (metadata.author !== void 0) lines.push(`author: ${emitScalar(metadata.author)}`);
12
- if (metadata.subject !== void 0) lines.push(`subject: ${emitScalar(metadata.subject)}`);
13
- if (metadata.creator !== void 0) lines.push(`creator: ${emitScalar(metadata.creator)}`);
14
- if (metadata.createdIso !== void 0) lines.push(`date: ${emitScalar(metadata.createdIso)}`);
27
+ for (const [key, accessor] of STRING_FIELD_ENTRIES) {
28
+ const value = accessor(metadata);
29
+ if (value !== void 0) lines.push(`${key}: ${emitScalar(value)}`);
30
+ }
31
+ if (metadata.direction !== void 0) lines.push(`direction: ${metadata.direction}`);
15
32
  if (metadata.keywords !== void 0 && metadata.keywords.length > 0) lines.push(`keywords: [${metadata.keywords.map((keyword) => emitScalar(keyword)).join(", ")}]`);
16
33
  if (lines.length === 0) return;
17
34
  return [
@@ -4,13 +4,30 @@ function emitScalar(value) {
4
4
  if (!NEEDS_QUOTING_PATTERN.test(value) && value.trim() === value && value.length > 0) return value;
5
5
  return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
6
6
  }
7
+ const STRING_FIELD_ENTRIES = [
8
+ ["title", (metadata) => metadata.title],
9
+ ["author", (metadata) => metadata.author],
10
+ ["subject", (metadata) => metadata.subject],
11
+ ["creator", (metadata) => metadata.creator],
12
+ ["date", (metadata) => metadata.createdIso],
13
+ ["modified", (metadata) => metadata.modifiedIso],
14
+ ["lastPrinted", (metadata) => metadata.lastPrintedIso],
15
+ ["language", (metadata) => metadata.language],
16
+ ["publisher", (metadata) => metadata.publisher],
17
+ ["contributor", (metadata) => metadata.contributor],
18
+ ["rights", (metadata) => metadata.rights],
19
+ ["identifier", (metadata) => metadata.identifier],
20
+ ["comments", (metadata) => metadata.comments],
21
+ ["company", (metadata) => metadata.company],
22
+ ["manager", (metadata) => metadata.manager]
23
+ ];
7
24
  function emitFrontMatter(metadata) {
8
25
  const lines = [];
9
- if (metadata.title !== void 0) lines.push(`title: ${emitScalar(metadata.title)}`);
10
- if (metadata.author !== void 0) lines.push(`author: ${emitScalar(metadata.author)}`);
11
- if (metadata.subject !== void 0) lines.push(`subject: ${emitScalar(metadata.subject)}`);
12
- if (metadata.creator !== void 0) lines.push(`creator: ${emitScalar(metadata.creator)}`);
13
- if (metadata.createdIso !== void 0) lines.push(`date: ${emitScalar(metadata.createdIso)}`);
26
+ for (const [key, accessor] of STRING_FIELD_ENTRIES) {
27
+ const value = accessor(metadata);
28
+ if (value !== void 0) lines.push(`${key}: ${emitScalar(value)}`);
29
+ }
30
+ if (metadata.direction !== void 0) lines.push(`direction: ${metadata.direction}`);
14
31
  if (metadata.keywords !== void 0 && metadata.keywords.length > 0) lines.push(`keywords: [${metadata.keywords.map((keyword) => emitScalar(keyword)).join(", ")}]`);
15
32
  if (lines.length === 0) return;
16
33
  return [