orz-markdown 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/attrs.d.ts.map +1 -1
- package/dist/plugins/attrs.js +10 -0
- package/dist/plugins/attrs.js.map +1 -1
- package/dist/plugins/mermaid.js +14 -1
- package/dist/plugins/mermaid.js.map +1 -1
- package/dist/plugins/qrcode.js +11 -1
- package/dist/plugins/qrcode.js.map +1 -1
- package/dist/plugins/smiles.js +4 -1
- package/dist/plugins/smiles.js.map +1 -1
- package/dist/plugins/youtube.js +9 -1
- package/dist/plugins/youtube.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +283 -0
- package/dist/runtime.js.map +1 -1
- package/orz-markdown-skills/SKILL.md +179 -0
- package/orz-markdown-skills/assets/minimal.css +658 -0
- package/orz-markdown-skills/assets/template.html +505 -0
- package/orz-markdown-skills/references/block-ids.md +51 -0
- package/orz-markdown-skills/references/css-classes.md +218 -0
- package/orz-markdown-skills/references/syntax.md +439 -0
- package/orz-markdown-skills/references/themes.md +302 -0
- package/package.json +4 -2
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Document</title>
|
|
7
|
+
|
|
8
|
+
<!-- Google Fonts preconnect hints (needed when using a theme with Google Fonts) -->
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
|
|
12
|
+
<!-- KaTeX CSS — required for all math rendering ($...$ and $$...$$) -->
|
|
13
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.35/dist/katex.min.css">
|
|
14
|
+
|
|
15
|
+
<!-- Highlight.js — pick a theme that matches your color scheme.
|
|
16
|
+
Light themes: github.min.css, stackoverflow-light.min.css
|
|
17
|
+
Dark themes: atom-one-dark.min.css, github-dark.min.css -->
|
|
18
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
19
|
+
|
|
20
|
+
<!-- Parser stylesheet — choose one of the bundled themes or link your own.
|
|
21
|
+
Bundled themes (each imports common.css automatically):
|
|
22
|
+
dark-elegant-1.css dark-elegant-2.css
|
|
23
|
+
light-neat-1.css light-neat-2.css
|
|
24
|
+
beige-decent-1.css beige-decent-2.css
|
|
25
|
+
light-academic-1.css light-academic-2.css
|
|
26
|
+
light-playful-1.css light-playful-2.css
|
|
27
|
+
Minimal no-frills stylesheet (feature support only): minimal.css -->
|
|
28
|
+
<link rel="stylesheet" href="./minimal.css">
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
|
|
32
|
+
<!-- All rendered HTML from md.render() goes inside this article element.
|
|
33
|
+
The .markdown-body class is required — all theme styles are scoped under it. -->
|
|
34
|
+
<article class="markdown-body">
|
|
35
|
+
<!-- INSERT RENDERED HTML HERE -->
|
|
36
|
+
</article>
|
|
37
|
+
|
|
38
|
+
<!-- =====================================================================
|
|
39
|
+
Required client-side libraries and runtime scripts.
|
|
40
|
+
These must appear after the body content so the DOM is ready.
|
|
41
|
+
===================================================================== -->
|
|
42
|
+
|
|
43
|
+
<!-- Highlight.js — syntax-highlights fenced code blocks -->
|
|
44
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
45
|
+
<script>if (typeof hljs !== 'undefined') hljs.highlightAll();</script>
|
|
46
|
+
|
|
47
|
+
<!-- Mermaid.js — renders {{mermaid}} / {{mm}} diagram blocks client-side.
|
|
48
|
+
The .mermaid div contains the raw Mermaid DSL emitted by the parser. -->
|
|
49
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
50
|
+
<script>if (typeof mermaid !== 'undefined') mermaid.initialize({ startOnLoad: true });</script>
|
|
51
|
+
|
|
52
|
+
<!-- SmilesDrawer — renders {{smiles}} / {{sm}} chemical structure canvases.
|
|
53
|
+
Draws into canvas[data-smiles] elements emitted by the parser. -->
|
|
54
|
+
<script src="https://unpkg.com/smiles-drawer@1.0.10/dist/smiles-drawer.min.js"></script>
|
|
55
|
+
<script>
|
|
56
|
+
if (typeof SmilesDrawer !== 'undefined') {
|
|
57
|
+
document.querySelectorAll('canvas[data-smiles]').forEach(function (canvas, i) {
|
|
58
|
+
if (!canvas.id) canvas.id = 'smiles-canvas-' + i;
|
|
59
|
+
var drawer = new SmilesDrawer.Drawer({ width: canvas.width || 250, height: canvas.height || 180 });
|
|
60
|
+
SmilesDrawer.parse(canvas.getAttribute('data-smiles'), function (tree) {
|
|
61
|
+
drawer.draw(tree, canvas, 'light', false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<!-- Tabs component — builds the .tabs-bar and activates .tab panels.
|
|
68
|
+
Must run after the DOM is painted so .tabs elements are found. -->
|
|
69
|
+
<script>
|
|
70
|
+
(function () {
|
|
71
|
+
document.querySelectorAll('.tabs').forEach(function (tabs) {
|
|
72
|
+
tabs.setAttribute('data-js', '1');
|
|
73
|
+
var panels = tabs.querySelectorAll(':scope > .tab');
|
|
74
|
+
if (!panels.length) return;
|
|
75
|
+
|
|
76
|
+
var bar = document.createElement('div');
|
|
77
|
+
bar.className = 'tabs-bar';
|
|
78
|
+
panels.forEach(function (panel, i) {
|
|
79
|
+
var label = panel.getAttribute('data-label') || 'Tab ' + (i + 1);
|
|
80
|
+
var btn = document.createElement('button');
|
|
81
|
+
btn.className = 'tabs-bar-btn' + (i === 0 ? ' active' : '');
|
|
82
|
+
btn.textContent = label;
|
|
83
|
+
btn.addEventListener('click', function () {
|
|
84
|
+
tabs.querySelectorAll('.tabs-bar-btn').forEach(function (b) { b.classList.remove('active'); });
|
|
85
|
+
panels.forEach(function (p) { p.classList.remove('active'); });
|
|
86
|
+
btn.classList.add('active');
|
|
87
|
+
panel.classList.add('active');
|
|
88
|
+
});
|
|
89
|
+
bar.appendChild(btn);
|
|
90
|
+
});
|
|
91
|
+
tabs.insertBefore(bar, tabs.firstChild);
|
|
92
|
+
panels[0].classList.add('active');
|
|
93
|
+
});
|
|
94
|
+
})();
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<!-- orz-markdown runtime — QR expand/collapse overlays and copy-as-Markdown
|
|
98
|
+
(copying a selection inside .markdown-body yields Markdown source).
|
|
99
|
+
Provides window.OrzMarkdownRuntime.init(root) / .elementToMarkdown(node). -->
|
|
100
|
+
<script>
|
|
101
|
+
(function (global) {
|
|
102
|
+
var expandedQr = null;
|
|
103
|
+
var expandedSourceQr = null;
|
|
104
|
+
|
|
105
|
+
function collapseQr() {
|
|
106
|
+
if (!expandedQr) return;
|
|
107
|
+
|
|
108
|
+
if (expandedSourceQr) {
|
|
109
|
+
expandedSourceQr.classList.remove('is-expanded');
|
|
110
|
+
expandedSourceQr.setAttribute('aria-expanded', 'false');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (expandedQr.parentNode) {
|
|
114
|
+
expandedQr.parentNode.removeChild(expandedQr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (global.document && global.document.body) {
|
|
118
|
+
global.document.body.style.overflow = '';
|
|
119
|
+
}
|
|
120
|
+
expandedQr = null;
|
|
121
|
+
expandedSourceQr = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function toggleQr(node) {
|
|
125
|
+
if (expandedSourceQr === node) {
|
|
126
|
+
collapseQr();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
collapseQr();
|
|
131
|
+
if (!global.document || !global.document.body) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
expandedSourceQr = node;
|
|
136
|
+
expandedSourceQr.classList.add('is-expanded');
|
|
137
|
+
expandedSourceQr.setAttribute('aria-expanded', 'true');
|
|
138
|
+
|
|
139
|
+
expandedQr = node.cloneNode(true);
|
|
140
|
+
expandedQr.classList.add('qrcode-overlay', 'is-expanded');
|
|
141
|
+
expandedQr.setAttribute('aria-hidden', 'true');
|
|
142
|
+
expandedQr.removeAttribute('aria-expanded');
|
|
143
|
+
expandedQr.removeAttribute('tabindex');
|
|
144
|
+
expandedQr.removeAttribute('role');
|
|
145
|
+
expandedQr.addEventListener('click', function (event) {
|
|
146
|
+
event.stopPropagation();
|
|
147
|
+
collapseQr();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
global.document.body.appendChild(expandedQr);
|
|
151
|
+
|
|
152
|
+
if (global.document && global.document.body) {
|
|
153
|
+
global.document.body.style.overflow = 'hidden';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function initQrCodes(root) {
|
|
158
|
+
if (!root || typeof root.querySelectorAll !== 'function') return;
|
|
159
|
+
|
|
160
|
+
Array.prototype.slice.call(root.querySelectorAll('.qrcode')).forEach(function (node) {
|
|
161
|
+
if (node.getAttribute('data-qr-ready') === '1') return;
|
|
162
|
+
|
|
163
|
+
node.setAttribute('data-qr-ready', '1');
|
|
164
|
+
node.addEventListener('click', function (event) {
|
|
165
|
+
event.stopPropagation();
|
|
166
|
+
toggleQr(node);
|
|
167
|
+
});
|
|
168
|
+
node.addEventListener('keydown', function (event) {
|
|
169
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
toggleQr(node);
|
|
172
|
+
}
|
|
173
|
+
if (event.key === 'Escape') {
|
|
174
|
+
collapseQr();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function init(root) {
|
|
181
|
+
initQrCodes(root || global.document);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---- copy-as-markdown: DOM -> Markdown walker ---------------------------
|
|
185
|
+
// Converts a selected DOM fragment back to Markdown so that copying rendered
|
|
186
|
+
// content yields source, not HTML. Standard constructs are reconstructed from
|
|
187
|
+
// their tags; generated constructs (math, mermaid, smiles, qr, youtube) carry
|
|
188
|
+
// a [data-md] breadcrumb that is emitted verbatim. Written without template
|
|
189
|
+
// literals/backticks because this whole file is a String.raw template.
|
|
190
|
+
var BT = String.fromCharCode(96);
|
|
191
|
+
var FENCE = BT + BT + BT;
|
|
192
|
+
|
|
193
|
+
function rt_repeat(s, n) {
|
|
194
|
+
var out = '';
|
|
195
|
+
for (var i = 0; i < n; i++) out += s;
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function rt_attr(node, name) {
|
|
200
|
+
return node && node.getAttribute ? node.getAttribute(name) : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function rt_isBlockElement(node) {
|
|
204
|
+
if (!node || node.nodeType !== 1) return false;
|
|
205
|
+
if (rt_attr(node, 'data-md') != null) return true;
|
|
206
|
+
var tag = node.tagName.toLowerCase();
|
|
207
|
+
if (/^(p|div|section|article|figure|h[1-6]|ul|ol|li|blockquote|pre|table|thead|tbody|tr|hr|details)$/.test(tag)) return true;
|
|
208
|
+
if (node.classList && (node.classList.contains('katex-display') || node.classList.contains('mermaid'))) return true;
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function rt_katex(node, display) {
|
|
213
|
+
var ann = node.querySelector ? node.querySelector('annotation[encoding="application/x-tex"]') : null;
|
|
214
|
+
var tex = (ann ? ann.textContent : node.textContent) || '';
|
|
215
|
+
tex = tex.replace(/^\s+|\s+$/g, '');
|
|
216
|
+
if (display) return '$$\n' + tex + '\n$$';
|
|
217
|
+
return '$' + tex + '$';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function rt_img(node) {
|
|
221
|
+
var alt = rt_attr(node, 'alt') || '';
|
|
222
|
+
var src = rt_attr(node, 'src') || '';
|
|
223
|
+
var title = rt_attr(node, 'title');
|
|
224
|
+
return ' + ')';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function rt_inlineChildren(node) {
|
|
228
|
+
var out = '';
|
|
229
|
+
var kids = node.childNodes;
|
|
230
|
+
for (var i = 0; i < kids.length; i++) out += rt_inlineNode(kids[i]);
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function rt_inlineNode(node) {
|
|
235
|
+
if (node.nodeType === 3) return node.nodeValue.replace(/\s+/g, ' ');
|
|
236
|
+
if (node.nodeType !== 1) return '';
|
|
237
|
+
var dm = rt_attr(node, 'data-md');
|
|
238
|
+
if (dm != null) return dm;
|
|
239
|
+
if (node.classList && node.classList.contains('katex')) return rt_katex(node, false);
|
|
240
|
+
var tag = node.tagName.toLowerCase();
|
|
241
|
+
switch (tag) {
|
|
242
|
+
case 'strong': case 'b': return '**' + rt_inlineChildren(node) + '**';
|
|
243
|
+
case 'em': case 'i': return '*' + rt_inlineChildren(node) + '*';
|
|
244
|
+
case 'code': return BT + (node.textContent || '') + BT;
|
|
245
|
+
case 'a':
|
|
246
|
+
var href = rt_attr(node, 'href') || '';
|
|
247
|
+
var title = rt_attr(node, 'title');
|
|
248
|
+
return '[' + rt_inlineChildren(node) + '](' + href + (title ? ' "' + title + '"' : '') + ')';
|
|
249
|
+
case 'del': case 's': return '~~' + rt_inlineChildren(node) + '~~';
|
|
250
|
+
case 'ins': return '++' + rt_inlineChildren(node) + '++';
|
|
251
|
+
case 'mark': return '==' + rt_inlineChildren(node) + '==';
|
|
252
|
+
case 'sub': return '~' + rt_inlineChildren(node) + '~';
|
|
253
|
+
case 'sup': return '^' + rt_inlineChildren(node) + '^';
|
|
254
|
+
case 'br': return ' \n';
|
|
255
|
+
case 'img': return rt_img(node);
|
|
256
|
+
case 'input': return '';
|
|
257
|
+
default: return rt_inlineChildren(node);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function rt_align(cell) {
|
|
262
|
+
var style = (rt_attr(cell, 'style') || '').toLowerCase();
|
|
263
|
+
if (style.indexOf('center') !== -1) return 'center';
|
|
264
|
+
if (style.indexOf('right') !== -1) return 'right';
|
|
265
|
+
if (style.indexOf('left') !== -1) return 'left';
|
|
266
|
+
return '';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function rt_sep(align) {
|
|
270
|
+
if (align === 'center') return ':---:';
|
|
271
|
+
if (align === 'right') return '---:';
|
|
272
|
+
if (align === 'left') return ':---';
|
|
273
|
+
return '---';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function rt_table(tbl) {
|
|
277
|
+
var rows = [];
|
|
278
|
+
var aligns = [];
|
|
279
|
+
var header = [];
|
|
280
|
+
var headRow = tbl.querySelector ? tbl.querySelector('thead tr') : null;
|
|
281
|
+
if (headRow) {
|
|
282
|
+
var hc = headRow.children;
|
|
283
|
+
for (var i = 0; i < hc.length; i++) { header.push(rt_inlineChildren(hc[i]).trim()); aligns.push(rt_align(hc[i])); }
|
|
284
|
+
}
|
|
285
|
+
rows.push('| ' + header.join(' | ') + ' |');
|
|
286
|
+
var seps = [];
|
|
287
|
+
for (var s = 0; s < header.length; s++) seps.push(rt_sep(aligns[s]));
|
|
288
|
+
rows.push('| ' + seps.join(' | ') + ' |');
|
|
289
|
+
var bodyRows = tbl.querySelectorAll ? tbl.querySelectorAll('tbody tr') : [];
|
|
290
|
+
for (var r = 0; r < bodyRows.length; r++) {
|
|
291
|
+
var cells = [];
|
|
292
|
+
var tds = bodyRows[r].children;
|
|
293
|
+
for (var c = 0; c < tds.length; c++) cells.push(rt_inlineChildren(tds[c]).trim());
|
|
294
|
+
rows.push('| ' + cells.join(' | ') + ' |');
|
|
295
|
+
}
|
|
296
|
+
return rows.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function rt_codeBlock(pre) {
|
|
300
|
+
var code = pre.querySelector ? (pre.querySelector('code') || pre) : pre;
|
|
301
|
+
var lang = '';
|
|
302
|
+
var cls = rt_attr(code, 'class') || '';
|
|
303
|
+
var m = cls.match(/language-([A-Za-z0-9_+#-]+)/);
|
|
304
|
+
if (m) lang = m[1];
|
|
305
|
+
var text = (code.textContent || '').replace(/\n$/, '');
|
|
306
|
+
return FENCE + lang + '\n' + text + '\n' + FENCE;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function rt_list(node, ordered, depth) {
|
|
310
|
+
var items = [];
|
|
311
|
+
var idx = 1;
|
|
312
|
+
var kids = node.childNodes;
|
|
313
|
+
for (var i = 0; i < kids.length; i++) {
|
|
314
|
+
var li = kids[i];
|
|
315
|
+
if (li.nodeType !== 1 || li.tagName.toLowerCase() !== 'li') continue;
|
|
316
|
+
items.push(rt_listItem(li, ordered, idx, depth));
|
|
317
|
+
idx++;
|
|
318
|
+
}
|
|
319
|
+
return items.join('\n');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function rt_listItem(li, ordered, idx, depth) {
|
|
323
|
+
var indent = rt_repeat(' ', depth);
|
|
324
|
+
var marker = ordered ? (idx + '. ') : '- ';
|
|
325
|
+
var task = '';
|
|
326
|
+
var cb = li.querySelector ? li.querySelector('input[type="checkbox"]') : null;
|
|
327
|
+
if (cb) task = (cb.checked || rt_attr(cb, 'checked') != null) ? '[x] ' : '[ ] ';
|
|
328
|
+
var inlineParts = '';
|
|
329
|
+
var nested = '';
|
|
330
|
+
var kids = li.childNodes;
|
|
331
|
+
for (var i = 0; i < kids.length; i++) {
|
|
332
|
+
var c = kids[i];
|
|
333
|
+
if (c.nodeType === 1 && /^(ul|ol)$/.test(c.tagName.toLowerCase())) {
|
|
334
|
+
nested += '\n' + rt_list(c, c.tagName.toLowerCase() === 'ol', depth + 1);
|
|
335
|
+
} else {
|
|
336
|
+
inlineParts += rt_inlineNode(c);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
inlineParts = inlineParts.replace(/\s+/g, ' ').trim();
|
|
340
|
+
return indent + marker + task + inlineParts + nested;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function rt_blockquote(node) {
|
|
344
|
+
var inner = rt_blockChildren(node);
|
|
345
|
+
var lines = inner.split('\n');
|
|
346
|
+
var out = [];
|
|
347
|
+
for (var i = 0; i < lines.length; i++) out.push(lines[i] ? '> ' + lines[i] : '>');
|
|
348
|
+
return out.join('\n');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function rt_blockChildren(node) {
|
|
352
|
+
var parts = [];
|
|
353
|
+
var kids = node.childNodes;
|
|
354
|
+
var i = 0;
|
|
355
|
+
while (i < kids.length) {
|
|
356
|
+
var k = kids[i];
|
|
357
|
+
// A selection can contain bare <li> elements (when the user selects a
|
|
358
|
+
// list's contents rather than the <ul>/<ol> wrapper). Group a run of them
|
|
359
|
+
// into a bullet list instead of emitting each as a separate block.
|
|
360
|
+
if (k.nodeType === 1 && k.tagName.toLowerCase() === 'li') {
|
|
361
|
+
var lis = [];
|
|
362
|
+
while (i < kids.length) {
|
|
363
|
+
var kk = kids[i];
|
|
364
|
+
if (kk.nodeType === 1 && kk.tagName.toLowerCase() === 'li') { lis.push(kk); i++; }
|
|
365
|
+
else if (kk.nodeType === 3 && !(kk.nodeValue || '').trim()) { i++; }
|
|
366
|
+
else break;
|
|
367
|
+
}
|
|
368
|
+
var items = [];
|
|
369
|
+
for (var j = 0; j < lis.length; j++) items.push(rt_listItem(lis[j], false, j + 1, 0));
|
|
370
|
+
parts.push(items.join('\n'));
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
var s = rt_blockNode(k);
|
|
374
|
+
if (s && s.replace(/\s/g, '') !== '') parts.push(s.replace(/\s+$/, ''));
|
|
375
|
+
i++;
|
|
376
|
+
}
|
|
377
|
+
return parts.join('\n\n');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function rt_blockNode(node) {
|
|
381
|
+
if (node.nodeType === 3) {
|
|
382
|
+
var t = node.nodeValue;
|
|
383
|
+
return (t && t.trim()) ? t.replace(/\s+/g, ' ').trim() : '';
|
|
384
|
+
}
|
|
385
|
+
if (node.nodeType !== 1) return '';
|
|
386
|
+
var dm = rt_attr(node, 'data-md');
|
|
387
|
+
if (dm != null) return dm;
|
|
388
|
+
if (node.classList) {
|
|
389
|
+
if (node.classList.contains('katex-display')) {
|
|
390
|
+
var k = node.querySelector ? node.querySelector('.katex') : null;
|
|
391
|
+
return rt_katex(k || node, true);
|
|
392
|
+
}
|
|
393
|
+
if (node.classList.contains('mermaid')) return '{{mermaid\n' + (node.textContent || '').trim() + '\n}}';
|
|
394
|
+
}
|
|
395
|
+
var tag = node.tagName.toLowerCase();
|
|
396
|
+
var hm = tag.match(/^h([1-6])$/);
|
|
397
|
+
if (hm) return rt_repeat('#', parseInt(hm[1], 10)) + ' ' + rt_inlineChildren(node).trim();
|
|
398
|
+
switch (tag) {
|
|
399
|
+
case 'p': return rt_inlineChildren(node).trim();
|
|
400
|
+
case 'ul': return rt_list(node, false, 0);
|
|
401
|
+
case 'ol': return rt_list(node, true, 0);
|
|
402
|
+
case 'pre': return rt_codeBlock(node);
|
|
403
|
+
case 'blockquote': return rt_blockquote(node);
|
|
404
|
+
case 'table': return rt_table(node);
|
|
405
|
+
case 'hr': return '---';
|
|
406
|
+
case 'li': return rt_inlineChildren(node).trim();
|
|
407
|
+
case 'figure': case 'div': case 'section': case 'article': case 'details': case 'summary':
|
|
408
|
+
return rt_blockChildren(node);
|
|
409
|
+
default: return rt_inlineNode(node).trim();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function elementToMarkdown(root) {
|
|
414
|
+
if (!root) return '';
|
|
415
|
+
var hasBlock = false;
|
|
416
|
+
var kids = root.childNodes || [];
|
|
417
|
+
for (var i = 0; i < kids.length; i++) {
|
|
418
|
+
if (rt_isBlockElement(kids[i])) { hasBlock = true; break; }
|
|
419
|
+
}
|
|
420
|
+
var out = hasBlock ? rt_blockChildren(root) : rt_inlineChildren(root);
|
|
421
|
+
return out.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').replace(/^\s+|\s+$/g, '');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function rt_isEditable(node) {
|
|
425
|
+
while (node) {
|
|
426
|
+
if (node.nodeType === 1) {
|
|
427
|
+
var tag = node.tagName.toLowerCase();
|
|
428
|
+
if (tag === 'input' || tag === 'textarea') return true;
|
|
429
|
+
if (node.isContentEditable) return true;
|
|
430
|
+
}
|
|
431
|
+
node = node.parentNode;
|
|
432
|
+
}
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function rt_withinCopyRoot(node) {
|
|
437
|
+
while (node) {
|
|
438
|
+
if (node.nodeType === 1 && node.classList &&
|
|
439
|
+
(node.classList.contains('markdown-body') || rt_attr(node, 'data-orz-copy') != null)) return true;
|
|
440
|
+
node = node.parentNode;
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function onCopy(event) {
|
|
446
|
+
if (!global.document) return;
|
|
447
|
+
var sel = global.getSelection ? global.getSelection()
|
|
448
|
+
: (global.document.getSelection ? global.document.getSelection() : null);
|
|
449
|
+
if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
|
|
450
|
+
if (rt_isEditable(sel.anchorNode) || rt_isEditable(sel.focusNode)) return;
|
|
451
|
+
if (!rt_withinCopyRoot(sel.anchorNode) && !rt_withinCopyRoot(sel.focusNode)) return;
|
|
452
|
+
var container = global.document.createElement('div');
|
|
453
|
+
for (var i = 0; i < sel.rangeCount; i++) container.appendChild(sel.getRangeAt(i).cloneContents());
|
|
454
|
+
var md = elementToMarkdown(container);
|
|
455
|
+
if (!md) return;
|
|
456
|
+
var cd = event.clipboardData || global.clipboardData;
|
|
457
|
+
if (cd && cd.setData) {
|
|
458
|
+
cd.setData('text/plain', md);
|
|
459
|
+
if (event.preventDefault) event.preventDefault();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function bindGlobalHandlers() {
|
|
464
|
+
if (!global.document || global.__orzMarkdownRuntimeBound) return;
|
|
465
|
+
global.__orzMarkdownRuntimeBound = true;
|
|
466
|
+
|
|
467
|
+
global.document.addEventListener('click', function (event) {
|
|
468
|
+
if (expandedQr && !expandedQr.contains(event.target)) {
|
|
469
|
+
collapseQr();
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
global.document.addEventListener('keydown', function (event) {
|
|
474
|
+
if (event.key === 'Escape') {
|
|
475
|
+
collapseQr();
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// Copy rendered selections as Markdown source.
|
|
480
|
+
global.document.addEventListener('copy', onCopy);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
global.OrzMarkdownRuntime = Object.assign({}, global.OrzMarkdownRuntime, {
|
|
484
|
+
init: init,
|
|
485
|
+
initQrCodes: initQrCodes,
|
|
486
|
+
collapseQr: collapseQr,
|
|
487
|
+
elementToMarkdown: elementToMarkdown,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
bindGlobalHandlers();
|
|
491
|
+
|
|
492
|
+
if (global.document) {
|
|
493
|
+
if (global.document.readyState === 'loading') {
|
|
494
|
+
global.document.addEventListener('DOMContentLoaded', function () {
|
|
495
|
+
init(global.document);
|
|
496
|
+
}, { once: true });
|
|
497
|
+
} else {
|
|
498
|
+
init(global.document);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window);
|
|
502
|
+
</script>
|
|
503
|
+
|
|
504
|
+
</body>
|
|
505
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Stable Block IDs — Preservation Rules
|
|
2
|
+
|
|
3
|
+
Host applications built on orz-markdown attach stable block identifiers to headings so that sections keep their identity across edits, AI rewrites, and document versions. This file is the contract an AI editor MUST follow whenever a document uses these IDs. Violating it silently breaks cross-references, progress tracking, and anything else the host keys off block identity.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What a block ID looks like
|
|
8
|
+
|
|
9
|
+
A block ID is `blk-` followed by 8 or more lowercase base36 characters (`a–z`, `0–9`), attached with the `attrs` plugin immediately after a heading:
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
## Acid-Base Theory{{attrs[#blk-abc12345]}}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Canonical form: the `{{attrs[...]}}` marker comes **directly after the heading text with no space before it**. (A space before the marker also parses, but the no-space form is canonical — emit it when writing new headings.)
|
|
16
|
+
|
|
17
|
+
Renders as:
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<h2 id="blk-abc12345" tabindex="-1">Acid-Base Theory</h2>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The ID overrides the auto-generated `markdown-it-anchor` slug, so the anchor stays fixed no matter what the heading says.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## The rules
|
|
28
|
+
|
|
29
|
+
1. **IDs are immutable.** When editing or rewriting a section, NEVER change, remove, regenerate, or "fix" its `{{attrs[#...]}}` marker. Preserve it character-for-character — even when you rewrite the heading text completely.
|
|
30
|
+
|
|
31
|
+
```markdown
|
|
32
|
+
Before: ## Acid-Base Theory{{attrs[#blk-abc12345]}}
|
|
33
|
+
After: ## Brønsted–Lowry Acids and Bases{{attrs[#blk-abc12345]}} ← same ID
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. **Never reuse an ID from a deleted block** for new content. A deleted block's identity dies with it.
|
|
37
|
+
|
|
38
|
+
3. **Replacing is not editing.** When a section is deliberately replaced with new content (rather than edited), the host application mints the new ID. Do not invent one yourself unless explicitly asked.
|
|
39
|
+
|
|
40
|
+
4. **Copies do not inherit IDs.** When copying a section into another document, do not carry the ID along verbatim if the host supplies a new one. An ID must never appear twice within one document.
|
|
41
|
+
|
|
42
|
+
5. **Never convert to or from Pandoc-style `{#id}` syntax.** orz-markdown does not support it: `## Title {#blk-x}` renders the braces as literal heading text and corrupts the anchor slug. The only supported form is `{{attrs[#...]}}` (or `{{attrs[id="..."]}}`).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick checklist before saving an edit
|
|
47
|
+
|
|
48
|
+
- Every heading that had a `{{attrs[#blk-...]}}` marker before your edit still has the **same** marker.
|
|
49
|
+
- No new ID was invented, regenerated, or copied from elsewhere.
|
|
50
|
+
- No ID appears more than once in the document.
|
|
51
|
+
- No Pandoc `{#id}` syntax anywhere.
|