@jarenjs/md 0.87.0 → 0.89.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/README.md CHANGED
@@ -117,6 +117,22 @@ output — asserted over the CommonMark corpus in
117
117
  [`test/md/to-html.test.js`](../../test/md/to-html.test.js), not on one
118
118
  fixture.
119
119
 
120
+ ### Page breaks
121
+
122
+ A standalone `<!-- pagebreak -->` line parses as `{ type: 'pageBreak' }`
123
+ and prints back to the same canonical marker with `toMarkdown`. It works
124
+ without plugins, including inside blockquotes and list items. Inline
125
+ comments, code examples and markers inside larger HTML blocks keep their
126
+ ordinary meaning.
127
+
128
+ Both emitters render a `.md-page-break` separator. Import
129
+ `@jarenjs/md/styles/md.css` for a dashed divider on screen and a forced
130
+ page boundary when printing or saving to PDF. The screen rule disappears
131
+ in print; pagination follows the host's printable layout. `toHtml` users
132
+ should wrap their output in an element with class `md` to apply these styles.
133
+ The marker is a core node and renders under every raw-HTML policy.
134
+ See [MD-FORMAT.md §4.9](docs/MD-FORMAT.md#49-page-breaks-normative) for its grammar.
135
+
120
136
  ### Heading anchors
121
137
 
122
138
  `[see below](#the-section)` needs something to land on, so the emitter can
@@ -45,6 +45,8 @@ export declare function paragraph(children: MdNode[]): MdNode;
45
45
  export declare function heading(depth: number, children: MdNode[]): MdNode;
46
46
  /** @returns {MdNode} */
47
47
  export declare function thematicBreak(): MdNode;
48
+ /** @returns {MdNode} */
49
+ export declare function pageBreak(): MdNode;
48
50
  /** @param {MdNode[]} children @returns {MdNode} */
49
51
  export declare function blockquote(children: MdNode[]): MdNode;
50
52
  /**
@@ -77,8 +77,7 @@ export type MdVnodeOptions = {
77
77
  headingAnchors?: boolean;
78
78
  /**
79
79
  * the accessible name of the
80
- * appended footnotes section (default `'Footnotes'`) — the one string
81
- * this emitter writes that a reader can hear.
80
+ * appended footnotes section (default `'Footnotes'`).
82
81
  */
83
82
  footnotesLabel?: string;
84
83
  /**
package/docs/MD-FORMAT.md CHANGED
@@ -38,7 +38,8 @@ blockquotes, ordered/unordered lists, inline emphasis/links/images/code,
38
38
  hard and soft breaks, backslash escapes, autolinks, raw HTML blocks and
39
39
  spans) plus the GFM extensions in universal use: **tables**,
40
40
  **strikethrough**, **task lists**, **footnotes** (§4.6) and **literal
41
- autolinks** (§4.7). The parser passes **every example in the CommonMark
41
+ autolinks** (§4.7). A standalone `<!-- pagebreak -->` comment is reserved
42
+ as a page-break extension (§4.9). The parser passes **every example in the CommonMark
42
43
  specification** through the string emitter (§4.4a), and the benchmark
43
44
  workspace scores both emitters against the official corpus — and against
44
45
  the GFM specification's extension sections — on every run.
@@ -46,7 +47,8 @@ the GFM specification's extension sections — on every run.
46
47
  This specification remains normative for the package: it covers the
47
48
  frontmatter, the AST, and the extensions neither spec describes
48
49
  (footnotes are GitHub's, documented nowhere but here). Where it and
49
- CommonMark speak about the same construct they agree. Two places where
50
+ CommonMark speak about the same construct they agree, except for the
51
+ reserved page-break comment. Two places where
50
52
  this package deliberately differs from the GFM reference implementation
51
53
  are stated with their reasons in the package README's scorecard section;
52
54
  neither is a dialect gap a document can fall into.
@@ -167,6 +169,7 @@ unchanged where possible.
167
169
  | `paragraph` | `children` | inline content |
168
170
  | `heading` | `depth` (1–6), `children` | ATX and setext |
169
171
  | `thematicBreak` | — | `***`, `---`, `___` |
172
+ | `pageBreak` | — | standalone `<!-- pagebreak -->` (§4.9) |
170
173
  | `blockquote` | `children` | block content |
171
174
  | `list` | `ordered` (boolean), `start` (number or `null`), `tight` (boolean), `children` | children are `listItem`s |
172
175
  | `listItem` | `checked` (`true`/`false`/`null`), `children` | `checked` non-null only for task-list items |
@@ -462,6 +465,32 @@ markers — a document with no directives MUST come back byte-identical —
462
465
  because canonical re-printing (§5) would reformat every hand-written
463
466
  document it touched.
464
467
 
468
+ ### 4.9 Page breaks (normative)
469
+
470
+ A complete standalone `<!-- pagebreak -->` line MUST produce
471
+ `{ "type": "pageBreak" }`, a block node without content. The keyword is
472
+ case-sensitive. Zero or more spaces or tabs MAY surround `pagebreak`
473
+ inside the comment and MAY follow the closing delimiter; normal block
474
+ indentation of up to three spaces applies after container prefixes.
475
+ The marker MAY interrupt a paragraph and MAY occur inside a blockquote,
476
+ list item or footnote definition. It is independent of the `gfm` option
477
+ and needs no plugin. Batch and incremental parsing share this rule.
478
+
479
+ Inline comments, fenced/indented code, escaped text, multiline comments,
480
+ comments with extra text and markers inside an already-open HTML block
481
+ MUST NOT become page breaks. The marker has no namespace or paired closing
482
+ marker and is not a value directive (§4.8).
483
+
484
+ `toMarkdown` MUST print the canonical spelling `<!-- pagebreak -->`.
485
+ Both renderers emit a `div` with class `md-page-break`, role `separator`
486
+ and accessible name `Page break`, independently of raw-HTML policy.
487
+ The component stylesheet shows a dashed divider on screen and removes
488
+ the rule/margins while setting `break-after: page` and the legacy
489
+ `page-break-after: always` in print. The following content starts on a
490
+ new printed page within the host's printable block layout; this is not
491
+ automatic screen pagination. String-rendering hosts MUST load the
492
+ stylesheet and provide a `.md` ancestor to obtain this presentation.
493
+
465
494
  ## 5. Canonical Markdown and round-trips
466
495
 
467
496
  `toMarkdown(doc)` prints **canonical Markdown**: ATX headings, `-`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/md",
3
3
  "private": false,
4
- "version": "0.87.0",
4
+ "version": "0.89.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./dist/types/index.d.ts",
@@ -73,8 +73,8 @@
73
73
  "prepack": "npm run build:types"
74
74
  },
75
75
  "dependencies": {
76
- "@jarenjs/core": "^0.87.0",
77
- "@jarenjs/mermaid": "^0.87.0",
78
- "@jarenjs/view": "^0.87.0"
76
+ "@jarenjs/core": "^0.89.0",
77
+ "@jarenjs/mermaid": "^0.89.0",
78
+ "@jarenjs/view": "^0.89.0"
79
79
  }
80
80
  }
@@ -28,6 +28,7 @@
28
28
  { "$ref": "#/$defs/paragraph" },
29
29
  { "$ref": "#/$defs/heading" },
30
30
  { "$ref": "#/$defs/thematicBreak" },
31
+ { "$ref": "#/$defs/pageBreak" },
31
32
  { "$ref": "#/$defs/blockquote" },
32
33
  { "$ref": "#/$defs/list" },
33
34
  { "$ref": "#/$defs/code" },
@@ -85,6 +86,11 @@
85
86
  "required": ["type"],
86
87
  "properties": { "type": { "const": "thematicBreak" } }
87
88
  },
89
+ "pageBreak": {
90
+ "type": "object",
91
+ "required": ["type"],
92
+ "properties": { "type": { "const": "pageBreak" } }
93
+ },
88
94
  "blockquote": {
89
95
  "type": "object",
90
96
  "required": ["type", "children"],
@@ -282,7 +288,7 @@
282
288
  "type": "string",
283
289
  "not": {
284
290
  "enum": [
285
- "paragraph", "heading", "thematicBreak", "blockquote",
291
+ "paragraph", "heading", "thematicBreak", "pageBreak", "blockquote",
286
292
  "list", "listItem", "code", "html", "table", "tableRow",
287
293
  "tableCell", "text", "emphasis", "strong", "strikethrough",
288
294
  "link", "image", "inlineCode", "break", "softBreak", "custom",
package/src/ast.js CHANGED
@@ -54,6 +54,11 @@ export function thematicBreak() {
54
54
  return { type: 'thematicBreak' };
55
55
  }
56
56
 
57
+ /** @returns {MdNode} */
58
+ export function pageBreak() {
59
+ return { type: 'pageBreak' };
60
+ }
61
+
57
62
  /** @param {MdNode[]} children @returns {MdNode} */
58
63
  export function blockquote(children) {
59
64
  return { type: 'blockquote', children };
package/src/parser.js CHANGED
@@ -46,13 +46,16 @@ import {
46
46
  import { scanEntity } from './entities.js';
47
47
  import {
48
48
  MD_VERSION,
49
- thematicBreak, blockquote, list, listItem,
49
+ thematicBreak, pageBreak, blockquote, list, listItem,
50
50
  code, htmlBlock, tableRow, tableCell,
51
51
  text, emphasis, strong, strikethrough, link, image, inlineCode,
52
52
  hardBreak, softBreak, textOf,
53
53
  autolink, footnoteDefinition, footnoteReference,
54
54
  } from './ast.js';
55
55
 
56
+ /** A one-line layout marker, with ordinary block indentation. */
57
+ const RE_PAGE_BREAK = /^ {0,3}<!--[ \t]*pagebreak[ \t]*-->[ \t]*$/;
58
+
56
59
  /**
57
60
  * @typedef {import('./ast.js').MdNode} MdNode
58
61
  * @typedef {import('./ast.js').MdDocument} MdDocument
@@ -711,7 +714,10 @@ class BlockParser {
711
714
  break;
712
715
  }
713
716
  case 'html':
714
- this.add(htmlBlock(leaf.lines.join('\n')));
717
+ // Only a complete standalone comment has layout semantics;
718
+ // inline comments, code and larger HTML blocks remain opaque.
719
+ this.add(leaf.lines.length === 1 && RE_PAGE_BREAK.test(leaf.lines[0])
720
+ ? pageBreak() : htmlBlock(leaf.lines.join('\n')));
715
721
  break;
716
722
  case 'table':
717
723
  this.add({ type: 'table', align: leaf.align, children: [], raw: leaf.rows });
package/src/to-html.js CHANGED
@@ -195,6 +195,7 @@ const BLOCK_HTML = {
195
195
  },
196
196
 
197
197
  thematicBreak: () => '<hr>',
198
+ pageBreak: () => '<div class="md-page-break" role="separator" aria-label="Page break"></div>',
198
199
 
199
200
  blockquote: (node, ctx) => '<blockquote>' + blockChildren(node.children, ctx) + '</blockquote>',
200
201
 
package/src/to-md.js CHANGED
@@ -217,6 +217,7 @@ const BLOCK_PRINTERS = {
217
217
  // `***`, not `---`: the printer's bullet is `-`, and `- ---` is a
218
218
  // thematic break in its own right rather than an item containing one.
219
219
  thematicBreak: () => '***',
220
+ pageBreak: () => '<!-- pagebreak -->',
220
221
 
221
222
  blockquote: (node) => {
222
223
  const inner = printBlocks(node.children, false);
package/src/to-vnode.js CHANGED
@@ -73,8 +73,7 @@ import {
73
73
  * so a reader can copy a link to the section (default `false`).
74
74
  * Requires `headingIds`; without ids there is nothing to link to.
75
75
  * @property {string} [footnotesLabel] the accessible name of the
76
- * appended footnotes section (default `'Footnotes'`) — the one string
77
- * this emitter writes that a reader can hear.
76
+ * appended footnotes section (default `'Footnotes'`).
78
77
  * @property {boolean} [keyed] give each top-level block a content-hash
79
78
  * `key` (default `true`).
80
79
  *
@@ -321,6 +320,7 @@ const BLOCK_RENDERERS = {
321
320
  },
322
321
 
323
322
  thematicBreak: () => ['hr', {}],
323
+ pageBreak: () => ['div', { class: 'md-page-break', role: 'separator', 'aria-label': 'Page break' }],
324
324
 
325
325
  blockquote: (node, rctx) => ['blockquote', {}, ...blockChildren(node.children, rctx, false)],
326
326
 
@@ -763,4 +763,3 @@ function hydrateIndex(docOrCompiled, tables) {
763
763
  memo.set(ast, index);
764
764
  return index;
765
765
  }
766
-
package/styles/md.css CHANGED
@@ -75,6 +75,21 @@
75
75
  margin: 1.4em 0;
76
76
  }
77
77
 
78
+ /* The screen divider marks a print boundary without adding printed ink. */
79
+ .md .md-page-break {
80
+ border-top: 1px dashed var(--md-rule, #d0d4dc);
81
+ margin: 2em 0;
82
+ }
83
+
84
+ @media print {
85
+ .md .md-page-break {
86
+ border: 0;
87
+ margin: 0;
88
+ page-break-after: always;
89
+ break-after: page;
90
+ }
91
+ }
92
+
78
93
  .md a { color: var(--md-link, #2456c4); }
79
94
 
80
95
  .md code {