rapier-html 1.2.45 → 1.2.46

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 (44) hide show
  1. package/BUILD.json +16 -16
  2. package/README.md +11 -11
  3. package/dist/ledger/LICENSE +12 -5
  4. package/dist/ledger/authored-origin.mjs +2 -2
  5. package/dist/ledger/authorship.mjs +5 -5
  6. package/dist/ledger/carried.mjs +5 -5
  7. package/dist/ledger/diff/concerns.mjs +1 -1
  8. package/dist/ledger/diff/diff.mjs +6 -6
  9. package/dist/ledger/diff/index.mjs +2 -2
  10. package/dist/ledger/diff/merge.mjs +3 -3
  11. package/dist/ledger/diff/moves.mjs +1 -1
  12. package/dist/ledger/diff/walk.mjs +1 -1
  13. package/dist/ledger/document-fonts.mjs +242 -0
  14. package/dist/ledger/document-settings.mjs +2 -2
  15. package/dist/ledger/format.mjs +14 -12
  16. package/dist/ledger/history.mjs +7 -7
  17. package/dist/ledger/inverse-source.mjs +5 -5
  18. package/dist/ledger/journal-records.mjs +3 -3
  19. package/dist/ledger/math/delimiters.mjs +38 -0
  20. package/dist/ledger/math/markdown-rules.mjs +103 -0
  21. package/dist/ledger/md-assets.mjs +3 -2
  22. package/dist/ledger/md-comments.mjs +384 -0
  23. package/dist/ledger/md-engine.mjs +157 -0
  24. package/dist/ledger/md-marks.mjs +10 -1
  25. package/dist/ledger/md-parser-config.mjs +463 -0
  26. package/dist/ledger/md-parser-runtime.mjs +23 -0
  27. package/dist/ledger/md-source.mjs +1 -1
  28. package/dist/ledger/template-page.mjs +1 -1
  29. package/dist/ledger/transport.mjs +66 -7
  30. package/dist/ledger/vendor/BROWSER-ENTITIES.json +36 -0
  31. package/dist/ledger/vendor/MARKDOWN-RUNTIME.json +136 -0
  32. package/dist/ledger/vendor/MARKDOWNIT.json +582 -0
  33. package/dist/ledger/vendor/NOTICE.txt +60 -0
  34. package/dist/ledger/vendor/markdown-licenses/entities-LICENSE.txt +11 -0
  35. package/dist/ledger/vendor/markdown-licenses/linkify-it-LICENSE.txt +22 -0
  36. package/dist/ledger/vendor/markdown-licenses/markdown-it-LICENSE.txt +22 -0
  37. package/dist/ledger/vendor/markdown-licenses/mdurl-LICENSE.txt +45 -0
  38. package/dist/ledger/vendor/markdown-licenses/punycode.js-LICENSE-MIT.txt +20 -0
  39. package/dist/ledger/vendor/markdown-licenses/uc.micro-LICENSE.txt +20 -0
  40. package/dist/ledger/vendor/markdown-plugins.json +46 -0
  41. package/dist/ledger/vendor/markdown-plugins.mjs +27 -0
  42. package/dist/ledger/vendor/markdown-runtime.mjs +13 -0
  43. package/dist/rapier.html +59 -67
  44. package/package.json +1 -1
