rapier-html 1.2.45 → 1.2.48
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/BUILD.json +16 -16
- package/README.md +13 -12
- package/dist/ledger/LICENSE +12 -5
- package/dist/ledger/authored-origin.mjs +2 -2
- package/dist/ledger/authorship.mjs +5 -5
- package/dist/ledger/carried.mjs +21 -12
- package/dist/ledger/diff/concerns.mjs +3 -3
- package/dist/ledger/diff/diff.mjs +6 -6
- package/dist/ledger/diff/index.mjs +2 -2
- package/dist/ledger/diff/merge.mjs +3 -3
- package/dist/ledger/diff/moves.mjs +1 -1
- package/dist/ledger/diff/walk.mjs +1 -1
- package/dist/ledger/document-fonts.mjs +296 -0
- package/dist/ledger/document-settings.mjs +2 -2
- package/dist/ledger/format.mjs +14 -12
- package/dist/ledger/history.mjs +7 -7
- package/dist/ledger/inverse-source.mjs +5 -5
- package/dist/ledger/journal-records.mjs +3 -3
- package/dist/ledger/math/delimiters.mjs +38 -0
- package/dist/ledger/math/markdown-rules.mjs +103 -0
- package/dist/ledger/md-assets.mjs +3 -2
- package/dist/ledger/md-comments.mjs +384 -0
- package/dist/ledger/md-engine.mjs +157 -0
- package/dist/ledger/md-marks.mjs +10 -1
- package/dist/ledger/md-parser-config.mjs +463 -0
- package/dist/ledger/md-parser-runtime.mjs +23 -0
- package/dist/ledger/md-source.mjs +1 -1
- package/dist/ledger/source.mjs +31 -2
- package/dist/ledger/template-page.mjs +1 -1
- package/dist/ledger/transport.mjs +66 -7
- package/dist/ledger/vendor/NOTICE.txt +60 -0
- package/dist/ledger/vendor/SOURCES.json +253 -0
- package/dist/ledger/vendor/entities/decode-codepoint.mjs +27 -0
- package/dist/ledger/vendor/entities.mjs +499 -0
- package/dist/ledger/vendor/linkify-it.mjs +427 -0
- package/dist/ledger/vendor/markdown-it.mjs +4027 -0
- package/dist/ledger/vendor/markdown-licenses/entities-LICENSE.txt +11 -0
- package/dist/ledger/vendor/markdown-licenses/linkify-it-LICENSE.txt +22 -0
- package/dist/ledger/vendor/markdown-licenses/markdown-it-LICENSE.txt +22 -0
- package/dist/ledger/vendor/markdown-licenses/mdurl-LICENSE.txt +45 -0
- package/dist/ledger/vendor/markdown-licenses/punycode.js-LICENSE-MIT.txt +20 -0
- package/dist/ledger/vendor/markdown-licenses/uc.micro-LICENSE.txt +20 -0
- package/dist/ledger/vendor/markdown-plugins.mjs +1344 -0
- package/dist/ledger/vendor/markdown-runtime.mjs +5 -0
- package/dist/ledger/vendor/mdurl/index.mjs +11 -0
- package/dist/ledger/vendor/mdurl/lib/decode.mjs +107 -0
- package/dist/ledger/vendor/mdurl/lib/encode.mjs +77 -0
- package/dist/ledger/vendor/mdurl/lib/format.mjs +20 -0
- package/dist/ledger/vendor/mdurl/lib/parse.mjs +231 -0
- package/dist/ledger/vendor/punycode.mjs +282 -0
- package/dist/ledger/vendor/uc.micro.mjs +49 -0
- package/dist/page.mjs +11 -3
- package/dist/rapier.html +113 -75
- package/dist/wrap.mjs +10 -5
- package/package.json +1 -1
package/BUILD.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "rapier-html",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.48",
|
|
4
4
|
"path": "dist/rapier.html",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
7
|
-
"builtAt": "2026-10-
|
|
5
|
+
"bytes": 5967960,
|
|
6
|
+
"sha256": "be629bc50b6a201e8889ce4c2768ee350015e8dfbafe7cfe880a6a6113865595",
|
|
7
|
+
"builtAt": "2026-10-28T02:28:38.799Z",
|
|
8
8
|
"node": "v22.23.4",
|
|
9
9
|
"canonical": true,
|
|
10
10
|
"packing": "zopfli",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"delivery": "built-in",
|
|
32
32
|
"kind": "files",
|
|
33
33
|
"element": "rapier-plugin-mermaid",
|
|
34
|
-
"bytes":
|
|
35
|
-
"packedBytes":
|
|
34
|
+
"bytes": 722820,
|
|
35
|
+
"packedBytes": 241155,
|
|
36
36
|
"files": [
|
|
37
37
|
{
|
|
38
38
|
"id": "rapier-mermaid-core",
|
|
39
|
-
"name": "pele-1.9.0-native-
|
|
40
|
-
"version": "1.9.0-native-
|
|
41
|
-
"bytes":
|
|
42
|
-
"sri": "
|
|
39
|
+
"name": "pele-1.9.0-native-2.js",
|
|
40
|
+
"version": "1.9.0-native-2",
|
|
41
|
+
"bytes": 722820,
|
|
42
|
+
"sri": "vPm/+HAI9fOqGc1GFl8wFvzMcZjXMhIUXWSdd2qAAVW6fBqjmK6Np6MVprxE8DFK"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
45
45
|
},
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"delivery": "built-in",
|
|
49
49
|
"kind": "files",
|
|
50
50
|
"element": "rapier-plugin-mermaid-zenuml",
|
|
51
|
-
"bytes":
|
|
52
|
-
"packedBytes":
|
|
51
|
+
"bytes": 421169,
|
|
52
|
+
"packedBytes": 111122,
|
|
53
53
|
"files": [
|
|
54
54
|
{
|
|
55
55
|
"id": "rapier-mermaid-zenuml",
|
|
56
|
-
"name": "rapier-zenuml-svg-4.5.0-
|
|
57
|
-
"version": "4.5.0-svg-
|
|
58
|
-
"bytes":
|
|
59
|
-
"sri": "
|
|
56
|
+
"name": "rapier-zenuml-svg-4.5.0-2.js",
|
|
57
|
+
"version": "4.5.0-svg-2",
|
|
58
|
+
"bytes": 421169,
|
|
59
|
+
"sri": "g5vdITVAGyvxD4BJdaFJjNZKvlTUXxXlJkqeFyfXREsJVsopr/iH4DC8gPhuEjDK"
|
|
60
60
|
}
|
|
61
61
|
]
|
|
62
62
|
},
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ Turn Markdown into one offline HTML file that contains the full Rapier editor.
|
|
|
7
7
|
<img src="https://raw.githubusercontent.com/jackskip22/rapier-plugins/main/assets/write-390-light.png" alt="The Rapier editor on a phone: a launch plan with an agent's change marked" width="320">
|
|
8
8
|
</picture>
|
|
9
9
|
|
|
10
|
-
Try it: `npx rapier-html@1.2.
|
|
11
|
-
Install: `npm install rapier-html@1.2.
|
|
10
|
+
Try it: `npx rapier-html@1.2.48 notes.md`, then open `notes.rapier.html` in any browser.
|
|
11
|
+
Install: `npm install rapier-html@1.2.48`.
|
|
12
12
|
|
|
13
13
|
It opens on a phone or computer, with no account. People read, edit, draw and paint; agents hand over documents, show
|
|
14
14
|
what changed and get the edited copy back.
|
|
@@ -21,19 +21,20 @@ The file needs no hosted workspace.
|
|
|
21
21
|
- Where scripts don't run (a mail preview, a strict viewer), the file shows the document's words.
|
|
22
22
|
- `--return` adds a one-use Send back address from the agent door.
|
|
23
23
|
- `--view draw` opens on Draw. `--compare original.md` opens the edit with its diff; `--by <name>` attributes it.
|
|
24
|
+
- `--editor page.html` uses another Rapier page as the editor, such as one the agent door exported.
|
|
24
25
|
- `writePage(text, name)` makes the page in one call; `unwrap` gives the document back, exactly.
|
|
25
26
|
|
|
26
27
|
Needs Node 22 or newer.
|
|
27
28
|
|
|
28
29
|
```sh
|
|
29
|
-
npx -- rapier-html@1.2.
|
|
30
|
-
npx -- rapier-html@1.2.
|
|
31
|
-
npx -- rapier-html@1.2.
|
|
32
|
-
npx -- rapier-html@1.2.
|
|
33
|
-
npx -- rapier-html@1.2.
|
|
34
|
-
npx -- rapier-html@1.2.
|
|
35
|
-
npx -- rapier-html@1.2.
|
|
36
|
-
npm install rapier-html@1.2.
|
|
30
|
+
npx -- rapier-html@1.2.48 notes.md # the editor, notes.md inside it, written beside the file
|
|
31
|
+
npx -- rapier-html@1.2.48 notes.md --view draw # opens on Draw: sketch and paint, the document behind it
|
|
32
|
+
npx -- rapier-html@1.2.48 notes.md --include draw,math # carries Draw and Math (default: what the document needs)
|
|
33
|
+
npx -- rapier-html@1.2.48 notes.md --drawing sketch.svg # carries a drawing, opened on Draw over the document
|
|
34
|
+
npx -- rapier-html@1.2.48 revised.md --compare original.md # opens showing what changed
|
|
35
|
+
npx -- rapier-html@1.2.48 notes.md --return <return_url> --return-expires-at <return_expires_at> # Send back
|
|
36
|
+
npx -- rapier-html@1.2.48 notes.md out.html # a named output
|
|
37
|
+
npm install rapier-html@1.2.48 # as a library: writePage, unwrap
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
The first `--` is for npm, so `--help` reaches the command. It never overwrites a file.
|
|
@@ -52,7 +53,7 @@ Will markers in the Markdown (`keep`, `append`, `edit`) control what agents may
|
|
|
52
53
|
## Send an edited document back
|
|
53
54
|
|
|
54
55
|
1. Call `show {target:"return"}` on the Rapier agent door for the document's workspace.
|
|
55
|
-
2. Pass its `return_url` and `return_expires_at` as `--return
|
|
56
|
+
2. Pass its `return_url` and `return_expires_at` as `--return <return_url> --return-expires-at <return_expires_at>`,
|
|
56
57
|
or `wrap(..., {return: return_url, return_expires_at})`. Only `https://mcp.rapier.website/return/…` is accepted.
|
|
57
58
|
Share then shows Send back; a page without an address doesn't.
|
|
58
59
|
3. The person edits offline and taps Send back. The source and filename go once; the page shows acceptance or
|
|
@@ -117,7 +118,7 @@ objects, and `unwrap(page)` returns the checked parts beside the text. Both are
|
|
|
117
118
|
history.
|
|
118
119
|
- Markdown saved with the kit's `ledger/carried.writeDocument` is read automatically when wrapping. Rewrapping a page
|
|
119
120
|
without parts drops the old parts.
|
|
120
|
-
- The published package carries
|
|
121
|
+
- The published package carries the portable MIT validator and canonical Markdown parser support, with the parser vendors' original MIT, ISC and BSD-2-Clause notices. Accepted comment history reopens without a source checkout or a parser supplied by the caller.
|
|
121
122
|
|
|
122
123
|
## License
|
|
123
124
|
|
package/dist/ledger/LICENSE
CHANGED
|
@@ -21,14 +21,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
21
21
|
SOFTWARE.
|
|
22
22
|
|
|
23
23
|
Files under this license carry `SPDX-License-Identifier: MIT` in their first lines: the standard's
|
|
24
|
-
grammar, marks and reference style (`spec/md-layout.mjs`, `spec/md-marks.mjs`, `spec/md-source.mjs`, `spec/flowchart.mjs`, `spec/markdown-style.css`),
|
|
25
|
-
(`
|
|
24
|
+
grammar, marks and reference style (`spec/md-layout.mjs`, `spec/md-marks.mjs`, `spec/md-source.mjs`, `spec/flowchart.mjs`, `spec/markdown-style.css`),
|
|
25
|
+
the reusable diagram source, layout and raising helpers (`draw/flowchart-mermaid.mjs`, `draw/flowchart-layout.mjs`,
|
|
26
|
+
`draw/flowchart-raise.mjs`) and diagram-core export facade (`shell/mermaid/api.mjs`),
|
|
27
|
+
ink geometry (`spec/ink.mjs`),
|
|
26
28
|
the layout model and line planner (`layout/model.mjs`, `layout/line-plan.mjs`, `layout/typography.mjs`, `layout/preparation.mjs`,
|
|
27
29
|
`layout/prepared-paragraph.mjs`, `layout/hyphenation.mjs`, `layout/paragraph-fit.mjs`), the animated-picture reader and playback
|
|
28
30
|
the HTML blocks use (`images/gif.mjs`, `images/playback.mjs`),
|
|
29
31
|
the portable HTML and document metadata helpers (`spec/html-blocks.mjs`, `spec/frontmatter.mjs`,
|
|
30
32
|
`spec/document-settings.mjs`, `spec/document-fonts.mjs`), the Will grammar (`agent/will.mjs`), the picture appendix reader and writer
|
|
31
|
-
(`spec/md-assets.mjs`), the
|
|
33
|
+
(`spec/md-assets.mjs`), the portable comment and canonical parser support owners
|
|
34
|
+
(`spec/md-comments.mjs`, `spec/md-parser-config.mjs`, `spec/md-parser-runtime.mjs`),
|
|
35
|
+
the neutral math boundary and parser adapters (`math/delimiters.mjs`, `math/markdown-rules.mjs`), the two shared-page reference readers (`tools/read-shared-page.mjs`,
|
|
32
36
|
`tools/read-shared-page.py`), and the host helper `packages/rapier-embed/embed.mjs` with its
|
|
33
37
|
contract owner `packages/rapier-embed/contract.mjs`. The vendored Pretext (`agent/vendor/pretext`) and micromark carry their
|
|
34
38
|
own MIT notices. The edit-ledger module (`kit/rapier-ledger.mjs`, `kit/ledger/*.mjs`) and
|
|
@@ -39,11 +43,14 @@ AGPL-3.0-only. The page wrapper (`skills/rapier-html/wrap.mjs`, `skills/rapier-h
|
|
|
39
43
|
`skills/rapier-html/return-address.mjs`) and its carrier-staging helper
|
|
40
44
|
(`skills/rapier-html/package-files.mjs`) are MIT. The kit's MIT-marked facades,
|
|
41
45
|
`kit/render-entry.mjs`, `kit/render*.mjs`, `kit/export-fonts.mjs`, `kit/print-capitals.mjs`,
|
|
42
|
-
`kit/pdf-vectors.mjs`, `kit/pdf-glyphs.mjs`, `kit/stage.mjs` and MIT-marked conformance
|
|
46
|
+
`kit/pdf-vectors.mjs`, `kit/pdf-glyphs.mjs`, `kit/stage.mjs`, `kit/markdown-vendor.mjs` and MIT-marked conformance
|
|
43
47
|
modules are covered too. These names describe the Rapier-owned files carrying the
|
|
44
48
|
MIT notice, not a grant over the editor or third-party code. Other Rapier-owned
|
|
45
49
|
files retain their stated license; the editor is AGPL-3.0-only (see `LICENSE`).
|
|
46
|
-
Third-party components retain their own copyright and license notices.
|
|
50
|
+
Third-party components retain their own copyright and license notices. The published Markdown parser,
|
|
51
|
+
its plugins and their dependencies retain their original MIT, ISC and BSD-2-Clause notices in `agent/vendor/NOTICE.txt` and
|
|
52
|
+
`agent/vendor/markdown-licenses/`; this inventory does not replace them. The Feather callout paths
|
|
53
|
+
in the canonical configuration retain the Feather copyright and MIT notice there.
|
|
47
54
|
|
|
48
55
|
The portable font helpers read document definitions and accept explicit host validation and
|
|
49
56
|
subsetting functions. The application binding (`editor/export-fonts.mjs`), captured-font
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {_rapierTransformSplices, _rapierValidAuthored} from
|
|
3
|
-
import {sha256} from
|
|
2
|
+
import {_rapierTransformSplices, _rapierValidAuthored} from "./journal-records.mjs";
|
|
3
|
+
import {sha256} from "./hash.mjs";
|
|
4
4
|
|
|
5
5
|
function authoredFrontier(records, visit = null) {
|
|
6
6
|
const dependencies = new Map(), frontier = new Set();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {readLedger, exportLedger, textRoot, rootAfter} from
|
|
3
|
-
import {_rapierTransformSplices, _rapierHistoryEffects} from
|
|
4
|
-
import {projectInverseSource} from
|
|
5
|
-
import {sha256} from
|
|
6
|
-
import {plainData, documentText, nonnegative, exactKeys, fault} from
|
|
2
|
+
import {readLedger, exportLedger, textRoot, rootAfter} from "./format.mjs";
|
|
3
|
+
import {_rapierTransformSplices, _rapierHistoryEffects} from "./journal-records.mjs";
|
|
4
|
+
import {projectInverseSource} from "./inverse-source.mjs";
|
|
5
|
+
import {sha256} from "./hash.mjs";
|
|
6
|
+
import {plainData, documentText, nonnegative, exactKeys, fault} from "./data.mjs";
|
|
7
7
|
function coalesce(runs) {
|
|
8
8
|
const out = [];
|
|
9
9
|
for (const run of runs) {
|
package/dist/ledger/carried.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {readLedger} from
|
|
3
|
-
import {sha256} from
|
|
4
|
-
import {authorship as projectAuthorship, readAuthorship} from
|
|
5
|
-
import {canonicalJSON, fault} from
|
|
2
|
+
import {readLedger} from "./format.mjs";
|
|
3
|
+
import {sha256} from "./hash.mjs";
|
|
4
|
+
import {authorship as projectAuthorship, readAuthorship} from "./authorship.mjs";
|
|
5
|
+
import {canonicalJSON, fault} from "./data.mjs";
|
|
6
6
|
export function encodeCarried(text) {
|
|
7
7
|
return String(text).replace(/[\\\r\0]|<[/!]/g, m => m === '\\' ? '\\\\' : m === '\r' ? '\\r' : m === '\0' ? '\\0' : m === '</' ? '\\/' : '\\!');
|
|
8
8
|
}
|
|
@@ -56,11 +56,19 @@ export function documentTrailer(text, options) {
|
|
|
56
56
|
export function writeDocument(text, options) {
|
|
57
57
|
return text + documentTrailer(text, options);
|
|
58
58
|
}
|
|
59
|
+
function trailerOpening(source) {
|
|
60
|
+
const marker = '<!-- rapier-carried/1', at = source.lastIndexOf(marker);
|
|
61
|
+
if (at < 0) return null;
|
|
62
|
+
if (source.slice(at - 2, at + marker.length + 1) === OPEN) return {start: at - 2, body: at + marker.length + 1};
|
|
63
|
+
const before = /(?:\r\n|\r|\n){2}$/.exec(source.slice(Math.max(0, at - 4), at));
|
|
64
|
+
const after = /^(?:\r\n|\r|\n)/.exec(source.slice(at + marker.length, at + marker.length + 2));
|
|
65
|
+
return before && after ? {start: at - before[0].length, body: at + marker.length + after[0].length} : null;
|
|
66
|
+
}
|
|
59
67
|
function unfinishedTrailer(source) {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
68
|
+
const opening = trailerOpening(source);
|
|
69
|
+
if (!opening) return false;
|
|
62
70
|
let fence = null;
|
|
63
|
-
for (const line of source.slice(0,
|
|
71
|
+
for (const line of source.slice(0, opening.start).split(/\r\n?|\n/)) {
|
|
64
72
|
const found = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
65
73
|
if (!found) continue;
|
|
66
74
|
if (!fence) fence = found[1];
|
|
@@ -69,17 +77,18 @@ function unfinishedTrailer(source) {
|
|
|
69
77
|
return !fence;
|
|
70
78
|
}
|
|
71
79
|
export function readDocument(source) {
|
|
72
|
-
|
|
80
|
+
const closing = /(?:\r\n|\r|\n)-->(?:\r\n|\r|\n)$/.exec(source.slice(-7));
|
|
81
|
+
if (!closing) {
|
|
73
82
|
if (unfinishedTrailer(source)) throw fault('unfinished document metadata');
|
|
74
83
|
return {text: source, ledger: null, authorship: null};
|
|
75
84
|
}
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
85
|
+
const opening = trailerOpening(source);
|
|
86
|
+
if (!opening) return {text: source, ledger: null, authorship: null};
|
|
78
87
|
let parts;
|
|
79
|
-
try { parts = JSON.parse(source.slice(
|
|
88
|
+
try { parts = JSON.parse(source.slice(opening.body, -closing[0].length)); }
|
|
80
89
|
catch (_) { throw fault('invalid document metadata'); }
|
|
81
90
|
if (!parts || Object.keys(parts).length !== 2 || !Object.hasOwn(parts, 'ledger') || !Object.hasOwn(parts, 'authorship')) throw fault('invalid document parts');
|
|
82
|
-
const text = source.slice(0,
|
|
91
|
+
const text = source.slice(0, opening.start);
|
|
83
92
|
return {text, ...validateParts(text, parts)};
|
|
84
93
|
}
|
|
85
94
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {letter, within, prev, next} from
|
|
2
|
+
import {letter, within, prev, next} from "./tokens.mjs";
|
|
3
3
|
|
|
4
4
|
export function separate(c, endA = c.before.length, endB = c.after.length) {
|
|
5
5
|
const {before, after, safe, raw, spend} = c;
|
|
6
6
|
if (!raw) for (let i = c.result.length - 1; i > 0; i--) {
|
|
7
7
|
const p = c.result[i - 1], q = c.result[i], side = p[0][0] === p[0][1] ? 1 : p[1][0] === p[1][1] ? 0 : -1, text = side ? after : before;
|
|
8
|
-
if (side >= 0 && !/\S/.test(text.slice(...p[side]))) {
|
|
8
|
+
if (!c.evidence && side >= 0 && !/\S/.test(text.slice(...p[side]))) {
|
|
9
9
|
let n = 0;
|
|
10
10
|
while (p[0][1] + n < q[0][0] && text[p[side][0] + n] === text[p[side][1] + n] && spend()) n++;
|
|
11
11
|
if (p[0][1] + n === q[0][0] && p.slice(0, 2).every((span, k) => safe(k ? after : before, span[0] + n) && safe(k ? after : before, span[1] + n)))
|
|
@@ -20,7 +20,7 @@ export function separate(c, endA = c.before.length, endB = c.after.length) {
|
|
|
20
20
|
keep[3][0] = x[0] === x[1] ? 2 : y[0] === y[1] ? 0 : 1;
|
|
21
21
|
c.result.splice(i - 1, 2, [x, y, false, keep[3]]);
|
|
22
22
|
}
|
|
23
|
-
if (!raw) for (let i = 0; i < c.result.length; i++) {
|
|
23
|
+
if (!raw && !c.evidence) for (let i = 0; i < c.result.length; i++) {
|
|
24
24
|
const p = c.result[i], q = c.result[i + 1], side = p[0][0] === p[0][1] ? 1 : p[1][0] === p[1][1] ? 0 : -1;
|
|
25
25
|
if (side < 0 || p[2]) continue;
|
|
26
26
|
const text = side ? after : before;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {observe} from
|
|
3
|
-
import {boundaries, tokenizer, wordSegmenter} from
|
|
4
|
-
import {matcher} from
|
|
5
|
-
import {walker} from
|
|
6
|
-
import {moves} from
|
|
7
|
-
import {separate, concerns} from
|
|
2
|
+
import {observe} from "./structure.mjs";
|
|
3
|
+
import {boundaries, tokenizer, wordSegmenter} from "./tokens.mjs";
|
|
4
|
+
import {matcher} from "./matching.mjs";
|
|
5
|
+
import {walker} from "./walk.mjs";
|
|
6
|
+
import {moves} from "./moves.mjs";
|
|
7
|
+
import {separate, concerns} from "./concerns.mjs";
|
|
8
8
|
|
|
9
9
|
export function diff(before, after, options = {}) {
|
|
10
10
|
if (before === after) return {edits: [], changes: []};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {diff} from
|
|
3
|
-
import {observe} from
|
|
4
|
-
import {boundaries, words, wordSegmenter} from
|
|
2
|
+
import {diff} from "./diff.mjs";
|
|
3
|
+
import {observe} from "./structure.mjs";
|
|
4
|
+
import {boundaries, words, wordSegmenter} from "./tokens.mjs";
|
|
5
5
|
|
|
6
6
|
export function merge(base, mine, theirs, options = {}) {
|
|
7
7
|
if (mine === theirs || base === theirs || base === mine) return {text: base === mine ? theirs : mine, conflicts: []};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {separate, phrases} from
|
|
2
|
+
import {separate, phrases} from "./concerns.mjs";
|
|
3
3
|
|
|
4
4
|
export function moves(c) {
|
|
5
5
|
const {before, after, spend, safe, budget, raw, code, tokens, matches, walk, structures, pa, pb, la, aliases} = c;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {words, letter, within, prev, next} from
|
|
2
|
+
import {words, letter, within, prev, next} from "./tokens.mjs";
|
|
3
3
|
|
|
4
4
|
export function walker(c) {
|
|
5
5
|
const {before, after, spend, safe, budget, raw, chars, code, segmenter, tokens, matches} = c;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {markdownParser, installMarkdownImages, normalizeLabel} from "./md-assets.mjs";
|
|
3
|
+
import {splitOpeningFrontmatter} from "./frontmatter.mjs";
|
|
4
|
+
import {readDocumentSettings, documentFont} from "./document-settings.mjs";
|
|
5
|
+
import {normalizeFontFamily, fontFamilyCss, FONT_STACKS, typography, fontDataUrl} from "./md-marks.mjs";
|
|
6
|
+
export {fontDataUrl};
|
|
7
|
+
|
|
8
|
+
export const CSS_GENERIC_FONT_FAMILIES = Object.freeze(['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui',
|
|
9
|
+
'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'emoji', 'math', 'fangsong', 'initial', 'revert', 'revert-layer']);
|
|
10
|
+
|
|
11
|
+
function parserFor(parser) {
|
|
12
|
+
const md = installMarkdownImages(markdownParser(parser));
|
|
13
|
+
if (!md.__rapierFontsInstalled) {
|
|
14
|
+
const validate = md.validateLink;
|
|
15
|
+
md.validateLink = url => !!fontDataUrl(url) || validate(url);
|
|
16
|
+
md.__rapierFontsInstalled = true;
|
|
17
|
+
}
|
|
18
|
+
return md;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sourceMetadataFields = ['license', 'origin'];
|
|
22
|
+
function sourceMetadata(value) {
|
|
23
|
+
return Object.fromEntries(sourceMetadataFields.filter(field => value[field] != null).map(field => [field, value[field]]));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function faceOf(value) {
|
|
27
|
+
const family = normalizeFontFamily(value?.family), style = value?.style || 'normal';
|
|
28
|
+
const weight = value?.weight ?? 400, stretch = value?.stretch ?? 100;
|
|
29
|
+
if (!family || !['normal', 'italic', 'oblique'].includes(style) || !Number.isFinite(weight) || weight < 1 || weight > 1000 ||
|
|
30
|
+
!Number.isFinite(stretch) || stretch <= 0 || stretch > 1000) return null;
|
|
31
|
+
const result = {family, style, weight, stretch};
|
|
32
|
+
if (value.subsetted != null && typeof value.subsetted !== 'boolean') return null;
|
|
33
|
+
if (value.subsetted === true || value.subset?.subset === true) result.subsetted = true;
|
|
34
|
+
if (value.weightRange != null) {
|
|
35
|
+
const range = value.weightRange;
|
|
36
|
+
if (!Array.isArray(range) || range.length !== 2 || range.some(number => !Number.isFinite(number) || number < 1 || number > 1000) || range[0] > range[1]) return null;
|
|
37
|
+
result.weightRange = range.slice();
|
|
38
|
+
}
|
|
39
|
+
if (value.axes != null) {
|
|
40
|
+
if (!Array.isArray(value.axes) || value.axes.length > 64) return null;
|
|
41
|
+
const seen = new Set(), axes = [];
|
|
42
|
+
for (const axis of value.axes) {
|
|
43
|
+
if (!axis || !/^[ -~]{4}$/.test(axis.tag) || seen.has(axis.tag) ||
|
|
44
|
+
![axis.min, axis.default, axis.max].every(Number.isFinite) || axis.min > axis.default || axis.default > axis.max) return null;
|
|
45
|
+
seen.add(axis.tag);
|
|
46
|
+
axes.push({tag: axis.tag, min: axis.min, default: axis.default, max: axis.max, ...(typeof axis.name === 'string' ? {name: axis.name} : {})});
|
|
47
|
+
}
|
|
48
|
+
if (axes.length) result.axes = axes;
|
|
49
|
+
}
|
|
50
|
+
if (value.sourceId != null) {
|
|
51
|
+
if (!/^f[0-9a-f]{24}$/.test(value.sourceId)) return null;
|
|
52
|
+
result.sourceId = value.sourceId;
|
|
53
|
+
}
|
|
54
|
+
if (value.license != null) {
|
|
55
|
+
const license = value.license;
|
|
56
|
+
if (!license || !['OFL-1.0', 'OFL-1.1', 'Apache-2.0', 'Ubuntu-font-1.0'].includes(license.id) ||
|
|
57
|
+
typeof license.text !== 'string' || !license.text.trim() || new TextEncoder().encode(license.text).length > 128 * 1024 ||
|
|
58
|
+
typeof license.url !== 'string' || typeof license.sri !== 'string' || !/^sha384-[A-Za-z0-9+/]{64}$/.test(license.sri)) return null;
|
|
59
|
+
let url;
|
|
60
|
+
try { url = new URL(license.url); } catch (_) { return null; }
|
|
61
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) return null;
|
|
62
|
+
result.license = {id: license.id, text: license.text, url: license.url, sri: license.sri};
|
|
63
|
+
}
|
|
64
|
+
if (value.origin != null) {
|
|
65
|
+
const origin = value.origin;
|
|
66
|
+
if (!result.license || result.subsetted || !origin || origin.catalog !== 'google-fonts' ||
|
|
67
|
+
typeof origin.family !== 'string' || !/^[a-z0-9][a-z0-9-]{0,95}$/.test(origin.family) ||
|
|
68
|
+
typeof origin.version !== 'string' || !origin.version || origin.version.length > 128 || /[\u0000-\u001f\u007f]/u.test(origin.version) ||
|
|
69
|
+
typeof origin.file !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}\.woff2$/.test(origin.file) ||
|
|
70
|
+
typeof origin.sri !== 'string' || !/^sha384-[A-Za-z0-9+/]{64}$/.test(origin.sri)) return null;
|
|
71
|
+
result.origin = {catalog: origin.catalog, family: origin.family, version: origin.version, file: origin.file, sri: origin.sri};
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function fontAssetKey(font) {
|
|
77
|
+
const face = faceOf(font);
|
|
78
|
+
if (!face) return '';
|
|
79
|
+
return [face.family.toLowerCase(), face.style, face.weight, face.stretch,
|
|
80
|
+
(face.axes || []).map(axis => [axis.tag, axis.min, axis.max].join(':')).sort().join(',')].join('/');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function readFontAssets(markdown, parser) {
|
|
84
|
+
const source = String(markdown ?? ''), assets = [], blocks = [], refused = [];
|
|
85
|
+
if (!/\[font:/i.test(source)) return {assets, blocks, refused, references: Object.create(null)};
|
|
86
|
+
const md = parserFor(parser), frame = splitOpeningFrontmatter(source), env = {}, tokens = [], starts = [frame.bodyOffset];
|
|
87
|
+
for (let at = frame.bodyOffset; at < source.length; at++) {
|
|
88
|
+
if (source[at] === '\r') { if (source[at + 1] === '\n') at++; starts.push(at + 1); }
|
|
89
|
+
else if (source[at] === '\n') starts.push(at + 1);
|
|
90
|
+
}
|
|
91
|
+
md.block.parse(frame.body.replace(/\r\n?/g, '\n').replace(/\0/g, '\ufffd'), md, env, tokens);
|
|
92
|
+
const labels = new Set(), faces = new Set(), metadata = new Map(), faceMetadata = new Map(), subsetUrls = new Set();
|
|
93
|
+
for (const token of tokens) {
|
|
94
|
+
if (token.type !== 'reference_definition' || !token.map) continue;
|
|
95
|
+
const id = token.meta.label, first = !labels.has(id);
|
|
96
|
+
labels.add(id);
|
|
97
|
+
const definition = token.meta.mdImageDefinition || env.references?.[id];
|
|
98
|
+
const url = definition?.href, data = fontDataUrl(url);
|
|
99
|
+
if (!/^FONT:/i.test(id)) continue;
|
|
100
|
+
const start = starts[token.map[0]], next = starts[token.map[1]] ?? source.length;
|
|
101
|
+
let end = next;
|
|
102
|
+
if (source[end - 1] === '\n') end--;
|
|
103
|
+
if (source[end - 1] === '\r') end--;
|
|
104
|
+
const raw = source.slice(start, end), match = /^[ \t]{0,3}\[((?:\\.|[^\]\\\r\n])+)\]:/.exec(raw);
|
|
105
|
+
if (!match) continue;
|
|
106
|
+
const label = md.utils.unescapeAll(match[1]);
|
|
107
|
+
if (!data) { refused.push({name: label, reason: 'The font reference must contain an embedded font.'}); continue; }
|
|
108
|
+
let definitionMetadata = {family: label.slice(5)};
|
|
109
|
+
if (definition.title) {
|
|
110
|
+
try { definitionMetadata = JSON.parse(definition.title); }
|
|
111
|
+
catch (_) { refused.push({name: label, reason: 'The font reference has invalid metadata.'}); continue; }
|
|
112
|
+
}
|
|
113
|
+
const face = faceOf(definitionMetadata);
|
|
114
|
+
if (!face) { refused.push({name: label, reason: 'The font reference has invalid metadata.'}); continue; }
|
|
115
|
+
const key = fontAssetKey(face), active = first && !faces.has(key);
|
|
116
|
+
const conflict = sourceMetadataFields.find(field => face[field] && [metadata.get(url)?.[field], faceMetadata.get(key)?.[field]]
|
|
117
|
+
.some(prior => prior && JSON.stringify(prior) !== JSON.stringify(face[field])));
|
|
118
|
+
if (conflict) { refused.push({name: label, reason: 'The font references have conflicting ' + conflict + ' metadata.'}); continue; }
|
|
119
|
+
if (face.subsetted && metadata.get(url)?.origin || face.origin && subsetUrls.has(url)) {
|
|
120
|
+
refused.push({name: label, reason: 'A font subset cannot claim an original catalog file.'}); continue;
|
|
121
|
+
}
|
|
122
|
+
metadata.set(url, {...metadata.get(url), ...sourceMetadata(face)});
|
|
123
|
+
faceMetadata.set(key, {...faceMetadata.get(key), ...sourceMetadata(face)});
|
|
124
|
+
if (face.subsetted) subsetUrls.add(url);
|
|
125
|
+
const row = {...face, ...data, url, label, id, key, source: raw, start, end, next, active, topLevel: token.level === 0};
|
|
126
|
+
blocks.push(row);
|
|
127
|
+
if (active) { assets.push(row); faces.add(key); }
|
|
128
|
+
}
|
|
129
|
+
for (let index = 0; index < assets.length; index++) {
|
|
130
|
+
const asset = assets[index], declared = metadata.get(asset.url);
|
|
131
|
+
for (const field of sourceMetadataFields) if (!asset[field] && declared?.[field])
|
|
132
|
+
assets[index] = {...assets[index], [field]: declared[field]};
|
|
133
|
+
if (subsetUrls.has(asset.url) && !asset.subsetted) assets[index] = {...assets[index], subsetted: true};
|
|
134
|
+
}
|
|
135
|
+
return {assets, blocks, refused, references: env.references || Object.create(null)};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function fontLabel(face) {
|
|
139
|
+
const variants = face.style !== 'normal' || face.weight !== 400 || face.stretch !== 100 || face.axes?.length;
|
|
140
|
+
return 'font:' + face.family + (variants ? '/' + face.style + '/' + face.weight + (face.stretch !== 100 ? '/' + face.stretch : '')
|
|
141
|
+
+ (face.axes?.length ? '/' + face.axes.map(axis => axis.tag + '=' + axis.min + ':' + axis.max).join(',') : '') : '');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function serializeFontAsset(asset, label = null) {
|
|
145
|
+
const face = faceOf(asset), data = fontDataUrl(asset?.url);
|
|
146
|
+
if (!face || !data) throw new TypeError('font: use an embedded font with valid family metadata');
|
|
147
|
+
const escaped = escapeFontLabel(label || fontLabel(face));
|
|
148
|
+
const title = JSON.stringify(face).replace(/&/g, '&').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
149
|
+
return '[' + escaped + ']: ' + asset.url + ' "' + title + '"';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function escapeFontLabel(label) {
|
|
153
|
+
return String(label).replace(/[\\[\]]/g, '\\$&').replace(/&/g, '&');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function writeFontAssets(markdown, assets, parser) {
|
|
157
|
+
const source = String(markdown ?? ''), md = parserFor(parser), index = readFontAssets(source, md), wanted = new Map(), metadata = new Map(), subsetUrls = new Set();
|
|
158
|
+
for (const asset of assets || []) {
|
|
159
|
+
const key = fontAssetKey(asset), face = faceOf(asset);
|
|
160
|
+
if (!key || !fontDataUrl(asset?.url)) throw new TypeError('font: invalid embedded asset');
|
|
161
|
+
if (wanted.has(key) && wanted.get(key).url !== asset.url) throw new TypeError('font: one file owns each family face');
|
|
162
|
+
const conflict = sourceMetadataFields.find(field => face[field] && metadata.get(asset.url)?.[field] &&
|
|
163
|
+
JSON.stringify(metadata.get(asset.url)[field]) !== JSON.stringify(face[field]));
|
|
164
|
+
if (conflict) throw new TypeError('font: conflicting ' + conflict + ' metadata');
|
|
165
|
+
if (face.subsetted && metadata.get(asset.url)?.origin || face.origin && subsetUrls.has(asset.url))
|
|
166
|
+
throw new TypeError('font: a subset cannot claim an original catalog file');
|
|
167
|
+
metadata.set(asset.url, {...metadata.get(asset.url), ...sourceMetadata(face)});
|
|
168
|
+
if (face.subsetted) subsetUrls.add(asset.url);
|
|
169
|
+
wanted.set(key, asset);
|
|
170
|
+
}
|
|
171
|
+
for (const [key, asset] of wanted) {
|
|
172
|
+
const declared = metadata.get(asset.url);
|
|
173
|
+
for (const field of sourceMetadataFields) if (!asset[field] && declared?.[field])
|
|
174
|
+
wanted.set(key, {...wanted.get(key), [field]: declared[field]});
|
|
175
|
+
if (subsetUrls.has(asset.url) && !asset.subsetted) wanted.set(key, {...wanted.get(key), subsetted: true});
|
|
176
|
+
}
|
|
177
|
+
const edits = [], consumed = new Set(), labels = new Set(Object.keys(index.references));
|
|
178
|
+
const retired = index.assets.filter(row => !wanted.has(row.key));
|
|
179
|
+
for (const row of index.blocks) {
|
|
180
|
+
if (!row.active) {
|
|
181
|
+
if (retired.some(active => active.key === row.key || active.id === row.id)) edits.push({start: row.start, end: row.next, text: ''});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const asset = wanted.get(row.key);
|
|
185
|
+
if (!asset) { edits.push({start: row.start, end: row.next, text: ''}); continue; }
|
|
186
|
+
consumed.add(row.key);
|
|
187
|
+
if (asset.url === row.url && JSON.stringify(faceOf(asset)) === JSON.stringify(faceOf(row))) continue;
|
|
188
|
+
edits.push({start: row.start, end: row.end, text: serializeFontAsset(asset, row.label)});
|
|
189
|
+
}
|
|
190
|
+
let result = source;
|
|
191
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
192
|
+
const pending = [];
|
|
193
|
+
for (const [key, asset] of wanted) {
|
|
194
|
+
if (consumed.has(key)) continue;
|
|
195
|
+
const base = fontLabel(faceOf(asset));
|
|
196
|
+
let label = base, suffix = 2;
|
|
197
|
+
while (labels.has(normalizeLabel(escapeFontLabel(label)))) label = base + '/' + suffix++;
|
|
198
|
+
labels.add(normalizeLabel(escapeFontLabel(label)));
|
|
199
|
+
pending.push(serializeFontAsset(asset, label));
|
|
200
|
+
}
|
|
201
|
+
if (pending.length) {
|
|
202
|
+
const eol = /\r\n|\r|\n/.exec(source)?.[0] || '\n';
|
|
203
|
+
result += (result ? (/(?:\r\n|\r(?!\n)|\n)[ \t]*(?:\r\n|\r(?!\n)|\n)$/.test(result) ? '' : /[\r\n]$/.test(result) ? eol : eol + eol) : '') + pending.join(eol) + eol;
|
|
204
|
+
const written = readFontAssets(result, md).assets;
|
|
205
|
+
for (const [key, asset] of wanted) if (!written.some(row => row.key === key && row.url === asset.url)) throw new Error('font: close the code or HTML block before embedding a font');
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function documentFontsCss(assets) {
|
|
211
|
+
const rules = [];
|
|
212
|
+
for (const asset of assets || []) {
|
|
213
|
+
const face = faceOf(asset);
|
|
214
|
+
if (!face || !fontDataUrl(asset?.url)) continue;
|
|
215
|
+
const weight = face.axes?.find(axis => axis.tag === 'wght');
|
|
216
|
+
const width = face.axes?.find(axis => axis.tag === 'wdth');
|
|
217
|
+
const range = weight ? [weight.min, weight.max] : face.weightRange || [face.weight, face.weight];
|
|
218
|
+
const weightCss = range[0] === range[1] ? String(range[0]) : range.join(' ');
|
|
219
|
+
const stretchCss = width ? width.min + '% ' + width.max + '%' : face.stretch + '%';
|
|
220
|
+
rules.push('@font-face{font-family:' + fontFamilyCss(face.family) + ';font-style:' + face.style + ';font-weight:' + weightCss +
|
|
221
|
+
';font-stretch:' + stretchCss + ';src:url("' + asset.url + '");font-display:swap}');
|
|
222
|
+
}
|
|
223
|
+
return rules.join('');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function htmlFontFamilies(html, md) {
|
|
227
|
+
const families = new Set();
|
|
228
|
+
const decode = value => md.utils.unescapeAll(value.replace(/\\/g, '\\\\'));
|
|
229
|
+
for (const tag of html.matchAll(/<[A-Za-z][A-Za-z0-9:-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g)) {
|
|
230
|
+
for (const attr of tag[0].matchAll(/\s(data-md-font|style)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi)) {
|
|
231
|
+
const value = decode(attr[2] ?? attr[3] ?? attr[4]), names = [];
|
|
232
|
+
if (/^<span(?=[\s>])/i.test(tag[0])) {
|
|
233
|
+
const family = attr[1].toLowerCase() === 'style' ? typography.parseStyle(value).family : null;
|
|
234
|
+
if (family?.custom) families.add(family.name);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (attr[1].toLowerCase() === 'data-md-font') names.push(value);
|
|
238
|
+
else for (const declared of value.matchAll(/(?:^|;)\s*font-family\s*:\s*((?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^;])*)/gi)) {
|
|
239
|
+
const match = /^\s*(?:"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'|([^,!]+))/.exec(declared[1]);
|
|
240
|
+
if (!match) continue;
|
|
241
|
+
const name = (match[1] ?? match[2] ?? match[3]).replace(/\\(?:([0-9a-f]{1,6})\s?|([^\r\n\f]))/gi, (_whole, hex, escaped) => {
|
|
242
|
+
const code = hex ? parseInt(hex, 16) : 0;
|
|
243
|
+
return hex ? code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : '\ufffd' : escaped;
|
|
244
|
+
});
|
|
245
|
+
if (match[3] != null && CSS_GENERIC_FONT_FAMILIES.includes(name.trim().toLowerCase())) continue;
|
|
246
|
+
names.push(name);
|
|
247
|
+
}
|
|
248
|
+
for (const name of names) { const family = normalizeFontFamily(name); if (family) families.add(family); }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return families;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function documentFontUsage(markdown, parser, defaultFont) {
|
|
255
|
+
const source = String(markdown ?? ''), md = parserFor(parser), settings = readDocumentSettings(source), use = new Map();
|
|
256
|
+
const family = setting => setting?.stack === 'named' ? setting.family : null;
|
|
257
|
+
const inherited = defaultFont === undefined ? settings.defaultfont : documentFont(defaultFont);
|
|
258
|
+
const main = family(settings.mainfont || inherited || settings.sansfont), sans = family(settings.sansfont || settings.mainfont || inherited), mono = family(settings.monofont);
|
|
259
|
+
const add = (name, text) => { if (name && !Object.hasOwn(FONT_STACKS, name.toLowerCase()) && text) use.set(name, (use.get(name) || '') + text + '\n'); };
|
|
260
|
+
if (!main && !sans && !mono && !source.includes('<')) return use;
|
|
261
|
+
const plain = html => md.utils.unescapeAll(String(html).replace(/<!--[^]*?-->/g, '').replace(/<(script|style)\b[^>]*>[^]*?<\/\1\s*>/gi, '').replace(/<[^>]*>/g, '').replace(/\\/g, '\\\\'));
|
|
262
|
+
let blockFamily = main;
|
|
263
|
+
for (const token of md.parse(splitOpeningFrontmatter(source).body, {})) {
|
|
264
|
+
if (token.type === 'heading_open') blockFamily = sans;
|
|
265
|
+
else if (token.type === 'heading_close' || token.type === 'paragraph_open') blockFamily = main;
|
|
266
|
+
else if (token.type === 'fence' && /^html(?:\s+\{[^{}]*\})?\s*$/i.test(token.info)) {
|
|
267
|
+
const text = plain(token.content);
|
|
268
|
+
add(blockFamily, text);
|
|
269
|
+
for (const name of htmlFontFamilies(token.content, md)) add(name, text);
|
|
270
|
+
}
|
|
271
|
+
else if (token.type === 'fence' || token.type === 'code_block') add(mono, token.content);
|
|
272
|
+
else if (token.type === 'html_block') {
|
|
273
|
+
const text = plain(token.content);
|
|
274
|
+
add(blockFamily, text);
|
|
275
|
+
for (const name of htmlFontFamilies(token.content, md)) add(name, text);
|
|
276
|
+
}
|
|
277
|
+
else if (token.type === 'inline') {
|
|
278
|
+
const stack = [blockFamily];
|
|
279
|
+
let html = '', text = '';
|
|
280
|
+
for (const child of token.children || []) {
|
|
281
|
+
if (child.type === 'html_inline') html += child.content;
|
|
282
|
+
if (['text', 'text_special', 'emoji', 'image', 'code_inline'].includes(child.type)) text += child.content;
|
|
283
|
+
const typed = child.meta?.typography;
|
|
284
|
+
if (typed && child.nesting === 1) {
|
|
285
|
+
stack.push(typed.style.family ? typed.style.family.custom ? typed.style.family.name : null : stack.at(-1)); continue;
|
|
286
|
+
}
|
|
287
|
+
if (typed && child.nesting === -1) { if (stack.length > 1) stack.pop(); continue; }
|
|
288
|
+
if (child.type === 'code_inline') add(mono, child.content);
|
|
289
|
+
else if (['text', 'text_special', 'emoji', 'image'].includes(child.type)) add(stack.at(-1), child.content);
|
|
290
|
+
}
|
|
291
|
+
for (const name of htmlFontFamilies(html, md)) add(name, text);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
add(main, settings.title); add(main, settings.subtitle);
|
|
295
|
+
return use;
|
|
296
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {frontMatterFrame, frontMatterLine} from
|
|
3
|
-
import {normalizeFontFamily, fontFamilyCss, FONT_STACKS} from
|
|
2
|
+
import {frontMatterFrame, frontMatterLine} from "./frontmatter.mjs";
|
|
3
|
+
import {normalizeFontFamily, fontFamilyCss, FONT_STACKS} from "./md-marks.mjs";
|
|
4
4
|
|
|
5
5
|
const NUMBER = /^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6
6
|
export const DOCUMENT_LEADING = 1.75 / 1.1;
|