tosijs-ui 1.6.11 → 1.6.13
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/CHANGELOG.md +78 -0
- package/README.md +0 -1
- package/dist/doc-browser.js +35 -0
- package/dist/doc-system/book-html.d.ts +26 -0
- package/dist/doc-system/book-html.js +129 -0
- package/dist/doc-system/doc-system.js +38 -0
- package/dist/doc-system/render.d.ts +0 -2
- package/dist/doc-system/render.js +31 -9
- package/dist/doc-system/site/epub.d.ts +50 -0
- package/dist/doc-system/site/epub.js +511 -0
- package/dist/doc-system/site/index.d.ts +4 -0
- package/dist/doc-system/site/index.js +2 -0
- package/dist/doc-system/site/orchestrator.js +7 -0
- package/dist/doc-system/site/pdf.d.ts +17 -0
- package/dist/doc-system/site/pdf.js +48 -0
- package/dist/doc-system/site/site-config.d.ts +15 -0
- package/dist/float.js +33 -22
- package/dist/iife.js +172 -89
- package/dist/iife.js.map +13 -12
- package/dist/live-example/insert-examples.js +8 -0
- package/dist/menu.js +32 -15
- package/dist/size-break.js +2 -2
- package/dist/tooltip.js +14 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +40 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.6.13
|
|
4
|
+
|
|
5
|
+
The documentation system becomes a real publishing pipeline: every doc site can
|
|
6
|
+
now emit an **ePub** of the whole corpus, **print to PDF** from the browser, and
|
|
7
|
+
**deep-link every example from the book back to the live site**. All changes are
|
|
8
|
+
**additive — no breaking changes** — and concern the doc-site tooling
|
|
9
|
+
(`tosijs-ui/site`) and doc browser, not the components.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **ePub of the whole doc site** (`tosijs-ui/site`). Set `epub: true` (or an
|
|
14
|
+
options object) in the site config and the build emits a valid EPUB 3 of the
|
|
15
|
+
corpus alongside the static pages, regenerated on every build:
|
|
16
|
+
- one chapter per doc in nav-tree order, a readable **Contents page** in the
|
|
17
|
+
reading flow plus the reader's Contents drawer (EPUB3 `nav.xhtml` + EPUB2
|
|
18
|
+
`toc.ncx`), and a customizable stylesheet (`epub.css` / default force-wraps
|
|
19
|
+
code listings)
|
|
20
|
+
- an **auto-generated cover** from the title + favicon (`epub.cover` /
|
|
21
|
+
`epub.coverColor` to override); needs the optional `@resvg/resvg-js`, and is
|
|
22
|
+
omitted gracefully if it's absent
|
|
23
|
+
- **Print to PDF** from the doc browser — a "Print as PDF" item in the settings
|
|
24
|
+
menu assembles the whole corpus into a print-styled window and opens the print
|
|
25
|
+
dialog. No server, no headless browser. (A headless `buildPdf` is also available
|
|
26
|
+
for CI via `bun run book:pdf`, not deployed.)
|
|
27
|
+
- **"Download ePub"** item in the settings menu (alongside Print), linking the
|
|
28
|
+
built book.
|
|
29
|
+
- **Example anchors + book deep links.** Every live example gets a stable anchor
|
|
30
|
+
(`/{slug}/#example-1`, or a custom `id` via a ` ```js#my-id ` fence on any block
|
|
31
|
+
of the group). Arriving at such a URL scrolls the example into view with a brief
|
|
32
|
+
highlight. Each example in the ePub/PDF links back to its anchor on the live
|
|
33
|
+
site — a reader is one tap from the real, interactive, editable version.
|
|
34
|
+
|
|
35
|
+
### Removed
|
|
36
|
+
|
|
37
|
+
- The broken **bundlejs** size badge (bundlejs errors computing the bundle).
|
|
38
|
+
|
|
39
|
+
### Notes
|
|
40
|
+
|
|
41
|
+
- The ePub needs `happy-dom` (dev dep) + the `zip` CLI; the generated cover needs
|
|
42
|
+
the optional `@resvg/resvg-js`.
|
|
43
|
+
|
|
44
|
+
## 1.6.12
|
|
45
|
+
|
|
46
|
+
Packaging improvements for independent, tree-shakeable consumption. All changes
|
|
47
|
+
are **additive — no breaking changes**; one minor behavioral edge case is noted
|
|
48
|
+
below.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **Subpath exports**, so an app can import only what it needs without the barrel
|
|
53
|
+
dragging in dev tools:
|
|
54
|
+
- curated: `tosijs-ui/icons`, `tosijs-ui/code-editor`, `tosijs-ui/live-example`,
|
|
55
|
+
`tosijs-ui/doc-browser`, `tosijs-ui/diff`, `tosijs-ui/theme`
|
|
56
|
+
- per-component wildcard: `import { tosiRating } from 'tosijs-ui/rating'`
|
|
57
|
+
(resolves `dist/rating.js`, registering just that element)
|
|
58
|
+
|
|
59
|
+
The full barrel `import 'tosijs-ui'` is unchanged and still registers every
|
|
60
|
+
`<tosi-*>` element.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- **`menu`, `tooltip`, and `float` now inject their stylesheet and register their
|
|
65
|
+
global listeners on first use, not at import.** Importing these modules (or the
|
|
66
|
+
barrel) no longer has import-time side effects. This also fixes a latent bug
|
|
67
|
+
where the menu `keydown` handler attached to `document.body` at import time,
|
|
68
|
+
which may not exist yet.
|
|
69
|
+
|
|
70
|
+
### Possible edge case
|
|
71
|
+
|
|
72
|
+
If you imported `menu` / `tooltip` / `float` **purely for the side effect** — i.e.
|
|
73
|
+
you hand-author markup with the internal classes (`.tosi-menu`, `.tosi-tooltip`,
|
|
74
|
+
…) and relied on a bare `import` injecting the stylesheet, **without** ever
|
|
75
|
+
calling the API (`popMenu`, `popDropMenu`, `initTooltips`, `showTooltip`) or using
|
|
76
|
+
the `<tosi-*>` element — those styles now apply only once the API/element is first
|
|
77
|
+
used. Normal usage (the element creators, the `<tosi-*>` elements, and the
|
|
78
|
+
`popMenu`/`initTooltips` entry points) is unaffected.
|
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
[ui.tosijs.net live demo](https://ui.tosijs.net) | [tosijs](https://tosijs.net) | [discord](https://discord.gg/ramJ9rgky5) | [github](https://github.com/tonioloewald/tosijs-ui#readme) | [npm](https://www.npmjs.com/package/tosijs-ui)
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/tosijs-ui)
|
|
8
|
-
[](https://bundlejs.com/?q=tosijs-ui&badge=)
|
|
9
8
|
[](https://www.jsdelivr.com/package/npm/tosijs-ui)
|
|
10
9
|
|
|
11
10
|
<center>
|
package/dist/doc-browser.js
CHANGED
|
@@ -241,6 +241,15 @@ const testIndicatorStyleSpec = {
|
|
|
241
241
|
justifyContent: 'center',
|
|
242
242
|
fontWeight: 'bold',
|
|
243
243
|
},
|
|
244
|
+
// Deep-linked example (e.g. arriving at /slug/#example-2): brief highlight pulse
|
|
245
|
+
'@keyframes example-target-pulse': {
|
|
246
|
+
from: { boxShadow: `0 0 0 3px ${varDefault.accent('#007aff')}` },
|
|
247
|
+
to: { boxShadow: '0 0 0 3px transparent' },
|
|
248
|
+
},
|
|
249
|
+
'.example-target': {
|
|
250
|
+
animation: 'example-target-pulse 1.5s ease-out',
|
|
251
|
+
borderRadius: vars.roundedRadius,
|
|
252
|
+
},
|
|
244
253
|
};
|
|
245
254
|
export function createDocBrowser(options) {
|
|
246
255
|
const { docs, context = {}, projectName = '', projectLinks = {}, navSize = 200, minSize = 600, routing = 'query', initialRoute, onRouteChange, navbarLinks, contentElement, } = options;
|
|
@@ -560,6 +569,31 @@ export function createDocBrowser(options) {
|
|
|
560
569
|
}
|
|
561
570
|
}
|
|
562
571
|
};
|
|
572
|
+
// Deep-link to a specific live example: arriving at /slug/#example-2 (or a
|
|
573
|
+
// custom ```js#my-id anchor) scrolls it into view with a brief highlight. The
|
|
574
|
+
// example ids are set by insertExamples, so this runs after it. Skipped in
|
|
575
|
+
// memory routing (which must never touch window.location).
|
|
576
|
+
const scrollToHashExample = () => {
|
|
577
|
+
if (memoryRouting)
|
|
578
|
+
return;
|
|
579
|
+
const hash = location.hash.replace(/^#/, '');
|
|
580
|
+
if (!hash)
|
|
581
|
+
return;
|
|
582
|
+
requestAnimationFrame(() => {
|
|
583
|
+
let el = null;
|
|
584
|
+
try {
|
|
585
|
+
el = docContent.querySelector(`#${CSS.escape(hash)}`);
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
el = null;
|
|
589
|
+
}
|
|
590
|
+
if (!el)
|
|
591
|
+
return;
|
|
592
|
+
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
593
|
+
el.classList.add('example-target');
|
|
594
|
+
setTimeout(() => el && el.classList.remove('example-target'), 1600);
|
|
595
|
+
});
|
|
596
|
+
};
|
|
563
597
|
let adoptInitialContent = contentElement !== undefined && !memoryRouting;
|
|
564
598
|
const showDoc = (doc) => {
|
|
565
599
|
if (adoptInitialContent) {
|
|
@@ -572,6 +606,7 @@ export function createDocBrowser(options) {
|
|
|
572
606
|
// Stamp each example with its source file (for the source↔doc map). doc.path
|
|
573
607
|
// is the extracted file (.md, or a source file with doc comments).
|
|
574
608
|
LiveExample.insertExamples(docContent, context, doc.path || undefined);
|
|
609
|
+
scrollToHashExample();
|
|
575
610
|
if (routing === 'path') {
|
|
576
611
|
document.title = projectName
|
|
577
612
|
? `${doc.title} — ${projectName}`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NavNode, NavDoc } from './nav-tree';
|
|
2
|
+
export interface BookDoc {
|
|
3
|
+
filename: string;
|
|
4
|
+
title: string;
|
|
5
|
+
text: string;
|
|
6
|
+
parent?: string;
|
|
7
|
+
hidden?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT_BOOK_CSS = "/* doc-system book stylesheet */\nhtml { font-size: 100%; }\nbody {\n font-family: Georgia, 'Times New Roman', serif;\n line-height: 1.5;\n margin: 0 1em;\n color: #1a1a1a;\n}\nh1, h2, h3, h4, h5, h6 {\n font-family: -apple-system, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.2;\n margin: 1.4em 0 0.5em;\n}\nh1 { font-size: 1.8em; }\nh2 { font-size: 1.4em; }\nh3 { font-size: 1.2em; }\np { margin: 0.6em 0; }\na { color: #08c; text-decoration: none; }\nul, ol { margin: 0.6em 0; padding-left: 1.4em; }\nblockquote {\n margin: 0.8em 0;\n padding: 0 0 0 1em;\n border-left: 3px solid #ccc;\n color: #555;\n}\nimg { max-width: 100%; height: auto; }\ntable { border-collapse: collapse; margin: 0.8em 0; font-size: 0.85em; }\nth, td { border: 1px solid #ccc; padding: 0.3em 0.6em; text-align: left; }\ncode, pre {\n font-family: 'SF Mono', Menlo, Consolas, monospace;\n font-size: 0.8em;\n}\n:not(pre) > code {\n background: #f3f3f3;\n padding: 0.1em 0.3em;\n border-radius: 3px;\n}\npre {\n background: #f6f8fa;\n border: 1px solid #e1e4e8;\n border-radius: 4px;\n padding: 0.7em 0.9em;\n margin: 0.8em 0;\n white-space: pre-wrap;\n word-break: break-word;\n overflow-wrap: anywhere;\n}\npre code { background: none; padding: 0; }\n/* the in-flow Contents page (ePub spine TOC) */\n.toc-title { text-align: center; }\nol.toc, ol.toc ol { list-style: none; padding-left: 0; }\nol.toc ol { padding-left: 1.2em; }\nol.toc li { margin: 0.3em 0; }\nol.toc > li > a { font-weight: bold; }\n/* per-example \"run this live\" deep link */\n.example-live-link { margin: 0.6em 0 0; font-size: 0.85em; }\n.example-live-link a { color: #08c; font-weight: bold; }\n";
|
|
10
|
+
export declare const PRINT_CSS = "\n@page { margin: 18mm 16mm; }\n.book-title { text-align: center; margin: 30vh 0 1em; }\n.book-toc { page-break-after: always; }\n.book-toc h2 { page-break-before: avoid; }\n.book-toc ol { list-style: none; padding-left: 0; }\n.book-toc ol ol { padding-left: 1.2em; }\n.book-toc a { color: inherit; }\n.chapter { page-break-before: always; }\n";
|
|
11
|
+
export declare function escapeHtml(s: string): string;
|
|
12
|
+
export declare function slugify(s: string): string;
|
|
13
|
+
/** Drop the `<!--{ … }-->` metadata directives the extractor leaves in the text. */
|
|
14
|
+
export declare function stripDocMeta(text: string): string;
|
|
15
|
+
/** Flatten a nav-tree depth-first into reading order. */
|
|
16
|
+
export declare function flatten<T extends NavDoc>(nodes: NavNode<T>[]): NavNode<T>[];
|
|
17
|
+
export interface BookHtmlOptions {
|
|
18
|
+
title: string;
|
|
19
|
+
/** full stylesheet (defaults to DEFAULT_BOOK_CSS + PRINT_CSS) */
|
|
20
|
+
css?: string;
|
|
21
|
+
lang?: string;
|
|
22
|
+
/** inject a script that opens the print dialog once the page has loaded */
|
|
23
|
+
autoPrint?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Assemble the whole corpus into one self-contained printable HTML document. */
|
|
26
|
+
export declare function buildBookHtml(docs: BookDoc[], opts: BookHtmlOptions): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Shared, browser-safe assembly of the whole doc corpus into one printable HTML
|
|
3
|
+
"book": a title page, a generated table of contents, then every doc as a chapter
|
|
4
|
+
that starts on a new page. Used by the client-side **Print** button in the
|
|
5
|
+
doc-browser (the user's browser prints it to PDF) and by the headless PDF builder
|
|
6
|
+
(site/pdf.ts). Pure rendering — no fs, no Node-only APIs — so it bundles into the
|
|
7
|
+
iife. Live examples are pretty-printed, force-wrapped code listings, not executed.
|
|
8
|
+
*/
|
|
9
|
+
import { renderDocMarkdown } from './render';
|
|
10
|
+
import { buildSlugMap } from './routing';
|
|
11
|
+
import { buildNavTree } from './nav-tree';
|
|
12
|
+
// Clean book typography; force-wraps code so listings never overflow a page.
|
|
13
|
+
export const DEFAULT_BOOK_CSS = `/* doc-system book stylesheet */
|
|
14
|
+
html { font-size: 100%; }
|
|
15
|
+
body {
|
|
16
|
+
font-family: Georgia, 'Times New Roman', serif;
|
|
17
|
+
line-height: 1.5;
|
|
18
|
+
margin: 0 1em;
|
|
19
|
+
color: #1a1a1a;
|
|
20
|
+
}
|
|
21
|
+
h1, h2, h3, h4, h5, h6 {
|
|
22
|
+
font-family: -apple-system, 'Helvetica Neue', Arial, sans-serif;
|
|
23
|
+
line-height: 1.2;
|
|
24
|
+
margin: 1.4em 0 0.5em;
|
|
25
|
+
}
|
|
26
|
+
h1 { font-size: 1.8em; }
|
|
27
|
+
h2 { font-size: 1.4em; }
|
|
28
|
+
h3 { font-size: 1.2em; }
|
|
29
|
+
p { margin: 0.6em 0; }
|
|
30
|
+
a { color: #08c; text-decoration: none; }
|
|
31
|
+
ul, ol { margin: 0.6em 0; padding-left: 1.4em; }
|
|
32
|
+
blockquote {
|
|
33
|
+
margin: 0.8em 0;
|
|
34
|
+
padding: 0 0 0 1em;
|
|
35
|
+
border-left: 3px solid #ccc;
|
|
36
|
+
color: #555;
|
|
37
|
+
}
|
|
38
|
+
img { max-width: 100%; height: auto; }
|
|
39
|
+
table { border-collapse: collapse; margin: 0.8em 0; font-size: 0.85em; }
|
|
40
|
+
th, td { border: 1px solid #ccc; padding: 0.3em 0.6em; text-align: left; }
|
|
41
|
+
code, pre {
|
|
42
|
+
font-family: 'SF Mono', Menlo, Consolas, monospace;
|
|
43
|
+
font-size: 0.8em;
|
|
44
|
+
}
|
|
45
|
+
:not(pre) > code {
|
|
46
|
+
background: #f3f3f3;
|
|
47
|
+
padding: 0.1em 0.3em;
|
|
48
|
+
border-radius: 3px;
|
|
49
|
+
}
|
|
50
|
+
pre {
|
|
51
|
+
background: #f6f8fa;
|
|
52
|
+
border: 1px solid #e1e4e8;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
padding: 0.7em 0.9em;
|
|
55
|
+
margin: 0.8em 0;
|
|
56
|
+
white-space: pre-wrap;
|
|
57
|
+
word-break: break-word;
|
|
58
|
+
overflow-wrap: anywhere;
|
|
59
|
+
}
|
|
60
|
+
pre code { background: none; padding: 0; }
|
|
61
|
+
/* the in-flow Contents page (ePub spine TOC) */
|
|
62
|
+
.toc-title { text-align: center; }
|
|
63
|
+
ol.toc, ol.toc ol { list-style: none; padding-left: 0; }
|
|
64
|
+
ol.toc ol { padding-left: 1.2em; }
|
|
65
|
+
ol.toc li { margin: 0.3em 0; }
|
|
66
|
+
ol.toc > li > a { font-weight: bold; }
|
|
67
|
+
/* per-example "run this live" deep link */
|
|
68
|
+
.example-live-link { margin: 0.6em 0 0; font-size: 0.85em; }
|
|
69
|
+
.example-live-link a { color: #08c; font-weight: bold; }
|
|
70
|
+
`;
|
|
71
|
+
// Print-only layout: page margins, a chapter per page, an avoid-break TOC.
|
|
72
|
+
export const PRINT_CSS = `
|
|
73
|
+
@page { margin: 18mm 16mm; }
|
|
74
|
+
.book-title { text-align: center; margin: 30vh 0 1em; }
|
|
75
|
+
.book-toc { page-break-after: always; }
|
|
76
|
+
.book-toc h2 { page-break-before: avoid; }
|
|
77
|
+
.book-toc ol { list-style: none; padding-left: 0; }
|
|
78
|
+
.book-toc ol ol { padding-left: 1.2em; }
|
|
79
|
+
.book-toc a { color: inherit; }
|
|
80
|
+
.chapter { page-break-before: always; }
|
|
81
|
+
`;
|
|
82
|
+
export function escapeHtml(s) {
|
|
83
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
84
|
+
}
|
|
85
|
+
export function slugify(s) {
|
|
86
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
87
|
+
}
|
|
88
|
+
/** Drop the `<!--{ … }-->` metadata directives the extractor leaves in the text. */
|
|
89
|
+
export function stripDocMeta(text) {
|
|
90
|
+
return text.replace(/<!--\{[\s\S]*?\}-->\s*/g, '');
|
|
91
|
+
}
|
|
92
|
+
/** Flatten a nav-tree depth-first into reading order. */
|
|
93
|
+
export function flatten(nodes) {
|
|
94
|
+
const out = [];
|
|
95
|
+
for (const node of nodes) {
|
|
96
|
+
out.push(node);
|
|
97
|
+
out.push(...flatten(node.children));
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function tocHtml(nodes) {
|
|
102
|
+
return nodes
|
|
103
|
+
.map((node) => `<li><a href="#${slugify(node.doc.filename)}">${escapeHtml(node.doc.title)}</a>${node.children.length ? `<ol>${tocHtml(node.children)}</ol>` : ''}</li>`)
|
|
104
|
+
.join('');
|
|
105
|
+
}
|
|
106
|
+
/** Assemble the whole corpus into one self-contained printable HTML document. */
|
|
107
|
+
export function buildBookHtml(docs, opts) {
|
|
108
|
+
const visible = docs.filter((d) => !d.hidden);
|
|
109
|
+
const slugMap = buildSlugMap(visible);
|
|
110
|
+
const roots = buildNavTree(visible, slugMap);
|
|
111
|
+
const css = opts.css ?? `${DEFAULT_BOOK_CSS}\n${PRINT_CSS}`;
|
|
112
|
+
const chapters = flatten(roots)
|
|
113
|
+
.map((node) => `<section class="chapter" id="${slugify(node.doc.filename)}">\n${renderDocMarkdown(stripDocMeta(node.doc.text))}\n</section>`)
|
|
114
|
+
.join('\n');
|
|
115
|
+
return `<!DOCTYPE html>
|
|
116
|
+
<html lang="${opts.lang ?? 'en'}">
|
|
117
|
+
<head>
|
|
118
|
+
<meta charset="utf-8"/>
|
|
119
|
+
<title>${escapeHtml(opts.title)}</title>
|
|
120
|
+
<style>${css}</style>
|
|
121
|
+
</head>
|
|
122
|
+
<body>
|
|
123
|
+
<h1 class="book-title">${escapeHtml(opts.title)}</h1>
|
|
124
|
+
<nav class="book-toc"><h2>Contents</h2><ol>${tocHtml(roots)}</ol></nav>
|
|
125
|
+
${chapters}
|
|
126
|
+
${opts.autoPrint ? '<script>addEventListener("load",function(){setTimeout(function(){print()},300)})</script>' : ''}
|
|
127
|
+
</body>
|
|
128
|
+
</html>`;
|
|
129
|
+
}
|
|
@@ -59,6 +59,7 @@ A self-contained, controllable embed (e.g. docs in a floating panel):
|
|
|
59
59
|
import { Component, StyleSheet, elements, tosi, vars } from 'tosijs';
|
|
60
60
|
import { createDocBrowser } from '../doc-browser';
|
|
61
61
|
import { buildSlugMap, legacyQueryPath } from './routing';
|
|
62
|
+
import { buildBookHtml, slugify } from './book-html';
|
|
62
63
|
import { docSystemStyleSpec } from './doc-system-styles';
|
|
63
64
|
import { icons } from '../icons';
|
|
64
65
|
import { popMenu } from '../menu';
|
|
@@ -204,6 +205,43 @@ export class TosiDocSystem extends Component {
|
|
|
204
205
|
title: 'settings',
|
|
205
206
|
onClick: (event) => {
|
|
206
207
|
const menuItems = [];
|
|
208
|
+
// Print / ePub of the whole corpus, before a separator + the prefs.
|
|
209
|
+
let projectName = '';
|
|
210
|
+
try {
|
|
211
|
+
projectName = JSON.parse(this.config || '{}').projectName || '';
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// ignore — fall through to document.title
|
|
215
|
+
}
|
|
216
|
+
const bookTitle = projectName || document.title || 'Documentation';
|
|
217
|
+
menuItems.push({
|
|
218
|
+
caption: 'Print as PDF',
|
|
219
|
+
icon: 'printer',
|
|
220
|
+
action: () => {
|
|
221
|
+
const win = window.open('', '_blank');
|
|
222
|
+
if (!win) {
|
|
223
|
+
window.alert('Allow pop-ups to print the documentation as a book.');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
win.document.open();
|
|
227
|
+
win.document.write(buildBookHtml(this.corpus, {
|
|
228
|
+
title: bookTitle,
|
|
229
|
+
autoPrint: true,
|
|
230
|
+
}));
|
|
231
|
+
win.document.close();
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
menuItems.push({
|
|
235
|
+
caption: 'Download ePub',
|
|
236
|
+
icon: 'book',
|
|
237
|
+
action: () => {
|
|
238
|
+
const link = document.createElement('a');
|
|
239
|
+
link.href = `/${slugify(bookTitle)}.epub`;
|
|
240
|
+
link.download = '';
|
|
241
|
+
link.click();
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
menuItems.push(null);
|
|
207
245
|
const localeOptions = i18n.localeOptions.value || [];
|
|
208
246
|
if (localeOptions.length > 1) {
|
|
209
247
|
menuItems.push({
|
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
/*
|
|
2
2
|
Shared markdown rendering for the static doc system.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
so the
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Both the build-time pre-render and the client (doc-browser) call THIS function, so
|
|
5
|
+
the static page hydrates byte-identically. Fenced code blocks are left as
|
|
6
|
+
<pre><code class="language-*"> so the static page shows readable, indexable code
|
|
7
|
+
AND the component can later upgrade consecutive blocks into <live-example> widgets
|
|
8
|
+
via insertExamples(). A fence info string may carry a `#id` (```js#my-example) to
|
|
9
|
+
give that example a stable anchor — see the docMarked renderer below.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import { Marked, Renderer } from 'marked';
|
|
12
|
+
const baseRenderer = new Renderer();
|
|
13
|
+
// A doc-scoped marked instance. Its ONLY customization: a fenced code block whose
|
|
14
|
+
// info string carries a `#id` suffix — e.g. ```js#my-example — renders the lang
|
|
15
|
+
// clean (`language-js`, so all existing grouping/highlighting is unaffected) but
|
|
16
|
+
// stamps `data-example-id="my-example"` on the <pre>. insertExamples (client) and
|
|
17
|
+
// the book builders both read that to give the live example a stable anchor for
|
|
18
|
+
// deep-linking. A block with no `#id` is byte-identical to default marked output.
|
|
19
|
+
const docMarked = new Marked();
|
|
20
|
+
docMarked.use({
|
|
21
|
+
renderer: {
|
|
22
|
+
code(token) {
|
|
23
|
+
const info = String(token.lang || '');
|
|
24
|
+
const hash = info.indexOf('#');
|
|
25
|
+
if (hash === -1)
|
|
26
|
+
return false; // default rendering
|
|
27
|
+
const id = info.slice(hash + 1).match(/^[A-Za-z0-9_-]+/)?.[0];
|
|
28
|
+
if (!id)
|
|
29
|
+
return false;
|
|
30
|
+
const html = baseRenderer.code({ ...token, lang: info.slice(0, hash) });
|
|
31
|
+
return html.replace(/^<pre>/, `<pre data-example-id="${id}">`);
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
13
35
|
/** Render a doc's markdown text to HTML (synchronous, default marked options). */
|
|
14
36
|
export function renderDocMarkdown(text) {
|
|
15
|
-
return
|
|
37
|
+
return docMarked.parse(text);
|
|
16
38
|
}
|
|
17
39
|
/**
|
|
18
40
|
* Derive a clean <meta name="description"> from a doc's first prose paragraph.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SiteConfig } from './site-config';
|
|
2
|
+
import { DEFAULT_BOOK_CSS, stripDocMeta } from '../book-html';
|
|
3
|
+
export { DEFAULT_BOOK_CSS, stripDocMeta };
|
|
4
|
+
declare global {
|
|
5
|
+
var Bun: any;
|
|
6
|
+
}
|
|
7
|
+
export interface BookMeta {
|
|
8
|
+
title: string;
|
|
9
|
+
author: string;
|
|
10
|
+
language: string;
|
|
11
|
+
/** unique identifier (a URL or urn:) */
|
|
12
|
+
identifier: string;
|
|
13
|
+
/** dcterms:modified timestamp, e.g. 2026-06-28T00:00:00Z */
|
|
14
|
+
modified: string;
|
|
15
|
+
}
|
|
16
|
+
export interface BuildEpubOptions {
|
|
17
|
+
/** corpus path; default config.docsJson ?? 'demo/docs.json' */
|
|
18
|
+
docsJson?: string;
|
|
19
|
+
/** output .epub path; default `${outputDir}/${slug(name)}.epub` */
|
|
20
|
+
output?: string;
|
|
21
|
+
/** book title; default config.name */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** author / publisher line */
|
|
24
|
+
author?: string;
|
|
25
|
+
/** BCP-47 language; default 'en' */
|
|
26
|
+
language?: string;
|
|
27
|
+
/** override the whole book stylesheet */
|
|
28
|
+
css?: string;
|
|
29
|
+
/** extra CSS appended to the default stylesheet (ignored if `css` is set) */
|
|
30
|
+
extraCss?: string;
|
|
31
|
+
/** ISO timestamp for dcterms:modified; default now (seconds precision) */
|
|
32
|
+
modified?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Cover image path (png/jpeg/gif). If omitted, a cover is generated from the
|
|
35
|
+
* book title + the site favicon (rasterized to PNG via @resvg/resvg-js).
|
|
36
|
+
*/
|
|
37
|
+
cover?: string;
|
|
38
|
+
/** background color for the generated cover, default '#1f2933' */
|
|
39
|
+
coverColor?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function escapeXml(s: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Best-effort normalize marked's HTML output to well-formed XHTML: self-close
|
|
44
|
+
* void elements and escape bare `&` that isn't already part of an entity.
|
|
45
|
+
*/
|
|
46
|
+
export declare function toXhtml(html: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Build an EPUB 3 book from the extracted corpus. Returns the output path.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildEpub(config: SiteConfig, opts?: BuildEpubOptions): Promise<string>;
|