@@ -0,0 +1,157 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ const NEWLINES = /\r\n?/g, NULLS = /\0/g;
4
+
5
+ function lineScanner(Base, Token) {
6
+ function StateBlock(src, md, env, tokens) {
7
+ this.bMarks = []; this.eMarks = []; this.tShift = []; this.sCount = []; this.bsCount = [];
8
+ this.blkIndent = 0; this.line = 0; this.lineMax = 0; this.tight = false; this.listIndent = -1;
9
+ this.parentType = 'root'; this.level = 0; this.Token = Token;
10
+ this.src = src; this.md = md; this.env = env; this.tokens = tokens;
11
+ const length = src.length;
12
+ for (let start = 0; start < length;) {
13
+ let at = start, indent = 0, offset = 0;
14
+ for (; at < length; at++) {
15
+ const code = src.charCodeAt(at);
16
+ if (code === 0x20) offset++;
17
+ else if (code === 0x09) offset += 4 - offset % 4;
18
+ else break;
19
+ indent++;
20
+ }
21
+ if (at === length) break;
22
+ let end = src.indexOf('\n', at);
23
+ if (end < 0) end = length;
24
+ this.bMarks.push(start); this.eMarks.push(end); this.tShift.push(indent); this.sCount.push(offset); this.bsCount.push(0);
25
+ start = end + 1;
26
+ }
27
+ this.bMarks.push(length); this.eMarks.push(length); this.tShift.push(0); this.sCount.push(0); this.bsCount.push(0);
28
+ this.lineMax = this.bMarks.length - 1;
29
+ }
30
+ StateBlock.prototype = Base.prototype;
31
+ return StateBlock;
32
+ }
33
+
34
+ function backticks(state, silent) {
35
+ const start = state.pos, src = state.src;
36
+ if (src.charCodeAt(start) !== 0x60) return false;
37
+ const max = state.posMax;
38
+ let pos = start + 1;
39
+ while (pos < max && src.charCodeAt(pos) === 0x60) pos++;
40
+ const marker = src.slice(start, pos), length = marker.length, runs = state.backticks;
41
+ const scanned = state.backticksScanned;
42
+ if (!scanned || pos < scanned || (runs[length] ?? -1) >= pos) {
43
+ let matchStart, matchEnd = pos;
44
+ while ((matchStart = src.indexOf('`', matchEnd)) !== -1 && matchStart < max) {
45
+ matchEnd = matchStart + 1;
46
+ while (src.charCodeAt(matchEnd) === 0x60) matchEnd++;
47
+ if (matchEnd > max) break;
48
+ const run = matchEnd - matchStart;
49
+ if (run === length) {
50
+ if (!silent) {
51
+ const token = state.push('code_inline', 'code', 0);
52
+ token.markup = marker;
53
+ let content = src.slice(pos, matchStart).replace(/\n/g, ' ');
54
+ if (content.startsWith(' ') && content.endsWith(' ') && /[^ ]/.test(content)) content = content.slice(1, -1);
55
+ token.content = content;
56
+ }
57
+ state.pos = matchEnd;
58
+ return true;
59
+ }
60
+ if (!(runs[run] >= matchStart)) runs[run] = matchStart;
61
+ }
62
+ if (matchStart === -1 && (!scanned || pos < scanned)) state.backticksScanned = pos;
63
+ }
64
+ if (!silent) state.pending += marker;
65
+ state.pos = pos;
66
+ return true;
67
+ }
68
+
69
+ const CLEAN_WEB_LINK = /^https?:\/\/([a-z0-9-]{1,63}(?:\.[a-z0-9-]{1,63})*)(?:\/[A-Za-z0-9\-._~/]*)?$/;
70
+ function cleanWebLink(url) {
71
+ const match = CLEAN_WEB_LINK.exec(url);
72
+ return match !== null && match[1].length <= 255;
73
+ }
74
+
75
+ function releasedState(state, md) {
76
+ for (const key of Object.keys(state)) {
77
+ const value = state[key];
78
+ if (typeof value === 'string') state[key] = '';
79
+ else if (typeof value === 'function') state[key] = released;
80
+ else if (value !== null && typeof value === 'object' && value !== md) state[key] = Array.isArray(value) ? [] : {};
81
+ }
82
+ return state;
83
+ }
84
+ function released() {}
85
+ function sampleTokens(tokens) {
86
+ const kept = new Map();
87
+ for (let index = 0; index < tokens.length; index++) {
88
+ const token = tokens[index];
89
+ if (!kept.has(token.type)) kept.set(token.type, token);
90
+ const children = token.children;
91
+ if (children) for (let at = 0; at < children.length; at++) {
92
+ const child = children[at];
93
+ if (!kept.has(child.type)) kept.set(child.type, child);
94
+ }
95
+ }
96
+ return kept;
97
+ }
98
+
99
+ function keepClasses(md) {
100
+ const anchors = {core: null, block: null, inline: null, tokens: null};
101
+ const finish = (state, tokens) => {
102
+ anchors.core = releasedState(state, md);
103
+ if (anchors.block) releasedState(anchors.block, md);
104
+ if (anchors.inline) releasedState(anchors.inline, md);
105
+ anchors.tokens = sampleTokens(tokens);
106
+ };
107
+ const release = (owner, key) => Object.getPrototypeOf(owner)[key] === owner[key];
108
+ if (release(md, 'parse') && release(md, 'parseInline')) {
109
+ md.parse = function (src, env) {
110
+ if (typeof src !== 'string') throw new Error('Input data should be a String');
111
+ const state = new this.core.State(src, this, env);
112
+ this.core.process(state);
113
+ const tokens = state.tokens;
114
+ finish(state, tokens);
115
+ return tokens;
116
+ };
117
+ md.parseInline = function (src, env) {
118
+ const state = new this.core.State(src, this, env);
119
+ state.inlineMode = true;
120
+ this.core.process(state);
121
+ const tokens = state.tokens;
122
+ finish(state, tokens);
123
+ return tokens;
124
+ };
125
+ }
126
+ if (release(md.block, 'parse')) md.block.parse = function (src, md, env, outTokens) {
127
+ if (!src) return;
128
+ const state = new this.State(src, md, env, outTokens);
129
+ this.tokenize(state, state.line, state.lineMax);
130
+ anchors.block = state;
131
+ };
132
+ if (release(md.inline, 'parse')) md.inline.parse = function (str, md, env, outTokens) {
133
+ const state = new this.State(str, md, env, outTokens);
134
+ this.tokenize(state);
135
+ const rules = this.ruler2.getRules(''), length = rules.length;
136
+ for (let index = 0; index < length; index++) rules[index](state);
137
+ anchors.inline = state;
138
+ };
139
+ }
140
+
141
+ export function tuneMarkdownEngine(md) {
142
+ if (md.__rapierEngineTuned) return md;
143
+ md.__rapierEngineTuned = true;
144
+ md.core.ruler.at('normalize', state => {
145
+ let src = state.src;
146
+ if (src.indexOf('\r') >= 0) src = src.replace(NEWLINES, '\n');
147
+ if (src.indexOf('\0') >= 0) src = src.replace(NULLS, '�');
148
+ state.src = src;
149
+ });
150
+ const BaseBlock = md.block.State;
151
+ md.block.State = lineScanner(BaseBlock, new BaseBlock('', md, {}, []).Token);
152
+ md.inline.ruler.at('backticks', backticks);
153
+ const normalizeLink = md.normalizeLink;
154
+ md.normalizeLink = url => cleanWebLink(url) ? url : normalizeLink.call(md, url);
155
+ keepClasses(md);
156
+ return md;
157
+ }
@@ -1,5 +1,14 @@
1
1
  // SPDX-License-Identifier: MIT
