orz-markdown 1.1.0 → 1.2.1
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 +15 -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 +343 -0
- package/dist/runtime.js.map +1 -1
- package/orz-markdown-skills/SKILL.md +4 -0
- package/orz-markdown-skills/assets/template.html +343 -20
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ document.body.innerHTML = `<article class="markdown-body">${html}</article>`;
|
|
|
37
37
|
|
|
38
38
|
## Browser Runtime
|
|
39
39
|
|
|
40
|
-
Some rendered features expect a small browser runtime layer after the HTML is mounted. This
|
|
40
|
+
Some rendered features expect a small browser runtime layer after the HTML is mounted. This includes QR-code expand/collapse behavior and **copy-as-Markdown**, and is designed so more client-side enhancements can share the same entry point.
|
|
41
41
|
|
|
42
42
|
```javascript
|
|
43
43
|
import { getBrowserRuntimeScript } from 'orz-markdown/runtime';
|
|
@@ -49,6 +49,20 @@ document.body.appendChild(runtimeScript);
|
|
|
49
49
|
|
|
50
50
|
If your app controls initialization directly, the runtime also exposes `window.OrzMarkdownRuntime.init(root)` and `window.OrzMarkdownRuntime.initQrCodes(root)`.
|
|
51
51
|
|
|
52
|
+
### Copy as Markdown
|
|
53
|
+
|
|
54
|
+
Once the runtime is loaded, selecting rendered content and copying it (Cmd/Ctrl-C) places **Markdown source** on the clipboard instead of HTML. A built-in DOM→Markdown walker reconstructs headings, emphasis, inline code, links, `==mark==`/`++ins++`/`~~del~~`/`~sub~`/`^sup^`, bullet/ordered/nested lists, task lists, tables (with alignment), blockquotes, fenced code (with language), images, and inline/display math.
|
|
55
|
+
|
|
56
|
+
It only transforms selections inside an element with class `markdown-body` (wrap your rendered output in one, e.g. `<article class="markdown-body">…</article>`, or mark a container with `data-orz-copy`), and never touches selections inside `<input>`, `<textarea>`, or `contenteditable` regions.
|
|
57
|
+
|
|
58
|
+
Generated constructs that lose their source after client-side rendering — `mermaid`, `smiles`, `qrcode`, `youtube` — carry a `data-md` attribute that the walker emits verbatim. As a result a copied table of contents yields its heading links (not `{{toc 2,3}}`) and a copied QR code yields `{{qr ...}}` (not its SVG). **Do not strip `data-md` attributes** if you post-process the HTML.
|
|
59
|
+
|
|
60
|
+
You can also convert a node programmatically:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const markdown = window.OrzMarkdownRuntime.elementToMarkdown(someElement);
|
|
64
|
+
```
|
|
65
|
+
|
|
52
66
|
## Themes
|
|
53
67
|
|
|
54
68
|
We provide multiple ready-to-use CSS themes for rendering the output! You do not need to style the custom plugins from scratch.
|
package/dist/plugins/mermaid.js
CHANGED
|
@@ -7,12 +7,25 @@ function escapeHtml(str) {
|
|
|
7
7
|
.replace(/</g, '<')
|
|
8
8
|
.replace(/>/g, '>');
|
|
9
9
|
}
|
|
10
|
+
// Encode a value for a double-quoted HTML attribute, preserving newlines (as
|
|
11
|
+
// ) so multi-line source survives in `data-md`.
|
|
12
|
+
function escapeAttr(str) {
|
|
13
|
+
return str
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/"/g, '"')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/\n/g, ' ');
|
|
19
|
+
}
|
|
10
20
|
(0, registry_js_1.register)({
|
|
11
21
|
type: 'block',
|
|
12
22
|
aliases: ['mermaid', 'mm'],
|
|
13
23
|
render(_args, body, _env) {
|
|
14
24
|
const content = body?.trim() ?? '';
|
|
15
|
-
|
|
25
|
+
// `data-md` lets copy-as-markdown recover the source after client-side
|
|
26
|
+
// mermaid rendering replaces the div contents with an <svg>.
|
|
27
|
+
const directive = `{{mermaid\n${content}\n}}`;
|
|
28
|
+
return `<div class="mermaid" data-md="${escapeAttr(directive)}">${escapeHtml(content)}</div>\n`;
|
|
16
29
|
},
|
|
17
30
|
});
|
|
18
31
|
//# sourceMappingURL=mermaid.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../src/plugins/mermaid.ts"],"names":[],"mappings":";;AAAA,gDAA0C;AAE1C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACnC,OAAO,
|
|
1
|
+
{"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../src/plugins/mermaid.ts"],"names":[],"mappings":";;AAAA,gDAA0C;AAE1C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,6EAA6E;AAC7E,qDAAqD;AACrD,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACnC,uEAAuE;QACvE,6DAA6D;QAC7D,MAAM,SAAS,GAAG,cAAc,OAAO,MAAM,CAAC;QAC9C,OAAO,iCAAiC,UAAU,CAAC,SAAS,CAAC,KAAK,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC;IAClG,CAAC;CACF,CAAC,CAAC"}
|
package/dist/plugins/qrcode.js
CHANGED
|
@@ -5,6 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const qrcode_svg_1 = __importDefault(require("qrcode-svg"));
|
|
7
7
|
const registry_js_1 = require("../registry.js");
|
|
8
|
+
function escapeAttr(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/"/g, '"')
|
|
12
|
+
.replace(/</g, '<')
|
|
13
|
+
.replace(/>/g, '>');
|
|
14
|
+
}
|
|
8
15
|
(0, registry_js_1.register)({
|
|
9
16
|
type: 'inline',
|
|
10
17
|
aliases: ['qr', 'qrcode'],
|
|
@@ -15,7 +22,10 @@ const registry_js_1 = require("../registry.js");
|
|
|
15
22
|
const size = 96;
|
|
16
23
|
const rawSvg = new qrcode_svg_1.default({ content, width: size, height: size, padding: 0, color: '#000000', background: '#ffffff' }).svg();
|
|
17
24
|
const svg = rawSvg.replace('<svg ', `<svg viewBox="0 0 ${size} ${size}" preserveAspectRatio="xMidYMid meet" `);
|
|
18
|
-
|
|
25
|
+
// `data-md` carries the source: the QR content is otherwise encoded only in
|
|
26
|
+
// the SVG modules and cannot be recovered for copy-as-markdown.
|
|
27
|
+
const directive = escapeAttr(`{{qr ${content}}}`);
|
|
28
|
+
return `<span class="qrcode" data-md="${directive}" role="button" tabindex="0" aria-label="Expand QR code" aria-expanded="false"><span class="qrcode__icon" aria-hidden="true">⤢</span>${svg}</span>`;
|
|
19
29
|
},
|
|
20
30
|
});
|
|
21
31
|
//# sourceMappingURL=qrcode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qrcode.js","sourceRoot":"","sources":["../../src/plugins/qrcode.ts"],"names":[],"mappings":";;;;;AAAA,4DAAgC;AAChC,gDAA0C;AAE1C,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;IACzB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,IAAI,oBAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7H,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CACxB,OAAO,EACP,qBAAqB,IAAI,IAAI,IAAI,wCAAwC,CAC1E,CAAC;QACF,OAAO,
|
|
1
|
+
{"version":3,"file":"qrcode.js","sourceRoot":"","sources":["../../src/plugins/qrcode.ts"],"names":[],"mappings":";;;;;AAAA,4DAAgC;AAChC,gDAA0C;AAE1C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;IACzB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,IAAI,oBAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7H,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CACxB,OAAO,EACP,qBAAqB,IAAI,IAAI,IAAI,wCAAwC,CAC1E,CAAC;QACF,4EAA4E;QAC5E,gEAAgE;QAChE,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,OAAO,IAAI,CAAC,CAAC;QAClD,OAAO,iCAAiC,SAAS,wIAAwI,GAAG,SAAS,CAAC;IACxM,CAAC;CACF,CAAC,CAAC"}
|
package/dist/plugins/smiles.js
CHANGED
|
@@ -13,7 +13,10 @@ function escapeAttr(str) {
|
|
|
13
13
|
aliases: ['smiles', 'sm'],
|
|
14
14
|
render(_args, body, _env) {
|
|
15
15
|
const smiles = body?.trim() ?? '';
|
|
16
|
-
|
|
16
|
+
// `data-md` lets copy-as-markdown recover the source after smiles-drawer
|
|
17
|
+
// paints the <canvas> (which has no recoverable text content).
|
|
18
|
+
const directive = escapeAttr(`{{smiles ${smiles}}}`);
|
|
19
|
+
return `<div class="smiles-render" data-md="${directive}">\n <canvas data-smiles="${escapeAttr(smiles)}" width="250" height="180"></canvas>\n</div>\n`;
|
|
17
20
|
},
|
|
18
21
|
});
|
|
19
22
|
//# sourceMappingURL=smiles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smiles.js","sourceRoot":"","sources":["../../src/plugins/smiles.ts"],"names":[],"mappings":";;AAAA,gDAA0C;AAE1C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;IACzB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAClC,OAAO,
|
|
1
|
+
{"version":3,"file":"smiles.js","sourceRoot":"","sources":["../../src/plugins/smiles.ts"],"names":[],"mappings":";;AAAA,gDAA0C;AAE1C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;IACzB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAClC,yEAAyE;QACzE,+DAA+D;QAC/D,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,MAAM,IAAI,CAAC,CAAC;QACrD,OAAO,uCAAuC,SAAS,8BAA8B,UAAU,CAAC,MAAM,CAAC,gDAAgD,CAAC;IAC1J,CAAC;CACF,CAAC,CAAC"}
|
package/dist/plugins/youtube.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const registry_js_1 = require("../registry.js");
|
|
4
|
+
function escapeAttr(str) {
|
|
5
|
+
return str
|
|
6
|
+
.replace(/&/g, '&')
|
|
7
|
+
.replace(/"/g, '"')
|
|
8
|
+
.replace(/</g, '<')
|
|
9
|
+
.replace(/>/g, '>');
|
|
10
|
+
}
|
|
4
11
|
(0, registry_js_1.register)({
|
|
5
12
|
type: 'block',
|
|
6
13
|
aliases: ['youtube', 'yt'],
|
|
@@ -8,7 +15,8 @@ const registry_js_1 = require("../registry.js");
|
|
|
8
15
|
const id = body?.trim();
|
|
9
16
|
if (!id)
|
|
10
17
|
return '';
|
|
11
|
-
|
|
18
|
+
const directive = escapeAttr(`{{youtube ${id}}}`);
|
|
19
|
+
return (`<div class="youtube-embed" data-md="${directive}">\n` +
|
|
12
20
|
` <iframe src="https://www.youtube.com/embed/${id}"\n` +
|
|
13
21
|
` allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"\n` +
|
|
14
22
|
` referrerpolicy="strict-origin-when-cross-origin"\n` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"youtube.js","sourceRoot":"","sources":["../../src/plugins/youtube.ts"],"names":[],"mappings":";;AAAA,gDAA0C;AAE1C,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QACnB,OAAO,CACL
|
|
1
|
+
{"version":3,"file":"youtube.js","sourceRoot":"","sources":["../../src/plugins/youtube.ts"],"names":[],"mappings":";;AAAA,gDAA0C;AAE1C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,IAAA,sBAAQ,EAAC;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI;QACtB,MAAM,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAClD,OAAO,CACL,uCAAuC,SAAS,MAAM;YACtD,gDAAgD,EAAE,KAAK;YACvD,mHAAmH;YACnH,wDAAwD;YACxD,iCAAiC;YACjC,UAAU,CACX,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,QA8chC,CAAC;AAEF,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"}
|
package/dist/runtime.js
CHANGED
|
@@ -86,6 +86,345 @@ exports.browserRuntimeScript = String.raw `
|
|
|
86
86
|
initQrCodes(root || global.document);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// ---- copy-as-markdown: DOM -> Markdown walker ---------------------------
|
|
90
|
+
// Converts a selected DOM fragment back to Markdown so that copying rendered
|
|
91
|
+
// content yields source, not HTML. Standard constructs are reconstructed from
|
|
92
|
+
// their tags; generated constructs (math, mermaid, smiles, qr, youtube) carry
|
|
93
|
+
// a [data-md] breadcrumb that is emitted verbatim. Written without template
|
|
94
|
+
// literals/backticks because this whole file is a String.raw template.
|
|
95
|
+
var BT = String.fromCharCode(96);
|
|
96
|
+
var FENCE = BT + BT + BT;
|
|
97
|
+
|
|
98
|
+
function rt_repeat(s, n) {
|
|
99
|
+
var out = '';
|
|
100
|
+
for (var i = 0; i < n; i++) out += s;
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function rt_attr(node, name) {
|
|
105
|
+
return node && node.getAttribute ? node.getAttribute(name) : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function rt_isBlockElement(node) {
|
|
109
|
+
if (!node || node.nodeType !== 1) return false;
|
|
110
|
+
if (rt_attr(node, 'data-md') != null) return true;
|
|
111
|
+
var tag = node.tagName.toLowerCase();
|
|
112
|
+
if (/^(p|div|section|article|figure|h[1-6]|ul|ol|li|blockquote|pre|table|thead|tbody|tr|hr|details)$/.test(tag)) return true;
|
|
113
|
+
if (node.classList && (node.classList.contains('katex-display') || node.classList.contains('mermaid'))) return true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function rt_katex(node, display) {
|
|
118
|
+
var ann = node.querySelector ? node.querySelector('annotation[encoding="application/x-tex"]') : null;
|
|
119
|
+
var tex = (ann ? ann.textContent : node.textContent) || '';
|
|
120
|
+
tex = tex.replace(/^\s+|\s+$/g, '');
|
|
121
|
+
if (display) return '$$\n' + tex + '\n$$';
|
|
122
|
+
return '$' + tex + '$';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function rt_img(node) {
|
|
126
|
+
var alt = rt_attr(node, 'alt') || '';
|
|
127
|
+
var src = rt_attr(node, 'src') || '';
|
|
128
|
+
var title = rt_attr(node, 'title');
|
|
129
|
+
return ' + ')';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function rt_inlineChildren(node) {
|
|
133
|
+
var out = '';
|
|
134
|
+
var kids = node.childNodes;
|
|
135
|
+
for (var i = 0; i < kids.length; i++) out += rt_inlineNode(kids[i]);
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function rt_inlineNode(node) {
|
|
140
|
+
if (node.nodeType === 3) return node.nodeValue.replace(/\s+/g, ' ');
|
|
141
|
+
if (node.nodeType !== 1) return '';
|
|
142
|
+
var dm = rt_attr(node, 'data-md');
|
|
143
|
+
if (dm != null) return dm;
|
|
144
|
+
if (node.classList && node.classList.contains('katex')) return rt_katex(node, false);
|
|
145
|
+
var tag = node.tagName.toLowerCase();
|
|
146
|
+
switch (tag) {
|
|
147
|
+
case 'strong': case 'b': return '**' + rt_inlineChildren(node) + '**';
|
|
148
|
+
case 'em': case 'i': return '*' + rt_inlineChildren(node) + '*';
|
|
149
|
+
case 'code': return BT + (node.textContent || '') + BT;
|
|
150
|
+
case 'a':
|
|
151
|
+
var href = rt_attr(node, 'href') || '';
|
|
152
|
+
var title = rt_attr(node, 'title');
|
|
153
|
+
return '[' + rt_inlineChildren(node) + '](' + href + (title ? ' "' + title + '"' : '') + ')';
|
|
154
|
+
case 'del': case 's': return '~~' + rt_inlineChildren(node) + '~~';
|
|
155
|
+
case 'ins': return '++' + rt_inlineChildren(node) + '++';
|
|
156
|
+
case 'mark': return '==' + rt_inlineChildren(node) + '==';
|
|
157
|
+
case 'sub': return '~' + rt_inlineChildren(node) + '~';
|
|
158
|
+
case 'sup': return '^' + rt_inlineChildren(node) + '^';
|
|
159
|
+
case 'br': return ' \n';
|
|
160
|
+
case 'img': return rt_img(node);
|
|
161
|
+
case 'input': return '';
|
|
162
|
+
default: return rt_inlineChildren(node);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function rt_align(cell) {
|
|
167
|
+
var style = (rt_attr(cell, 'style') || '').toLowerCase();
|
|
168
|
+
if (style.indexOf('center') !== -1) return 'center';
|
|
169
|
+
if (style.indexOf('right') !== -1) return 'right';
|
|
170
|
+
if (style.indexOf('left') !== -1) return 'left';
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function rt_sep(align) {
|
|
175
|
+
if (align === 'center') return ':---:';
|
|
176
|
+
if (align === 'right') return '---:';
|
|
177
|
+
if (align === 'left') return ':---';
|
|
178
|
+
return '---';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function rt_tableRows(headRow, bodyRows) {
|
|
182
|
+
var rows = [];
|
|
183
|
+
var header = [];
|
|
184
|
+
var aligns = [];
|
|
185
|
+
if (headRow) {
|
|
186
|
+
var hc = headRow.children;
|
|
187
|
+
for (var i = 0; i < hc.length; i++) { header.push(rt_inlineChildren(hc[i]).trim()); aligns.push(rt_align(hc[i])); }
|
|
188
|
+
}
|
|
189
|
+
rows.push('| ' + header.join(' | ') + ' |');
|
|
190
|
+
var seps = [];
|
|
191
|
+
for (var s = 0; s < header.length; s++) seps.push(rt_sep(aligns[s]));
|
|
192
|
+
rows.push('| ' + seps.join(' | ') + ' |');
|
|
193
|
+
for (var r = 0; r < bodyRows.length; r++) {
|
|
194
|
+
var cells = [];
|
|
195
|
+
var tds = bodyRows[r].children;
|
|
196
|
+
for (var c = 0; c < tds.length; c++) cells.push(rt_inlineChildren(tds[c]).trim());
|
|
197
|
+
rows.push('| ' + cells.join(' | ') + ' |');
|
|
198
|
+
}
|
|
199
|
+
return rows.join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function rt_table(tbl) {
|
|
203
|
+
var headRow = tbl.querySelector ? tbl.querySelector('thead tr') : null;
|
|
204
|
+
var bodyRows = tbl.querySelectorAll ? tbl.querySelectorAll('tbody tr') : [];
|
|
205
|
+
return rt_tableRows(headRow, bodyRows);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Reconstruct a table from bare table-internal nodes (thead/tbody/tr), e.g.
|
|
209
|
+
// when a selection inside a table loses its <table> wrapper.
|
|
210
|
+
function rt_tableFromParts(parts) {
|
|
211
|
+
var headRow = null;
|
|
212
|
+
var bodyRows = [];
|
|
213
|
+
for (var i = 0; i < parts.length; i++) {
|
|
214
|
+
var p = parts[i];
|
|
215
|
+
var tag = p.tagName.toLowerCase();
|
|
216
|
+
if (tag === 'thead') { var tr = p.querySelector ? p.querySelector('tr') : null; if (tr && !headRow) headRow = tr; }
|
|
217
|
+
else if (tag === 'tbody') { var trs = p.querySelectorAll ? p.querySelectorAll('tr') : []; for (var j = 0; j < trs.length; j++) bodyRows.push(trs[j]); }
|
|
218
|
+
else if (tag === 'tr') { if (!headRow) headRow = p; else bodyRows.push(p); }
|
|
219
|
+
}
|
|
220
|
+
return rt_tableRows(headRow, bodyRows);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function rt_codeBlock(pre) {
|
|
224
|
+
var code = pre.querySelector ? (pre.querySelector('code') || pre) : pre;
|
|
225
|
+
var lang = '';
|
|
226
|
+
var cls = rt_attr(code, 'class') || '';
|
|
227
|
+
var m = cls.match(/language-([A-Za-z0-9_+#-]+)/);
|
|
228
|
+
if (m) lang = m[1];
|
|
229
|
+
var text = (code.textContent || '').replace(/\n$/, '');
|
|
230
|
+
return FENCE + lang + '\n' + text + '\n' + FENCE;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function rt_list(node, ordered, depth) {
|
|
234
|
+
var items = [];
|
|
235
|
+
var idx = 1;
|
|
236
|
+
var kids = node.childNodes;
|
|
237
|
+
for (var i = 0; i < kids.length; i++) {
|
|
238
|
+
var li = kids[i];
|
|
239
|
+
if (li.nodeType !== 1 || li.tagName.toLowerCase() !== 'li') continue;
|
|
240
|
+
items.push(rt_listItem(li, ordered, idx, depth));
|
|
241
|
+
idx++;
|
|
242
|
+
}
|
|
243
|
+
return items.join('\n');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function rt_listItem(li, ordered, idx, depth) {
|
|
247
|
+
var indent = rt_repeat(' ', depth);
|
|
248
|
+
var marker = ordered ? (idx + '. ') : '- ';
|
|
249
|
+
var task = '';
|
|
250
|
+
var cb = li.querySelector ? li.querySelector('input[type="checkbox"]') : null;
|
|
251
|
+
if (cb) task = (cb.checked || rt_attr(cb, 'checked') != null) ? '[x] ' : '[ ] ';
|
|
252
|
+
var inlineParts = '';
|
|
253
|
+
var nested = '';
|
|
254
|
+
var kids = li.childNodes;
|
|
255
|
+
for (var i = 0; i < kids.length; i++) {
|
|
256
|
+
var c = kids[i];
|
|
257
|
+
if (c.nodeType === 1 && /^(ul|ol)$/.test(c.tagName.toLowerCase())) {
|
|
258
|
+
nested += '\n' + rt_list(c, c.tagName.toLowerCase() === 'ol', depth + 1);
|
|
259
|
+
} else {
|
|
260
|
+
inlineParts += rt_inlineNode(c);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
inlineParts = inlineParts.replace(/\s+/g, ' ').trim();
|
|
264
|
+
return indent + marker + task + inlineParts + nested;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function rt_blockquote(node) {
|
|
268
|
+
var inner = rt_blockChildren(node);
|
|
269
|
+
var lines = inner.split('\n');
|
|
270
|
+
var out = [];
|
|
271
|
+
for (var i = 0; i < lines.length; i++) out.push(lines[i] ? '> ' + lines[i] : '>');
|
|
272
|
+
return out.join('\n');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function rt_blockChildren(node) {
|
|
276
|
+
var parts = [];
|
|
277
|
+
var kids = node.childNodes;
|
|
278
|
+
var i = 0;
|
|
279
|
+
while (i < kids.length) {
|
|
280
|
+
var k = kids[i];
|
|
281
|
+
// A selection can contain bare <li> elements (when the user selects a
|
|
282
|
+
// list's contents rather than the <ul>/<ol> wrapper). Group a run of them
|
|
283
|
+
// into a bullet list instead of emitting each as a separate block.
|
|
284
|
+
if (k.nodeType === 1 && k.tagName.toLowerCase() === 'li') {
|
|
285
|
+
var lis = [];
|
|
286
|
+
while (i < kids.length) {
|
|
287
|
+
var kk = kids[i];
|
|
288
|
+
if (kk.nodeType === 1 && kk.tagName.toLowerCase() === 'li') { lis.push(kk); i++; }
|
|
289
|
+
else if (kk.nodeType === 3 && !(kk.nodeValue || '').trim()) { i++; }
|
|
290
|
+
else break;
|
|
291
|
+
}
|
|
292
|
+
var items = [];
|
|
293
|
+
for (var j = 0; j < lis.length; j++) items.push(rt_listItem(lis[j], false, j + 1, 0));
|
|
294
|
+
parts.push(items.join('\n'));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
// Bare table-internal nodes (selection inside a table that lost its
|
|
298
|
+
// <table> wrapper) — reconstruct a Markdown table.
|
|
299
|
+
if (k.nodeType === 1 && /^(thead|tbody|tr)$/.test(k.tagName.toLowerCase())) {
|
|
300
|
+
var trows = [];
|
|
301
|
+
while (i < kids.length) {
|
|
302
|
+
var tk = kids[i];
|
|
303
|
+
if (tk.nodeType === 1 && /^(thead|tbody|tr)$/.test(tk.tagName.toLowerCase())) { trows.push(tk); i++; }
|
|
304
|
+
else if (tk.nodeType === 3 && !(tk.nodeValue || '').trim()) { i++; }
|
|
305
|
+
else break;
|
|
306
|
+
}
|
|
307
|
+
parts.push(rt_tableFromParts(trows));
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
var s = rt_blockNode(k);
|
|
311
|
+
if (s && s.replace(/\s/g, '') !== '') parts.push(s.replace(/\s+$/, ''));
|
|
312
|
+
i++;
|
|
313
|
+
}
|
|
314
|
+
return parts.join('\n\n');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function rt_blockNode(node) {
|
|
318
|
+
if (node.nodeType === 3) {
|
|
319
|
+
var t = node.nodeValue;
|
|
320
|
+
return (t && t.trim()) ? t.replace(/\s+/g, ' ').trim() : '';
|
|
321
|
+
}
|
|
322
|
+
if (node.nodeType !== 1) return '';
|
|
323
|
+
var dm = rt_attr(node, 'data-md');
|
|
324
|
+
if (dm != null) return dm;
|
|
325
|
+
if (node.classList) {
|
|
326
|
+
if (node.classList.contains('katex-display')) {
|
|
327
|
+
var k = node.querySelector ? node.querySelector('.katex') : null;
|
|
328
|
+
return rt_katex(k || node, true);
|
|
329
|
+
}
|
|
330
|
+
if (node.classList.contains('mermaid')) return '{{mermaid\n' + (node.textContent || '').trim() + '\n}}';
|
|
331
|
+
}
|
|
332
|
+
var tag = node.tagName.toLowerCase();
|
|
333
|
+
var hm = tag.match(/^h([1-6])$/);
|
|
334
|
+
if (hm) return rt_repeat('#', parseInt(hm[1], 10)) + ' ' + rt_inlineChildren(node).trim();
|
|
335
|
+
switch (tag) {
|
|
336
|
+
case 'p': return rt_inlineChildren(node).trim();
|
|
337
|
+
case 'ul': return rt_list(node, false, 0);
|
|
338
|
+
case 'ol': return rt_list(node, true, 0);
|
|
339
|
+
case 'pre': return rt_codeBlock(node);
|
|
340
|
+
case 'blockquote': return rt_blockquote(node);
|
|
341
|
+
case 'table': return rt_table(node);
|
|
342
|
+
case 'hr': return '---';
|
|
343
|
+
case 'li': return rt_inlineChildren(node).trim();
|
|
344
|
+
case 'figure': case 'div': case 'section': case 'article': case 'details': case 'summary':
|
|
345
|
+
return rt_blockChildren(node);
|
|
346
|
+
default: return rt_inlineNode(node).trim();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function elementToMarkdown(root) {
|
|
351
|
+
if (!root) return '';
|
|
352
|
+
var hasBlock = false;
|
|
353
|
+
var kids = root.childNodes || [];
|
|
354
|
+
for (var i = 0; i < kids.length; i++) {
|
|
355
|
+
if (rt_isBlockElement(kids[i])) { hasBlock = true; break; }
|
|
356
|
+
}
|
|
357
|
+
var out = hasBlock ? rt_blockChildren(root) : rt_inlineChildren(root);
|
|
358
|
+
return out.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').replace(/^\s+|\s+$/g, '');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function rt_isEditable(node) {
|
|
362
|
+
while (node) {
|
|
363
|
+
if (node.nodeType === 1) {
|
|
364
|
+
var tag = node.tagName.toLowerCase();
|
|
365
|
+
if (tag === 'input' || tag === 'textarea') return true;
|
|
366
|
+
if (node.isContentEditable) return true;
|
|
367
|
+
}
|
|
368
|
+
node = node.parentNode;
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function rt_isCopyRoot(node) {
|
|
374
|
+
return node.nodeType === 1 && node.classList &&
|
|
375
|
+
(node.classList.contains('markdown-body') || rt_attr(node, 'data-orz-copy') != null);
|
|
376
|
+
}
|
|
377
|
+
function rt_withinCopyRoot(node) {
|
|
378
|
+
while (node) {
|
|
379
|
+
if (rt_isCopyRoot(node)) return true;
|
|
380
|
+
node = node.parentNode;
|
|
381
|
+
}
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// When a selection sits entirely within one table/blockquote/pre, copy that
|
|
386
|
+
// whole block: a partial table/quote/code fragment isn't valid Markdown, and
|
|
387
|
+
// browsers often clone such selections without the wrapping element.
|
|
388
|
+
function rt_promotableBlock(node) {
|
|
389
|
+
while (node && !rt_isCopyRoot(node)) {
|
|
390
|
+
if (node.nodeType === 1) {
|
|
391
|
+
var tag = node.tagName.toLowerCase();
|
|
392
|
+
if (tag === 'table' || tag === 'blockquote' || tag === 'pre') return node;
|
|
393
|
+
}
|
|
394
|
+
node = node.parentNode;
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function onCopy(event) {
|
|
400
|
+
if (!global.document) return;
|
|
401
|
+
var sel = global.getSelection ? global.getSelection()
|
|
402
|
+
: (global.document.getSelection ? global.document.getSelection() : null);
|
|
403
|
+
if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
|
|
404
|
+
if (rt_isEditable(sel.anchorNode) || rt_isEditable(sel.focusNode)) return;
|
|
405
|
+
if (!rt_withinCopyRoot(sel.anchorNode) && !rt_withinCopyRoot(sel.focusNode)) return;
|
|
406
|
+
|
|
407
|
+
var md;
|
|
408
|
+
var range0 = sel.getRangeAt(0);
|
|
409
|
+
var common = range0.commonAncestorContainer;
|
|
410
|
+
var promoted = rt_promotableBlock(common.nodeType === 1 ? common : common.parentNode);
|
|
411
|
+
if (promoted && sel.rangeCount === 1) {
|
|
412
|
+
var pwrap = global.document.createElement('div');
|
|
413
|
+
pwrap.appendChild(promoted.cloneNode(true));
|
|
414
|
+
md = elementToMarkdown(pwrap);
|
|
415
|
+
} else {
|
|
416
|
+
var container = global.document.createElement('div');
|
|
417
|
+
for (var i = 0; i < sel.rangeCount; i++) container.appendChild(sel.getRangeAt(i).cloneContents());
|
|
418
|
+
md = elementToMarkdown(container);
|
|
419
|
+
}
|
|
420
|
+
if (!md) return;
|
|
421
|
+
var cd = event.clipboardData || global.clipboardData;
|
|
422
|
+
if (cd && cd.setData) {
|
|
423
|
+
cd.setData('text/plain', md);
|
|
424
|
+
if (event.preventDefault) event.preventDefault();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
89
428
|
function bindGlobalHandlers() {
|
|
90
429
|
if (!global.document || global.__orzMarkdownRuntimeBound) return;
|
|
91
430
|
global.__orzMarkdownRuntimeBound = true;
|
|
@@ -101,12 +440,16 @@ exports.browserRuntimeScript = String.raw `
|
|
|
101
440
|
collapseQr();
|
|
102
441
|
}
|
|
103
442
|
});
|
|
443
|
+
|
|
444
|
+
// Copy rendered selections as Markdown source.
|
|
445
|
+
global.document.addEventListener('copy', onCopy);
|
|
104
446
|
}
|
|
105
447
|
|
|
106
448
|
global.OrzMarkdownRuntime = Object.assign({}, global.OrzMarkdownRuntime, {
|
|
107
449
|
init: init,
|
|
108
450
|
initQrCodes: initQrCodes,
|
|
109
451
|
collapseQr: collapseQr,
|
|
452
|
+
elementToMarkdown: elementToMarkdown,
|
|
110
453
|
});
|
|
111
454
|
|
|
112
455
|
bindGlobalHandlers();
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":";;;AAgdA,0DAEC;AAldY,QAAA,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8c7C,CAAC;AAEF,SAAgB,uBAAuB;IACrC,OAAO,4BAAoB,CAAC;AAC9B,CAAC"}
|
|
@@ -67,6 +67,10 @@ document.body.appendChild(script);
|
|
|
67
67
|
// or call directly: window.OrzMarkdownRuntime.init(rootElement)
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
The runtime also provides **copy-as-Markdown**: with it loaded, copying a selection inside `.markdown-body` puts Markdown source on the clipboard, not HTML (tables, lists, math, code, etc. are reconstructed). It skips selections inside `<input>`/`<textarea>`/`contenteditable`. Convert a node directly with `window.OrzMarkdownRuntime.elementToMarkdown(node)`.
|
|
71
|
+
|
|
72
|
+
> **Do not strip `data-md` attributes.** `mermaid`, `smiles`, `qrcode`, and `youtube` output carry a `data-md` breadcrumb so copy recovers their source after client-side rendering (e.g. a copied QR yields `{{qr ...}}`, not its SVG). Preserve these attributes if you post-process the HTML.
|
|
73
|
+
|
|
70
74
|
---
|
|
71
75
|
|
|
72
76
|
## Themes
|
|
@@ -94,8 +94,9 @@ if (typeof SmilesDrawer !== 'undefined') {
|
|
|
94
94
|
})();
|
|
95
95
|
</script>
|
|
96
96
|
|
|
97
|
-
<!--
|
|
98
|
-
|
|
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). -->
|
|
99
100
|
<script>
|
|
100
101
|
(function (global) {
|
|
101
102
|
var expandedQr = null;
|
|
@@ -103,22 +104,38 @@ if (typeof SmilesDrawer !== 'undefined') {
|
|
|
103
104
|
|
|
104
105
|
function collapseQr() {
|
|
105
106
|
if (!expandedQr) return;
|
|
107
|
+
|
|
106
108
|
if (expandedSourceQr) {
|
|
107
109
|
expandedSourceQr.classList.remove('is-expanded');
|
|
108
110
|
expandedSourceQr.setAttribute('aria-expanded', 'false');
|
|
109
111
|
}
|
|
110
|
-
|
|
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
|
+
}
|
|
111
120
|
expandedQr = null;
|
|
112
121
|
expandedSourceQr = null;
|
|
113
|
-
if (global.document && global.document.body) global.document.body.style.overflow = '';
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
function toggleQr(node) {
|
|
117
|
-
if (expandedSourceQr === node) {
|
|
118
|
-
|
|
125
|
+
if (expandedSourceQr === node) {
|
|
126
|
+
collapseQr();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
collapseQr();
|
|
131
|
+
if (!global.document || !global.document.body) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
expandedSourceQr = node;
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
expandedSourceQr.classList.add('is-expanded');
|
|
137
|
+
expandedSourceQr.setAttribute('aria-expanded', 'true');
|
|
138
|
+
|
|
122
139
|
expandedQr = node.cloneNode(true);
|
|
123
140
|
expandedQr.classList.add('qrcode-overlay', 'is-expanded');
|
|
124
141
|
expandedQr.setAttribute('aria-hidden', 'true');
|
|
@@ -129,18 +146,21 @@ if (typeof SmilesDrawer !== 'undefined') {
|
|
|
129
146
|
event.stopPropagation();
|
|
130
147
|
collapseQr();
|
|
131
148
|
});
|
|
149
|
+
|
|
132
150
|
global.document.body.appendChild(expandedQr);
|
|
133
|
-
|
|
151
|
+
|
|
152
|
+
if (global.document && global.document.body) {
|
|
153
|
+
global.document.body.style.overflow = 'hidden';
|
|
154
|
+
}
|
|
134
155
|
}
|
|
135
156
|
|
|
136
157
|
function initQrCodes(root) {
|
|
137
158
|
if (!root || typeof root.querySelectorAll !== 'function') return;
|
|
159
|
+
|
|
138
160
|
Array.prototype.slice.call(root.querySelectorAll('.qrcode')).forEach(function (node) {
|
|
139
161
|
if (node.getAttribute('data-qr-ready') === '1') return;
|
|
162
|
+
|
|
140
163
|
node.setAttribute('data-qr-ready', '1');
|
|
141
|
-
node.setAttribute('tabindex', '0');
|
|
142
|
-
node.setAttribute('role', 'button');
|
|
143
|
-
node.setAttribute('aria-expanded', 'false');
|
|
144
164
|
node.addEventListener('click', function (event) {
|
|
145
165
|
event.stopPropagation();
|
|
146
166
|
toggleQr(node);
|
|
@@ -150,27 +170,330 @@ if (typeof SmilesDrawer !== 'undefined') {
|
|
|
150
170
|
event.preventDefault();
|
|
151
171
|
toggleQr(node);
|
|
152
172
|
}
|
|
173
|
+
if (event.key === 'Escape') {
|
|
174
|
+
collapseQr();
|
|
175
|
+
}
|
|
153
176
|
});
|
|
154
177
|
});
|
|
155
178
|
}
|
|
156
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
|
+
|
|
157
463
|
function bindGlobalHandlers() {
|
|
158
|
-
global.document
|
|
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
|
+
|
|
159
473
|
global.document.addEventListener('keydown', function (event) {
|
|
160
|
-
if (event.key === 'Escape')
|
|
474
|
+
if (event.key === 'Escape') {
|
|
475
|
+
collapseQr();
|
|
476
|
+
}
|
|
161
477
|
});
|
|
162
|
-
}
|
|
163
478
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
bindGlobalHandlers();
|
|
479
|
+
// Copy rendered selections as Markdown source.
|
|
480
|
+
global.document.addEventListener('copy', onCopy);
|
|
167
481
|
}
|
|
168
482
|
|
|
169
|
-
global.OrzMarkdownRuntime = {
|
|
483
|
+
global.OrzMarkdownRuntime = Object.assign({}, global.OrzMarkdownRuntime, {
|
|
484
|
+
init: init,
|
|
485
|
+
initQrCodes: initQrCodes,
|
|
486
|
+
collapseQr: collapseQr,
|
|
487
|
+
elementToMarkdown: elementToMarkdown,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
bindGlobalHandlers();
|
|
170
491
|
|
|
171
492
|
if (global.document) {
|
|
172
493
|
if (global.document.readyState === 'loading') {
|
|
173
|
-
global.document.addEventListener('DOMContentLoaded', function () {
|
|
494
|
+
global.document.addEventListener('DOMContentLoaded', function () {
|
|
495
|
+
init(global.document);
|
|
496
|
+
}, { once: true });
|
|
174
497
|
} else {
|
|
175
498
|
init(global.document);
|
|
176
499
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orz-markdown",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Customized markdown-it parser with official and custom plugins",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"@types/markdown-it-footnote": "^3.0.4",
|
|
65
65
|
"@types/node": "^25.3.5",
|
|
66
66
|
"@types/qrcode-svg": "^1.1.5",
|
|
67
|
+
"happy-dom": "^15.11.0",
|
|
67
68
|
"tsx": "^4.21.0",
|
|
68
69
|
"typescript": "^5.9.3",
|
|
69
70
|
"vitest": "^4.0.18"
|