orz-paged 0.2.0 → 0.4.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/dist/browser-entry.d.ts +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +31 -68
- package/dist/doc/dynamic.d.ts +31 -0
- package/dist/doc/elements.d.ts +16 -0
- package/dist/doc/fonts.d.ts +25 -0
- package/dist/doc/nyml.d.ts +33 -0
- package/dist/doc/page-css.d.ts +15 -0
- package/dist/doc/settings.d.ts +35 -0
- package/dist/doc/templates.d.ts +49 -0
- package/dist/doc/theme-fonts.d.ts +12 -0
- package/dist/lib.d.ts +62 -0
- package/dist/lib.js +143 -0
- package/dist/orz-paged.browser.js +90 -94
- package/dist/paged.d.ts +9 -0
- package/dist/render-paged.d.ts +21 -0
- package/dist/template.d.ts +60 -0
- package/dist/types.d.ts +144 -0
- package/package.json +10 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -8,57 +8,11 @@
|
|
|
8
8
|
* references the published `orz-paged-browser` on jsDelivr. Fonts, images, and the
|
|
9
9
|
* math/diagram libs always load from CDN at view time (DESIGN §4).
|
|
10
10
|
*/
|
|
11
|
-
import { readFileSync, writeFileSync
|
|
11
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { dirname, join, basename, resolve } from 'node:path';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
14
13
|
import { getBrowserRuntimeScript } from 'orz-markdown/runtime';
|
|
15
|
-
import { PREVIEW_CDN } from 'orz-markdown/preview-frame';
|
|
16
14
|
import { buildHtml } from './template.js';
|
|
17
|
-
import {
|
|
18
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const ROOT = join(HERE, '..');
|
|
20
|
-
const selfVersion = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version || '0.0.0';
|
|
21
|
-
/** Pinned CDN assets used at view time — sourced from orz-markdown's shared
|
|
22
|
-
* preview-frame helper so every host app loads identical, tested versions.
|
|
23
|
-
* Paged is light-only, so highlight.js uses the light (github) stylesheet. */
|
|
24
|
-
const CDN = {
|
|
25
|
-
katexCss: PREVIEW_CDN.katexCss,
|
|
26
|
-
mermaidJs: PREVIEW_CDN.mermaidJs,
|
|
27
|
-
smilesJs: PREVIEW_CDN.smilesJs,
|
|
28
|
-
chartJs: PREVIEW_CDN.chartJs,
|
|
29
|
-
hljsJs: PREVIEW_CDN.hljsJs,
|
|
30
|
-
hljsCss: PREVIEW_CDN.hljsLightCss,
|
|
31
|
-
};
|
|
32
|
-
function findAsset(rel) {
|
|
33
|
-
const p = join(ROOT, rel);
|
|
34
|
-
if (!existsSync(p))
|
|
35
|
-
throw new Error('missing asset: ' + rel + ' (run `npm run build` first)');
|
|
36
|
-
return p;
|
|
37
|
-
}
|
|
38
|
-
/** Drop a theme's `@import url("./base.css")` — base is inlined separately, so the
|
|
39
|
-
* relative import points nowhere (404 on http; a fetch hazard on file://). */
|
|
40
|
-
function stripBaseImport(css) {
|
|
41
|
-
return css.replace(/@import\s+url\(\s*['"]?\.\/base\.css['"]?\s*\)\s*;?/gi, '');
|
|
42
|
-
}
|
|
43
|
-
function loadThemes() {
|
|
44
|
-
const dir = findAsset('assets/themes');
|
|
45
|
-
const baseCss = readFileSync(join(dir, 'base.css'), 'utf8');
|
|
46
|
-
const themes = readdirSync(dir)
|
|
47
|
-
.filter((f) => f.endsWith('.css') && f !== 'base.css')
|
|
48
|
-
.map((f) => ({ id: f.replace(/\.css$/, ''), css: stripBaseImport(readFileSync(join(dir, f), 'utf8')) }));
|
|
49
|
-
return { baseCss, themes };
|
|
50
|
-
}
|
|
51
|
-
/** Load every template's starter skeleton from `assets/templates/<stem>.md`. */
|
|
52
|
-
function loadTemplates() {
|
|
53
|
-
const dir = findAsset('assets/templates');
|
|
54
|
-
return templateList().map((t) => ({
|
|
55
|
-
id: t.id,
|
|
56
|
-
label: t.label,
|
|
57
|
-
description: t.description,
|
|
58
|
-
group: t.group,
|
|
59
|
-
skeleton: readFileSync(join(dir, t.skeleton + '.md'), 'utf8'),
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
15
|
+
import { selfVersion, CDN, findAsset, loadThemes, loadTemplates, pickDefaultTheme, composeInlineHtml, } from './lib.js';
|
|
62
16
|
function parseArgs(argv) {
|
|
63
17
|
const a = { mode: 'inline' };
|
|
64
18
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -138,30 +92,39 @@ function main() {
|
|
|
138
92
|
const inDir = args.input ? dirname(resolve(args.input)) : process.cwd();
|
|
139
93
|
const outPath = args.out ? resolve(args.out) : join(inDir, base + '.paged.html');
|
|
140
94
|
const title = args.title || base;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const runtime = getBrowserRuntimeScript(); // copy-as-Markdown (+ qr) from orz-markdown
|
|
144
|
-
let renderer;
|
|
95
|
+
let html;
|
|
96
|
+
let themeCount;
|
|
145
97
|
if (args.mode === 'cdn') {
|
|
146
|
-
|
|
98
|
+
// CDN mode references the published engine on jsDelivr (not inlined), so it
|
|
99
|
+
// has its own composition; the default inline path shares composeInlineHtml.
|
|
100
|
+
const { baseCss, themes } = loadThemes();
|
|
101
|
+
const appJs = readFileSync(findAsset('assets/app.js'), 'utf8');
|
|
102
|
+
const runtime = getBrowserRuntimeScript(); // copy-as-Markdown (+ qr) from orz-markdown
|
|
103
|
+
const renderer = {
|
|
104
|
+
mode: 'cdn',
|
|
105
|
+
src: `https://cdn.jsdelivr.net/npm/orz-paged-browser@${selfVersion}/orz-paged.browser.js`,
|
|
106
|
+
};
|
|
107
|
+
themeCount = themes.length;
|
|
108
|
+
html = buildHtml({
|
|
109
|
+
source,
|
|
110
|
+
title,
|
|
111
|
+
rendererVersion: selfVersion,
|
|
112
|
+
renderer,
|
|
113
|
+
baseCss,
|
|
114
|
+
themes,
|
|
115
|
+
defaultTheme: pickDefaultTheme(themes),
|
|
116
|
+
templates,
|
|
117
|
+
cdn: CDN,
|
|
118
|
+
appJs,
|
|
119
|
+
runtime,
|
|
120
|
+
});
|
|
147
121
|
}
|
|
148
122
|
else {
|
|
149
|
-
|
|
123
|
+
// Default: fully-inline, via the shared composition used by the library.
|
|
124
|
+
themeCount = loadThemes().themes.length;
|
|
125
|
+
html = composeInlineHtml(source, title);
|
|
150
126
|
}
|
|
151
|
-
const html = buildHtml({
|
|
152
|
-
source,
|
|
153
|
-
title,
|
|
154
|
-
rendererVersion: selfVersion,
|
|
155
|
-
renderer,
|
|
156
|
-
baseCss,
|
|
157
|
-
themes,
|
|
158
|
-
defaultTheme: themes.some((t) => t.id === 'light-neat-1') ? 'light-neat-1' : (themes[0]?.id ?? 'none'),
|
|
159
|
-
templates,
|
|
160
|
-
cdn: CDN,
|
|
161
|
-
appJs,
|
|
162
|
-
runtime,
|
|
163
|
-
});
|
|
164
127
|
writeFileSync(outPath, html, 'utf8');
|
|
165
|
-
console.log(`Wrote ${outPath} (${args.mode}, ${
|
|
128
|
+
console.log(`Wrote ${outPath} (${args.mode}, ${themeCount} themes)`);
|
|
166
129
|
}
|
|
167
130
|
main();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic switch — conditional content driven by the `dynamic_choices` document
|
|
3
|
+
* setting. Elements carry `data-show-when="key=value"` / `data-hide-when="key=value"`;
|
|
4
|
+
* at render time non-matching ones are removed and the attribute is stripped from
|
|
5
|
+
* those that stay. One source can then print several versions (e.g. an exam with
|
|
6
|
+
* `answer-key: hide` for students and `show` for the instructor key).
|
|
7
|
+
*
|
|
8
|
+
* Operates on a live DOM (browser render) so nested removals are correct.
|
|
9
|
+
*/
|
|
10
|
+
/** Normalize a choice key: lowercase, hyphens → underscores (matches settings). */
|
|
11
|
+
export declare function normalizeChoiceKey(key: string): string;
|
|
12
|
+
interface CondEl {
|
|
13
|
+
getAttribute(name: string): string | null;
|
|
14
|
+
removeAttribute(name: string): void;
|
|
15
|
+
remove(): void;
|
|
16
|
+
}
|
|
17
|
+
interface CondRoot {
|
|
18
|
+
querySelectorAll(sel: string): ArrayLike<CondEl>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve every `data-show-when` / `data-hide-when` under `root` against
|
|
22
|
+
* `choices`, mutating the DOM in place: drop non-matching elements, strip the
|
|
23
|
+
* attribute from the rest.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyDynamicChoices(root: CondRoot, choices: Record<string, string>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Collect the dynamic keys present under `root` and the set of values each is
|
|
28
|
+
* used with — for building a live switcher UI. Returns key → sorted values.
|
|
29
|
+
*/
|
|
30
|
+
export declare function collectDynamicChoices(root: CondRoot): Record<string, string[]>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orz-paged — built-in document elements.
|
|
3
|
+
*
|
|
4
|
+
* `renderElement(spec, ctx)` turns one `{{nyml kind: element}}` block into
|
|
5
|
+
* semantic HTML + a small scoped stylesheet. Every element is wrapped in
|
|
6
|
+
* `<section class="orz-element orz-el-<kind>">…</section>`. Markdown-typed
|
|
7
|
+
* fields go through the injected `ctx.renderInline` / `ctx.renderBlock` so this
|
|
8
|
+
* module never depends on orz-markdown directly.
|
|
9
|
+
*
|
|
10
|
+
* The `css` returned per kind is identical on every call (the assembler dedupes
|
|
11
|
+
* by kind), uses `var(--accent)` for the accent color, and stays light-only.
|
|
12
|
+
*
|
|
13
|
+
* Spec: docs/document-model.md (Built-in Elements) — reimplemented cleanly.
|
|
14
|
+
*/
|
|
15
|
+
import type { ElementSpec, ElementCtx, ElementResult } from '../types.js';
|
|
16
|
+
export declare function renderElement(spec: ElementSpec, ctx: ElementCtx): ElementResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font presets — M3.
|
|
3
|
+
*
|
|
4
|
+
* Maps each curated {@link FontPresetName} to a {@link FontPreset}: a CDN
|
|
5
|
+
* stylesheet URL (`@fontsource/*` served via jsDelivr) plus the matching CSS
|
|
6
|
+
* `font-family` stack. orz-paged assumes internet access, so fonts load from
|
|
7
|
+
* the CDN rather than being bundled.
|
|
8
|
+
*
|
|
9
|
+
* `system-serif` is the one preset with **no** webfont — it relies on the
|
|
10
|
+
* platform's Times-style serif and ships an empty `cssUrl`.
|
|
11
|
+
*
|
|
12
|
+
* Source of truth for the curated list: `docs/document-model.md` (Tier A) and
|
|
13
|
+
* `src/types.ts` (`FontPresetName`).
|
|
14
|
+
*/
|
|
15
|
+
import type { FontPreset, FontPresetName } from '../types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Every curated preset → its CDN stylesheet + CSS family stack. `system-serif`
|
|
18
|
+
* carries an empty `cssUrl` (no network font).
|
|
19
|
+
*/
|
|
20
|
+
export declare const FONT_PRESETS: Record<FontPresetName, FontPreset>;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a preset by name. Unknown names fall back to a webfont-less system
|
|
23
|
+
* serif (empty `cssUrl`) rather than throwing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fontPreset(name: string): FontPreset;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic nyml-block scanner — shared by the settings reader (M1) and the
|
|
3
|
+
* assembler (A1).
|
|
4
|
+
*
|
|
5
|
+
* `settings.ts` parses the single `kind: document` block inline; this module is
|
|
6
|
+
* the reusable version: it finds **every** `{{nyml … }}` block in a source,
|
|
7
|
+
* parses its YAML-ish `key: value` body (including `key: |` multiline blocks),
|
|
8
|
+
* reads the `kind:` discriminator, and reports the block's char offsets so a
|
|
9
|
+
* caller can splice it out / replace it.
|
|
10
|
+
*
|
|
11
|
+
* Values are always strings: surrounding quotes are stripped, multiline `|`
|
|
12
|
+
* blocks are dedented and joined with `\n`. The `{{nyml … }}` grammar matches
|
|
13
|
+
* what `parseDocSettings` already accepts (`}` is allowed inside the body; only
|
|
14
|
+
* the first `}}` terminates the block).
|
|
15
|
+
*/
|
|
16
|
+
/** One scanned `{{nyml … }}` block: its kind, parsed fields, and char range. */
|
|
17
|
+
export interface NymlBlock {
|
|
18
|
+
/** The `kind:` value (e.g. `'document'`, `'element'`), or `''` if absent. */
|
|
19
|
+
kind: string;
|
|
20
|
+
/** snake_case field → raw string value (multiline values preserved). */
|
|
21
|
+
fields: Record<string, string>;
|
|
22
|
+
/** Offset of the opening `{` of `{{nyml` in `source`. */
|
|
23
|
+
start: number;
|
|
24
|
+
/** Offset just past the closing `}}` (so `source.slice(start, end)` is the block). */
|
|
25
|
+
end: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Scan a source for every `{{nyml … }}` block, in document order. Each result
|
|
29
|
+
* carries its parsed `kind`, `fields`, and `[start, end)` char offsets. Blocks
|
|
30
|
+
* inside an HTML comment (`<!-- … -->`) are skipped — so a commented-out element
|
|
31
|
+
* (e.g. content the editor's template picker preserves) does not render.
|
|
32
|
+
*/
|
|
33
|
+
export declare function scanNymlBlocks(source: string): NymlBlock[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page CSS — M5.
|
|
3
|
+
*
|
|
4
|
+
* Turns a fully-resolved {@link DocSettings} into the `@page` rules, margin-box
|
|
5
|
+
* running headers/footers, page-number counters, `:root` design tokens, and
|
|
6
|
+
* layout-behavior CSS that paged.js realizes into physical page boxes.
|
|
7
|
+
*
|
|
8
|
+
* Behavior SPEC: `docs/document-model.md`; token set + page model:
|
|
9
|
+
* `docs/dom-contract.md`. Reimplemented cleanly in TS (light-only output).
|
|
10
|
+
*/
|
|
11
|
+
import type { DocSettings } from '../types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Build the full page CSS string for a document. Pure: depends only on `s`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildPageCss(s: DocSettings): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document settings — M1.
|
|
3
|
+
*
|
|
4
|
+
* Reads the single `{{nyml kind: document}}` block from a Markdown source,
|
|
5
|
+
* normalizes its snake_case keys into a {@link DocSettingsLayer}, and merges
|
|
6
|
+
* template + user layers over {@link DEFAULTS} into a fully-resolved
|
|
7
|
+
* {@link DocSettings}.
|
|
8
|
+
*
|
|
9
|
+
* Source of truth for defaults and aliases: `docs/document-model.md`
|
|
10
|
+
* (the curated subset; see DESIGN.md §8).
|
|
11
|
+
*/
|
|
12
|
+
import type { DocSettings, RawDocSettings, DocSettingsLayer } from '../types.js';
|
|
13
|
+
/** Every field at its documented default (docs/document-model.md). */
|
|
14
|
+
export declare const DEFAULTS: DocSettings;
|
|
15
|
+
/**
|
|
16
|
+
* Locate the first `{{nyml ... }}` block whose body declares `kind: document`,
|
|
17
|
+
* parse its `key: value` lines (including `key: |` multiline blocks) into a
|
|
18
|
+
* {@link RawDocSettings}, and return the source with that block removed so it
|
|
19
|
+
* is not rendered as content. No document block → `{ raw: {}, body: source }`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseDocSettings(source: string): {
|
|
22
|
+
raw: RawDocSettings;
|
|
23
|
+
body: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Map snake_case raw keys → camelCase {@link DocSettings} fields, coercing
|
|
27
|
+
* types and normalizing aliases. Only keys present in `raw` are included;
|
|
28
|
+
* unknown keys are ignored.
|
|
29
|
+
*/
|
|
30
|
+
export declare function normalizeRawToLayer(raw: RawDocSettings): DocSettingsLayer;
|
|
31
|
+
/**
|
|
32
|
+
* Shallow-merge layers left → right (later wins). Intended use:
|
|
33
|
+
* `mergeSettings(DEFAULTS, templateLayer, userLayer)`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeSettings(...layers: DocSettingsLayer[]): DocSettings;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated document templates — LAYOUT defaults + a starter skeleton.
|
|
3
|
+
*
|
|
4
|
+
* Responsibility split: a **template** owns layout only (page size, margins,
|
|
5
|
+
* furniture, and which elements appear / where). A **theme** owns the look —
|
|
6
|
+
* font, accent/decoration color, and element styling — so the same theme renders
|
|
7
|
+
* the same across every template (see ../doc/theme-fonts.ts + assets/themes/*).
|
|
8
|
+
* The default theme is `light-academic-1` (settings.ts DEFAULTS.theme).
|
|
9
|
+
*
|
|
10
|
+
* Each template carries:
|
|
11
|
+
* - a {@link DocSettingsLayer} of *layout defaults* (page size; no font/theme),
|
|
12
|
+
* merged under any `kind: document` field the user sets,
|
|
13
|
+
* - picker metadata (`label`, `description`, `group`),
|
|
14
|
+
* - a `skeleton` stem naming the starter Markdown in `assets/templates/<stem>.md`.
|
|
15
|
+
*
|
|
16
|
+
* `article` / `report` / `exam` ship in two variants — `-page` (a dedicated
|
|
17
|
+
* title/cover page) and `-section` (title-on-content) — that differ only in their
|
|
18
|
+
* skeleton (the title element's `placement: page | section`). Legacy names
|
|
19
|
+
* (`article`/`report`/`exam`) resolve to the `-section` variant.
|
|
20
|
+
*/
|
|
21
|
+
import type { TemplateName, DocSettingsLayer } from '../types.js';
|
|
22
|
+
export interface TemplateDef {
|
|
23
|
+
/** Friendly name for the picker, e.g. "Article — title page". */
|
|
24
|
+
label: string;
|
|
25
|
+
/** One-line description (picker tooltip). */
|
|
26
|
+
description: string;
|
|
27
|
+
/** Grouping for the picker, e.g. "Article". */
|
|
28
|
+
group: string;
|
|
29
|
+
/** Starter-markdown file stem: `assets/templates/<skeleton>.md`. */
|
|
30
|
+
skeleton: string;
|
|
31
|
+
/** Settings defaults this template applies. */
|
|
32
|
+
settings: DocSettingsLayer;
|
|
33
|
+
}
|
|
34
|
+
/** The 10 curated templates. */
|
|
35
|
+
export declare const TEMPLATES: Record<TemplateName, TemplateDef>;
|
|
36
|
+
/** Canonicalize a (possibly legacy) template name, or `undefined` if unknown. */
|
|
37
|
+
export declare function canonicalTemplate(name: string): TemplateName | undefined;
|
|
38
|
+
/** Return a template's *settings* defaults layer, or `{}` for an unknown name. */
|
|
39
|
+
export declare function resolveTemplate(name: TemplateName | string): DocSettingsLayer;
|
|
40
|
+
/** The starter-markdown file stem for a template, or `undefined` if unknown. */
|
|
41
|
+
export declare function templateSkeletonName(name: TemplateName | string): string | undefined;
|
|
42
|
+
/** Picker catalog: id + metadata for every template (no settings/skeleton text). */
|
|
43
|
+
export declare function templateList(): Array<{
|
|
44
|
+
id: TemplateName;
|
|
45
|
+
label: string;
|
|
46
|
+
description: string;
|
|
47
|
+
group: string;
|
|
48
|
+
skeleton: string;
|
|
49
|
+
}>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme typography — each theme OWNS its font (alongside its colors + element
|
|
3
|
+
* style in the theme CSS). The engine loads the active theme's webfonts at view
|
|
4
|
+
* time and the theme CSS sets the matching `--font-body` / `--font-heading`, so
|
|
5
|
+
* the same theme yields the same font across every template. An explicit
|
|
6
|
+
* `font_preset` in the document settings still overrides this (page CSS wins).
|
|
7
|
+
*/
|
|
8
|
+
import type { ThemeName, FontPresetName } from '../types.js';
|
|
9
|
+
export declare const THEME_FONTS: Record<ThemeName, {
|
|
10
|
+
body: FontPresetName;
|
|
11
|
+
heading: FontPresetName;
|
|
12
|
+
}>;
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ThemeAsset, type TemplateAsset } from './template.js';
|
|
2
|
+
export declare const selfVersion: string;
|
|
3
|
+
/** Pinned CDN assets used at view time — sourced from orz-markdown's shared
|
|
4
|
+
* preview-frame helper so every host app loads identical, tested versions.
|
|
5
|
+
* Paged is light-only, so highlight.js uses the light (github) stylesheet. */
|
|
6
|
+
export declare const CDN: {
|
|
7
|
+
katexCss: "https://cdn.jsdelivr.net/npm/katex@0.16.35/dist/katex.min.css";
|
|
8
|
+
mermaidJs: "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
|
|
9
|
+
smilesJs: "https://unpkg.com/smiles-drawer@1.0.10/dist/smiles-drawer.min.js";
|
|
10
|
+
chartJs: "https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js";
|
|
11
|
+
hljsJs: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js";
|
|
12
|
+
hljsCss: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css";
|
|
13
|
+
};
|
|
14
|
+
export declare function findAsset(rel: string): string;
|
|
15
|
+
/** Drop a theme's `@import url("./base.css")` — base is inlined separately, so the
|
|
16
|
+
* relative import points nowhere (404 on http; a fetch hazard on file://). */
|
|
17
|
+
export declare function stripBaseImport(css: string): string;
|
|
18
|
+
export declare function loadThemes(): {
|
|
19
|
+
baseCss: string;
|
|
20
|
+
themes: ThemeAsset[];
|
|
21
|
+
};
|
|
22
|
+
/** Load every template's starter skeleton from `assets/templates/<stem>.md`. */
|
|
23
|
+
export declare function loadTemplates(): TemplateAsset[];
|
|
24
|
+
/** The default theme id, matching the CLI: prefer `light-neat-1`, else the first
|
|
25
|
+
* loaded theme, else `'none'`. */
|
|
26
|
+
export declare function pickDefaultTheme(themes: ThemeAsset[]): string;
|
|
27
|
+
/**
|
|
28
|
+
* The shared inline composition — called by both the CLI (`--inline`, the
|
|
29
|
+
* default) and {@link buildPagedHtml}. Loads all assets from the package, builds
|
|
30
|
+
* an inline renderer, and returns the full document string. Deterministic: paged
|
|
31
|
+
* has no per-document random id, so the same `(source, title, theme?)` always
|
|
32
|
+
* yields byte-identical output.
|
|
33
|
+
*
|
|
34
|
+
* @param theme optional theme id; when omitted (or unknown) the default theme
|
|
35
|
+
* ({@link pickDefaultTheme}) drives the picker's initial selection.
|
|
36
|
+
*/
|
|
37
|
+
export declare function composeInlineHtml(source: string, title: string, theme?: string, delivery?: 'inline' | 'cdn'): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate a self-contained `.paged.html` document, fully inline.
|
|
40
|
+
*
|
|
41
|
+
* Mirrors the CLI's default `--inline` mode; the returned string is
|
|
42
|
+
* byte-identical to CLI inline output for the same source/title/theme.
|
|
43
|
+
*
|
|
44
|
+
* @param opts.markdown the document source (the host passes content).
|
|
45
|
+
* @param opts.title `<title>` + generator title; defaults to `'Untitled'`.
|
|
46
|
+
* @param opts.theme initial theme id; unknown/omitted → the default theme.
|
|
47
|
+
* @param opts.template a starter-skeleton selector, mirroring the CLI's
|
|
48
|
+
* `--template <name>` with no input file: used as the source ONLY when
|
|
49
|
+
* `opts.markdown` is empty/whitespace. When `markdown` has content, it wins.
|
|
50
|
+
* The full template catalog is always embedded regardless (it powers the
|
|
51
|
+
* in-editor picker), so this never changes the catalog — only the initial
|
|
52
|
+
* source. An unknown template name is ignored (falls back to `markdown`).
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildPagedHtml(opts: {
|
|
55
|
+
markdown: string;
|
|
56
|
+
title?: string;
|
|
57
|
+
theme?: string;
|
|
58
|
+
template?: string;
|
|
59
|
+
/** `inline` (default, offline) or `cdn` (small file — engine from jsDelivr;
|
|
60
|
+
* requires orz-paged-browser published at this version). */
|
|
61
|
+
delivery?: 'inline' | 'cdn';
|
|
62
|
+
}): string;
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orz-paged library entry — generate a self-contained `.paged.html` in-process.
|
|
3
|
+
*
|
|
4
|
+
* This is the programmatic twin of the CLI's default (`--inline`) render path: a
|
|
5
|
+
* host platform calls {@link buildPagedHtml} and gets back the FULL document
|
|
6
|
+
* string, fully inline (inlined engine bundle + inlined base/theme CSS + full
|
|
7
|
+
* template catalog + embedded source). Math/diagram/font libs still load from
|
|
8
|
+
* CDN at view time — exactly as the CLI's inline output does.
|
|
9
|
+
*
|
|
10
|
+
* Both the CLI and this module compose through {@link composeInlineHtml}, so
|
|
11
|
+
* there is a single render path. The CLI keeps its argument parsing, template
|
|
12
|
+
* scaffolding (`--new`/`--list-templates`), source-selection (`--template`), and
|
|
13
|
+
* the `--cdn` mode; the shared inline composition lives here.
|
|
14
|
+
*
|
|
15
|
+
* Asset resolution is relative to this module (`import.meta.url`), never
|
|
16
|
+
* `process.cwd()`, so it works from an installed npm package.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { getBrowserRuntimeScript } from 'orz-markdown/runtime';
|
|
22
|
+
import { PREVIEW_CDN } from 'orz-markdown/preview-frame';
|
|
23
|
+
import { buildHtml } from './template.js';
|
|
24
|
+
import { templateList } from './doc/templates.js';
|
|
25
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const ROOT = join(HERE, '..');
|
|
27
|
+
export const selfVersion = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version || '0.0.0';
|
|
28
|
+
/** Pinned CDN assets used at view time — sourced from orz-markdown's shared
|
|
29
|
+
* preview-frame helper so every host app loads identical, tested versions.
|
|
30
|
+
* Paged is light-only, so highlight.js uses the light (github) stylesheet. */
|
|
31
|
+
export const CDN = {
|
|
32
|
+
katexCss: PREVIEW_CDN.katexCss,
|
|
33
|
+
mermaidJs: PREVIEW_CDN.mermaidJs,
|
|
34
|
+
smilesJs: PREVIEW_CDN.smilesJs,
|
|
35
|
+
chartJs: PREVIEW_CDN.chartJs,
|
|
36
|
+
hljsJs: PREVIEW_CDN.hljsJs,
|
|
37
|
+
hljsCss: PREVIEW_CDN.hljsLightCss,
|
|
38
|
+
};
|
|
39
|
+
export function findAsset(rel) {
|
|
40
|
+
const p = join(ROOT, rel);
|
|
41
|
+
if (!existsSync(p))
|
|
42
|
+
throw new Error('missing asset: ' + rel + ' (run `npm run build` first)');
|
|
43
|
+
return p;
|
|
44
|
+
}
|
|
45
|
+
/** Drop a theme's `@import url("./base.css")` — base is inlined separately, so the
|
|
46
|
+
* relative import points nowhere (404 on http; a fetch hazard on file://). */
|
|
47
|
+
export function stripBaseImport(css) {
|
|
48
|
+
return css.replace(/@import\s+url\(\s*['"]?\.\/base\.css['"]?\s*\)\s*;?/gi, '');
|
|
49
|
+
}
|
|
50
|
+
export function loadThemes() {
|
|
51
|
+
const dir = findAsset('assets/themes');
|
|
52
|
+
const baseCss = readFileSync(join(dir, 'base.css'), 'utf8');
|
|
53
|
+
const themes = readdirSync(dir)
|
|
54
|
+
.filter((f) => f.endsWith('.css') && f !== 'base.css')
|
|
55
|
+
.map((f) => ({ id: f.replace(/\.css$/, ''), css: stripBaseImport(readFileSync(join(dir, f), 'utf8')) }));
|
|
56
|
+
return { baseCss, themes };
|
|
57
|
+
}
|
|
58
|
+
/** Load every template's starter skeleton from `assets/templates/<stem>.md`. */
|
|
59
|
+
export function loadTemplates() {
|
|
60
|
+
const dir = findAsset('assets/templates');
|
|
61
|
+
return templateList().map((t) => ({
|
|
62
|
+
id: t.id,
|
|
63
|
+
label: t.label,
|
|
64
|
+
description: t.description,
|
|
65
|
+
group: t.group,
|
|
66
|
+
skeleton: readFileSync(join(dir, t.skeleton + '.md'), 'utf8'),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/** The default theme id, matching the CLI: prefer `light-neat-1`, else the first
|
|
70
|
+
* loaded theme, else `'none'`. */
|
|
71
|
+
export function pickDefaultTheme(themes) {
|
|
72
|
+
return themes.some((t) => t.id === 'light-neat-1') ? 'light-neat-1' : (themes[0]?.id ?? 'none');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The shared inline composition — called by both the CLI (`--inline`, the
|
|
76
|
+
* default) and {@link buildPagedHtml}. Loads all assets from the package, builds
|
|
77
|
+
* an inline renderer, and returns the full document string. Deterministic: paged
|
|
78
|
+
* has no per-document random id, so the same `(source, title, theme?)` always
|
|
79
|
+
* yields byte-identical output.
|
|
80
|
+
*
|
|
81
|
+
* @param theme optional theme id; when omitted (or unknown) the default theme
|
|
82
|
+
* ({@link pickDefaultTheme}) drives the picker's initial selection.
|
|
83
|
+
*/
|
|
84
|
+
export function composeInlineHtml(source, title, theme, delivery) {
|
|
85
|
+
const templates = loadTemplates();
|
|
86
|
+
const { baseCss, themes } = loadThemes();
|
|
87
|
+
const appJs = readFileSync(findAsset('assets/app.js'), 'utf8');
|
|
88
|
+
const runtime = getBrowserRuntimeScript(); // copy-as-Markdown (+ qr) from orz-markdown
|
|
89
|
+
// `cdn` references the published engine on jsDelivr (small file); `inline`
|
|
90
|
+
// (default) embeds it. Only the renderer differs from the inline path.
|
|
91
|
+
const renderer = delivery === 'cdn'
|
|
92
|
+
? {
|
|
93
|
+
mode: 'cdn',
|
|
94
|
+
src: `https://cdn.jsdelivr.net/npm/orz-paged-browser@${selfVersion}/orz-paged.browser.js`,
|
|
95
|
+
}
|
|
96
|
+
: {
|
|
97
|
+
mode: 'inline',
|
|
98
|
+
js: readFileSync(findAsset('dist/orz-paged.browser.js'), 'utf8'),
|
|
99
|
+
};
|
|
100
|
+
const known = theme && themes.some((t) => t.id === theme);
|
|
101
|
+
const defaultTheme = known ? theme : pickDefaultTheme(themes);
|
|
102
|
+
return buildHtml({
|
|
103
|
+
source,
|
|
104
|
+
title,
|
|
105
|
+
rendererVersion: selfVersion,
|
|
106
|
+
renderer,
|
|
107
|
+
baseCss,
|
|
108
|
+
themes,
|
|
109
|
+
defaultTheme,
|
|
110
|
+
templates,
|
|
111
|
+
cdn: CDN,
|
|
112
|
+
appJs,
|
|
113
|
+
runtime,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Generate a self-contained `.paged.html` document, fully inline.
|
|
118
|
+
*
|
|
119
|
+
* Mirrors the CLI's default `--inline` mode; the returned string is
|
|
120
|
+
* byte-identical to CLI inline output for the same source/title/theme.
|
|
121
|
+
*
|
|
122
|
+
* @param opts.markdown the document source (the host passes content).
|
|
123
|
+
* @param opts.title `<title>` + generator title; defaults to `'Untitled'`.
|
|
124
|
+
* @param opts.theme initial theme id; unknown/omitted → the default theme.
|
|
125
|
+
* @param opts.template a starter-skeleton selector, mirroring the CLI's
|
|
126
|
+
* `--template <name>` with no input file: used as the source ONLY when
|
|
127
|
+
* `opts.markdown` is empty/whitespace. When `markdown` has content, it wins.
|
|
128
|
+
* The full template catalog is always embedded regardless (it powers the
|
|
129
|
+
* in-editor picker), so this never changes the catalog — only the initial
|
|
130
|
+
* source. An unknown template name is ignored (falls back to `markdown`).
|
|
131
|
+
*/
|
|
132
|
+
export function buildPagedHtml(opts) {
|
|
133
|
+
let source = opts.markdown;
|
|
134
|
+
if (opts.template && !opts.markdown.trim()) {
|
|
135
|
+
const templates = loadTemplates();
|
|
136
|
+
const t = templates.find((x) => x.id === opts.template)
|
|
137
|
+
?? templates.find((x) => x.id === opts.template + '-section'); // legacy: article→article-section
|
|
138
|
+
if (t)
|
|
139
|
+
source = t.skeleton;
|
|
140
|
+
}
|
|
141
|
+
const title = opts.title || 'Untitled';
|
|
142
|
+
return composeInlineHtml(source, title, opts.theme, opts.delivery);
|
|
143
|
+
}
|