2
- import {markdownSourcePositions} from './md-source.mjs';
2
+ import {markdownSourcePositions} from "./md-source.mjs";
3
+
4
+ export function fontDataUrl(url) {
5
+ if (typeof url !== 'string') return null;
6
+ const match = /^data:font\/(woff2|woff|ttf|otf);base64,([A-Za-z0-9+/]+={0,2})$/i.exec(url);
7
+ if (!match || match[2].length % 4) return null;
8
+ const padding = match[2].endsWith('==') ? 2 : match[2].endsWith('=') ? 1 : 0;
9
+ const byteLength = match[2].length / 4 * 3 - padding;
10
+ return byteLength >= 12 ? {format: match[1].toLowerCase(), byteLength, data: match[2]} : null;
11
+ }
3
12
 
4
13
  const inkReader = () => {
5
14
  // SPDX-License-Identifier: MIT
@@ -0,0 +1,463 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 Jack Skipworth. See LICENSE-MIT.
3
+ // Feather callout paths retain the copyright and MIT notice in agent/vendor/NOTICE.txt.
4
+ import {tuneMarkdownEngine} from "./md-engine.mjs";
5
+ import {fontDataUrl as rapierFontDataUrl} from "./md-marks.mjs";
6
+ import {typography as rapierTypography, matchInkOpen as rapierMatchInkOpen, isInkClose as rapierIsInkClose,
7
+ inkOpenBody as rapierInkOpenBody, parseInkOpen as rapierParseInkOpen, isPageBreakBlock as rapierIsPageBreakBlock} from "./md-marks.mjs";
8
+ import {installMathRules} from "./math/markdown-rules.mjs";
9
+
10
+ const RAPIER_HIGHLIGHT_COLORS = Object.freeze({
11
+ green: '🟢',
12
+ red: '🔴',
13
+ blue: '🔵',
14
+ yellow: '🟡',
15
+ purple: '🟣',
16
+ });
17
+ const RAPIER_MARKDOWN_SPEC = Object.freeze({
18
+ options: Object.freeze({ html: true, linkify: true, typographer: false, maxNesting: 1000 }),
19
+ linkify: Object.freeze({ fuzzyLink: true }),
20
+ core: Object.freeze({ retainReferenceDefinitions: true }),
21
+ highlights: RAPIER_HIGHLIGHT_COLORS,
22
+ callouts: Object.freeze({
23
+ note: '<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>',
24
+ tip: '<path d="M9 18v-2a6 6 0 1 1 6 0v2M9 18h6M10 22h4"/>',
25
+ important: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
26
+ warning: '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
27
+ caution: '<polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>',
28
+ }),
29
+ plugins: Object.freeze([
30
+ Object.freeze({ global: 'markdownitTaskLists', options: Object.freeze({ enabled: true }) }),
31
+ Object.freeze({ global: 'markdownitFootnote' }),
32
+ Object.freeze({ global: 'markdownitMark' }),
33
+ Object.freeze({ global: 'markdownitSup' }),
34
+ Object.freeze({ global: 'markdownitEmoji' }),
35
+ Object.freeze({ global: 'markdownitAbbr' }),
36
+ Object.freeze({ global: 'markdownitIns' }),
37
+ Object.freeze({ global: 'markdownitDeflist' }),
38
+ ]),
39
+ rules: Object.freeze({ mathBlock: 'temml_math_block' }),
40
+ });
41
+
42
+ const _rapierLinkifyCandidate = {names: null, pattern: null};
43
+ function _rapierLinkifyText(state) {
44
+ const md = state.md;
45
+ if (!md.options.linkify) return;
46
+ const names = md.linkify.re.get_schema_names();
47
+ if (_rapierLinkifyCandidate.names !== names) {
48
+ _rapierLinkifyCandidate.names = names;
49
+ _rapierLinkifyCandidate.pattern = new RegExp(names.source + '|@|\\.[^ \\t\\n\\v\\f\\r]', 'i');
50
+ }
51
+ const candidate = _rapierLinkifyCandidate.pattern;
52
+ for (let at = 0; at < state.tokens.length; at++) {
53
+ const block = state.tokens[at];
54
+ if (block.type !== 'inline' || !candidate.test(block.content)) continue;
55
+ const children = block.children;
56
+ const source = block.content;
57
+ let htmlDepth = 0, wholeSource, changes;
58
+ for (let index = children.length - 1; index >= 0; index--) {
59
+ const child = children[index];
60
+ if (child.type === 'link_close') {
61
+ index--;
62
+ while (children[index].level !== child.level && children[index].type !== 'link_open') index--;
63
+ continue;
64
+ }
65
+ if (child.type === 'html_inline') {
66
+ if (/^<a[>\s]/i.test(child.content) && htmlDepth > 0) htmlDepth--;
67
+ if (/^<\/a\s*>/i.test(child.content)) htmlDepth++;
68
+ }
69
+ if (htmlDepth || child.type !== 'text' || !candidate.test(child.content)) continue;
70
+ let matches = md.linkify.match(child.content);
71
+ if (!matches?.length) continue;
72
+ wholeSource ??= md.linkify.test(source);
73
+ if (!wholeSource) break;
74
+ if (matches[0].index === 0 && index > 0 && children[index - 1].type === 'text_special') matches = matches.slice(1);
75
+ const replacement = [];
76
+ let position = 0, level = child.level;
77
+ const textToken = text => {
78
+ const token = new state.Token('text', '', 0);
79
+ token.content = text; token.level = level; replacement.push(token);
80
+ };
81
+ for (const match of matches) {
82
+ const destination = md.normalizeLink(match.url);
83
+ if (!md.validateLink(destination)) continue;
84
+ if (match.index > position) textToken(child.content.slice(position, match.index));
85
+ const open = new state.Token('link_open', 'a', 1);
86
+ open.attrs = [['href', destination]]; open.level = level++;
87
+ open.markup = 'linkify'; open.info = 'auto'; replacement.push(open);
88
+ let label;
89
+ if (!match.schema) label = md.normalizeLinkText('http://' + match.text).replace(/^http:\/\//, '');
90
+ else if (match.schema === 'mailto:' && !/^mailto:/i.test(match.text)) label = md.normalizeLinkText('mailto:' + match.text).replace(/^mailto:/, '');
91
+ else label = md.normalizeLinkText(match.text);
92
+ textToken(label);
93
+ const close = new state.Token('link_close', 'a', -1);
94
+ close.level = --level; close.markup = 'linkify'; close.info = 'auto'; replacement.push(close);
95
+ position = match.lastIndex;
96
+ }
97
+ if (position < child.content.length) textToken(child.content.slice(position));
98
+ (changes ||= []).push({index, tokens: replacement});
99
+ }
100
+ if (changes) {
101
+ const result = [];
102
+ let next = changes.length - 1;
103
+ for (let index = 0; index < children.length; index++) {
104
+ const change = changes[next];
105
+ if (change?.index === index) {
106
+ for (const token of change.tokens) result.push(token);
107
+ next--;
108
+ }
109
+ else result.push(children[index]);
110
+ }
111
+ block.children = result;
112
+ }
113
+ }
114
+ }
115
+
116
+ function _rapierApplyMarkdownSpec(instance, root, spec = RAPIER_MARKDOWN_SPEC) {
117
+ const pluginSpecs = Array.from(spec.plugins || []);
118
+ const missing = pluginSpecs
119
+ .map(pluginSpec => pluginSpec.global)
120
+ .filter(globalName => !root || typeof root[globalName] !== 'function');
121
+ if (missing.length) throw new Error('Required Markdown runtime missing: ' + missing.join(', '));
122
+ if (spec.linkify && (!instance.linkify || typeof instance.linkify.set !== 'function')) {
123
+ throw new Error('Markdown linkifier unavailable');
124
+ }
125
+ if (spec.core && spec.core.retainReferenceDefinitions && (!instance.core || !instance.core.ruler)) {
126
+ throw new Error('Markdown core ruler unavailable');
127
+ }
128
+
129
+ tuneMarkdownEngine(instance);
130
+ if (!instance.__rapierDeepFlat) {
131
+ instance.block.ruler.before('table', 'rapier_deep_flat', (state, startLine, endLine, silent) => {
132
+ if (silent || state.level < state.md.options.maxNesting - 2) return false;
133
+ const content = state.getLines(startLine, endLine, state.blkIndent, false).trim();
134
+ if (!content) return false;
135
+ state.push('paragraph_open', 'p', 1).map = [startLine, endLine];
136
+ const inline = state.push('inline', '', 0);
137
+ inline.content = content; inline.map = [startLine, endLine]; inline.children = [];
138
+ state.push('paragraph_close', 'p', -1);
139
+ state.line = endLine;
140
+ return true;
141
+ });
142
+ instance.__rapierDeepFlat = true;
143
+ }
144
+ for (const pluginSpec of pluginSpecs) {
145
+ const plugin = root[pluginSpec.global];
146
+ if (pluginSpec.options) instance.use(plugin, pluginSpec.options);
147
+ else instance.use(plugin);
148
+ }
149
+ if (!instance.__rapierFontsInstalled) {
150
+ const validate = instance.validateLink;
151
+ instance.validateLink = url => !!rapierFontDataUrl(url) || validate(url);
152
+ instance.__rapierFontsInstalled = true;
153
+ }
154
+ instance.renderer.rules.s_open = (tokens, index, _options, _env, renderer) =>
155
+ '<del' + renderer.renderAttrs(tokens[index]) + '>';
156
+ instance.renderer.rules.s_close = () => '</del>';
157
+ const SINGLE = -0x7e, strikeRule = instance.inline.ruler.__rules__.find(rule => rule.name === 'strikethrough').fn;
158
+ instance.inline.ruler.at('strikethrough', function rapierStrikeDelimiter(state, silent) {
159
+ if (!silent && state.src.charCodeAt(state.pos) === 0x7e) {
160
+ const scanned = state.scanDelims(state.pos, true);
161
+ if (scanned.length > 2) {
162
+ state.push('text', '', 0).content = state.src.slice(state.pos, state.pos + scanned.length);
163
+ state.pos += scanned.length;
164
+ return true;
165
+ }
166
+ if (scanned.length === 1) {
167
+ state.push('text', '', 0).content = '~';
168
+ state.delimiters.push({marker: SINGLE, length: 0, token: state.tokens.length - 1, end: -1,
169
+ open: scanned.can_open, close: scanned.can_close});
170
+ state.pos++;
171
+ return true;
172
+ }
173
+ }
174
+ return strikeRule(state, silent);
175
+ });
176
+ instance.inline.ruler2.after('strikethrough', 'rapier-single-tilde', function rapierSingleTilde(state) {
177
+ const retype = delimiters => {
178
+ for (const open of delimiters || []) {
179
+ if (open.marker !== SINGLE || open.end < 0) continue;
180
+ for (const [at, type, nesting] of [[open.token, 's_open', 1], [delimiters[open.end].token, 's_close', -1]])
181
+ Object.assign(state.tokens[at], {type, tag: 's', nesting, markup: '~', content: ''});
182
+ }
183
+ };
184
+ retype(state.delimiters);
185
+ for (const meta of state.tokens_meta) if (meta) retype(meta.delimiters);
186
+ });
187
+ const render = instance.renderer.render;
188
+ instance.renderer.rapierTransformHtml = html => html.indexOf('[!') < 0 ? html : html.replace(
189
+ /<blockquote>\s*(<p(?:\s[^>]*)?>)\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER|INFO)\]\s*(?:<br\s*\/?>)?\s*/gi,
190
+ (_match, paragraph, value) => {
191
+ const type = value.toLowerCase(), icons = spec.callouts;
192
+ const icon = icons[type === 'info' ? 'note' : type === 'danger' ? 'caution' : type];
193
+ return '<blockquote class="callout callout-' + type + '">' + paragraph + '<span class="callout__label">' +
194
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
195
+ icon + '</svg>' + type[0].toUpperCase() + type.slice(1) + '</span>';
196
+ }
197
+ );
198
+ instance.renderer.render = function (tokens, options, env) {
199
+ return this.rapierTransformHtml(render.call(this, tokens, options, env));
200
+ };
201
+
202
+ {
203
+ const taskLists = instance.core.ruler.__rules__.find(rule => rule.name === 'github-task-lists').fn;
204
+ instance.core.ruler.at('github-task-lists', function rapierTaskLists(state) {
205
+ const stranded = [], levels = new Set();
206
+ for (let at = 0; at < state.tokens.length; at++) {
207
+ const token = state.tokens[at];
208
+ if (token.type === 'list_item_open' && !levels.has(token.level - 1)) stranded.push(token);
209
+ levels.add(token.level);
210
+ }
211
+ for (const item of stranded) item.type = '';
212
+ try { taskLists(state); } finally { for (const item of stranded) item.type = 'list_item_open'; }
213
+ });
214
+ instance.core.ruler.after('github-task-lists', 'rapier-empty-task-lists', function rapierEmptyTaskLists(state) {
215
+ const tokens = state.tokens || [];
216
+ for (let index = 0; index < tokens.length; index++) {
217
+ const token = tokens[index];
218
+ if (token?.type !== 'inline' || !Array.isArray(token.children)) continue;
219
+ if (tokens[index - 2]?.type !== 'list_item_open' && !token.content.includes('"type="checkbox"')) continue;
220
+ for (const child of token.children) {
221
+ if (child?.type === 'html_inline' && typeof child.content === 'string' &&
222
+ child.content.includes('"type="checkbox"')) {
223
+ child.content = child.content.replace('"type="checkbox"', '" type="checkbox"');
224
+ }
225
+ }
226
+ }
227
+ const addClass = (token, className) => {
228
+ if (!token) return;
229
+ const current = token.attrGet('class') || '';
230
+ const classes = current.split(/\s+/).filter(Boolean);
231
+ if (classes.includes(className)) return;
232
+ classes.push(className);
233
+ token.attrSet('class', classes.join(' '));
234
+ };
235
+ const owningList = itemIndex => {
236
+ const targetLevel = tokens[itemIndex].level - 1;
237
+ for (let index = itemIndex - 1; index >= 0; index--) {
238
+ const token = tokens[index];
239
+ if (token.level < targetLevel) break;
240
+ if (token.level === targetLevel &&
241
+ (token.type === 'bullet_list_open' || token.type === 'ordered_list_open')) return token;
242
+ }
243
+ return null;
244
+ };
245
+ for (let index = 2; index < tokens.length; index++) {
246
+ const inline = tokens[index];
247
+ const paragraph = tokens[index - 1];
248
+ const item = tokens[index - 2];
249
+ if (inline?.type !== 'inline' || paragraph?.type !== 'paragraph_open' || item?.type !== 'list_item_open') continue;
250
+ if (inline.content?.length !== 3) continue;
251
+ if (!/^\[(?: |x|X)\]$/.test(inline.content || '')) continue;
252
+ if (!Array.isArray(inline.children) || inline.children.length !== 1 || inline.children[0].type !== 'text') continue;
253
+ const checked = inline.content[1] === 'x' || inline.content[1] === 'X';
254
+ const checkbox = new state.Token('html_inline', '', 0);
255
+ checkbox.content = '<input class="task-list-item-checkbox"' +
256
+ (checked ? ' checked=""' : '') + ' type="checkbox">';
257
+ inline.children = [checkbox];
258
+ inline.content = '';
259
+ addClass(item, 'task-list-item');
260
+ addClass(item, 'enabled');
261
+ addClass(owningList(index - 2), 'contains-task-list');
262
+ }
263
+ });
264
+ }
265
+
266
+ const highlightMarkers = Object.values(spec.highlights || {});
267
+ if (highlightMarkers.length) {
268
+ instance.inline.ruler.at('mark', function rapierMarkDelimiter(state, silent) {
269
+ const start = state.pos;
270
+ const marker = state.src.charCodeAt(start);
271
+ if (silent || marker !== 0x3d) return false;
272
+ const scanned = state.scanDelims(start, true);
273
+ let length = scanned.length;
274
+ if (length < 2) return false;
275
+ if (length % 2) {
276
+ state.push('text', '', 0).content = '=';
277
+ length--;
278
+ }
279
+ const markerAfterRun = highlightMarkers.some(value =>
280
+ state.src.startsWith(value, start + scanned.length));
281
+ for (let offset = 0; offset < length; offset += 2) {
282
+ state.push('text', '', 0).content = '==';
283
+ const lastPair = offset + 2 === length;
284
+ const canOpen = scanned.can_open || (lastPair && markerAfterRun);
285
+ if (canOpen || scanned.can_close) {
286
+ state.delimiters.push({
287
+ marker,
288
+ length: 0,
289
+ jump: offset / 2,
290
+ token: state.tokens.length - 1,
291
+ end: -1,
292
+ open: canOpen,
293
+ close: scanned.can_close,
294
+ });
295
+ }
296
+ }
297
+ state.pos += scanned.length;
298
+ return true;
299
+ });
300
+ }
301
+ {
302
+ const blockRules = instance.block && instance.block.ruler && instance.block.ruler.__rules__;
303
+ const htmlBlockRule = Array.isArray(blockRules) ? blockRules.find(function (rule) { return rule.name === 'html_block'; }) : null;
304
+ if (htmlBlockRule && typeof htmlBlockRule.fn === 'function' && !htmlBlockRule.fn.rapierInkAware) {
305
+ const originalHtmlBlock = htmlBlockRule.fn;
306
+ const inkAwareHtmlBlock = function rapierInkAwareHtmlBlock(state, startLine, endLine, silent) {
307
+ const start = state.bMarks[startLine] + state.tShift[startLine];
308
+ if (state.src.charCodeAt(start) === 0x3C && rapierMatchInkOpen(state.src.slice(start, start + 2048))) return false;
309
+ return originalHtmlBlock(state, startLine, endLine, silent);
310
+ };
311
+ inkAwareHtmlBlock.rapierInkAware = true;
312
+ instance.block.ruler.at('html_block', inkAwareHtmlBlock, { alt: ['paragraph', 'reference', 'blockquote'] });
313
+ }
314
+ rapierTypography.install(instance);
315
+ instance.core.ruler.after('rapier-typography', 'rapier-ink', function rapierInkPairs(state) {
316
+ for (let at = 0; at < state.tokens.length; at++) {
317
+ const blockToken = state.tokens[at];
318
+ if (blockToken.type !== 'inline' || !Array.isArray(blockToken.children) || blockToken.content.indexOf('<') < 0) continue;
319
+ const children = blockToken.children;
320
+ const stack = [];
321
+ let depth = 0;
322
+ for (let index = 0; index < children.length; index++) {
323
+ const token = children[index];
324
+ if (token.type !== 'html_inline') {
325
+ depth += token.nesting || 0;
326
+ while (stack.length && depth < stack.at(-1).depth) stack.pop();
327
+ continue;
328
+ }
329
+ const content = token.content;
330
+ if (rapierIsInkClose(content)) {
331
+ if (!stack.length || depth !== stack.at(-1).depth) continue;
332
+ const opener = children[stack.pop().index];
333
+ opener.type = 'rapier_ink_open';
334
+ opener.tag = 'span';
335
+ opener.nesting = 1;
336
+ opener.attrSet('data-rapier-ink', rapierInkOpenBody(opener.content));
337
+ opener.content = '';
338
+ token.type = 'rapier_ink_close';
339
+ token.tag = 'span';
340
+ token.nesting = -1;
341
+ token.content = '';
342
+ continue;
343
+ }
344
+ if (rapierParseInkOpen(content)) stack.push({index, depth});
345
+ }
346
+ }
347
+ });
348
+ }
349
+ {
350
+ instance.core.ruler.after('block', 'rapier-page-break', function rapierPageBreak(state) {
351
+ for (let at = 0; at < state.tokens.length; at++) {
352
+ const token = state.tokens[at];
353
+ if (token.type !== 'html_block' || !rapierIsPageBreakBlock(token.content)) continue;
354
+ token.type = 'rapier_break';
355
+ token.tag = 'div';
356
+ token.content = '';
357
+ }
358
+ });
359
+ }
360
+ instance.core.ruler.after('block', 'rapier-fence-boundaries', function rapierFenceBoundaries(state) {
361
+ for (let at = 0; at < state.tokens.length; at++) {
362
+ const token = state.tokens[at];
363
+ if (token.type !== 'fence' || !token.map) continue;
364
+ const body = token.content;
365
+ let lines = body && !body.endsWith('\n') ? 1 : 0;
366
+ for (let at = body.indexOf('\n'); at >= 0; at = body.indexOf('\n', at + 1)) lines++;
367
+ token.meta = { ...token.meta, rapierFenceClosed: token.map[1] - token.map[0] === lines + 2 };
368
+ }
369
+ });
370
+ if (spec.linkify) {
371
+ const linkify = instance.linkify;
372
+ linkify.set(spec.linkify);
373
+ instance.core.ruler.at('linkify', _rapierLinkifyText);
374
+ const allowedStart = (source, index) => index === 0 || /[ \t\n\v\f\r*_~(]/.test(source[index - 1]);
375
+ linkify.add('www.', {
376
+ validate(source, start) {
377
+ const host = /^[\p{L}\p{N}_-]+(?:\.[\p{L}\p{N}_-]+)+/u.exec(source.slice(start - 4));
378
+ if (!host || host[0].split('.').slice(-2).some(label => label.includes('_'))) return 0;
379
+ return host[0].length - 4;
380
+ },
381
+ normalize(match) { match.url = 'http://' + match.raw; },
382
+ });
383
+ linkify.add('xmpp:', {
384
+ validate(source, start) {
385
+ const address = /^[a-z\d._+-]+@([a-z\d_-]+(?:\.[a-z\d_-]+)+)/i.exec(source.slice(start));
386
+ if (!address || /[-_]$/.test(address[1]) || source[start + address[0].length] === '+') return 0;
387
+ const resource = /^\/[a-z\d@.]+/i.exec(source.slice(start + address[0].length));
388
+ return address[0].length + (resource ? resource[0].length : 0);
389
+ },
390
+ });
391
+ const refineLink = (match, source) => {
392
+ if (!match) return match;
393
+ const www = match.schema === 'www.' || (!match.schema && /^www\./i.test(match.raw));
394
+ const web = www || match.schema === 'http:' || match.schema === 'https:';
395
+ const email = match.schema === 'mailto:' || match.schema === 'xmpp:';
396
+ if ((web || email) && !allowedStart(source, match.index)) return null;
397
+ if (email && match.raw.slice(match.raw.indexOf('@') + 1).split('/')[0].includes('+')) return null;
398
+ if (!web) return match;
399
+ let end = match.index;
400
+ while (end < source.length && !/[ \t\n\v\f\r<]/.test(source[end])) end++;
401
+ while (end > match.index && /[?!.,:*_~]/.test(source[end - 1])) end--;
402
+ let raw = source.slice(match.index, end);
403
+ if (raw.endsWith(')')) {
404
+ let extra = 0;
405
+ for (const character of raw) extra += character === ')' ? 1 : character === '(' ? -1 : 0;
406
+ while (extra > 0 && raw.endsWith(')')) { raw = raw.slice(0, -1); extra--; }
407
+ }
408
+ raw = raw.replace(/&[a-z\d]+;$/i, '');
409
+ match.lastIndex = match.index + raw.length;
410
+ match.raw = match.text = raw;
411
+ match.url = www ? 'http://' + raw : raw;
412
+ return match;
413
+ };
414
+ const nativeMatch = linkify.match;
415
+ linkify.match = function (source) {
416
+ const matches = nativeMatch.call(this, source);
417
+ if (!matches) return matches;
418
+ const result = [];
419
+ let end = 0;
420
+ for (const match of matches) {
421
+ if (match.index < end) continue;
422
+ const refined = refineLink(match, source);
423
+ if (refined) { result.push(refined); end = refined.lastIndex; }
424
+ }
425
+ return result;
426
+ };
427
+ const nativeMatchAtStart = linkify.matchAtStart;
428
+ linkify.matchAtStart = function (source) { return refineLink(nativeMatchAtStart.call(this, source), source); };
429
+ const inlineLinkify = instance.inline.ruler.__rules__.find(rule => rule.name === 'linkify').fn;
430
+ instance.inline.ruler.at('linkify', function rapierInlineLinkify(state, silent) {
431
+ if (state.src.charCodeAt(state.pos) === 0x3a) {
432
+ const prefix = state.src.slice(Math.max(0, state.pos - 5), state.pos).toLowerCase();
433
+ const width = prefix.endsWith('https') ? 5 : prefix.endsWith('http') ? 4 : 0;
434
+ if (width && !allowedStart(state.src, state.pos - width)) return false;
435
+ }
436
+ return inlineLinkify(state, silent);
437
+ });
438
+ }
439
+ const disallowedTag = /<\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)(?=[ \t\n\v\f\r/>])/gi;
440
+ instance.core.ruler.push('rapier-tagfilter', function rapierTagfilter(state) {
441
+ const filter = token => {
442
+ if (token.type === 'html_inline' || token.type === 'html_block') {
443
+ token.content = token.content.replace(disallowedTag, tag => '&lt;' + tag.slice(1));
444
+ }
445
+ };
446
+ for (let at = 0; at < state.tokens.length; at++) {
447
+ const token = state.tokens[at];
448
+ filter(token);
449
+ if (token.type === 'inline' && token.content.indexOf('<') >= 0) for (const child of token.children || []) filter(child);
450
+ }
451
+ });
452
+ if (spec.core && spec.core.retainReferenceDefinitions) {
453
+ instance.core.ruler.disable('strip_references');
454
+ }
455
+ return instance;
456
+ }
457
+
458
+ function _rapierInstallMarkdownMath(instance, ruleName) {
459
+ installMathRules(instance, {singleDollar: true, brackets: false, inLink: true, ruleName});
460
+ }
461
+
462
+ export {RAPIER_HIGHLIGHT_COLORS, RAPIER_MARKDOWN_SPEC, _rapierApplyMarkdownSpec as applyMarkdownSpec,
463
+ _rapierInstallMarkdownMath as installMarkdownMath};
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2026 Jack Skipworth. See LICENSE-MIT.
3
+ import {markdownItConstructor} from "./vendor/markdown-runtime.mjs";
4
+ import {createMarkdownPlugins} from "./vendor/markdown-plugins.mjs";
5
+ import {RAPIER_MARKDOWN_SPEC, applyMarkdownSpec, installMarkdownMath} from "./md-parser-config.mjs";
6
+ import {configuredMarkdownParser, configureParser, installMarkdownImages} from "./md-assets.mjs";
7
+ import {splitOpeningFrontmatter} from "./frontmatter.mjs";
8
+
9
+ let owned = null;
10
+ const parserMath = Object.freeze({ceil: Math.ceil, random() {
11
+ throw new Error('Markdown task labels need a rendering context.');
12
+ }});
13
+ export function ensureMarkdownParser() {
14
+ const configured = configuredMarkdownParser();
15
+ if (configured) return configured;
16
+ if (!owned) {
17
+ owned = applyMarkdownSpec(markdownItConstructor()(RAPIER_MARKDOWN_SPEC.options), createMarkdownPlugins(parserMath));
18
+ installMarkdownMath(owned, RAPIER_MARKDOWN_SPEC.rules.mathBlock);
19
+ installMarkdownImages(owned);
20
+ }
21
+ configureParser(owned, splitOpeningFrontmatter);
22
+ return owned;
23
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- import {splitOpeningFrontmatter} from './frontmatter.mjs';
2
+ import {splitOpeningFrontmatter} from "./frontmatter.mjs";
3
3
 
4
4
  export function markdownSourcePositions(source, parser) {
5
5
  if (typeof source !== 'string' || !parser) throw new TypeError('Source positions require text and its Markdown parser');
@@ -1,4 +1,4 @@
1
1
  // SPDX-License-Identifier: MIT
2
- import {readDocumentSettings} from './document-settings.mjs';
2
+ import {readDocumentSettings} from "./document-settings.mjs";
3
3
  export const isTemplatePage = source => readDocumentSettings(source).template === 'page';
4
4
  export const isPageBorderLayout = layout => layout?.wrap === 'behind' && layout.lock === 'on';