@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 +16 -0
- package/dist/types/ast.d.ts +2 -0
- package/dist/types/to-vnode.d.ts +1 -2
- package/docs/MD-FORMAT.md +31 -2
- package/package.json +4 -4
- package/schemas/jaren-md-ast.schema.json +7 -1
- package/src/ast.js +5 -0
- package/src/parser.js +8 -2
- package/src/to-html.js +1 -0
- package/src/to-md.js +1 -0
- package/src/to-vnode.js +2 -3
- package/styles/md.css +15 -0
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
|
package/dist/types/ast.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/types/to-vnode.d.ts
CHANGED
|
@@ -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'`)
|
|
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).
|
|
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
|
|
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.
|
|
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.
|
|
77
|
-
"@jarenjs/mermaid": "^0.
|
|
78
|
-
"@jarenjs/view": "^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
|
-
|
|
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'`)
|
|
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 {
|