orz-mdhtml 0.1.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 +113 -0
- package/assets/app.js +555 -0
- package/dist/browser-entry.js +34 -0
- package/dist/cli.js +158 -0
- package/dist/orzmd.browser.js +326 -0
- package/dist/template.js +263 -0
- package/orz-mdhtml-skills/SKILL.md +90 -0
- package/package.json +41 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser entry for the orz-markdown renderer.
|
|
3
|
+
*
|
|
4
|
+
* esbuild bundles this (with orz-markdown + its deps) into a single IIFE,
|
|
5
|
+
* `dist/orzmd.browser.js`, which exposes `window.orzmd.render(src)`.
|
|
6
|
+
*
|
|
7
|
+
* Two consumers:
|
|
8
|
+
* 1. `--inline` generation embeds this bundle directly in each .md.html.
|
|
9
|
+
* 2. `--cdn` generation references a published copy on jsDelivr
|
|
10
|
+
* (package `orz-mdhtml-browser`) so files stay small and the bundle
|
|
11
|
+
* is browser-cached across documents.
|
|
12
|
+
*/
|
|
13
|
+
import { md } from 'orz-markdown';
|
|
14
|
+
// Stamp every block element with its source line (`data-src-line`) so the
|
|
15
|
+
// editor and preview can be scroll-synced. Uses markdown-it's token.map; only
|
|
16
|
+
// affects this browser bundle's output (not orz-markdown core), and the
|
|
17
|
+
// copy-as-markdown walker ignores the attribute.
|
|
18
|
+
md.core.ruler.push('orz_src_line', function (state) {
|
|
19
|
+
const tokens = state.tokens;
|
|
20
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
21
|
+
const t = tokens[i];
|
|
22
|
+
if (t.map && t.block && t.nesting !== -1 && !t.hidden) {
|
|
23
|
+
t.attrSet('data-src-line', String(t.map[0]));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
});
|
|
28
|
+
window.orzmd = {
|
|
29
|
+
version: typeof __ORZMD_VERSION__ !== 'undefined' ? __ORZMD_VERSION__ : '0.0.0',
|
|
30
|
+
render(source) {
|
|
31
|
+
// markdownBasePath is omitted: filesystem includes don't resolve in-browser.
|
|
32
|
+
return md.render(source);
|
|
33
|
+
},
|
|
34
|
+
};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* orz-mdhtml — generate a self-contained, editable .md.html from a .md file.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* orz-mdhtml <input.md> [options]
|
|
7
|
+
*
|
|
8
|
+
* Options:
|
|
9
|
+
* -o, --out <file> output path (default: <input>.md.html)
|
|
10
|
+
* --theme <name> orz-markdown theme (default: light-academic-1)
|
|
11
|
+
* --cdn reference the renderer from jsDelivr (default; small files)
|
|
12
|
+
* --inline embed the renderer bundle in the file (larger, no renderer fetch)
|
|
13
|
+
* --title <text> document <title> (default: input filename)
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
16
|
+
import { basename, extname, dirname, resolve, join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
20
|
+
import { getBrowserRuntimeScript } from 'orz-markdown/runtime';
|
|
21
|
+
import { buildHtml } from './template.js';
|
|
22
|
+
/** orz-markdown's bundled themes (CDN-loaded), with light/dark scheme. */
|
|
23
|
+
const THEME_DEFS = [
|
|
24
|
+
{ id: 'light-academic-1', name: 'Academic I', scheme: 'light' },
|
|
25
|
+
{ id: 'light-academic-2', name: 'Academic II', scheme: 'light' },
|
|
26
|
+
{ id: 'light-neat-1', name: 'Neat I', scheme: 'light' },
|
|
27
|
+
{ id: 'light-neat-2', name: 'Neat II', scheme: 'light' },
|
|
28
|
+
{ id: 'light-playful-1', name: 'Playful I', scheme: 'light' },
|
|
29
|
+
{ id: 'light-playful-2', name: 'Playful II', scheme: 'light' },
|
|
30
|
+
{ id: 'beige-decent-1', name: 'Decent I', scheme: 'light' },
|
|
31
|
+
{ id: 'beige-decent-2', name: 'Decent II', scheme: 'light' },
|
|
32
|
+
{ id: 'dark-elegant-1', name: 'Elegant I', scheme: 'dark' },
|
|
33
|
+
{ id: 'dark-elegant-2', name: 'Elegant II', scheme: 'dark' },
|
|
34
|
+
];
|
|
35
|
+
const CDN = {
|
|
36
|
+
hl: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0',
|
|
37
|
+
cm: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16',
|
|
38
|
+
};
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
/** orz-markdown's `exports` hides ./package.json, so find it by walking up. */
|
|
41
|
+
function orzVersionOf() {
|
|
42
|
+
let dir = dirname(require.resolve('orz-markdown'));
|
|
43
|
+
while (!existsSync(join(dir, 'package.json')))
|
|
44
|
+
dir = dirname(dir);
|
|
45
|
+
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).version;
|
|
46
|
+
}
|
|
47
|
+
const orzVersion = orzVersionOf();
|
|
48
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
/** orz-mdhtml's own version — pins the renderer bundle + version check. */
|
|
50
|
+
function selfVersionOf() {
|
|
51
|
+
for (const p of [join(HERE, '..', 'package.json'), join(HERE, '..', '..', 'package.json')]) {
|
|
52
|
+
try {
|
|
53
|
+
const j = JSON.parse(readFileSync(p, 'utf8'));
|
|
54
|
+
if (j.name === 'orz-mdhtml' && j.version)
|
|
55
|
+
return j.version;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* keep looking */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return '0.0.0';
|
|
62
|
+
}
|
|
63
|
+
const selfVersion = selfVersionOf();
|
|
64
|
+
// assets/ sits next to dist/ when published, and next to src/ in dev.
|
|
65
|
+
function findAsset(name) {
|
|
66
|
+
for (const p of [join(HERE, '..', 'assets', name), join(HERE, '..', '..', 'assets', name)]) {
|
|
67
|
+
if (existsSync(p))
|
|
68
|
+
return p;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`asset not found: ${name}`);
|
|
71
|
+
}
|
|
72
|
+
function parseArgs(argv) {
|
|
73
|
+
const a = { theme: 'light-academic-1', delivery: 'cdn' };
|
|
74
|
+
for (let i = 0; i < argv.length; i++) {
|
|
75
|
+
const arg = argv[i];
|
|
76
|
+
if (arg === '-o' || arg === '--out')
|
|
77
|
+
a.out = argv[++i];
|
|
78
|
+
else if (arg === '--theme')
|
|
79
|
+
a.theme = argv[++i];
|
|
80
|
+
else if (arg === '--inline')
|
|
81
|
+
a.delivery = 'inline';
|
|
82
|
+
else if (arg === '--cdn')
|
|
83
|
+
a.delivery = 'cdn';
|
|
84
|
+
else if (arg === '--title')
|
|
85
|
+
a.title = argv[++i];
|
|
86
|
+
else if (!arg.startsWith('-'))
|
|
87
|
+
a.input = arg;
|
|
88
|
+
}
|
|
89
|
+
return a;
|
|
90
|
+
}
|
|
91
|
+
function main() {
|
|
92
|
+
const args = parseArgs(process.argv.slice(2));
|
|
93
|
+
if (!args.input) {
|
|
94
|
+
console.error('Usage: orz-mdhtml <input.md> [-o out] [--theme name] [--inline|--cdn]');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const inputPath = resolve(args.input);
|
|
98
|
+
const source = readFileSync(inputPath, 'utf8');
|
|
99
|
+
const base = basename(inputPath, extname(inputPath));
|
|
100
|
+
const outPath = args.out ? resolve(args.out) : join(dirname(inputPath), `${base}.md.html`);
|
|
101
|
+
const appJs = readFileSync(findAsset('app.js'), 'utf8');
|
|
102
|
+
const themeBase = `https://cdn.jsdelivr.net/npm/orz-markdown@${orzVersion}/themes`;
|
|
103
|
+
const themes = THEME_DEFS.map((t) => ({ ...t, href: `${themeBase}/${t.id}.css` }));
|
|
104
|
+
const defaultTheme = themes.some((t) => t.id === args.theme) ? args.theme : themes[0].id;
|
|
105
|
+
// Renderer delivery.
|
|
106
|
+
let renderer;
|
|
107
|
+
if (args.delivery === 'inline') {
|
|
108
|
+
// dist/cli.js (published) → sibling; src/cli.ts (dev) → ../dist.
|
|
109
|
+
const bundlePath = [
|
|
110
|
+
join(HERE, 'orzmd.browser.js'),
|
|
111
|
+
join(HERE, '..', 'dist', 'orzmd.browser.js'),
|
|
112
|
+
].find(existsSync);
|
|
113
|
+
if (!bundlePath) {
|
|
114
|
+
console.error('Inline mode needs the browser bundle. Run: npm run bundle');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
renderer = { mode: 'inline', js: readFileSync(bundlePath, 'utf8') };
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
renderer = {
|
|
121
|
+
mode: 'cdn',
|
|
122
|
+
src: `https://cdn.jsdelivr.net/npm/orz-mdhtml-browser@${selfVersion}/orzmd.browser.js`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const html = buildHtml({
|
|
126
|
+
source,
|
|
127
|
+
title: args.title ?? base,
|
|
128
|
+
filename: base,
|
|
129
|
+
docId: randomUUID(),
|
|
130
|
+
rendererVersion: selfVersion,
|
|
131
|
+
appJs,
|
|
132
|
+
runtimeScript: getBrowserRuntimeScript(),
|
|
133
|
+
renderer,
|
|
134
|
+
versionManifest: 'https://data.jsdelivr.com/v1/packages/npm/orz-mdhtml-browser/resolved',
|
|
135
|
+
defaultTheme,
|
|
136
|
+
themes,
|
|
137
|
+
frame: {
|
|
138
|
+
katexCss: 'https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css',
|
|
139
|
+
hljsLightCss: `${CDN.hl}/styles/github.min.css`,
|
|
140
|
+
hljsDarkCss: `${CDN.hl}/styles/atom-one-dark.min.css`,
|
|
141
|
+
hljsJs: `${CDN.hl}/highlight.min.js`,
|
|
142
|
+
mermaidJs: 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js',
|
|
143
|
+
},
|
|
144
|
+
editorLibs: {
|
|
145
|
+
codemirrorCss: `${CDN.cm}/codemirror.min.css`,
|
|
146
|
+
codemirrorLightThemeCss: `${CDN.cm}/theme/eclipse.min.css`,
|
|
147
|
+
codemirrorDarkThemeCss: `${CDN.cm}/theme/material-darker.min.css`,
|
|
148
|
+
codemirrorJs: `${CDN.cm}/codemirror.min.js`,
|
|
149
|
+
codemirrorMarkdownJs: `${CDN.cm}/mode/markdown/markdown.min.js`,
|
|
150
|
+
codemirrorContinuelistJs: `${CDN.cm}/addon/edit/continuelist.min.js`,
|
|
151
|
+
splitJs: 'https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.5/split.min.js',
|
|
152
|
+
morphdomJs: 'https://cdn.jsdelivr.net/npm/morphdom@2.7.4/dist/morphdom-umd.min.js',
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
writeFileSync(outPath, html, 'utf8');
|
|
156
|
+
console.log(`Wrote ${outPath} (${args.delivery}, theme: ${defaultTheme})`);
|
|
157
|
+
}
|
|
158
|
+
main();
|