rapier-markdown-kit 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.
- package/LICENSE +12 -5
- package/README.md +131 -10
- package/dist/agent/vendor/BROWSER-ENTITIES.json +36 -0
- package/dist/agent/vendor/MARKDOWN-RUNTIME.json +136 -0
- package/dist/agent/vendor/MARKDOWNIT.json +582 -0
- package/dist/agent/vendor/NOTICE.txt +60 -0
- package/dist/agent/vendor/markdown-licenses/entities-LICENSE.txt +11 -0
- package/dist/agent/vendor/markdown-licenses/linkify-it-LICENSE.txt +22 -0
- package/dist/agent/vendor/markdown-licenses/markdown-it-LICENSE.txt +22 -0
- package/dist/agent/vendor/markdown-licenses/mdurl-LICENSE.txt +45 -0
- package/dist/agent/vendor/markdown-licenses/punycode.js-LICENSE-MIT.txt +20 -0
- package/dist/agent/vendor/markdown-licenses/uc.micro-LICENSE.txt +20 -0
- package/dist/agent/vendor/markdown-plugins.json +46 -0
- package/dist/agent/vendor/markdown-plugins.mjs +27 -0
- package/dist/agent/vendor/markdown-runtime.mjs +13 -0
- package/dist/kit/assets.d.mts +106 -0
- package/dist/kit/index.d.mts +151 -0
- package/dist/kit/layout.d.mts +61 -0
- package/dist/kit/ledger/carried.d.mts +12 -6
- package/dist/kit/ledger/carried.mjs +1 -1
- package/dist/kit/ledger/format.mjs +5 -3
- package/dist/kit/ledger/text-merge.mjs +33 -0
- package/dist/kit/ledger/transport.mjs +61 -2
- package/dist/kit/ledger.d.mts +137 -31
- package/dist/kit/markdown-it.d.mts +2 -2
- package/dist/kit/marks.d.mts +241 -0
- package/dist/kit/marks.mjs +39 -1
- package/dist/kit/model.d.mts +191 -0
- package/dist/kit/render-entry.d.mts +654 -0
- package/dist/kit/render.mjs +8 -7
- package/dist/kit/typography.d.mts +42 -0
- package/dist/kit/will.d.mts +26 -3
- package/dist/math/delimiters.mjs +38 -0
- package/dist/math/markdown-rules.mjs +103 -0
- package/dist/spec/document-fonts.mjs +2 -10
- package/dist/spec/md-assets.mjs +1 -0
- package/dist/spec/md-comments.mjs +384 -0
- package/dist/spec/md-marks.mjs +9 -0
- package/dist/spec/md-parser-config.mjs +463 -0
- package/dist/spec/md-parser-runtime.mjs +23 -0
- package/package.json +30 -8
|
@@ -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 => '<' + 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 '../agent/vendor/markdown-runtime.mjs';
|
|
4
|
+
import {createMarkdownPlugins} from '../agent/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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.46",
|
|
4
4
|
"description": "Read, render and write Self-contained Markdown: one .md file that carries its pictures, drawings and layout, and that any Markdown app can read. HTML renderer, stylesheet, text layout, Will/1 agent permissions, and an edit history with authorship and offline merge. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -39,7 +39,10 @@
|
|
|
39
39
|
],
|
|
40
40
|
"main": "./dist/kit/index.mjs",
|
|
41
41
|
"exports": {
|
|
42
|
-
".":
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/kit/index.d.mts",
|
|
44
|
+
"default": "./dist/kit/index.mjs"
|
|
45
|
+
},
|
|
43
46
|
"./markdown-it": {
|
|
44
47
|
"types": "./dist/kit/markdown-it.d.mts",
|
|
45
48
|
"default": "./dist/kit/markdown-it.mjs"
|
|
@@ -48,18 +51,36 @@
|
|
|
48
51
|
"types": "./dist/kit/parsed.d.mts",
|
|
49
52
|
"default": "./dist/kit/parsed.mjs"
|
|
50
53
|
},
|
|
51
|
-
"./layout":
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
"./layout": {
|
|
55
|
+
"types": "./dist/kit/layout.d.mts",
|
|
56
|
+
"default": "./dist/kit/layout.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./marks": {
|
|
59
|
+
"types": "./dist/kit/marks.d.mts",
|
|
60
|
+
"default": "./dist/kit/marks.mjs"
|
|
61
|
+
},
|
|
62
|
+
"./model": {
|
|
63
|
+
"types": "./dist/kit/model.d.mts",
|
|
64
|
+
"default": "./dist/kit/model.mjs"
|
|
65
|
+
},
|
|
66
|
+
"./typography": {
|
|
67
|
+
"types": "./dist/kit/typography.d.mts",
|
|
68
|
+
"default": "./dist/kit/typography.mjs"
|
|
69
|
+
},
|
|
55
70
|
"./will": {
|
|
56
71
|
"types": "./dist/kit/will.d.mts",
|
|
57
72
|
"default": "./dist/kit/will.mjs"
|
|
58
73
|
},
|
|
59
|
-
"./assets":
|
|
74
|
+
"./assets": {
|
|
75
|
+
"types": "./dist/kit/assets.d.mts",
|
|
76
|
+
"default": "./dist/kit/assets.mjs"
|
|
77
|
+
},
|
|
60
78
|
"./style.css": "./style.css",
|
|
61
79
|
"./package.json": "./package.json",
|
|
62
|
-
"./render":
|
|
80
|
+
"./render": {
|
|
81
|
+
"types": "./dist/kit/render-entry.d.mts",
|
|
82
|
+
"default": "./dist/kit/render-entry.mjs"
|
|
83
|
+
},
|
|
63
84
|
"./rapier-ledger": {
|
|
64
85
|
"types": "./dist/kit/ledger.d.mts",
|
|
65
86
|
"default": "./dist/kit/ledger.mjs"
|
|
@@ -73,6 +94,7 @@
|
|
|
73
94
|
"default": "./dist/kit/ledger/carried.mjs"
|
|
74
95
|
}
|
|
75
96
|
},
|
|
97
|
+
"types": "./dist/kit/index.d.mts",
|
|
76
98
|
"repository": {
|
|
77
99
|
"type": "git",
|
|
78
100
|
"url": "git+https://github.com/jackskip22/rapier-plugins.git",
|