orz-paged 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 +146 -0
- package/assets/app.js +413 -0
- package/assets/templates/article-page.md +71 -0
- package/assets/templates/article-section.md +83 -0
- package/assets/templates/cover-letter.md +53 -0
- package/assets/templates/cv-elegant.md +93 -0
- package/assets/templates/cv-modern.md +86 -0
- package/assets/templates/cv.md +45 -0
- package/assets/templates/exam-page.md +76 -0
- package/assets/templates/exam-section.md +59 -0
- package/assets/templates/letter.md +47 -0
- package/assets/templates/note.md +28 -0
- package/assets/templates/report-page.md +72 -0
- package/assets/templates/report-section.md +80 -0
- package/assets/themes/base.css +538 -0
- package/assets/themes/beige-decent-1.css +103 -0
- package/assets/themes/beige-decent-2.css +92 -0
- package/assets/themes/light-academic-1.css +114 -0
- package/assets/themes/light-academic-2.css +99 -0
- package/assets/themes/light-neat-1.css +90 -0
- package/assets/themes/light-neat-2.css +101 -0
- package/assets/themes/light-neat-3.css +91 -0
- package/dist/browser-entry.js +302 -0
- package/dist/cli.js +167 -0
- package/dist/doc/dynamic.js +57 -0
- package/dist/doc/elements.js +813 -0
- package/dist/doc/fonts.js +83 -0
- package/dist/doc/nyml.js +140 -0
- package/dist/doc/page-css.js +230 -0
- package/dist/doc/settings.js +390 -0
- package/dist/doc/templates.js +147 -0
- package/dist/doc/theme-fonts.js +10 -0
- package/dist/orz-paged.browser.js +1524 -0
- package/dist/paged.js +31 -0
- package/dist/render-paged.js +123 -0
- package/dist/template.js +242 -0
- package/dist/types.js +11 -0
- package/orz-paged-skills/SKILL.md +547 -0
- package/package.json +51 -0
package/dist/paged.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around paged.js (M7). Keeps the rest of the engine ignorant of
|
|
3
|
+
* paged.js internals: it paginates a content string into a target element using
|
|
4
|
+
* a set of stylesheets, and triggers a browser print for PDF export.
|
|
5
|
+
*
|
|
6
|
+
* paged.js' `Polisher.add(...stylesheets)` accepts either a URL string (fetched)
|
|
7
|
+
* or an object `{ name: cssText }` for INLINE CSS — see node_modules/pagedjs.
|
|
8
|
+
*/
|
|
9
|
+
import { Previewer } from 'pagedjs';
|
|
10
|
+
/**
|
|
11
|
+
* Flow `content` (body HTML) into `target` as `.pagedjs_page` boxes, applying
|
|
12
|
+
* `stylesheets` (the `@page` rules + content/theme CSS). Clears `target` first.
|
|
13
|
+
*/
|
|
14
|
+
export async function paginate(content, stylesheets, target) {
|
|
15
|
+
target.innerHTML = '';
|
|
16
|
+
// paged.js' Polisher appends a compiled <style data-pagedjs-inserted-styles>
|
|
17
|
+
// to <head> on every preview() and never removes the prior one. Without this
|
|
18
|
+
// cleanup, re-rendering (e.g. a theme switch) stacks each theme's :root tokens
|
|
19
|
+
// and construct rules on top of the last, so old decoration lingers and the
|
|
20
|
+
// result stops matching the selected theme. Drop the stale ones first.
|
|
21
|
+
document
|
|
22
|
+
.querySelectorAll('style[data-pagedjs-inserted-styles]')
|
|
23
|
+
.forEach((el) => el.remove());
|
|
24
|
+
const previewer = new Previewer();
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
await previewer.preview(content, stylesheets, target);
|
|
27
|
+
}
|
|
28
|
+
/** Export to PDF via the browser's own print engine (no Puppeteer). */
|
|
29
|
+
export function printDocument() {
|
|
30
|
+
window.print();
|
|
31
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assembler — A1.
|
|
3
|
+
*
|
|
4
|
+
* `assemble(source)` turns a Markdown source into a {@link PagedAssembly}: pure
|
|
5
|
+
* data (CSS, font URLs, theme, body HTML, resolved settings) that the browser
|
|
6
|
+
* engine then hands to paged.js for pagination. No DOM here.
|
|
7
|
+
*
|
|
8
|
+
* Pipeline (DESIGN §3/§6):
|
|
9
|
+
* 1. `parseDocSettings` extracts + strips the `{{nyml kind: document}}` block.
|
|
10
|
+
* 2. Merge DEFAULTS ← template layer ← user overrides → resolved settings.
|
|
11
|
+
* 3. Scan the remaining body for `{{nyml kind: element}}` blocks, render each
|
|
12
|
+
* via `renderElement`, and replace it with a placeholder token.
|
|
13
|
+
* 4. Render the placeholdered body Markdown, then swap each placeholder for
|
|
14
|
+
* its element's HTML (so element HTML is never re-parsed as Markdown).
|
|
15
|
+
* 5. Compose page CSS + deduped element CSS + custom CSS; collect font URLs.
|
|
16
|
+
*/
|
|
17
|
+
import { md } from 'orz-markdown';
|
|
18
|
+
/**
|
|
19
|
+
* The curated element kinds `renderElement` knows how to render. A `{{nyml … }}`
|
|
20
|
+
* block whose `kind:` is one of these is an *element* block; any other `kind:`
|
|
21
|
+
* (e.g. `document`) is not. This is the set switched on in `doc/elements.ts`.
|
|
22
|
+
*/
|
|
23
|
+
const ELEMENT_KINDS = new Set([
|
|
24
|
+
'article-title',
|
|
25
|
+
'report-title',
|
|
26
|
+
'exam-title',
|
|
27
|
+
'abstract',
|
|
28
|
+
'letterhead',
|
|
29
|
+
'letter-inside-address',
|
|
30
|
+
'letter-signature',
|
|
31
|
+
'toc',
|
|
32
|
+
'cv-header',
|
|
33
|
+
'question-mc',
|
|
34
|
+
'question-open',
|
|
35
|
+
'timestamp',
|
|
36
|
+
]);
|
|
37
|
+
import { DEFAULTS, parseDocSettings, normalizeRawToLayer, mergeSettings, } from './doc/settings.js';
|
|
38
|
+
import { resolveTemplate } from './doc/templates.js';
|
|
39
|
+
import { buildPageCss } from './doc/page-css.js';
|
|
40
|
+
import { fontPreset } from './doc/fonts.js';
|
|
41
|
+
import { renderElement } from './doc/elements.js';
|
|
42
|
+
import { scanNymlBlocks } from './doc/nyml.js';
|
|
43
|
+
/** Unique placeholder token for the n-th element, kept off Markdown's radar. */
|
|
44
|
+
function placeholderFor(index) {
|
|
45
|
+
return `\n\n<!--ORZEL-${index}-->\n\n`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Assemble a Markdown source into a {@link PagedAssembly}.
|
|
49
|
+
*/
|
|
50
|
+
export function assemble(source) {
|
|
51
|
+
// 1. Document settings — strips the `kind: document` block from the body.
|
|
52
|
+
const { raw, body } = parseDocSettings(source);
|
|
53
|
+
// 2. Resolve settings: DEFAULTS ← template defaults ← user overrides.
|
|
54
|
+
const templateLayer = resolveTemplate(String(raw.template ?? ''));
|
|
55
|
+
const settings = mergeSettings(DEFAULTS, templateLayer, normalizeRawToLayer(raw));
|
|
56
|
+
// 3. Elements: scan the body, render each, splice in placeholders.
|
|
57
|
+
const ctx = {
|
|
58
|
+
renderInline: (s) => md.renderInline(s),
|
|
59
|
+
renderBlock: (s) => md.render(s),
|
|
60
|
+
settings,
|
|
61
|
+
};
|
|
62
|
+
// Element blocks are `{{nyml … }}` blocks whose `kind:` names a curated
|
|
63
|
+
// element (e.g. `article-title`, `abstract`). The `kind:` value is the
|
|
64
|
+
// ElementKind that `renderElement` switches on and `orz-el-<kind>` reflects.
|
|
65
|
+
const blocks = scanNymlBlocks(body).filter((b) => ELEMENT_KINDS.has(b.kind));
|
|
66
|
+
// dedupe element CSS by kind; collect each block's rendered HTML.
|
|
67
|
+
const cssByKind = new Map();
|
|
68
|
+
const htmlByIndex = [];
|
|
69
|
+
// Rebuild the body left-to-right, swapping each element block for a token.
|
|
70
|
+
let bodyWithPlaceholders = '';
|
|
71
|
+
let cursor = 0;
|
|
72
|
+
blocks.forEach((block, index) => {
|
|
73
|
+
bodyWithPlaceholders += body.slice(cursor, block.start);
|
|
74
|
+
bodyWithPlaceholders += placeholderFor(index);
|
|
75
|
+
cursor = block.end;
|
|
76
|
+
const placement = block.fields.placement?.trim().toLowerCase() === 'page' ? 'page' : undefined;
|
|
77
|
+
const spec = {
|
|
78
|
+
kind: block.kind,
|
|
79
|
+
fields: block.fields,
|
|
80
|
+
placement,
|
|
81
|
+
};
|
|
82
|
+
const result = renderElement(spec, ctx);
|
|
83
|
+
htmlByIndex[index] = result.html;
|
|
84
|
+
if (result.css && !cssByKind.has(spec.kind)) {
|
|
85
|
+
cssByKind.set(spec.kind, result.css);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
bodyWithPlaceholders += body.slice(cursor);
|
|
89
|
+
// 4. Render the placeholdered body, then swap placeholders → element HTML.
|
|
90
|
+
let contentHtml = md.render(bodyWithPlaceholders);
|
|
91
|
+
htmlByIndex.forEach((html, index) => {
|
|
92
|
+
// The token survives Markdown rendering as an HTML comment; replace it.
|
|
93
|
+
contentHtml = contentHtml.replace(`<!--ORZEL-${index}-->`, html ?? '');
|
|
94
|
+
});
|
|
95
|
+
// 5. CSS: page CSS + deduped element CSS + custom CSS.
|
|
96
|
+
const elementCss = [...cssByKind.values()].join('\n');
|
|
97
|
+
const css = buildPageCss(settings) +
|
|
98
|
+
(elementCss ? '\n' + elementCss : '') +
|
|
99
|
+
(settings.customCss ? '\n' + settings.customCss : '');
|
|
100
|
+
// 6. Fonts: unique non-empty stylesheet URLs across the active presets.
|
|
101
|
+
const fontCssUrls = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
const addFont = (name) => {
|
|
104
|
+
if (!name)
|
|
105
|
+
return;
|
|
106
|
+
const url = fontPreset(name).cssUrl;
|
|
107
|
+
if (url && !seen.has(url)) {
|
|
108
|
+
seen.add(url);
|
|
109
|
+
fontCssUrls.push(url);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
addFont(settings.fontPreset);
|
|
113
|
+
addFont(settings.fontHeadingPreset);
|
|
114
|
+
addFont(settings.fontMarginBoxPreset);
|
|
115
|
+
// 7. Assemble.
|
|
116
|
+
return {
|
|
117
|
+
css,
|
|
118
|
+
fontCssUrls,
|
|
119
|
+
theme: settings.theme,
|
|
120
|
+
bodyHtml: '<main class="orz-doc markdown-body">' + contentHtml + '</main>',
|
|
121
|
+
settings,
|
|
122
|
+
};
|
|
123
|
+
}
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the self-contained `.paged.html` shell (A3):
|
|
3
|
+
* <head> editor-chrome CSS (the document/page CSS is applied by the engine via
|
|
4
|
+
* paged.js' polisher, not here)
|
|
5
|
+
* <body> an empty #orz-pages render target, the editor chrome (edit FAB +
|
|
6
|
+
* docked CodeMirror panel + brand header + banners), the embedded
|
|
7
|
+
* Markdown source in <script id="orz-src">, the engine bundle (inline or
|
|
8
|
+
* CDN), the __ORZ_PAGED__ config, the boot call, and the in-file app.
|
|
9
|
+
*
|
|
10
|
+
* The source in #orz-src is the single source of truth: the engine renders it on
|
|
11
|
+
* load and the editor re-serialises it on save (self-reproducing). Mirrors
|
|
12
|
+
* orz-slides' template (live preview = the real rendered pages).
|
|
13
|
+
*/
|
|
14
|
+
function escapeHtml(s) {
|
|
15
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
16
|
+
}
|
|
17
|
+
/** Prevent an embedded `</script>` in the source from closing the block. */
|
|
18
|
+
function escapeForScript(s) {
|
|
19
|
+
return s.replace(/<\/(script)/gi, '<\\/$1');
|
|
20
|
+
}
|
|
21
|
+
/* ---------- brand (shared with orz-slides / orz-mdhtml) ---------- */
|
|
22
|
+
const ORZ_LOGO = '<svg viewBox="-5 -5 180 100" xmlns="http://www.w3.org/2000/svg" class="orz-mark" aria-hidden="true"><defs><filter id="orzlogo-rough" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.04" numOctaves="3" result="noise"/><feDisplacementMap in="SourceGraphic" in2="noise" scale="2.5" xChannelSelector="R" yChannelSelector="G"/></filter><path d="M 20,23 C 33,16 57,17 84,13 C 111,8 137,4 153,6 C 165,7 170,13 169,25 L 167,66 C 166,79 159,87 146,89 C 121,93 92,92 60,91 C 41,90 27,92 16,89 C 6,86 1,79 1,67 L 1,50 C 1,35 7,25 20,23 Z" id="orzlogo-seal"/><filter id="orzlogo-stone" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.95" numOctaves="2" seed="11" result="noise"/><feColorMatrix in="noise" type="saturate" values="0" result="mono"/><feComponentTransfer in="mono" result="grain"><feFuncA type="table" tableValues="0 0.16"/></feComponentTransfer></filter><mask id="orzlogo-mask"><use href="#orzlogo-seal" fill="white"/><path d="M37.81 80.31Q30.44 80.31 24.50 77Q18.56 73.69 15.25 67.75Q11.94 61.81 11.94 54.19L11.94 54.19Q11.94 41.69 21.13 35.56Q30.31 29.44 43.69 29.44L43.69 29.44Q49.81 29.44 55.13 32.44Q60.44 35.44 63.69 41.06Q66.94 46.69 66.94 54.06L66.94 54.06Q66.94 62.06 62.81 68Q58.69 73.94 52 77.13Q45.31 80.31 37.81 80.31L37.81 80.31ZM36.44 73.19Q48.06 73.19 54.06 67.94Q60.06 62.69 60.06 52.56L60.06 52.56Q60.06 45.69 55.38 40.69Q50.69 35.69 42.31 35.69L42.31 35.69Q36.69 35.69 30.94 37.81Q25.19 39.94 21.44 44.19Q17.69 48.44 17.69 54.44L17.69 54.44Q17.69 59.44 20.19 63.75Q22.69 68.06 27 70.63Q31.31 73.19 36.44 73.19L36.44 73.19ZM87.94 80.19Q82.81 70.19 79.88 56.69Q76.94 43.19 76.94 33.31L76.94 33.31Q76.94 29.44 78.69 28.31L78.69 28.31Q79.69 27.69 80.19 27.69L80.19 27.69Q81.06 27.69 81.50 29.06Q81.94 30.44 82.44 34.06L82.44 34.06L83.06 38.56Q84.44 31.81 88.38 27.88Q92.31 23.94 98.06 23.94L98.06 23.94Q104.44 23.94 107.56 26.38Q110.69 28.81 110.69 34.44L110.69 34.44Q104.44 31.44 99.31 31.44L99.31 31.44Q94.44 31.44 90.94 34.06Q87.44 36.69 86.06 41.81L86.06 41.81Q84.94 45.31 84.94 48.44L84.94 48.44Q84.94 51.81 85.88 54.75Q86.81 57.69 88.81 61.94L88.81 61.94Q90.69 66.31 91.75 69.44Q92.81 72.56 92.94 76.19L92.94 76.19Q92.94 78.31 91.44 79.25Q89.94 80.19 87.94 80.19L87.94 80.19ZM160.56 66.19Q163.06 68.06 163.06 70.19L163.06 70.19Q163.06 73.56 157.31 75.81Q151.56 78.06 144.31 79.13Q137.06 80.19 132.81 80.19L132.81 80.19Q128.06 80.19 123.75 78.63Q119.44 77.06 119.44 73.19L119.44 73.19Q119.44 69.44 123.63 63.44Q127.81 57.44 133.69 51.19L133.69 51.19L117.56 51.19Q116.31 51.19 116.31 48.94L116.31 48.94Q116.31 47.19 116.69 46.50Q117.06 45.81 117.69 45.69Q118.31 45.56 120.06 45.56L120.06 45.56L139.31 45.56Q143.94 39.69 146.38 35.50Q148.81 31.31 148.81 27.69L148.81 27.69Q148.81 24.06 146.31 21.19L146.31 21.19Q142.44 20.44 138.81 20.44L138.81 20.44Q131.94 20.44 127.19 22.75Q122.44 25.06 121.94 28.81L121.94 28.81Q119.06 28.81 118.13 27.81Q117.19 26.81 117.19 24.44L117.19 24.44Q117.19 21.94 119.75 19.63Q122.31 17.31 126.94 15.88Q131.56 14.44 137.31 14.44L137.31 14.44Q142.06 14.44 147.31 15.56L147.31 15.56Q150.56 16.19 152.81 19.69Q155.06 23.19 155.06 27.44L155.06 27.44Q155.06 31.19 152.75 35.31Q150.44 39.44 145.94 45.56L145.94 45.56L152.06 45.56Q156.81 45.56 158.38 46.06Q159.94 46.56 159.94 48.06L159.94 48.06Q159.94 49.31 159.13 50.19Q158.31 51.06 157.44 51.06L157.44 51.06L140.19 51.06Q134.06 57.94 130.81 63.25Q127.56 68.56 128.19 70.81L128.19 70.81Q128.69 73.19 135.94 74.06L135.94 74.06Q144.06 74.06 149.69 72.13Q155.31 70.19 160.56 66.19L160.56 66.19Z" fill="black" stroke="black" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/></mask></defs><g filter="url(#orzlogo-rough)"><use href="#orzlogo-seal" fill="#96d969" mask="url(#orzlogo-mask)"/><use href="#orzlogo-seal" fill="#6ea84d" opacity="0.28" filter="url(#orzlogo-stone)" mask="url(#orzlogo-mask)"/><use href="#orzlogo-seal" fill="none" stroke="#c8f7a5" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" stroke-opacity="0.42" mask="url(#orzlogo-mask)"/><use href="#orzlogo-seal" fill="none" stroke="#5f8f44" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round" stroke-opacity="0.35" mask="url(#orzlogo-mask)"/></g></svg>';
|
|
23
|
+
const GH_ICON = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.6 7.6 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>';
|
|
24
|
+
/* Canonical icon set — shared, thin-stroke line icons (from the orz-markdown
|
|
25
|
+
* editor) so the same function shows the same glyph across every orz surface. */
|
|
26
|
+
function ic(path) {
|
|
27
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${path}</svg>`;
|
|
28
|
+
}
|
|
29
|
+
const ICON = {
|
|
30
|
+
save: ic('<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8M7 3v5h8"/>'),
|
|
31
|
+
print: ic('<path d="M6 9V3h12v6"/><path d="M6 18H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2"/><path d="M6 14h12v7H6z"/>'),
|
|
32
|
+
sync: ic('<path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 0 1 0 10h-2"/><path d="M8 12h8"/>'),
|
|
33
|
+
pencil: ic('<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/>'),
|
|
34
|
+
collapse: ic('<path d="M15 6l-6 6 6 6"/>'),
|
|
35
|
+
template: ic('<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 7h8M8 11h8M8 15h5"/>'),
|
|
36
|
+
};
|
|
37
|
+
const BRAND = '<a id="orz-brand" href="https://github.com/wangyu16/orz-paged" target="_blank" rel="noopener noreferrer" title="orz-paged on GitHub">' +
|
|
38
|
+
`<span class="orz-logo">${ORZ_LOGO}</span>` +
|
|
39
|
+
'<span class="orz-brand-name">paged</span>' +
|
|
40
|
+
`<span class="orz-gh">${GH_ICON}</span>` +
|
|
41
|
+
'</a>';
|
|
42
|
+
/* ---------- chrome ---------- */
|
|
43
|
+
const CHROME_CSS = `
|
|
44
|
+
html, body { margin: 0; height: 100%; background: #f0f0f2; }
|
|
45
|
+
:root { --orz-split: 44%; }
|
|
46
|
+
/* the preview is a fixed full-window scroll container; in edit mode its left
|
|
47
|
+
edge animates inward to make room for the docked editor (side-by-side). */
|
|
48
|
+
#orz-pages { box-sizing: border-box; position: fixed; inset: 0; left: 0;
|
|
49
|
+
overflow: auto; background: #f0f0f2; transition: left .22s ease; }
|
|
50
|
+
[data-mode="edit"] #orz-pages { left: calc(var(--orz-split) + 6px); }
|
|
51
|
+
/* paged.js page boxes get a subtle drop shadow + centered layout */
|
|
52
|
+
.pagedjs_pages { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 18px 0; }
|
|
53
|
+
.pagedjs_page { background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.18); }
|
|
54
|
+
@media print {
|
|
55
|
+
body { background: #fff; }
|
|
56
|
+
#orz-bar, #orz-panel, #orz-edit-fab, #orz-divider, #orz-close, .orz-banner, #orz-toast { display: none !important; }
|
|
57
|
+
#orz-pages { position: static; overflow: visible; left: 0; }
|
|
58
|
+
.pagedjs_pages { gap: 0; padding: 0; zoom: 1 !important; }
|
|
59
|
+
.pagedjs_page { box-shadow: none; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#orz-edit-fab {
|
|
63
|
+
position: fixed; left: 22px; bottom: 18px; z-index: 30;
|
|
64
|
+
width: 42px; height: 42px; border: 0; border-radius: 50%;
|
|
65
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
66
|
+
background: #2f6fe0; color: #fff; cursor: pointer;
|
|
67
|
+
box-shadow: 0 2px 10px rgba(0,0,0,.25); opacity: .9;
|
|
68
|
+
}
|
|
69
|
+
#orz-edit-fab svg { width: 19px; height: 19px; display: block; }
|
|
70
|
+
#orz-edit-fab:hover { opacity: 1; transform: scale(1.06); }
|
|
71
|
+
[data-mode="edit"] #orz-edit-fab { display: none; }
|
|
72
|
+
|
|
73
|
+
#orz-divider { display: none; }
|
|
74
|
+
[data-mode="edit"] #orz-divider {
|
|
75
|
+
display: block; position: fixed; top: 0; bottom: 0; left: var(--orz-split);
|
|
76
|
+
width: 6px; z-index: 45; cursor: col-resize; background: #34383f;
|
|
77
|
+
}
|
|
78
|
+
#orz-divider:hover, #orz-divider.dragging { background: #3b82f6; }
|
|
79
|
+
|
|
80
|
+
#orz-panel {
|
|
81
|
+
display: flex; flex-direction: column;
|
|
82
|
+
position: fixed; left: 0; top: 0; bottom: 0; width: var(--orz-split); z-index: 40;
|
|
83
|
+
background: #1f2228; border-right: 1px solid #333; box-shadow: 2px 0 16px rgba(0,0,0,.25);
|
|
84
|
+
/* extra offset so the protruding close tab also clears the viewport */
|
|
85
|
+
transform: translateX(calc(-100% - 24px)); transition: transform .22s ease;
|
|
86
|
+
}
|
|
87
|
+
[data-mode="edit"] #orz-panel { transform: translateX(0); }
|
|
88
|
+
/* close tab — a small handle on the editor's right edge that slides it away */
|
|
89
|
+
#orz-close {
|
|
90
|
+
position: absolute; top: 50%; right: -19px; transform: translateY(-50%);
|
|
91
|
+
width: 19px; height: 48px; z-index: 46; padding: 0;
|
|
92
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
93
|
+
border: 0; border-radius: 0 8px 8px 0; background: #23262c; color: #c2c8d0;
|
|
94
|
+
cursor: pointer; box-shadow: 2px 0 8px rgba(0,0,0,.18);
|
|
95
|
+
}
|
|
96
|
+
#orz-close:hover { background: #383d45; color: #fff; }
|
|
97
|
+
#orz-close svg { width: 15px; height: 15px; display: block; }
|
|
98
|
+
#orz-toolbar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
|
|
99
|
+
padding: 7px 10px; background: #23262c; border-bottom: 1px solid #34383f; }
|
|
100
|
+
#orz-toolbar .ic { width: 32px; height: 30px; display: inline-flex; align-items: center; justify-content: center;
|
|
101
|
+
background: transparent; border: 0; border-radius: 7px; color: #c2c8d0; cursor: pointer; font-size: 16px; }
|
|
102
|
+
#orz-toolbar .ic:hover { background: #383d45; color: #fff; }
|
|
103
|
+
#orz-toolbar .ic svg { width: 17px; height: 17px; display: block; }
|
|
104
|
+
#orz-toolbar .ic[aria-pressed="true"] { color: #3b82f6; }
|
|
105
|
+
#orz-toolbar .ic[aria-pressed="false"] { opacity: .5; }
|
|
106
|
+
#orz-toolbar .ic.primary { background: #3b82f6; color: #fff; }
|
|
107
|
+
#orz-toolbar select { font: 500 12.5px/1 system-ui, sans-serif; color: #e6e8ec; background: #34383f;
|
|
108
|
+
border: 1px solid #454b55; border-radius: 7px; padding: 6px 8px; cursor: pointer; margin: 0 2px; }
|
|
109
|
+
#orz-toolbar .orz-sep { width: 1px; height: 20px; background: #3c414a; margin: 0 5px; }
|
|
110
|
+
#orz-dyn { display: inline-flex; align-items: center; gap: 4px; }
|
|
111
|
+
#orz-dyn .orz-dyn-ctl { display: inline-flex; align-items: center; gap: 4px; }
|
|
112
|
+
#orz-dyn label { font: 600 11px/1 system-ui, sans-serif; color: #9aa3b2; text-transform: capitalize; }
|
|
113
|
+
#orz-toolbar .orz-spacer { flex: 1; }
|
|
114
|
+
#orz-brand { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: #cdd3df; padding: 2px 7px; border-radius: 7px; }
|
|
115
|
+
#orz-brand:hover { color: #fff; background: #383d45; }
|
|
116
|
+
#orz-brand .orz-logo svg { height: 22px; width: auto; display: block; }
|
|
117
|
+
#orz-brand .orz-brand-name { font: 700 13px/1 system-ui, sans-serif; }
|
|
118
|
+
#orz-brand .orz-gh { display: inline-flex; opacity: .55; }
|
|
119
|
+
#orz-brand:hover .orz-gh { opacity: 1; }
|
|
120
|
+
#orz-brand .orz-gh svg { width: 15px; height: 15px; display: block; }
|
|
121
|
+
#orz-editor-host { flex: 1; min-height: 0; overflow: hidden; }
|
|
122
|
+
#orz-editor-host .CodeMirror { height: 100%; font-size: 14px; }
|
|
123
|
+
#orz-foot { flex: 0 0 auto; height: 24px; display: flex; align-items: center; justify-content: center;
|
|
124
|
+
font: 11.5px/1 system-ui, sans-serif; color: #8b93a0; background: #23262c; border-top: 1px solid #34383f; gap: 4px; }
|
|
125
|
+
#orz-foot a { color: #9aa3b2; text-decoration: none; }
|
|
126
|
+
#orz-foot a:hover { color: #fff; text-decoration: underline; }
|
|
127
|
+
#orz-ta { width: 100%; height: 100%; box-sizing: border-box; border: 0; padding: 10px;
|
|
128
|
+
font: 14px/1.5 ui-monospace, Menlo, Consolas, monospace; }
|
|
129
|
+
#orz-toast { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%) translateY(20px);
|
|
130
|
+
z-index: 70; background: #111; color: #fff; padding: 9px 16px; border-radius: 8px;
|
|
131
|
+
font: 13px system-ui, sans-serif; opacity: 0; transition: opacity .2s, transform .2s; pointer-events: none; }
|
|
132
|
+
#orz-toast.show { opacity: .95; transform: translateX(-50%) translateY(0); }
|
|
133
|
+
|
|
134
|
+
/* template picker — a popover under the toolbar listing starter templates */
|
|
135
|
+
#orz-template-menu { position: absolute; left: 10px; right: 10px; top: 46px; z-index: 60;
|
|
136
|
+
max-height: 70vh; overflow: auto; padding: 8px; border-radius: 10px;
|
|
137
|
+
background: #23262c; border: 1px solid #3c414a; box-shadow: 0 8px 26px rgba(0,0,0,.4); }
|
|
138
|
+
#orz-template-menu[hidden] { display: none; }
|
|
139
|
+
#orz-template-menu .tpl-group { font: 700 10.5px/1 system-ui, sans-serif; text-transform: uppercase;
|
|
140
|
+
letter-spacing: .06em; color: #7f8896; padding: 8px 8px 4px; }
|
|
141
|
+
#orz-template-menu .tpl {
|
|
142
|
+
display: flex; gap: 9px; align-items: flex-start; width: 100%; text-align: left;
|
|
143
|
+
padding: 8px 9px; border: 0; border-radius: 8px; background: transparent; color: #dfe3ea; cursor: pointer; }
|
|
144
|
+
#orz-template-menu .tpl:hover { background: #34383f; }
|
|
145
|
+
#orz-template-menu .tpl .tpl-ic { flex: 0 0 auto; width: 30px; height: 38px; border-radius: 4px;
|
|
146
|
+
background: #fff; border: 1px solid #c9ccd2; position: relative; overflow: hidden; }
|
|
147
|
+
#orz-template-menu .tpl .tpl-ic::before { content: ""; position: absolute; left: 4px; right: 4px; top: 4px;
|
|
148
|
+
height: 7px; border-radius: 2px; background: var(--tpl-accent, #2f6fe0); }
|
|
149
|
+
#orz-template-menu .tpl .tpl-ic::after { content: ""; position: absolute; left: 4px; right: 4px; top: 15px;
|
|
150
|
+
bottom: 4px; background:
|
|
151
|
+
repeating-linear-gradient(#c7ccd4 0 1.5px, transparent 1.5px 5px); }
|
|
152
|
+
#orz-template-menu .tpl .tpl-meta { min-width: 0; }
|
|
153
|
+
#orz-template-menu .tpl .tpl-label { display: block; font: 600 13px/1.2 system-ui, sans-serif; }
|
|
154
|
+
#orz-template-menu .tpl .tpl-desc { display: block; font: 11.5px/1.35 system-ui, sans-serif; color: #98a0ae; margin-top: 2px; }
|
|
155
|
+
|
|
156
|
+
/* update banner — shown only in edit view (the editor triggers the check) */
|
|
157
|
+
.orz-banner { position: fixed; left: 50%; top: 14px; transform: translateX(-50%); z-index: 80;
|
|
158
|
+
display: none; align-items: center; gap: 10px; background: #2b2f36; color: #eee;
|
|
159
|
+
border: 1px solid #444; border-radius: 9px; padding: 9px 14px;
|
|
160
|
+
font: 13px/1.3 system-ui, sans-serif; box-shadow: 0 4px 16px rgba(0,0,0,.3); }
|
|
161
|
+
.orz-banner.show { display: flex; }
|
|
162
|
+
.orz-banner button { font: 500 12.5px system-ui, sans-serif; color: #e6e8ec; background: #3a3f48;
|
|
163
|
+
border: 1px solid #4a505a; border-radius: 6px; padding: 5px 11px; cursor: pointer; }
|
|
164
|
+
.orz-banner button.upd-primary { background: #3b82f6; border-color: #3b82f6; color: #fff; }
|
|
165
|
+
.orz-banner button:hover { filter: brightness(1.1); }
|
|
166
|
+
`;
|
|
167
|
+
export function buildHtml(o) {
|
|
168
|
+
const config = {
|
|
169
|
+
mode: o.renderer.mode === 'inline' ? 'inline' : 'cdn',
|
|
170
|
+
baseCss: o.renderer.mode === 'inline' ? o.baseCss : undefined,
|
|
171
|
+
themes: o.renderer.mode === 'inline'
|
|
172
|
+
? Object.fromEntries(o.themes.map((t) => [t.id, t.css]))
|
|
173
|
+
: undefined,
|
|
174
|
+
katexCss: o.cdn.katexCss,
|
|
175
|
+
hljsCss: o.cdn.hljsCss,
|
|
176
|
+
enhancers: { mermaidJs: o.cdn.mermaidJs, smilesJs: o.cdn.smilesJs, chartJs: o.cdn.chartJs, hljsJs: o.cdn.hljsJs },
|
|
177
|
+
// self-update: only the file's OWN version lives here (for the "is there a
|
|
178
|
+
// newer one?" comparison). The update SOURCE (packages, manifest, host) is
|
|
179
|
+
// hardcoded in app.js — never config-driven — so it can't be redirected.
|
|
180
|
+
version: o.rendererVersion,
|
|
181
|
+
// template catalog for the in-editor picker (skeleton scaffolding).
|
|
182
|
+
templates: o.templates ?? [],
|
|
183
|
+
};
|
|
184
|
+
const themeOptions = o.themes
|
|
185
|
+
.map((t) => `<option value="${escapeHtml(t.id)}">${escapeHtml(t.id)}</option>`)
|
|
186
|
+
.join('');
|
|
187
|
+
const engineTag = o.renderer.mode === 'inline'
|
|
188
|
+
? `<script data-orz-asset="engine">${escapeForScript(o.renderer.js)}</script>`
|
|
189
|
+
: `<script data-orz-asset="engine" src="${escapeHtml(o.renderer.src)}"></script>`;
|
|
190
|
+
return `<!DOCTYPE html>
|
|
191
|
+
<html lang="en">
|
|
192
|
+
<head>
|
|
193
|
+
<meta charset="utf-8">
|
|
194
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
195
|
+
<meta name="generator" content="orz-paged ${escapeHtml(o.rendererVersion)}">
|
|
196
|
+
<title>${escapeHtml(o.title)}</title>
|
|
197
|
+
<style>${CHROME_CSS}</style>
|
|
198
|
+
</head>
|
|
199
|
+
<body>
|
|
200
|
+
|
|
201
|
+
<div id="orz-pages" data-orz-copy></div>
|
|
202
|
+
<div id="orz-divider" title="Drag to resize"></div>
|
|
203
|
+
|
|
204
|
+
<button id="orz-edit-fab" title="Edit">${ICON.pencil}</button>
|
|
205
|
+
|
|
206
|
+
<div id="orz-panel">
|
|
207
|
+
<div id="orz-toolbar">
|
|
208
|
+
${BRAND}
|
|
209
|
+
<span class="orz-sep"></span>
|
|
210
|
+
<button id="orz-save" class="ic primary" title="Save (Ctrl/Cmd+S)">${ICON.save}</button>
|
|
211
|
+
<button id="orz-export" class="ic" title="Export PDF (print)">${ICON.print}</button>
|
|
212
|
+
<button id="orz-template" class="ic" title="Start from a template">${ICON.template}</button>
|
|
213
|
+
<span class="orz-spacer"></span>
|
|
214
|
+
<button id="orz-sync" class="ic" title="Sync scrolling on/off" aria-pressed="true">${ICON.sync}</button>
|
|
215
|
+
<span id="orz-dyn"></span>
|
|
216
|
+
<select id="orz-theme" title="Theme">${themeOptions}</select>
|
|
217
|
+
</div>
|
|
218
|
+
<div id="orz-template-menu" hidden></div>
|
|
219
|
+
<button id="orz-close" title="Close editor">${ICON.collapse}</button>
|
|
220
|
+
<div id="orz-editor-host"><textarea id="orz-ta" spellcheck="false"></textarea></div>
|
|
221
|
+
<div id="orz-foot">© <a href="https://markdown.orz.how" target="_blank" rel="noopener noreferrer">orz-markdown</a></div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div id="orz-update" class="orz-banner">
|
|
225
|
+
<span class="upd-text"></span>
|
|
226
|
+
<button id="orz-upd-apply" class="upd-primary">Update</button>
|
|
227
|
+
<button id="orz-upd-dismiss">Dismiss</button>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div id="orz-toast"></div>
|
|
231
|
+
|
|
232
|
+
<script type="text/markdown" id="orz-src">
|
|
233
|
+
${escapeForScript(o.source)}
|
|
234
|
+
</script>
|
|
235
|
+
|
|
236
|
+
${engineTag}
|
|
237
|
+
<script>${escapeForScript(o.runtime)}</script>
|
|
238
|
+
<script data-orz-asset="config">window.__ORZ_PAGED__ = ${JSON.stringify(config)};</script>
|
|
239
|
+
<script data-orz-asset="app">${escapeForScript(o.appJs)}</script>
|
|
240
|
+
</body>
|
|
241
|
+
</html>`;
|
|
242
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orz-paged — LOCKED INTERFACE.
|
|
3
|
+
*
|
|
4
|
+
* Every module codes against these types and nothing else. Changing a type here
|
|
5
|
+
* ripples across the document layer, the assembler, and the engine — treat edits
|
|
6
|
+
* as a coordinated change. (Mirrors orz-slides' `src/types.ts` role.)
|
|
7
|
+
*
|
|
8
|
+
* Vocabulary follows DESIGN.md §6–§9 and docs/document-model.md (the curated
|
|
9
|
+
* subset). Lengths: margins in **mm**, font size in **pt**, line-height unitless.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|