@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 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
- # Or separately:
14
- npm run build # Build default bundle to dist/
15
- npm run serve # Preview on http://localhost:5173
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
- # Full builds
238
- npm run build # Build default bundle
239
- npm run build:tenants # Build all registered tenants
240
- npm run build:tenants my-tenant # Build specific tenant
241
-
242
- # Incremental builds (git-aware)
243
- npm run build:incremental my-tenant # Only rebuild changed files
244
-
245
- # Development
246
- npm run dev # Build + serve with watch
247
- npm run serve # Serve dist/ on localhost:5173
248
-
249
- # Utilities
250
- npm run lint:content # Check for trailing whitespace/tabs
251
- npm run check:seo # Verify SEO metadata
252
- npm run check # Run all checks
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
- "my-docs": {
264
- "source": "/absolute/path/to/my-docs",
265
- "domain": "my-docs.local"
266
- },
267
- "client-portal": {
268
- "source": "git:https://github.com/org/client-docs.git#main",
269
- "domain": "docs.client.com"
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**: `/absolute/path/to/content`
276
- - **Git repository**: `git:https://github.com/org/repo.git#branch`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagenary/publisher",
3
- "version": "2026.5.1",
3
+ "version": "2026.5.2",
4
4
  "type": "module",
5
5
  "description": "Multi-tenant static publishing component for Pagenary platform.",
6
6
  "license": "AGPL-3.0-or-later",
@@ -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.seo?.siteUrl || ''
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 = seoConfig.siteUrl || '';
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 = seoConfig.siteUrl || '';
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 = seoConfig.siteUrl || '';
157
- const canonicalUrl = `${baseUrl}/#${section.id}`;
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 = seoConfig.siteUrl || '';
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
- const canonicalUrl = `${baseUrl}/#${sectionId}`;
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="summary" />
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('${baseUrl}/#${sectionId}');
363
+ window.location.replace('${spaUrl}');
313
364
  }
314
365
  </script>
315
366
  <noscript>
316
- <meta http-equiv="refresh" content="0; url=${baseUrl}/#${sectionId}" />
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="${canonicalUrl}">${safeTitle}</a></p>
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 = seoConfig.siteUrl || '';
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 = seoConfig.siteUrl || '';
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-26T05:39:33.402Z" />
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.
@@ -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="/#api" />
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="/#api" />
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": "/#api",
30
- "dateModified": "2026-05-26",
29
+ "url": "https://docs.pagenary.com/pages/api.html",
30
+ "dateModified": "2026-05-27",
31
31
  "mainEntityOfPage": {
32
32
  "@type": "WebPage",
33
- "@id": "/#api"
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": "/#api"
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=/#api" />
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>