tosijs-ui 1.6.12 → 1.6.14

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 CHANGED
@@ -1,5 +1,66 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.14
4
+
5
+ Lighter live-example transpilation. Additive; affects only how the live-example
6
+ runner loads its (optional, lazy) transpiler.
7
+
8
+ ### Changed
9
+
10
+ - **Live examples now use tjs-lang 0.8.5's self-contained browser bundles**
11
+ (`tjs-lang/browser` + `tjs-lang/browser/from-ts`). The transpiler is a single
12
+ self-contained chunk, and the TypeScript compiler is **lazy-loaded from a CDN at
13
+ runtime** only when a `ts` example actually transpiles — so `typescript` (~MB)
14
+ and the transpiler's own deps are never in a consumer's dependency graph. This
15
+ also fixes the `ts` example path, which previously tried to load the TypeScript
16
+ compiler through a CDN transform that timed out on its size.
17
+ - `tjs-lang` optional peer bumped to `^0.8.6`.
18
+
19
+ ### Fixed
20
+
21
+ - A garbled character in the first `example` doc snippet that made it throw.
22
+
23
+ ## 1.6.13
24
+
25
+ The documentation system becomes a real publishing pipeline: every doc site can
26
+ now emit an **ePub** of the whole corpus, **print to PDF** from the browser, and
27
+ **deep-link every example from the book back to the live site**. All changes are
28
+ **additive — no breaking changes** — and concern the doc-site tooling
29
+ (`tosijs-ui/site`) and doc browser, not the components.
30
+
31
+ ### Added
32
+
33
+ - **ePub of the whole doc site** (`tosijs-ui/site`). Set `epub: true` (or an
34
+ options object) in the site config and the build emits a valid EPUB 3 of the
35
+ corpus alongside the static pages, regenerated on every build:
36
+ - one chapter per doc in nav-tree order, a readable **Contents page** in the
37
+ reading flow plus the reader's Contents drawer (EPUB3 `nav.xhtml` + EPUB2
38
+ `toc.ncx`), and a customizable stylesheet (`epub.css` / default force-wraps
39
+ code listings)
40
+ - an **auto-generated cover** from the title + favicon (`epub.cover` /
41
+ `epub.coverColor` to override); needs the optional `@resvg/resvg-js`, and is
42
+ omitted gracefully if it's absent
43
+ - **Print to PDF** from the doc browser — a "Print as PDF" item in the settings
44
+ menu assembles the whole corpus into a print-styled window and opens the print
45
+ dialog. No server, no headless browser. (A headless `buildPdf` is also available
46
+ for CI via `bun run book:pdf`, not deployed.)
47
+ - **"Download ePub"** item in the settings menu (alongside Print), linking the
48
+ built book.
49
+ - **Example anchors + book deep links.** Every live example gets a stable anchor
50
+ (`/{slug}/#example-1`, or a custom `id` via a ` ```js#my-id ` fence on any block
51
+ of the group). Arriving at such a URL scrolls the example into view with a brief
52
+ highlight. Each example in the ePub/PDF links back to its anchor on the live
53
+ site — a reader is one tap from the real, interactive, editable version.
54
+
55
+ ### Removed
56
+
57
+ - The broken **bundlejs** size badge (bundlejs errors computing the bundle).
58
+
59
+ ### Notes
60
+
61
+ - The ePub needs `happy-dom` (dev dep) + the `zip` CLI; the generated cover needs
62
+ the optional `@resvg/resvg-js`.
63
+
3
64
  ## 1.6.12
4
65
 
5
66
  Packaging improvements for independent, tree-shakeable consumption. All changes
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
  [![tosijs is on NPM](https://badge.fury.io/js/tosijs-ui.svg)](https://www.npmjs.com/package/tosijs-ui)
8
- [![tosijs is about 10kB gzipped](https://deno.bundlejs.com/?q=tosijs-ui&badge=)](https://bundlejs.com/?q=tosijs-ui&badge=)
9
8
  [![tosijs on jsdelivr](https://data.jsdelivr.com/v1/package/npm/tosijs-ui/badge)](https://www.jsdelivr.com/package/npm/tosijs-ui)
10
9
 
11
10
  <center>
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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,5 +1,3 @@
1
- import { MarkedOptions } from 'marked';
2
- export declare const docMarkedOptions: MarkedOptions;
3
1
  /** Render a doc's markdown text to HTML (synchronous, default marked options). */
4
2
  export declare function renderDocMarkdown(text: string): string;
5
3
  /**
@@ -1,18 +1,40 @@
1
1
  /*
2
2
  Shared markdown rendering for the static doc system.
3
3
 
4
- `renderDocMarkdown` MUST stay in lockstep with how <tosi-md> renders doc text
5
- (see src/markdown-viewer.ts: `marked(source, this.options)` with default options),
6
- so the build-time pre-render is byte-identical to what the component would produce
7
- on the client. Fenced code blocks are left as <pre><code class="language-*"> so the
8
- static page shows readable, indexable code AND the component can later upgrade
9
- consecutive blocks into <live-example> widgets via insertExamples().
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 { marked } from 'marked';
12
- export const docMarkedOptions = {};
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 marked(text, docMarkedOptions);
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>;