@pagenary/publisher 2026.5.1 → 2026.5.2
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 +56 -34
- package/package.json +1 -1
- package/scripts/build-tenants.js +5 -3
- package/scripts/lib/seo-generator.js +78 -14
- package/site/app.js +1 -1
- package/site/index.html +1 -1
- package/site/llms.txt +9 -9
- package/site/pages/api.html +16 -12
- package/site/pages/architecture.html +16 -12
- package/site/pages/deployment.html +16 -12
- package/site/pages/developer-guide.html +16 -12
- package/site/pages/extending.html +16 -12
- package/site/pages/quickstart.html +50 -38
- package/site/pages/seo-strategy.html +47 -26
- package/site/pages/tenant-config.html +46 -12
- package/site/pages/welcome.html +15 -11
- package/site/robots.txt +2 -2
- package/site/sections/quickstart.js +1 -1
- package/site/sections/seo-strategy.js +1 -1
- package/site/sections/tenant-config.js +1 -1
- package/site/seo.js +1 -1
- package/site/sitemap.xml +20 -20
- package/src/app.js +2 -1
- package/src/seo.js +28 -7
package/README.md
CHANGED
|
@@ -6,13 +6,25 @@ Transform shared documentation templates into tenant-specific bundles with custo
|
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
|
+
Install the package and drive it with the `pagenary` CLI — **no clone required**.
|
|
10
|
+
New here? Follow the [Getting Started guide](docs/GETTING-STARTED.md).
|
|
11
|
+
|
|
9
12
|
```bash
|
|
10
|
-
npm install
|
|
11
|
-
npm run dev # Build + serve with watch mode
|
|
13
|
+
npm install --save-dev @pagenary/publisher
|
|
12
14
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
npx pagenary build:tenants my-docs # build your tenant (see Tenant Registry below)
|
|
16
|
+
npx pagenary serve # preview on http://localhost:5173
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Commands: `build`, `build:tenants [id]`, `tenants:list`, `serve` (run
|
|
20
|
+
`npx pagenary --help`). The package also ships a compiled reference site under `site/`.
|
|
21
|
+
|
|
22
|
+
**Building from source** (contributors / modifying Pagenary):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install
|
|
26
|
+
npm run dev # build + serve with watch mode
|
|
27
|
+
npm run build # build default bundle to dist/
|
|
16
28
|
```
|
|
17
29
|
|
|
18
30
|
## Features
|
|
@@ -233,47 +245,56 @@ export async function load() {
|
|
|
233
245
|
|
|
234
246
|
## Build Commands
|
|
235
247
|
|
|
248
|
+
With the package installed (the default):
|
|
249
|
+
|
|
236
250
|
```bash
|
|
237
|
-
#
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
npm run
|
|
248
|
-
|
|
249
|
-
#
|
|
250
|
-
npm run
|
|
251
|
-
npm run check
|
|
252
|
-
npm
|
|
253
|
-
npm run sync:docs # Regenerate section templates
|
|
254
|
-
npm test # Run test suite
|
|
251
|
+
npx pagenary build # build the default bundle to dist/
|
|
252
|
+
npx pagenary build:tenants # build all enabled tenants
|
|
253
|
+
npx pagenary build:tenants my-tenant # build a specific tenant
|
|
254
|
+
npx pagenary tenants:list # list configured tenants
|
|
255
|
+
npx pagenary serve # serve dist/ on localhost:5173
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
From source (clone — adds dev/utility scripts):
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm run build:incremental my-tenant # git-aware incremental rebuild
|
|
262
|
+
npm run dev # build + serve with watch
|
|
263
|
+
npm run lint:content # check trailing whitespace/tabs
|
|
264
|
+
npm run check:seo # verify SEO metadata
|
|
265
|
+
npm run check # run all checks
|
|
266
|
+
npm test # run test suite
|
|
255
267
|
```
|
|
256
268
|
|
|
257
269
|
## Tenant Registry
|
|
258
270
|
|
|
259
|
-
Register tenants in `tenants.json
|
|
271
|
+
Register tenants in a `tenants.json` at your project root (validated by the
|
|
272
|
+
bundled `tenants.schema.json`):
|
|
260
273
|
|
|
261
274
|
```json
|
|
262
275
|
{
|
|
263
|
-
"
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
"tenants": [
|
|
277
|
+
{
|
|
278
|
+
"id": "my-docs",
|
|
279
|
+
"source": { "type": "local", "path": "./docs" },
|
|
280
|
+
"strictLinks": true
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"id": "client-portal",
|
|
284
|
+
"source": { "type": "git", "url": "https://github.com/org/client-docs.git", "ref": "main" },
|
|
285
|
+
"domains": ["docs.client.com"]
|
|
286
|
+
}
|
|
287
|
+
]
|
|
271
288
|
}
|
|
272
289
|
```
|
|
273
290
|
|
|
274
291
|
**Source types:**
|
|
275
|
-
- **Local path
|
|
276
|
-
- **Git
|
|
292
|
+
- **Local**: `{ "type": "local", "path": "./relative/or/abs/path" }`
|
|
293
|
+
- **Git**: `{ "type": "git", "url": "https://…", "ref": "main", "path": "subdir" }`
|
|
294
|
+
|
|
295
|
+
Per-tenant options include `enabled` (default `true`), `strictLinks` (default
|
|
296
|
+
`true` — fail the build on broken internal links), and `domains` (for the
|
|
297
|
+
multi-tenant Caddy router). See [Tenant Configuration](docs/TENANT-CONFIG.md).
|
|
277
298
|
|
|
278
299
|
## Docker Caddy Workflow
|
|
279
300
|
|
|
@@ -329,6 +350,7 @@ apps/publisher/
|
|
|
329
350
|
|
|
330
351
|
## Documentation
|
|
331
352
|
|
|
353
|
+
- [Getting Started](docs/GETTING-STARTED.md) - **start here**: zero to a published site with the npm package
|
|
332
354
|
- [Quick Start Guide](docs/QUICKSTART.md) - Step-by-step tenant creation
|
|
333
355
|
- [Tenant Configuration](docs/TENANT-CONFIG.md) - All config options
|
|
334
356
|
- [Architecture](docs/ARCHITECTURE.md) - System design
|
package/package.json
CHANGED
package/scripts/build-tenants.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import { spawn, execSync } from 'child_process';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
8
|
import os from 'os';
|
|
9
|
-
import { generateSeoArtifacts } from './lib/seo-generator.js';
|
|
9
|
+
import { generateSeoArtifacts, resolveBaseUrl, resolveOgImage } from './lib/seo-generator.js';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
12
|
const root = process.cwd();
|
|
@@ -2646,9 +2646,11 @@ async function processNestedContent(sourceDir, distDir, tenantId, contentRoot, o
|
|
|
2646
2646
|
const siteConfig = {
|
|
2647
2647
|
bottomNav: rootManifest?.bottomNav || 'mobile',
|
|
2648
2648
|
bottomNavSections: rootManifest?.bottomNavSections || [],
|
|
2649
|
-
// Pass SEO-relevant config to SPA for dynamic meta tag updates
|
|
2649
|
+
// Pass SEO-relevant config to SPA for dynamic meta tag updates.
|
|
2650
|
+
// siteUrl falls back to `domain` (#15); ogImage drives social cards (#16).
|
|
2650
2651
|
siteTitle: config.title || '',
|
|
2651
|
-
siteUrl: config
|
|
2652
|
+
siteUrl: resolveBaseUrl(config),
|
|
2653
|
+
ogImage: resolveOgImage(config, resolveBaseUrl(config))
|
|
2652
2654
|
};
|
|
2653
2655
|
|
|
2654
2656
|
// Build export branding configuration
|
|
@@ -6,6 +6,43 @@
|
|
|
6
6
|
import * as fsp from 'node:fs/promises';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the absolute base URL for a tenant's SEO output.
|
|
11
|
+
*
|
|
12
|
+
* Precedence: `seo.siteUrl` > `domain` (https:// prefixed) > '' (relative fallback).
|
|
13
|
+
* Returns a URL with no trailing slash, or '' when neither is configured.
|
|
14
|
+
* Tenants that declare only `domain` (the common case) get absolute SEO URLs
|
|
15
|
+
* for free — see issue #15.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} config - Tenant configuration
|
|
18
|
+
* @returns {string} Absolute base URL (no trailing slash) or ''
|
|
19
|
+
*/
|
|
20
|
+
export function resolveBaseUrl(config = {}) {
|
|
21
|
+
const seoConfig = config.seo || {};
|
|
22
|
+
const raw = String(seoConfig.siteUrl || config.domain || '').trim();
|
|
23
|
+
if (!raw) return '';
|
|
24
|
+
const withScheme = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
25
|
+
return withScheme.replace(/\/+$/, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a social-share image URL (og:image / twitter:image).
|
|
30
|
+
* Absolute URLs pass through; site-relative paths are joined to baseUrl.
|
|
31
|
+
* Returns '' when no image is configured. See issue #16.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} config - Tenant configuration
|
|
34
|
+
* @param {string} baseUrl - Resolved base URL (may be '')
|
|
35
|
+
* @returns {string} Image URL, or '' when none configured
|
|
36
|
+
*/
|
|
37
|
+
export function resolveOgImage(config = {}, baseUrl = '') {
|
|
38
|
+
const seoConfig = config.seo || {};
|
|
39
|
+
const img = String(seoConfig.ogImage || '').trim();
|
|
40
|
+
if (!img) return '';
|
|
41
|
+
if (/^https?:\/\//i.test(img)) return img;
|
|
42
|
+
const pathPart = img.startsWith('/') ? img : `/${img}`;
|
|
43
|
+
return baseUrl ? `${baseUrl}${pathPart}` : pathPart;
|
|
44
|
+
}
|
|
45
|
+
|
|
9
46
|
/**
|
|
10
47
|
* Encode section ID for use as filename (replace / with --)
|
|
11
48
|
* @param {string} sectionId - Section ID like "guides/getting-started"
|
|
@@ -30,7 +67,8 @@ export function collectAllSections(manifest, parentTitle = null) {
|
|
|
30
67
|
title: entry.title,
|
|
31
68
|
summary: entry.summary || '',
|
|
32
69
|
module: entry.module,
|
|
33
|
-
parent: parentTitle
|
|
70
|
+
parent: parentTitle,
|
|
71
|
+
ogImage: entry.ogImage || ''
|
|
34
72
|
});
|
|
35
73
|
}
|
|
36
74
|
if (entry.subsections) {
|
|
@@ -85,7 +123,7 @@ export async function generateSitemap(distDir, manifest, config) {
|
|
|
85
123
|
const seoConfig = config.seo || {};
|
|
86
124
|
if (seoConfig.generateSitemap === false) return;
|
|
87
125
|
|
|
88
|
-
const baseUrl =
|
|
126
|
+
const baseUrl = resolveBaseUrl(config);
|
|
89
127
|
const defaultChangeFreq = seoConfig.defaultChangeFreq || 'weekly';
|
|
90
128
|
const urls = [];
|
|
91
129
|
|
|
@@ -125,7 +163,7 @@ export async function generateRobotsTxt(distDir, config) {
|
|
|
125
163
|
const seoConfig = config.seo || {};
|
|
126
164
|
if (seoConfig.generateRobotsTxt === false) return;
|
|
127
165
|
|
|
128
|
-
const baseUrl =
|
|
166
|
+
const baseUrl = resolveBaseUrl(config);
|
|
129
167
|
const sitemapUrl = baseUrl ? `${baseUrl}/sitemap.xml` : '/sitemap.xml';
|
|
130
168
|
const buildDate = new Date().toISOString();
|
|
131
169
|
|
|
@@ -153,8 +191,10 @@ Sitemap: ${sitemapUrl}
|
|
|
153
191
|
*/
|
|
154
192
|
export function buildPageJsonLd(section, config) {
|
|
155
193
|
const seoConfig = config.seo || {};
|
|
156
|
-
const baseUrl =
|
|
157
|
-
|
|
194
|
+
const baseUrl = resolveBaseUrl(config);
|
|
195
|
+
// Canonical points at the crawlable static snapshot, not the SPA hash route
|
|
196
|
+
// (search engines ignore #fragments, which collapsed every page to home). #17
|
|
197
|
+
const canonicalUrl = `${baseUrl}/pages/${encodePathForFilename(section.id)}.html`;
|
|
158
198
|
const buildDate = new Date().toISOString().split('T')[0];
|
|
159
199
|
|
|
160
200
|
const articleSchema = {
|
|
@@ -229,7 +269,7 @@ export function buildPageJsonLd(section, config) {
|
|
|
229
269
|
*/
|
|
230
270
|
export function buildHomePageJsonLd(config) {
|
|
231
271
|
const seoConfig = config.seo || {};
|
|
232
|
-
const baseUrl =
|
|
272
|
+
const baseUrl = resolveBaseUrl(config);
|
|
233
273
|
|
|
234
274
|
const schema = {
|
|
235
275
|
'@context': 'https://schema.org',
|
|
@@ -265,10 +305,14 @@ export function buildStaticPage(options) {
|
|
|
265
305
|
contentHtml,
|
|
266
306
|
siteTitle,
|
|
267
307
|
baseUrl,
|
|
308
|
+
ogImage = '',
|
|
268
309
|
config
|
|
269
310
|
} = options;
|
|
270
311
|
|
|
271
|
-
|
|
312
|
+
// Canonical = the crawlable static snapshot (#17). The SPA hash route is for
|
|
313
|
+
// humans (JS redirect / noscript / "interactive version" link).
|
|
314
|
+
const canonicalUrl = `${baseUrl}/pages/${encodePathForFilename(sectionId)}.html`;
|
|
315
|
+
const spaUrl = `${baseUrl}/#${sectionId}`;
|
|
272
316
|
const jsonLd = buildPageJsonLd({
|
|
273
317
|
id: sectionId,
|
|
274
318
|
title: sectionTitle,
|
|
@@ -281,6 +325,13 @@ export function buildStaticPage(options) {
|
|
|
281
325
|
const safeSiteTitle = escapeHtml(siteTitle);
|
|
282
326
|
const safeSummary = escapeHtml(sectionSummary || '');
|
|
283
327
|
|
|
328
|
+
// Social share image (#16): summary_large_image when present, else summary.
|
|
329
|
+
const safeImage = ogImage ? escapeHtml(ogImage) : '';
|
|
330
|
+
const twitterCard = safeImage ? 'summary_large_image' : 'summary';
|
|
331
|
+
const imageTags = safeImage
|
|
332
|
+
? `\n <meta property="og:image" content="${safeImage}" />\n <meta name="twitter:image" content="${safeImage}" />`
|
|
333
|
+
: '';
|
|
334
|
+
|
|
284
335
|
return `<!doctype html>
|
|
285
336
|
<html lang="en">
|
|
286
337
|
<head>
|
|
@@ -294,10 +345,10 @@ export function buildStaticPage(options) {
|
|
|
294
345
|
<meta property="og:title" content="${safeTitle}" />
|
|
295
346
|
<meta property="og:description" content="${safeSummary}" />
|
|
296
347
|
<meta property="og:type" content="article" />
|
|
297
|
-
<meta property="og:url" content="${canonicalUrl}"
|
|
348
|
+
<meta property="og:url" content="${canonicalUrl}" />${imageTags}
|
|
298
349
|
|
|
299
350
|
<!-- Twitter Card -->
|
|
300
|
-
<meta name="twitter:card" content="
|
|
351
|
+
<meta name="twitter:card" content="${twitterCard}" />
|
|
301
352
|
<meta name="twitter:title" content="${safeTitle}" />
|
|
302
353
|
<meta name="twitter:description" content="${safeSummary}" />
|
|
303
354
|
|
|
@@ -309,11 +360,11 @@ ${jsonLd}
|
|
|
309
360
|
<!-- Redirect to SPA for JavaScript-enabled browsers -->
|
|
310
361
|
<script>
|
|
311
362
|
if (typeof window !== 'undefined') {
|
|
312
|
-
window.location.replace('${
|
|
363
|
+
window.location.replace('${spaUrl}');
|
|
313
364
|
}
|
|
314
365
|
</script>
|
|
315
366
|
<noscript>
|
|
316
|
-
<meta http-equiv="refresh" content="0; url=${
|
|
367
|
+
<meta http-equiv="refresh" content="0; url=${spaUrl}" />
|
|
317
368
|
</noscript>
|
|
318
369
|
|
|
319
370
|
<link rel="stylesheet" href="../styles.css" />
|
|
@@ -332,7 +383,7 @@ ${contentHtml}
|
|
|
332
383
|
</div>
|
|
333
384
|
</article>
|
|
334
385
|
<footer class="static-footer">
|
|
335
|
-
<p>View interactive version: <a href="${
|
|
386
|
+
<p>View interactive version: <a href="${spaUrl}">${safeTitle}</a></p>
|
|
336
387
|
</footer>
|
|
337
388
|
</main>
|
|
338
389
|
</body>
|
|
@@ -398,7 +449,8 @@ export async function generateStaticSnapshots(distDir, manifest, config) {
|
|
|
398
449
|
const pagesDir = path.join(distDir, 'pages');
|
|
399
450
|
await fsp.mkdir(pagesDir, { recursive: true });
|
|
400
451
|
|
|
401
|
-
const baseUrl =
|
|
452
|
+
const baseUrl = resolveBaseUrl(config);
|
|
453
|
+
const siteOgImage = resolveOgImage(config, baseUrl);
|
|
402
454
|
const sections = collectAllSections(manifest);
|
|
403
455
|
let generated = 0;
|
|
404
456
|
|
|
@@ -415,6 +467,11 @@ export async function generateStaticSnapshots(distDir, manifest, config) {
|
|
|
415
467
|
continue;
|
|
416
468
|
}
|
|
417
469
|
|
|
470
|
+
// Per-section og:image override (frontmatter) falls back to the site image
|
|
471
|
+
const pageOgImage = section.ogImage
|
|
472
|
+
? resolveOgImage({ seo: { ogImage: section.ogImage } }, baseUrl)
|
|
473
|
+
: siteOgImage;
|
|
474
|
+
|
|
418
475
|
// Generate static page
|
|
419
476
|
const pageHtml = buildStaticPage({
|
|
420
477
|
sectionId: section.id,
|
|
@@ -424,6 +481,7 @@ export async function generateStaticSnapshots(distDir, manifest, config) {
|
|
|
424
481
|
contentHtml,
|
|
425
482
|
siteTitle: config.title || 'Documentation',
|
|
426
483
|
baseUrl,
|
|
484
|
+
ogImage: pageOgImage,
|
|
427
485
|
config
|
|
428
486
|
});
|
|
429
487
|
|
|
@@ -471,7 +529,7 @@ export async function readManifestFromDist(distDir) {
|
|
|
471
529
|
*/
|
|
472
530
|
export async function generateLlmsTxt(distDir, manifest, config) {
|
|
473
531
|
const seoConfig = config.seo || {};
|
|
474
|
-
const baseUrl =
|
|
532
|
+
const baseUrl = resolveBaseUrl(config);
|
|
475
533
|
const title = config.title || 'Documentation';
|
|
476
534
|
const description = config.description || '';
|
|
477
535
|
|
|
@@ -543,6 +601,12 @@ export async function generateSeoArtifacts(distDir, config) {
|
|
|
543
601
|
return;
|
|
544
602
|
}
|
|
545
603
|
|
|
604
|
+
// Warn when neither seo.siteUrl nor domain is set — SEO output will use
|
|
605
|
+
// relative URLs (invalid sitemap <loc>, weak canonicals). See #15.
|
|
606
|
+
if (!resolveBaseUrl(config)) {
|
|
607
|
+
console.warn(` ⚠ SEO: no seo.siteUrl or domain set — sitemap/canonical URLs will be relative. Set "domain" or "seo.siteUrl" for absolute URLs.`);
|
|
608
|
+
}
|
|
609
|
+
|
|
546
610
|
// Read manifest from generated file
|
|
547
611
|
const manifest = await readManifestFromDist(distDir);
|
|
548
612
|
if (!manifest) {
|
package/site/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{MANIFEST as e,DEFAULT_SECTION as t,findSection as n,getAdjacentSections as a,SITE_CONFIG as s,EXPORT_CONFIG as o}from"./manifest.js";import{updateMetaTags as i}from"./seo.js";import{escapeRegExp as l,searchContent as c,flattenManifest as r,findPreferredIndex as d}from"./lib/search.js";import{resolveTarget as m,resolveEntry as p}from"./lib/router.js";import{composeExportDocument as u,collectExportableSections as v}from"./lib/export.js";import{renderMermaidBlocks as h}from"./mermaid-init.js";import{highlightCodeBlocks as f}from"./syntax-highlight.js";const y=document.getElementById("app"),b=document.getElementById("nav"),g=document.getElementById("year"),E=document.getElementById("exportBtn"),L=document.getElementById("commandToggle"),T=document.getElementById("commandPalette"),x=document.getElementById("commandInput"),N=document.getElementById("commandList"),w=document.getElementById("mobileMenuToggle"),C=document.querySelector(".sidebar"),k="docs-toolkit-command-query",$=new Map,H=new Map,M=new Map,I=new Set;let A=[],S=0,P=!1,q=(localStorage.getItem(k)||"").trim(),B=!1;function R(e,t){const n=document.createElement("a");return n.href=e.url,n.target="_blank",n.rel="noopener noreferrer",n.className=`${t} nav-external`,n.title=e.summary||e.title,n.innerHTML=`\n <span class="nav-title">${e.title}<span class="nav-external-icon" aria-label="(opens in new tab)">↗</span></span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,n}function F(e,t={}){const{scrollToHighlight:a=!1}=t,{targetId:s,parentId:o}=function(e){return m(e,n)}(e);P&&_(),o&&j(o,!0),B=a||Boolean(q),location.hash.replace("#","")===s?O():location.hash=`#${s}`}function D(){return location.hash.replace("#","")||t}async function O(){const e=D(),t=p(e,n);if(!t)return;const{entry:o,targetId:l,parentId:c}=t;l===e?(c&&j(c,!0),function(e,t=null){H.forEach(e=>{e.setAttribute("aria-current","false")});const n=H.get(e);if(n&&n.setAttribute("aria-current","page"),t){const e=H.get(t);e&&e.setAttribute("aria-current","page")}}(o.id,c),await async function(e){if(!e)return;const t=await import(e.module),n=t.load||t.default;if("function"!=typeof n)return void(y.innerHTML='<article class="section"><p>Section failed to load.</p></article>');const o=await n();y.innerHTML=o.html||"",await h(y),await f(y),function(e){const t=y.querySelector(".bottom-nav");if(t&&t.remove(),"never"===s.bottomNav)return;const n=(s.bottomNavSections||[]).some(t=>e.startsWith(t)),o="mobile"===s.bottomNav&&!n,{prev:i,next:l}=a(e);if(!i&&!l)return;const c=document.createElement("nav");if(c.className="bottom-nav",o&&c.classList.add("mobile-only"),i){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-prev",e.innerHTML='<span class="bottom-nav-chevron">‹</span>';const t=document.createElement("a");t.href=`#${i.id}`,t.className="bottom-nav-link",t.title=`Previous: ${i.title}`,t.textContent=i.title,t.addEventListener("click",e=>{e.preventDefault(),F(i.id)}),e.appendChild(t),c.appendChild(e)}else{const e=document.createElement("div");e.className="bottom-nav-spacer",c.appendChild(e)}if(l){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-next";const t=document.createElement("a");t.href=`#${l.id}`,t.className="bottom-nav-link",t.title=`Next: ${l.title}`,t.textContent=l.title,t.addEventListener("click",e=>{e.preventDefault(),F(l.id)}),e.appendChild(t),e.innerHTML+='<span class="bottom-nav-chevron">›</span>',c.appendChild(e)}(y.querySelector("section")||y).appendChild(c)}(e.id),y.scrollTop=0,window.scrollTo(0,0),"function"==typeof o.afterRender&&o.afterRender(y),i({title:e.title,description:e.summary,siteTitle:s.siteTitle,siteUrl:s.siteUrl,sectionId:e.id}),$.set(e.id,Date.now());const l=B;B=!1,K(l),requestAnimationFrame(()=>y.focus())}(o)):location.replace(`#${l}`)}function j(e,t){if(!e)return;const n=M.get(e);t?(I.add(e),n&&n.group.classList.add("expanded")):(I.delete(e),n&&n.group.classList.remove("expanded"))}function W(){if(!T||!x)return;P=!0,T.hidden=!1;const e=q;x.value=e,z(e),requestAnimationFrame(()=>{x.focus(),e&&x.select()})}function _(){T&&x&&(P=!1,T.hidden=!0,x.blur())}!function(){b.innerHTML="",H.clear(),M.clear();let t=I.size>0;e.forEach((e,n)=>{if(e.url){const t=R(e,"nav-leaf");return void b.appendChild(t)}if(e.subsections&&e.subsections.length){const n=document.createElement("div");n.className="nav-group";const a=Boolean(e.module),s=document.createElement("button");s.type="button",s.className="nav-parent"+(a?" nav-parent-with-content":""),s.dataset.section=e.id,s.title=e.summary,a?(s.innerHTML=`\n <span class="nav-title-link">${e.title}</span>\n <span class="nav-expand-toggle" aria-label="Expand"></span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,s.querySelector(".nav-title-link").addEventListener("click",t=>{t.stopPropagation(),F(e.id,{scrollToHighlight:Boolean(q)})}),s.querySelector(".nav-expand-toggle").addEventListener("click",t=>{t.stopPropagation();const n=!I.has(e.id);j(e.id,n)}),s.addEventListener("click",t=>{if(t.target===s){const t=!I.has(e.id);j(e.id,t)}})):(s.innerHTML=`\n <span class="nav-title">${e.title}</span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,s.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)}));const o=document.createElement("div");o.className="nav-sublist",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void o.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-nested";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-nested",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-nested",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void a.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-deep";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-deep",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)});const s=document.createElement("div");s.className="nav-sublist nav-sublist-deep",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void s.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-ultra";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-ultra",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-ultra",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void a.appendChild(t)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-ultra"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),s.appendChild(t),H.set(e.id,n),M.set(e.id,{group:t,button:n,list:a});const o=I.has(e.id)&&!e.collapsed;return void j(e.id,o)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-deep"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),s.appendChild(t),H.set(e.id,t)}),t.append(n,s),a.appendChild(t),H.set(e.id,n),M.set(e.id,{group:t,button:n,list:s});const o=I.has(e.id)&&!e.collapsed;return void j(e.id,o)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-nested"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),o.appendChild(t),H.set(e.id,n),M.set(e.id,{group:t,button:n,list:a});const s=I.has(e.id)&&!e.collapsed;return void j(e.id,s)}const t=document.createElement("button");t.type="button",t.className="nav-item"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),o.appendChild(t),H.set(e.id,t)}),n.append(s,o),b.appendChild(n),H.set(e.id,s),M.set(e.id,{group:n,button:s,list:o});const i=!e.collapsed&&(I.has(e.id)||!t&&!I.size);j(e.id,i),i&&(t=!0)}else{const t=document.createElement("button");t.type="button",t.className="nav-leaf"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),b.appendChild(t),H.set(e.id,t)}})}(),q&&(B=!0),window.addEventListener("hashchange",()=>{q&&(B=!0),O()}),g.textContent=(new Date).getFullYear(),O(),L&&T&&x&&N&&(L.addEventListener("click",()=>{P?_():W()}),x.addEventListener("input",()=>{const e=x.value;J(e,!0),z(e)}),x.addEventListener("keydown",e=>{const t=A.length-1;if("ArrowDown"===e.key)e.preventDefault(),S=Math.min(t,S+1),X();else if("ArrowUp"===e.key)e.preventDefault(),S=Math.max(0,S-1),X();else if("Enter"===e.key){e.preventDefault();const t=A[S];t&&(J(x.value,!0),F(t.id,{scrollToHighlight:!0}),_())}else"Escape"===e.key&&(e.preventDefault(),_())}),N.addEventListener("click",e=>{const t=e.target.closest("[data-section]");if(!t)return;const n=t.dataset.section;n&&(J(x.value,!0),F(n,{scrollToHighlight:!0}),_())}),T.addEventListener("click",e=>{e.target===T&&_()}),window.addEventListener("keydown",e=>{const t=e.target,n=t&&("INPUT"===t.tagName||"TEXTAREA"===t.tagName||t.isContentEditable),a=e.metaKey||e.ctrlKey;"k"===e.key.toLowerCase()&&a||"/"===e.key&&!n?(e.preventDefault(),P?_():W()):"Escape"===e.key&&P&&(e.preventDefault(),_())}));let V=null,U=!1;async function z(t){N&&(!U&&t.trim()&&(U=!0,N.innerHTML='<li class="cmd-item cmd-loading">Indexing content...</li>'),clearTimeout(V),V=setTimeout(async()=>{A=await c(e,t);const n=D();S=d(A,n),function(){if(N){if(N.innerHTML="",!A.length){const e=document.createElement("li");return e.className="cmd-item",e.setAttribute("aria-selected","false"),e.textContent="No matches.",void N.appendChild(e)}A.forEach(e=>{const t=document.createElement("li");t.className="cmd-item",t.dataset.section=e.id,t.setAttribute("role","option");const n=document.createElement("span");if(n.className="cmd-item-title",n.textContent=e.title,e.group){const t=document.createElement("span");t.className="cmd-item-group",t.textContent=e.group,n.prepend(t)}const a=document.createElement("span");a.className="cmd-item-summary",a.textContent=e.summary||"",t.append(n,a),N.appendChild(t)})}}(),X(),U=!1},t.trim()?150:0))}function X(){N&&Array.from(N.children).forEach((e,t)=>{const n=t===S&&A.length;e.setAttribute("aria-selected",n?"true":"false"),n&&e.scrollIntoView({block:"nearest"})})}function G(e){const t=document.createElement("div");t.innerHTML=e,t.querySelectorAll("script").forEach(e=>e.remove()),t.querySelectorAll("button").forEach(e=>e.removeAttribute("onclick")),t.querySelectorAll("mark.hl").forEach(e=>{const t=document.createTextNode(e.textContent||"");e.replaceWith(t)});const n=t.querySelector("section");return n?n.innerHTML:t.innerHTML}function J(e,t=!1){q=e.trim(),t&&(q?localStorage.setItem(k,q):localStorage.removeItem(k)),K()}function K(e=!1){y&&function(e,t,{scrollToFirst:n=!1}={}){if(!e)return;if(function(e){e&&e.querySelectorAll("mark.hl").forEach(e=>{const t=document.createTextNode(e.textContent||"");e.replaceWith(t)})}(e),!t)return;const a=t.split(/\s+/).map(e=>e.trim()).filter(Boolean);if(!a.length)return;const s=a.map(e=>e.toLowerCase()),o=new Set(["SCRIPT","STYLE","CODE","PRE"]),i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode(e){if(!e.nodeValue||!e.nodeValue.trim())return NodeFilter.FILTER_REJECT;const t=e.parentNode;return t&&o.has(t.tagName)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),c=[];let r;for(;r=i.nextNode();){const e=r.nodeValue.toLowerCase();s.some(t=>e.includes(t))&&c.push(r)}const d=new RegExp(`(${a.map(l).join("|")})`,"gi");c.forEach(e=>{const t=e.nodeValue,n=[];let a=0;t.replace(d,(e,s,o)=>{o>a&&n.push(document.createTextNode(t.slice(a,o)));const i=document.createElement("mark");return i.className="hl",i.textContent=e,n.push(i),a=o+e.length,e}),a<t.length&&n.push(document.createTextNode(t.slice(a)));const s=document.createDocumentFragment();n.forEach(e=>s.appendChild(e)),e.parentNode.replaceChild(s,e)}),n&&requestAnimationFrame(()=>{const t=e.querySelector("mark.hl");t&&t.scrollIntoView({behavior:"smooth",block:"center"})})}(y,q,{scrollToFirst:e})}E&&E.addEventListener("click",function(){const t=document.createElement("div");t.className="export-options-overlay",t.innerHTML='\n <div class="export-options-modal">\n <div class="export-options-header">EXPORT OPTIONS</div>\n <div class="export-options-buttons">\n <button type="button" class="export-option-btn" data-scope="page">\n <span class="export-option-title">Current Page</span>\n <span class="export-option-desc">Export only this section</span>\n </button>\n <button type="button" class="export-option-btn" data-scope="site">\n <span class="export-option-title">Entire Site</span>\n <span class="export-option-desc">Export all documentation</span>\n </button>\n </div>\n <button type="button" class="export-cancel-btn">Cancel</button>\n </div>\n ',document.body.appendChild(t),setTimeout(()=>t.classList.add("active"),10);const n=()=>{t.classList.remove("active"),setTimeout(()=>t.remove(),200)};t.querySelector(".export-cancel-btn").addEventListener("click",n),t.addEventListener("click",e=>{e.target===t&&n()}),t.querySelectorAll(".export-option-btn").forEach(t=>{t.addEventListener("click",()=>{const a=t.dataset.scope;n(),async function(t="site"){if(!E)return;const n=E.innerHTML,a=window.open("","_blank","width=1,height=1,left=0,top=0");if(!a||a.closed||void 0===a.closed)return void confirm("Pop-ups are blocked. Please allow pop-ups for this site to export the document.\n\nWould you like to try again after enabling pop-ups?");a.close(),E.disabled=!0;const s=document.createElement("div");s.className="export-loading-overlay",s.innerHTML='\n <div class="export-loading-modal">\n <div class="export-loading-header">\n <div class="export-loading-title">COMPILING DOCUMENTATION</div>\n <div class="export-loading-subtitle">Assembling all sections into unified document</div>\n </div>\n <div class="export-loading-progress">\n <div class="export-loading-bar">\n <div class="export-loading-fill"></div>\n </div>\n <div class="export-loading-status-container">\n <div class="export-loading-status">Initializing...</div>\n </div>\n </div>\n <div class="export-loading-scanner">\n <div class="scanner-line"></div>\n </div>\n </div>\n ',document.body.appendChild(s),setTimeout(()=>s.classList.add("active"),10);const i=s.querySelector(".export-loading-fill"),l=s.querySelector(".export-loading-status");try{let n;if("page"===t){const t=D(),a=v(e).find(e=>e.id===t);n=a?[a]:[]}else n=v(e);if(0===n.length)return alert("No content available to export."),s.remove(),void(E.disabled=!1);const a=[],c=n.length;let r=0;for(const e of n){r++;const n=r/c*100;i.style.width=`${n}%`,l.textContent="page"===t?`Exporting: ${e.title}`:`Processing section ${r} of ${c}: ${e.title}`,await new Promise(e=>setTimeout(e,50));try{const t=await import(e.module),n=t.load||t.default;if("function"!=typeof n)continue;const s=G((await n()).html||"");a.push({section:e,html:s})}catch(t){console.error("Failed to include section in export",e.id,t)}}l.textContent="Generating document...",await new Promise(e=>setTimeout(e,200));const d=u(a,o);l.textContent="Opening document viewer...",await new Promise(e=>setTimeout(e,100));const m=window.open("","_blank","width=900,height=860,scrollbars=yes,resizable=yes");if(!m)return alert("Please allow pop-ups to export the document."),void s.remove();m.document.open(),m.document.write(d),m.document.close(),m.focus(),s.classList.remove("active"),setTimeout(()=>s.remove(),300)}catch(e){console.error("Export failed",e),alert("Export failed. Check console for details."),s.remove()}finally{E.disabled=!1,E.innerHTML=n}}(a)})})}),w&&C&&(w.addEventListener("click",()=>{C.classList.contains("mobile-open")?(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false")):(C.classList.add("mobile-open"),document.body.classList.add("menu-open"),w.setAttribute("aria-expanded","true"))}),b.addEventListener("click",e=>{if(window.innerWidth<=960){const t=e.target.closest(".nav-item, .nav-leaf, .nav-parent");t&&(t.classList.contains("nav-item")||t.classList.contains("nav-leaf"))&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false"))}}),document.addEventListener("click",e=>{window.innerWidth<=960&&C.classList.contains("mobile-open")&&!C.contains(e.target)&&!w.contains(e.target)&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false"))}));
|
|
1
|
+
import{MANIFEST as e,DEFAULT_SECTION as t,findSection as n,getAdjacentSections as a,SITE_CONFIG as s,EXPORT_CONFIG as o}from"./manifest.js";import{updateMetaTags as i}from"./seo.js";import{escapeRegExp as l,searchContent as c,flattenManifest as r,findPreferredIndex as d}from"./lib/search.js";import{resolveTarget as m,resolveEntry as p}from"./lib/router.js";import{composeExportDocument as u,collectExportableSections as v}from"./lib/export.js";import{renderMermaidBlocks as h}from"./mermaid-init.js";import{highlightCodeBlocks as f}from"./syntax-highlight.js";const y=document.getElementById("app"),g=document.getElementById("nav"),b=document.getElementById("year"),E=document.getElementById("exportBtn"),L=document.getElementById("commandToggle"),T=document.getElementById("commandPalette"),x=document.getElementById("commandInput"),N=document.getElementById("commandList"),w=document.getElementById("mobileMenuToggle"),C=document.querySelector(".sidebar"),k="docs-toolkit-command-query",$=new Map,H=new Map,I=new Map,M=new Set;let A=[],S=0,P=!1,q=(localStorage.getItem(k)||"").trim(),B=!1;function R(e,t){const n=document.createElement("a");return n.href=e.url,n.target="_blank",n.rel="noopener noreferrer",n.className=`${t} nav-external`,n.title=e.summary||e.title,n.innerHTML=`\n <span class="nav-title">${e.title}<span class="nav-external-icon" aria-label="(opens in new tab)">↗</span></span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,n}function F(e,t={}){const{scrollToHighlight:a=!1}=t,{targetId:s,parentId:o}=function(e){return m(e,n)}(e);P&&_(),o&&j(o,!0),B=a||Boolean(q),location.hash.replace("#","")===s?O():location.hash=`#${s}`}function D(){return location.hash.replace("#","")||t}async function O(){const e=D(),t=p(e,n);if(!t)return;const{entry:o,targetId:l,parentId:c}=t;l===e?(c&&j(c,!0),function(e,t=null){H.forEach(e=>{e.setAttribute("aria-current","false")});const n=H.get(e);if(n&&n.setAttribute("aria-current","page"),t){const e=H.get(t);e&&e.setAttribute("aria-current","page")}}(o.id,c),await async function(e){if(!e)return;const t=await import(e.module),n=t.load||t.default;if("function"!=typeof n)return void(y.innerHTML='<article class="section"><p>Section failed to load.</p></article>');const o=await n();y.innerHTML=o.html||"",await h(y),await f(y),function(e){const t=y.querySelector(".bottom-nav");if(t&&t.remove(),"never"===s.bottomNav)return;const n=(s.bottomNavSections||[]).some(t=>e.startsWith(t)),o="mobile"===s.bottomNav&&!n,{prev:i,next:l}=a(e);if(!i&&!l)return;const c=document.createElement("nav");if(c.className="bottom-nav",o&&c.classList.add("mobile-only"),i){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-prev",e.innerHTML='<span class="bottom-nav-chevron">‹</span>';const t=document.createElement("a");t.href=`#${i.id}`,t.className="bottom-nav-link",t.title=`Previous: ${i.title}`,t.textContent=i.title,t.addEventListener("click",e=>{e.preventDefault(),F(i.id)}),e.appendChild(t),c.appendChild(e)}else{const e=document.createElement("div");e.className="bottom-nav-spacer",c.appendChild(e)}if(l){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-next";const t=document.createElement("a");t.href=`#${l.id}`,t.className="bottom-nav-link",t.title=`Next: ${l.title}`,t.textContent=l.title,t.addEventListener("click",e=>{e.preventDefault(),F(l.id)}),e.appendChild(t),e.innerHTML+='<span class="bottom-nav-chevron">›</span>',c.appendChild(e)}(y.querySelector("section")||y).appendChild(c)}(e.id),y.scrollTop=0,window.scrollTo(0,0),"function"==typeof o.afterRender&&o.afterRender(y),i({title:e.title,description:e.summary,siteTitle:s.siteTitle,siteUrl:s.siteUrl,sectionId:e.id,ogImage:e.ogImage||s.ogImage}),$.set(e.id,Date.now());const l=B;B=!1,K(l),requestAnimationFrame(()=>y.focus())}(o)):location.replace(`#${l}`)}function j(e,t){if(!e)return;const n=I.get(e);t?(M.add(e),n&&n.group.classList.add("expanded")):(M.delete(e),n&&n.group.classList.remove("expanded"))}function W(){if(!T||!x)return;P=!0,T.hidden=!1;const e=q;x.value=e,z(e),requestAnimationFrame(()=>{x.focus(),e&&x.select()})}function _(){T&&x&&(P=!1,T.hidden=!0,x.blur())}!function(){g.innerHTML="",H.clear(),I.clear();let t=M.size>0;e.forEach((e,n)=>{if(e.url){const t=R(e,"nav-leaf");return void g.appendChild(t)}if(e.subsections&&e.subsections.length){const n=document.createElement("div");n.className="nav-group";const a=Boolean(e.module),s=document.createElement("button");s.type="button",s.className="nav-parent"+(a?" nav-parent-with-content":""),s.dataset.section=e.id,s.title=e.summary,a?(s.innerHTML=`\n <span class="nav-title-link">${e.title}</span>\n <span class="nav-expand-toggle" aria-label="Expand"></span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,s.querySelector(".nav-title-link").addEventListener("click",t=>{t.stopPropagation(),F(e.id,{scrollToHighlight:Boolean(q)})}),s.querySelector(".nav-expand-toggle").addEventListener("click",t=>{t.stopPropagation();const n=!M.has(e.id);j(e.id,n)}),s.addEventListener("click",t=>{if(t.target===s){const t=!M.has(e.id);j(e.id,t)}})):(s.innerHTML=`\n <span class="nav-title">${e.title}</span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,s.addEventListener("click",()=>{const t=!M.has(e.id);j(e.id,t)}));const o=document.createElement("div");o.className="nav-sublist",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void o.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-nested";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-nested",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!M.has(e.id);j(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-nested",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void a.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-deep";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-deep",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!M.has(e.id);j(e.id,t)});const s=document.createElement("div");s.className="nav-sublist nav-sublist-deep",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void s.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-ultra";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-ultra",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!M.has(e.id);j(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-ultra",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void a.appendChild(t)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-ultra"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),s.appendChild(t),H.set(e.id,n),I.set(e.id,{group:t,button:n,list:a});const o=M.has(e.id)&&!e.collapsed;return void j(e.id,o)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-deep"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),s.appendChild(t),H.set(e.id,t)}),t.append(n,s),a.appendChild(t),H.set(e.id,n),I.set(e.id,{group:t,button:n,list:s});const o=M.has(e.id)&&!e.collapsed;return void j(e.id,o)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-nested"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),o.appendChild(t),H.set(e.id,n),I.set(e.id,{group:t,button:n,list:a});const s=M.has(e.id)&&!e.collapsed;return void j(e.id,s)}const t=document.createElement("button");t.type="button",t.className="nav-item"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),o.appendChild(t),H.set(e.id,t)}),n.append(s,o),g.appendChild(n),H.set(e.id,s),I.set(e.id,{group:n,button:s,list:o});const i=!e.collapsed&&(M.has(e.id)||!t&&!M.size);j(e.id,i),i&&(t=!0)}else{const t=document.createElement("button");t.type="button",t.className="nav-leaf"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),g.appendChild(t),H.set(e.id,t)}})}(),q&&(B=!0),window.addEventListener("hashchange",()=>{q&&(B=!0),O()}),b.textContent=(new Date).getFullYear(),O(),L&&T&&x&&N&&(L.addEventListener("click",()=>{P?_():W()}),x.addEventListener("input",()=>{const e=x.value;J(e,!0),z(e)}),x.addEventListener("keydown",e=>{const t=A.length-1;if("ArrowDown"===e.key)e.preventDefault(),S=Math.min(t,S+1),X();else if("ArrowUp"===e.key)e.preventDefault(),S=Math.max(0,S-1),X();else if("Enter"===e.key){e.preventDefault();const t=A[S];t&&(J(x.value,!0),F(t.id,{scrollToHighlight:!0}),_())}else"Escape"===e.key&&(e.preventDefault(),_())}),N.addEventListener("click",e=>{const t=e.target.closest("[data-section]");if(!t)return;const n=t.dataset.section;n&&(J(x.value,!0),F(n,{scrollToHighlight:!0}),_())}),T.addEventListener("click",e=>{e.target===T&&_()}),window.addEventListener("keydown",e=>{const t=e.target,n=t&&("INPUT"===t.tagName||"TEXTAREA"===t.tagName||t.isContentEditable),a=e.metaKey||e.ctrlKey;"k"===e.key.toLowerCase()&&a||"/"===e.key&&!n?(e.preventDefault(),P?_():W()):"Escape"===e.key&&P&&(e.preventDefault(),_())}));let V=null,U=!1;async function z(t){N&&(!U&&t.trim()&&(U=!0,N.innerHTML='<li class="cmd-item cmd-loading">Indexing content...</li>'),clearTimeout(V),V=setTimeout(async()=>{A=await c(e,t);const n=D();S=d(A,n),function(){if(N){if(N.innerHTML="",!A.length){const e=document.createElement("li");return e.className="cmd-item",e.setAttribute("aria-selected","false"),e.textContent="No matches.",void N.appendChild(e)}A.forEach(e=>{const t=document.createElement("li");t.className="cmd-item",t.dataset.section=e.id,t.setAttribute("role","option");const n=document.createElement("span");if(n.className="cmd-item-title",n.textContent=e.title,e.group){const t=document.createElement("span");t.className="cmd-item-group",t.textContent=e.group,n.prepend(t)}const a=document.createElement("span");a.className="cmd-item-summary",a.textContent=e.summary||"",t.append(n,a),N.appendChild(t)})}}(),X(),U=!1},t.trim()?150:0))}function X(){N&&Array.from(N.children).forEach((e,t)=>{const n=t===S&&A.length;e.setAttribute("aria-selected",n?"true":"false"),n&&e.scrollIntoView({block:"nearest"})})}function G(e){const t=document.createElement("div");t.innerHTML=e,t.querySelectorAll("script").forEach(e=>e.remove()),t.querySelectorAll("button").forEach(e=>e.removeAttribute("onclick")),t.querySelectorAll("mark.hl").forEach(e=>{const t=document.createTextNode(e.textContent||"");e.replaceWith(t)});const n=t.querySelector("section");return n?n.innerHTML:t.innerHTML}function J(e,t=!1){q=e.trim(),t&&(q?localStorage.setItem(k,q):localStorage.removeItem(k)),K()}function K(e=!1){y&&function(e,t,{scrollToFirst:n=!1}={}){if(!e)return;if(function(e){e&&e.querySelectorAll("mark.hl").forEach(e=>{const t=document.createTextNode(e.textContent||"");e.replaceWith(t)})}(e),!t)return;const a=t.split(/\s+/).map(e=>e.trim()).filter(Boolean);if(!a.length)return;const s=a.map(e=>e.toLowerCase()),o=new Set(["SCRIPT","STYLE","CODE","PRE"]),i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode(e){if(!e.nodeValue||!e.nodeValue.trim())return NodeFilter.FILTER_REJECT;const t=e.parentNode;return t&&o.has(t.tagName)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),c=[];let r;for(;r=i.nextNode();){const e=r.nodeValue.toLowerCase();s.some(t=>e.includes(t))&&c.push(r)}const d=new RegExp(`(${a.map(l).join("|")})`,"gi");c.forEach(e=>{const t=e.nodeValue,n=[];let a=0;t.replace(d,(e,s,o)=>{o>a&&n.push(document.createTextNode(t.slice(a,o)));const i=document.createElement("mark");return i.className="hl",i.textContent=e,n.push(i),a=o+e.length,e}),a<t.length&&n.push(document.createTextNode(t.slice(a)));const s=document.createDocumentFragment();n.forEach(e=>s.appendChild(e)),e.parentNode.replaceChild(s,e)}),n&&requestAnimationFrame(()=>{const t=e.querySelector("mark.hl");t&&t.scrollIntoView({behavior:"smooth",block:"center"})})}(y,q,{scrollToFirst:e})}E&&E.addEventListener("click",function(){const t=document.createElement("div");t.className="export-options-overlay",t.innerHTML='\n <div class="export-options-modal">\n <div class="export-options-header">EXPORT OPTIONS</div>\n <div class="export-options-buttons">\n <button type="button" class="export-option-btn" data-scope="page">\n <span class="export-option-title">Current Page</span>\n <span class="export-option-desc">Export only this section</span>\n </button>\n <button type="button" class="export-option-btn" data-scope="site">\n <span class="export-option-title">Entire Site</span>\n <span class="export-option-desc">Export all documentation</span>\n </button>\n </div>\n <button type="button" class="export-cancel-btn">Cancel</button>\n </div>\n ',document.body.appendChild(t),setTimeout(()=>t.classList.add("active"),10);const n=()=>{t.classList.remove("active"),setTimeout(()=>t.remove(),200)};t.querySelector(".export-cancel-btn").addEventListener("click",n),t.addEventListener("click",e=>{e.target===t&&n()}),t.querySelectorAll(".export-option-btn").forEach(t=>{t.addEventListener("click",()=>{const a=t.dataset.scope;n(),async function(t="site"){if(!E)return;const n=E.innerHTML,a=window.open("","_blank","width=1,height=1,left=0,top=0");if(!a||a.closed||void 0===a.closed)return void confirm("Pop-ups are blocked. Please allow pop-ups for this site to export the document.\n\nWould you like to try again after enabling pop-ups?");a.close(),E.disabled=!0;const s=document.createElement("div");s.className="export-loading-overlay",s.innerHTML='\n <div class="export-loading-modal">\n <div class="export-loading-header">\n <div class="export-loading-title">COMPILING DOCUMENTATION</div>\n <div class="export-loading-subtitle">Assembling all sections into unified document</div>\n </div>\n <div class="export-loading-progress">\n <div class="export-loading-bar">\n <div class="export-loading-fill"></div>\n </div>\n <div class="export-loading-status-container">\n <div class="export-loading-status">Initializing...</div>\n </div>\n </div>\n <div class="export-loading-scanner">\n <div class="scanner-line"></div>\n </div>\n </div>\n ',document.body.appendChild(s),setTimeout(()=>s.classList.add("active"),10);const i=s.querySelector(".export-loading-fill"),l=s.querySelector(".export-loading-status");try{let n;if("page"===t){const t=D(),a=v(e).find(e=>e.id===t);n=a?[a]:[]}else n=v(e);if(0===n.length)return alert("No content available to export."),s.remove(),void(E.disabled=!1);const a=[],c=n.length;let r=0;for(const e of n){r++;const n=r/c*100;i.style.width=`${n}%`,l.textContent="page"===t?`Exporting: ${e.title}`:`Processing section ${r} of ${c}: ${e.title}`,await new Promise(e=>setTimeout(e,50));try{const t=await import(e.module),n=t.load||t.default;if("function"!=typeof n)continue;const s=G((await n()).html||"");a.push({section:e,html:s})}catch(t){console.error("Failed to include section in export",e.id,t)}}l.textContent="Generating document...",await new Promise(e=>setTimeout(e,200));const d=u(a,o);l.textContent="Opening document viewer...",await new Promise(e=>setTimeout(e,100));const m=window.open("","_blank","width=900,height=860,scrollbars=yes,resizable=yes");if(!m)return alert("Please allow pop-ups to export the document."),void s.remove();m.document.open(),m.document.write(d),m.document.close(),m.focus(),s.classList.remove("active"),setTimeout(()=>s.remove(),300)}catch(e){console.error("Export failed",e),alert("Export failed. Check console for details."),s.remove()}finally{E.disabled=!1,E.innerHTML=n}}(a)})})}),w&&C&&(w.addEventListener("click",()=>{C.classList.contains("mobile-open")?(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false")):(C.classList.add("mobile-open"),document.body.classList.add("menu-open"),w.setAttribute("aria-expanded","true"))}),g.addEventListener("click",e=>{if(window.innerWidth<=960){const t=e.target.closest(".nav-item, .nav-leaf, .nav-parent");t&&(t.classList.contains("nav-item")||t.classList.contains("nav-leaf"))&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false"))}}),document.addEventListener("click",e=>{window.innerWidth<=960&&C.classList.contains("mobile-open")&&!C.contains(e.target)&&!w.contains(e.target)&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false"))}));
|
package/site/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="description" content="Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself." />
|
|
8
8
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
9
9
|
<link rel="stylesheet" href="./styles.css" />
|
|
10
|
-
<meta name="x-build" content="2026-05-
|
|
10
|
+
<meta name="x-build" content="2026-05-27T05:01:47.803Z" />
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<a class="skip-link" href="#app">Skip to content</a>
|
package/site/llms.txt
CHANGED
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
> Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself.
|
|
4
4
|
|
|
5
|
-
- [Welcome](/pages/welcome.html): What Pagenary is and how this dogfooded portal is built.
|
|
5
|
+
- [Welcome](https://docs.pagenary.com/pages/welcome.html): What Pagenary is and how this dogfooded portal is built.
|
|
6
6
|
|
|
7
7
|
## Getting Started
|
|
8
8
|
|
|
9
|
-
- [Quickstart](/pages/quickstart.html): Install, build the default bundle, and serve it locally.
|
|
9
|
+
- [Quickstart](https://docs.pagenary.com/pages/quickstart.html): Install, build the default bundle, and serve it locally.
|
|
10
10
|
|
|
11
11
|
## Guides
|
|
12
12
|
|
|
13
|
-
- [Developer Guide](/pages/developer-guide.html): Project layout, scripts, and the content authoring workflow.
|
|
14
|
-
- [Tenant Configuration](/pages/tenant-config.html): Every config.json option: branding, theming, SEO, and export.
|
|
15
|
-
- [Extending](/pages/extending.html): Add section templates, content types, and build behaviors.
|
|
13
|
+
- [Developer Guide](https://docs.pagenary.com/pages/developer-guide.html): Project layout, scripts, and the content authoring workflow.
|
|
14
|
+
- [Tenant Configuration](https://docs.pagenary.com/pages/tenant-config.html): Every config.json option: branding, theming, SEO, and export.
|
|
15
|
+
- [Extending](https://docs.pagenary.com/pages/extending.html): Add section templates, content types, and build behaviors.
|
|
16
16
|
|
|
17
17
|
## Reference
|
|
18
18
|
|
|
19
|
-
- [Architecture](/pages/architecture.html): The static SPA pattern, build pipeline, and tenant content model.
|
|
20
|
-
- [API Reference](/pages/api.html): Module-level documentation for the publisher internals.
|
|
21
|
-
- [Deployment](/pages/deployment.html): Hosting the static output and multi-tenant domain routing.
|
|
22
|
-
- [SEO Strategy](/pages/seo-strategy.html): Metadata, hash-routing considerations, and discoverability.
|
|
19
|
+
- [Architecture](https://docs.pagenary.com/pages/architecture.html): The static SPA pattern, build pipeline, and tenant content model.
|
|
20
|
+
- [API Reference](https://docs.pagenary.com/pages/api.html): Module-level documentation for the publisher internals.
|
|
21
|
+
- [Deployment](https://docs.pagenary.com/pages/deployment.html): Hosting the static output and multi-tenant domain routing.
|
|
22
|
+
- [SEO Strategy](https://docs.pagenary.com/pages/seo-strategy.html): Metadata, hash-routing considerations, and discoverability.
|
package/site/pages/api.html
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>API Reference | Pagenary Docs</title>
|
|
7
7
|
<meta name="description" content="Module-level documentation for the publisher internals." />
|
|
8
|
-
<link rel="canonical" href="
|
|
8
|
+
<link rel="canonical" href="https://docs.pagenary.com/pages/api.html" />
|
|
9
9
|
|
|
10
10
|
<!-- Open Graph -->
|
|
11
11
|
<meta property="og:title" content="API Reference" />
|
|
12
12
|
<meta property="og:description" content="Module-level documentation for the publisher internals." />
|
|
13
13
|
<meta property="og:type" content="article" />
|
|
14
|
-
<meta property="og:url" content="
|
|
14
|
+
<meta property="og:url" content="https://docs.pagenary.com/pages/api.html" />
|
|
15
15
|
|
|
16
16
|
<!-- Twitter Card -->
|
|
17
17
|
<meta name="twitter:card" content="summary" />
|
|
@@ -26,16 +26,20 @@
|
|
|
26
26
|
"@type": "TechArticle",
|
|
27
27
|
"headline": "API Reference",
|
|
28
28
|
"description": "Module-level documentation for the publisher internals.",
|
|
29
|
-
"url": "
|
|
30
|
-
"dateModified": "2026-05-
|
|
29
|
+
"url": "https://docs.pagenary.com/pages/api.html",
|
|
30
|
+
"dateModified": "2026-05-27",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
|
-
"@id": "
|
|
33
|
+
"@id": "https://docs.pagenary.com/pages/api.html"
|
|
34
34
|
},
|
|
35
35
|
"isPartOf": {
|
|
36
36
|
"@type": "WebSite",
|
|
37
37
|
"name": "Pagenary Docs",
|
|
38
|
-
"url": "
|
|
38
|
+
"url": "https://docs.pagenary.com"
|
|
39
|
+
},
|
|
40
|
+
"publisher": {
|
|
41
|
+
"@type": "Organization",
|
|
42
|
+
"name": "Pagenary"
|
|
39
43
|
}
|
|
40
44
|
},
|
|
41
45
|
{
|
|
@@ -46,19 +50,19 @@
|
|
|
46
50
|
"@type": "ListItem",
|
|
47
51
|
"position": 1,
|
|
48
52
|
"name": "Home",
|
|
49
|
-
"item": "
|
|
53
|
+
"item": "https://docs.pagenary.com"
|
|
50
54
|
},
|
|
51
55
|
{
|
|
52
56
|
"@type": "ListItem",
|
|
53
57
|
"position": 2,
|
|
54
58
|
"name": "Reference",
|
|
55
|
-
"item": "/#reference"
|
|
59
|
+
"item": "https://docs.pagenary.com/#reference"
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
"@type": "ListItem",
|
|
59
63
|
"position": 3,
|
|
60
64
|
"name": "API Reference",
|
|
61
|
-
"item": "
|
|
65
|
+
"item": "https://docs.pagenary.com/pages/api.html"
|
|
62
66
|
}
|
|
63
67
|
]
|
|
64
68
|
}
|
|
@@ -68,11 +72,11 @@
|
|
|
68
72
|
<!-- Redirect to SPA for JavaScript-enabled browsers -->
|
|
69
73
|
<script>
|
|
70
74
|
if (typeof window !== 'undefined') {
|
|
71
|
-
window.location.replace('/#api');
|
|
75
|
+
window.location.replace('https://docs.pagenary.com/#api');
|
|
72
76
|
}
|
|
73
77
|
</script>
|
|
74
78
|
<noscript>
|
|
75
|
-
<meta http-equiv="refresh" content="0; url
|
|
79
|
+
<meta http-equiv="refresh" content="0; url=https://docs.pagenary.com/#api" />
|
|
76
80
|
</noscript>
|
|
77
81
|
|
|
78
82
|
<link rel="stylesheet" href="../styles.css" />
|
|
@@ -332,7 +336,7 @@ interface Chapter {
|
|
|
332
336
|
</div>
|
|
333
337
|
</article>
|
|
334
338
|
<footer class="static-footer">
|
|
335
|
-
<p>View interactive version: <a href="/#api">API Reference</a></p>
|
|
339
|
+
<p>View interactive version: <a href="https://docs.pagenary.com/#api">API Reference</a></p>
|
|
336
340
|
</footer>
|
|
337
341
|
</main>
|
|
338
342
|
</body>
|