@pagenary/publisher 2026.5.4 → 2026.6.1

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.
Files changed (38) hide show
  1. package/README.md +12 -1
  2. package/package.json +1 -1
  3. package/scripts/build-tenants.js +114 -16
  4. package/scripts/lib/search-index-generator.js +111 -0
  5. package/site/app.js +1 -1
  6. package/site/index.html +4 -4
  7. package/site/lib/fortemi-corpus.js +1 -0
  8. package/site/lib/manifest-utils.js +1 -1
  9. package/site/lib/search.js +1 -1
  10. package/site/pages/api.html +50 -13
  11. package/site/pages/architecture.html +37 -15
  12. package/site/pages/deployment.html +1 -1
  13. package/site/pages/developer-guide.html +2 -2
  14. package/site/pages/extending.html +1 -1
  15. package/site/pages/quickstart.html +5 -2
  16. package/site/pages/seo-strategy.html +1 -1
  17. package/site/pages/tenant-config.html +1 -1
  18. package/site/pages/welcome.html +1 -1
  19. package/site/robots.txt +1 -1
  20. package/site/search-index/manifest.json +49 -0
  21. package/site/search-index/part-0000.json +358 -0
  22. package/site/sections/api.js +1 -1
  23. package/site/sections/architecture.js +1 -1
  24. package/site/sections/developer-guide.js +1 -1
  25. package/site/sections/quickstart.js +1 -1
  26. package/site/sitemap.xml +10 -10
  27. package/site/styles.css +43 -0
  28. package/site/vendor/fortemi-aiwg-index.d.ts +212 -0
  29. package/site/vendor/fortemi-aiwg-index.js +1 -0
  30. package/src/app.js +119 -3
  31. package/src/index.html +3 -3
  32. package/src/lib/fortemi-corpus.js +0 -0
  33. package/src/lib/manifest-utils.js +1 -1
  34. package/src/lib/search.js +291 -42
  35. package/src/manifest.js +1 -1
  36. package/src/styles.css +43 -0
  37. package/src/vendor/fortemi-aiwg-index.d.ts +212 -0
  38. package/src/vendor/fortemi-aiwg-index.js +564 -0
package/README.md CHANGED
@@ -6,6 +6,9 @@
6
6
 
7
7
  `@pagenary/publisher` is the static site generator behind Pagenary — it turns one shared template catalog into many branded, tenant-specific documentation sites. Zero runtime dependencies, hash-based routing, full-text search, and a Git-aware build pipeline. Install it as a dev dependency and drive it with the `pagenary` CLI.
8
8
 
9
+ Built with [AIWG](https://aiwg.io), the multi-agent AI framework used to plan,
10
+ audit, and ship this project.
11
+
9
12
  ```bash
10
13
  npm install --save-dev @pagenary/publisher # add Pagenary to your project
11
14
  npx pagenary build:tenants my-docs # build your docs tenant
@@ -17,6 +20,8 @@ npx pagenary serve # serve on http://localhost:5173
17
20
  [![Docs](https://img.shields.io/badge/docs-docs.pagenary.com-22d3ee?style=flat-square&logo=readthedocs&logoColor=white)](https://docs.pagenary.com)
18
21
  [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg?style=flat-square)](../../LICENSE)
19
22
  [![Node Version](https://img.shields.io/badge/node-%E2%89%A516.0.0-brightgreen?style=flat-square&logo=node.js)](https://nodejs.org)
23
+ [![Search: @fortemi/core](https://img.shields.io/npm/v/@fortemi/core?label=search%20%C2%B7%20%40fortemi%2Fcore&color=CB3837&logo=npm&style=flat-square)](https://www.npmjs.com/package/@fortemi/core)
24
+ [![Built with AIWG](https://img.shields.io/npm/v/aiwg?label=built%20with%20%C2%B7%20aiwg&color=7c3aed&logo=npm&style=flat-square)](https://www.npmjs.com/package/aiwg)
20
25
 
21
26
  [**Docs Site**](https://docs.pagenary.com) · [**Quick Start**](#quick-start) · [**Features**](#features) · [**Tenant Workflow**](#tenant-content-workflow) · [**Documentation**](#documentation)
22
27
 
@@ -53,6 +58,9 @@ npm run dev # build + serve with watch mode
53
58
  npm run build # build default bundle to dist/
54
59
  ```
55
60
 
61
+ Pagenary development uses [AIWG](https://aiwg.io). On this host, maintainers can
62
+ inspect, build, or run the AIWG project from `~/dev/aiwg`.
63
+
56
64
  ---
57
65
 
58
66
  ## Features
@@ -78,7 +86,10 @@ npm run build # build default bundle to dist/
78
86
 
79
87
  ### Navigation & Search
80
88
  - **Command Palette** — `Ctrl/Cmd+K` or `/` opens a global finder
81
- - **Full-Text Search** — searches all content, not just titles
89
+ - **Fortemi-backed full-text search** — ranked results with snippets over a static
90
+ chunked index emitted at build time; lazy chunk fetch (precache) and offset
91
+ paging for infinite scroll, with a clean in-browser fallback. No server, no WASM.
92
+ See `docs/ARCHITECTURE.md` and `.aiwg/architecture/adr/ADR-015-fortemi-core-search-adapter.md`.
82
93
  - **Manifest-Driven Nav** — declarative navigation structure
83
94
  - **Keyboard Navigation** — arrow keys, Enter to select
84
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagenary/publisher",
3
- "version": "2026.5.4",
3
+ "version": "2026.6.1",
4
4
  "type": "module",
5
5
  "description": "Multi-tenant static publishing component for Pagenary platform.",
6
6
  "license": "AGPL-3.0-or-later",
@@ -9,6 +9,7 @@ import os from 'os';
9
9
  import { generateSeoArtifacts, resolveBaseUrl, resolveOgImage } from './lib/seo-generator.js';
10
10
  import { generateCollections } from './lib/collections-generator.js';
11
11
  import { parseFrontmatter } from './lib/frontmatter.js';
12
+ import { generateSearchIndex } from './lib/search-index-generator.js';
12
13
  import { fileURLToPath } from 'node:url';
13
14
 
14
15
  const root = process.cwd();
@@ -1672,6 +1673,35 @@ async function ensureJavascriptModule(sourcePath, targetPath) {
1672
1673
  await fsp.copyFile(sourcePath, targetPath);
1673
1674
  }
1674
1675
 
1676
+ async function readMarkdownMetadata(sourcePath) {
1677
+ const raw = await fsp.readFile(sourcePath, 'utf8');
1678
+ const { data, body } = parseFrontmatter(raw);
1679
+ return {
1680
+ title: data.title || firstHeadingFromMarkdown(body) || null,
1681
+ summary: data.summary || data.description || '',
1682
+ date: data.date || null,
1683
+ reading_time: estimateReadingTime(body)
1684
+ };
1685
+ }
1686
+
1687
+ function firstHeadingFromMarkdown(body) {
1688
+ const match = body.match(/^#\s+(.+)$/m);
1689
+ return match ? match[1].trim() : null;
1690
+ }
1691
+
1692
+ function estimateReadingTime(body) {
1693
+ const words = String(body || '').trim().split(/\s+/).filter(Boolean);
1694
+ return Math.max(1, Math.ceil(words.length / 200));
1695
+ }
1696
+
1697
+ async function readContentMetadata(sourcePath) {
1698
+ const ext = path.extname(sourcePath).toLowerCase();
1699
+ if (ext === '.md' || ext === '.markdown') {
1700
+ return readMarkdownMetadata(sourcePath);
1701
+ }
1702
+ return {};
1703
+ }
1704
+
1675
1705
  // ============================================================================
1676
1706
  // Internal Link Transformation (ADR-011)
1677
1707
  // ============================================================================
@@ -1992,6 +2022,46 @@ function deriveSectionId(parentId, name, isIndex = false) {
1992
2022
  return stem;
1993
2023
  }
1994
2024
 
2025
+ function routePath(value) {
2026
+ return String(value || '').replace(/^\/+|\/+$/g, '');
2027
+ }
2028
+
2029
+ function collectionForRelPath(relPath, collections = []) {
2030
+ const normalized = relPath.split(path.sep).join('/');
2031
+ return collections.find((collection) => {
2032
+ const collectionPath = routePath(collection.path);
2033
+ return collectionPath && (normalized === collectionPath || normalized.startsWith(`${collectionPath}/`));
2034
+ }) || null;
2035
+ }
2036
+
2037
+ function decorateCollectionEntry(entry, metadata, collection) {
2038
+ if (!collection || !entry) return entry;
2039
+ entry.collection = routePath(collection.path);
2040
+ entry.showDate = collection.showDate === true;
2041
+ entry.showSummary = collection.showSummary === true;
2042
+ entry.showReadingTime = collection.showReadingTime !== false;
2043
+ if (metadata.date) entry.date = metadata.date;
2044
+ if (metadata.reading_time) entry.reading_time = metadata.reading_time;
2045
+ return entry;
2046
+ }
2047
+
2048
+ function sortCollectionEntries(entries, collection) {
2049
+ if (!collection || !Array.isArray(entries)) return entries;
2050
+ const sortBy = collection.sortBy || 'date';
2051
+ const dir = (collection.order || 'desc').toLowerCase() === 'asc' ? 1 : -1;
2052
+ entries.sort((a, b) => {
2053
+ const av = a?.[sortBy];
2054
+ const bv = b?.[sortBy];
2055
+ if (av == null && bv == null) return 0;
2056
+ if (av == null) return 1;
2057
+ if (bv == null) return -1;
2058
+ if (av < bv) return -1 * dir;
2059
+ if (av > bv) return 1 * dir;
2060
+ return 0;
2061
+ });
2062
+ return entries;
2063
+ }
2064
+
1995
2065
  /**
1996
2066
  * Encode section ID for use in output filename
1997
2067
  * Replaces / with -- to create flat output structure
@@ -2107,19 +2177,21 @@ async function scanContentDirectory(dirPath, parentId, context, depth = 0) {
2107
2177
  const indexFile = files.find(f => /^index\.(md|markdown|html|htm|js|mjs)$/i.test(f.name));
2108
2178
  if (indexFile) {
2109
2179
  const sectionId = parentId || path.basename(dirPath);
2110
- const title = manifest?.title || humanizeTitle(path.basename(dirPath));
2111
- const summary = manifest?.summary || '';
2112
-
2113
- // Calculate relative path from content root
2114
2180
  const relPath = path.relative(context.contentRoot, path.join(dirPath, indexFile.name));
2181
+ const metadata = await readContentMetadata(path.join(dirPath, indexFile.name));
2182
+ const collection = collectionForRelPath(relPath, context.collections);
2183
+ const title = manifest?.title || metadata.title || humanizeTitle(path.basename(dirPath));
2184
+ const summary = manifest?.summary || metadata.summary || '';
2115
2185
 
2116
- sections.push({
2186
+ const sectionEntry = {
2117
2187
  id: sectionId,
2118
2188
  title,
2119
2189
  summary,
2120
2190
  file: relPath,
2121
2191
  _isIndex: true
2122
- });
2192
+ };
2193
+ decorateCollectionEntry(sectionEntry, metadata, collection);
2194
+ sections.push(sectionEntry);
2123
2195
  }
2124
2196
 
2125
2197
  // Process other content files
@@ -2131,10 +2203,12 @@ async function scanContentDirectory(dirPath, parentId, context, depth = 0) {
2131
2203
  s.id === sectionId || s.file === file.name || s.id === file.name.replace(/\.[^.]+$/, '')
2132
2204
  );
2133
2205
 
2134
- const title = manifestEntry?.title || humanizeTitle(file.name);
2135
- const summary = manifestEntry?.summary || '';
2136
- const type = manifestEntry?.type || null;
2137
2206
  const relPath = path.relative(context.contentRoot, path.join(dirPath, file.name));
2207
+ const metadata = await readContentMetadata(path.join(dirPath, file.name));
2208
+ const collection = collectionForRelPath(relPath, context.collections);
2209
+ const title = manifestEntry?.title || metadata.title || humanizeTitle(file.name);
2210
+ const summary = manifestEntry?.summary || metadata.summary || '';
2211
+ const type = manifestEntry?.type || null;
2138
2212
 
2139
2213
  const sectionEntry = {
2140
2214
  id: sectionId,
@@ -2142,6 +2216,7 @@ async function scanContentDirectory(dirPath, parentId, context, depth = 0) {
2142
2216
  summary,
2143
2217
  file: relPath
2144
2218
  };
2219
+ decorateCollectionEntry(sectionEntry, metadata, collection);
2145
2220
  if (type) sectionEntry.type = type;
2146
2221
  sections.push(sectionEntry);
2147
2222
  }
@@ -2170,6 +2245,7 @@ async function scanContentDirectory(dirPath, parentId, context, depth = 0) {
2170
2245
  if (indexEntry) {
2171
2246
  // Remove index from subsections, it becomes the group itself
2172
2247
  const otherSections = subsections.filter(s => !s._isIndex);
2248
+ sortCollectionEntries(otherSections, collectionForRelPath(path.relative(context.contentRoot, subdirPath), context.collections));
2173
2249
  const entry = {
2174
2250
  id: subdirId,
2175
2251
  title,
@@ -2185,7 +2261,7 @@ async function scanContentDirectory(dirPath, parentId, context, depth = 0) {
2185
2261
  id: subdirId,
2186
2262
  title,
2187
2263
  summary,
2188
- subsections
2264
+ subsections: sortCollectionEntries(subsections, collectionForRelPath(path.relative(context.contentRoot, subdirPath), context.collections))
2189
2265
  };
2190
2266
  if (collapsed) entry.collapsed = true;
2191
2267
  sections.push(entry);
@@ -2441,7 +2517,7 @@ async function materializeScannedSections(sections, context) {
2441
2517
  const processed = [];
2442
2518
 
2443
2519
  for (const section of sections) {
2444
- const { id, title, summary, file, subsections, url, type, collapsed } = section;
2520
+ const { id, title, summary, file, subsections, url, type, collapsed, _isIndex, ...metadata } = section;
2445
2521
 
2446
2522
  // Pass through external links without processing
2447
2523
  if (url) {
@@ -2499,6 +2575,7 @@ async function materializeScannedSections(sections, context) {
2499
2575
  id,
2500
2576
  title,
2501
2577
  summary,
2578
+ ...metadata,
2502
2579
  module: `./sections/${outFile}`,
2503
2580
  subsections: processedSubsections
2504
2581
  };
@@ -2506,14 +2583,14 @@ async function materializeScannedSections(sections, context) {
2506
2583
  if (collapsed) entry.collapsed = true;
2507
2584
  processed.push(entry);
2508
2585
  } else {
2509
- const entry = { id, title, summary, module: `./sections/${outFile}` };
2586
+ const entry = { id, title, summary, ...metadata, module: `./sections/${outFile}` };
2510
2587
  if (type) entry.type = type;
2511
2588
  if (collapsed) entry.collapsed = true;
2512
2589
  processed.push(entry);
2513
2590
  }
2514
2591
  } else if (processedSubsections && processedSubsections.length > 0) {
2515
2592
  // Group without its own content
2516
- const entry = { id, title, summary, subsections: processedSubsections };
2593
+ const entry = { id, title, summary, ...metadata, subsections: processedSubsections };
2517
2594
  if (type) entry.type = type;
2518
2595
  if (collapsed) entry.collapsed = true;
2519
2596
  processed.push(entry);
@@ -2575,6 +2652,9 @@ function applyManifestHierarchy(scannedSections, rootManifest) {
2575
2652
  title: mEntry.title || scanned?.title || mEntry.id,
2576
2653
  summary: mEntry.summary || scanned?.summary || ''
2577
2654
  };
2655
+ for (const key of ['collection', 'showDate', 'showSummary', 'showReadingTime', 'date', 'reading_time']) {
2656
+ if (scanned?.[key] !== undefined) node[key] = scanned[key];
2657
+ }
2578
2658
 
2579
2659
  if (mEntry.collapsed) node.collapsed = true;
2580
2660
  if (mEntry.type) node.type = mEntry.type;
@@ -2671,6 +2751,7 @@ async function processNestedContent(sourceDir, distDir, tenantId, contentRoot, o
2671
2751
  keepFiles,
2672
2752
  leafOrder: [],
2673
2753
  siteConfig,
2754
+ collections: Array.isArray(config.collections) ? config.collections : [],
2674
2755
  // Link transformation context (populated after scan)
2675
2756
  sectionIndex: null,
2676
2757
  linkWarnings,
@@ -2731,6 +2812,14 @@ async function processNestedContent(sourceDir, distDir, tenantId, contentRoot, o
2731
2812
  await fsp.writeFile(path.join(distDir, 'manifest.js'), manifestModule, 'utf8');
2732
2813
  console.log(` ↳ applied nested content structure for ${tenantId} (${context.leafOrder.length} sections)`);
2733
2814
 
2815
+ // Emit the static Fortemi chunked search index. Failure here never breaks the
2816
+ // bundle — the runtime search adapter degrades to its legacy in-browser index.
2817
+ try {
2818
+ await generateSearchIndex(distDir, processedManifest, { tenantId });
2819
+ } catch (err) {
2820
+ console.warn(` ↳ search index generation skipped for ${tenantId}: ${err.message}`);
2821
+ }
2822
+
2734
2823
  return { success: true, sectionsCount: context.leafOrder.length };
2735
2824
  }
2736
2825
 
@@ -2779,7 +2868,7 @@ async function materializeSectionModule(entry, context) {
2779
2868
  }
2780
2869
 
2781
2870
  const ext = path.extname(sourcePath).toLowerCase();
2782
- const outFile = `${id}.js`;
2871
+ const outFile = `${encodePathForFilename(id)}.js`;
2783
2872
  const targetPath = path.join(context.sectionsDir, outFile);
2784
2873
  context.keepFiles.add(outFile);
2785
2874
 
@@ -3031,6 +3120,14 @@ async function processTenantManifestLegacy(sourceDir, distDir, tenantId, options
3031
3120
  const manifestModule = buildManifestModuleSource(processedManifest, defaultSection, context.siteConfig);
3032
3121
  await fsp.writeFile(path.join(distDir, 'manifest.js'), manifestModule, 'utf8');
3033
3122
  console.log(` ↳ applied manifest-driven content for ${tenantId}`);
3123
+
3124
+ // Emit the static Fortemi chunked search index (legacy manifest path).
3125
+ try {
3126
+ await generateSearchIndex(distDir, processedManifest, { tenantId });
3127
+ } catch (err) {
3128
+ console.warn(` ↳ search index generation skipped for ${tenantId}: ${err.message}`);
3129
+ }
3130
+
3034
3131
  return { success: true };
3035
3132
  }
3036
3133
 
@@ -3339,7 +3436,7 @@ async function processIncrementalManifest(sourceDir, distDir, tenantId, changedF
3339
3436
  const ext = path.extname(sourcePath).toLowerCase();
3340
3437
  // Use manifest section ID if available, otherwise fall back to filename
3341
3438
  const sectionId = fileToSectionId.get(relPath) || path.basename(relPath, ext);
3342
- const targetPath = path.join(sectionsDir, `${sectionId}.js`);
3439
+ const targetPath = path.join(sectionsDir, `${encodePathForFilename(sectionId)}.js`);
3343
3440
 
3344
3441
  try {
3345
3442
  if (ext === '.md' || ext === '.markdown') {
@@ -3349,7 +3446,8 @@ async function processIncrementalManifest(sourceDir, distDir, tenantId, changedF
3349
3446
  contentRoot: contentRoot.basePath || contentDir,
3350
3447
  sectionIndex,
3351
3448
  linkWarnings,
3352
- strictLinks: options.strictLinks !== false
3449
+ strictLinks: options.strictLinks !== false,
3450
+ collections: Array.isArray(config.collections) ? config.collections : []
3353
3451
  };
3354
3452
  await ensureMarkdownModule(sourcePath, targetPath, linkContext);
3355
3453
  console.log(` ↳ updated: ${sectionId} (markdown)`);
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Build-time Fortemi search-index generator.
3
+ *
4
+ * After a tenant's manifest.js + sections/ are materialized, this emits a
5
+ * deterministic chunked static index that the runtime adapter (src/lib/search.js)
6
+ * loads through the vendored @fortemi/core controller:
7
+ *
8
+ * dist/<tenant>/search-index/manifest.json (AiwgFortemiChunkManifest)
9
+ * dist/<tenant>/search-index/part-0000.json (AiwgFortemiChunkPart)
10
+ * ...
11
+ *
12
+ * Text is extracted by importing each section module and calling load(), the
13
+ * same contract the renderer uses, then stripping HTML without a DOM. The corpus
14
+ * is deterministic (sorted by record id, content-hashed generated_at) so repeat
15
+ * builds are byte-identical and incremental rebuilds refresh stale entries.
16
+ */
17
+ import fsp from 'fs/promises';
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+ import { pathToFileURL } from 'node:url';
21
+ import {
22
+ buildFortemiIndexExport,
23
+ chunkFortemiIndex,
24
+ stripHtml,
25
+ DEFAULT_PART_SIZE
26
+ } from '../../src/lib/fortemi-corpus.js';
27
+ import { flattenManifest } from '../../src/lib/search.js';
28
+
29
+ const SEARCH_INDEX_DIR = 'search-index';
30
+
31
+ async function pathExists(target) {
32
+ try {
33
+ await fsp.access(target, fs.constants.F_OK);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Import a materialized section module and return its plain-text content.
42
+ * Resilient: a module that fails to load degrades to title/summary text, exactly
43
+ * as the runtime search index does.
44
+ * @param {object} section - Flattened section with a `module` path (./sections/x.js)
45
+ * @param {string} distDir - Tenant output directory
46
+ * @returns {Promise<string>}
47
+ */
48
+ async function extractSectionText(section, distDir) {
49
+ if (!section.module) return '';
50
+ const rel = section.module.replace(/^\.?\//, '');
51
+ const abs = path.resolve(distDir, rel);
52
+ if (!(await pathExists(abs))) return '';
53
+ try {
54
+ const mod = await import(pathToFileURL(abs).href);
55
+ const loader = mod.load || mod.default;
56
+ if (typeof loader !== 'function') return '';
57
+ const payload = await loader();
58
+ return stripHtml(payload && payload.html ? payload.html : '');
59
+ } catch {
60
+ return '';
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Generate and write the chunked Fortemi index for one tenant bundle.
66
+ * @param {string} distDir - Tenant output directory (contains manifest.js, sections/)
67
+ * @param {Array} processedManifest - The nested manifest array written to manifest.js
68
+ * @param {object} [options]
69
+ * @param {string} [options.tenantId='pagenary']
70
+ * @param {number} [options.partSize=DEFAULT_PART_SIZE]
71
+ * @returns {Promise<{ total: number, parts: number, buildHash: string }>}
72
+ */
73
+ export async function generateSearchIndex(distDir, processedManifest, options = {}) {
74
+ const tenantId = options.tenantId || 'pagenary';
75
+ const partSize = options.partSize || DEFAULT_PART_SIZE;
76
+ const outDir = path.join(distDir, SEARCH_INDEX_DIR);
77
+
78
+ const flat = flattenManifest(processedManifest || []);
79
+ const entries = [];
80
+ for (const section of flat) {
81
+ if (!section || !section.id) continue;
82
+ const content = await extractSectionText(section, distDir);
83
+ const text = `${section.title || ''} ${section.summary || ''} ${section.group || ''} ${content}`.trim();
84
+ entries.push({ section, text });
85
+ }
86
+
87
+ const { index, buildHash } = buildFortemiIndexExport(entries, { repo: tenantId });
88
+ const { manifest, parts } = chunkFortemiIndex(index, { partSize });
89
+
90
+ // Replace the directory wholesale so stale parts from a larger prior corpus
91
+ // never linger (incremental-build correctness).
92
+ await fsp.rm(outDir, { recursive: true, force: true });
93
+ await fsp.mkdir(outDir, { recursive: true });
94
+
95
+ await fsp.writeFile(
96
+ path.join(outDir, 'manifest.json'),
97
+ `${JSON.stringify(manifest, null, 2)}\n`,
98
+ 'utf8'
99
+ );
100
+ await Promise.all(parts.map((part, i) =>
101
+ fsp.writeFile(
102
+ path.join(outDir, manifest.parts[i].href),
103
+ `${JSON.stringify(part, null, 2)}\n`,
104
+ 'utf8'
105
+ )));
106
+
107
+ console.log(` ↳ search index: ${manifest.total} record(s) in ${parts.length} part(s) [${buildHash.slice(0, 8)}]`);
108
+ return { total: manifest.total, parts: parts.length, buildHash };
109
+ }
110
+
111
+ export { SEARCH_INDEX_DIR };
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"),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"))}));
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,searchContentPage 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 g=document.getElementById("app"),y=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,S=new Map,I=new Set;let M=[],A=0,q=!1,P=(localStorage.getItem(k)||"").trim(),R=!1;function B(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 D(e,t={}){const{scrollToHighlight:a=!1}=t,{targetId:s,parentId:o}=function(e){return m(e,n)}(e);q&&W(),o&&O(o,!0),R=a||Boolean(P),location.hash.replace("#","")===s?j():location.hash=`#${s}`}function F(){return location.hash.replace("#","")||t}async function j(){const e=F(),t=p(e,n);if(!t)return;const{entry:o,targetId:l,parentId:c}=t;l===e?(c&&O(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(g.innerHTML='<article class="section"><p>Section failed to load.</p></article>');const o=await n();g.innerHTML=o.html||"",function(e){if(!e||!e.showDate&&!e.showReadingTime&&!e.showSummary)return;const t=(g.querySelector(".doc-content")||g.querySelector("article, section")||g).querySelector("h1");if(!t)return;const n=[];e.showDate&&e.date&&n.push(function(e){const t=String(e||"").trim();if(!t)return"";const n=/^\d{4}-\d{2}-\d{2}$/.test(t)?`${t}T00:00:00Z`:t,a=new Date(n);return Number.isNaN(a.getTime())?t:new Intl.DateTimeFormat(void 0,{year:"numeric",month:"long",day:"numeric",timeZone:"UTC"}).format(a)}(e.date)),e.showReadingTime&&e.reading_time&&n.push(`${e.reading_time} min read`);let a=t;if(n.length>0){const e=document.createElement("p");e.className="doc-meta",e.textContent=n.join(" · "),t.after(e),a=e}if(e.showSummary&&e.summary){const t=document.createElement("p");t.className="doc-summary",t.textContent=e.summary,a.after(t)}}(e),await h(g),await f(g),function(e){const t=g.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(),D(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(),D(l.id)}),e.appendChild(t),e.innerHTML+='<span class="bottom-nav-chevron">›</span>',c.appendChild(e)}(g.querySelector("section")||g).appendChild(c)}(e.id),g.scrollTop=0,window.scrollTo(0,0),"function"==typeof o.afterRender&&o.afterRender(g),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=R;R=!1,Q(l),requestAnimationFrame(()=>g.focus())}(o)):location.replace(`#${l}`)}function O(e,t){if(!e)return;const n=S.get(e);t?(I.add(e),n&&n.group.classList.add("expanded")):(I.delete(e),n&&n.group.classList.remove("expanded"))}function _(){if(!T||!x)return;q=!0,T.hidden=!1;const e=P;x.value=e,G(e),requestAnimationFrame(()=>{x.focus(),e&&x.select()})}function W(){T&&x&&(q=!1,T.hidden=!0,x.blur())}!function(){y.innerHTML="",H.clear(),S.clear();let t=I.size>0;e.forEach((e,n)=>{if(e.url){const t=B(e,"nav-leaf");return void y.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(),D(e.id,{scrollToHighlight:Boolean(P)})}),s.querySelector(".nav-expand-toggle").addEventListener("click",t=>{t.stopPropagation();const n=!I.has(e.id);O(e.id,n)}),s.addEventListener("click",t=>{if(t.target===s){const t=!I.has(e.id);O(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);O(e.id,t)}));const o=document.createElement("div");o.className="nav-sublist",e.subsections.forEach(e=>{if(e.url){const t=B(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);O(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-nested",e.subsections.forEach(e=>{if(e.url){const t=B(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);O(e.id,t)});const s=document.createElement("div");s.className="nav-sublist nav-sublist-deep",e.subsections.forEach(e=>{if(e.url){const t=B(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);O(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-ultra",e.subsections.forEach(e=>{if(e.url){const t=B(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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),s.appendChild(t),H.set(e.id,n),S.set(e.id,{group:t,button:n,list:a});const o=I.has(e.id)&&!e.collapsed;return void O(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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),s.appendChild(t),H.set(e.id,t)}),t.append(n,s),a.appendChild(t),H.set(e.id,n),S.set(e.id,{group:t,button:n,list:s});const o=I.has(e.id)&&!e.collapsed;return void O(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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),o.appendChild(t),H.set(e.id,n),S.set(e.id,{group:t,button:n,list:a});const s=I.has(e.id)&&!e.collapsed;return void O(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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),o.appendChild(t),H.set(e.id,t)}),n.append(s,o),y.appendChild(n),H.set(e.id,s),S.set(e.id,{group:n,button:s,list:o});const i=!e.collapsed&&(I.has(e.id)||!t&&!I.size);O(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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),y.appendChild(t),H.set(e.id,t)}})}(),P&&(R=!0),window.addEventListener("hashchange",()=>{P&&(R=!0),j()}),b.textContent=(new Date).getFullYear(),j(),L&&T&&x&&N&&(L.addEventListener("click",()=>{q?W():_()}),x.addEventListener("input",()=>{const e=x.value;Z(e,!0),G(e)}),x.addEventListener("keydown",e=>{const t=M.length-1;if("ArrowDown"===e.key)e.preventDefault(),A=Math.min(t,A+1),K();else if("ArrowUp"===e.key)e.preventDefault(),A=Math.max(0,A-1),K();else if("Enter"===e.key){e.preventDefault();const t=M[A];t&&(Z(x.value,!0),D(t.id,{scrollToHighlight:!0}),W())}else"Escape"===e.key&&(e.preventDefault(),W())}),N.addEventListener("click",e=>{const t=e.target.closest("[data-section]");if(!t)return;const n=t.dataset.section;n&&(Z(x.value,!0),D(n,{scrollToHighlight:!0}),W())}),T.addEventListener("click",e=>{e.target===T&&W()}),N.addEventListener("scroll",()=>{X.loading||X.complete||N.scrollTop+N.clientHeight>=N.scrollHeight-48&&J(!1)}),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(),q?W():_()):"Escape"===e.key&&q&&(e.preventDefault(),W())}));let U=null,V=!1;const z=25;let X={query:"",offset:0,total:0,complete:!0,loading:!1};async function G(e){N&&(X={query:e,offset:0,total:0,complete:!1,loading:!1},M=[],!V&&e.trim()&&(V=!0,N.innerHTML='<li class="cmd-item cmd-loading">Indexing content...</li>'),clearTimeout(U),U=setTimeout(async()=>{await J(!0);const e=F();A=d(M,e),K(),V=!1},e.trim()?150:0))}async function J(t=!1){if(X.loading)return;if(!t&&X.complete)return;const n=X.query;let a;X.loading=!0;try{a=await c(e,n,{offset:X.offset,limit:z})}catch{return void(X.loading=!1)}n===X.query?(M=t?a.items:M.concat(a.items),X.offset=M.length,X.total=a.total,X.complete=a.complete||0===a.items.length,X.loading=!1,function(){if(N){if(N.innerHTML="",!M.length){const e=document.createElement("li");return e.className="cmd-item",e.setAttribute("aria-selected","false"),e.textContent="No matches.",void N.appendChild(e)}if(M.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");if(a.className="cmd-item-summary",a.textContent=e.summary||"",t.append(n,a),e.searchSnippet&&e.searchSnippet!==e.summary){const n=document.createElement("span");n.className="cmd-item-snippet",n.textContent=e.searchSnippet,t.appendChild(n)}if("number"==typeof e.searchRank&&e.searchRank>0){const n=document.createElement("span");n.className="cmd-item-score",n.textContent=`Rank ${e.searchRank}`,t.appendChild(n)}N.appendChild(t)}),!X.complete&&X.total>M.length){const e=document.createElement("li");e.className="cmd-item cmd-more",e.setAttribute("aria-selected","false"),e.setAttribute("role","presentation"),e.textContent=`Showing ${M.length} of ${X.total} — scroll for more`,N.appendChild(e)}}}()):X.loading=!1}function K(){N&&Array.from(N.children).forEach((e,t)=>{const n=t===A&&M.length;e.setAttribute("aria-selected",n?"true":"false"),n&&e.scrollIntoView({block:"nearest"})})}function Y(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 Z(e,t=!1){P=e.trim(),t&&(P?localStorage.setItem(k,P):localStorage.removeItem(k)),Q()}function Q(e=!1){g&&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"})})}(g,P,{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=F(),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=Y((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"))}),y.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
@@ -5,9 +5,9 @@
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
6
6
  <title>Pagenary Docs</title>
7
7
  <meta name="description" content="Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself." />
8
- <link rel="icon" type="image/png" href="./favicon.png" />
9
- <link rel="stylesheet" href="./styles.css" />
10
- <meta name="x-build" content="2026-05-28T16:02:34.220Z" />
8
+ <link rel="icon" type="image/png" href="/favicon.png" />
9
+ <link rel="stylesheet" href="/styles.css" />
10
+ <meta name="x-build" content="2026-06-15T18:27:48.659Z" />
11
11
  </head>
12
12
  <body>
13
13
  <a class="skip-link" href="#app">Skip to content</a>
@@ -52,6 +52,6 @@
52
52
  <ul id="commandList" class="cmd-list" role="listbox"></ul>
53
53
  </div>
54
54
  </div>
55
- <script type="module" src="./app.js"></script>
55
+ <script type="module" src="/app.js"></script>
56
56
  </body>
57
57
  </html>
@@ -0,0 +1 @@
1
+ export const FORTEMI_INDEX_SCHEMA="aiwg.fortemi.index.export.v1";export const FORTEMI_RECORD_SCHEMA="aiwg.fortemi.index.record.v1";export const FORTEMI_CHUNK_MANIFEST_SCHEMA="aiwg.fortemi.index.chunk-manifest.v1";export const FORTEMI_CHUNK_PART_SCHEMA="aiwg.fortemi.index.chunk.v1";export const DEFAULT_PART_SIZE=100;export function stableHash(e){let t=0xcbf29ce484222325n;for(let r=0;r<e.length;r+=1)t^=BigInt(255&e.charCodeAt(r)),t=1099511628211n*t&18446744073709551615n;return t.toString(16).padStart(16,"0")}export function stripHtml(e){return e?String(e).replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi," ").replace(/<[^>]+>/g," ").replace(/&nbsp;/gi," ").replace(/&amp;/gi,"&").replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&#39;/gi,"'").replace(/&mdash;/gi,"—").replace(/&hellip;/gi,"…").replace(/\s+/g," ").trim():""}export function normalizeFacetValue(e){return String(e||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"general"}export function recordToSectionId(e){if(!e)return null;const t=e.source?.locator||"",r=/^#\/?(.+)$/.exec(t);if(r)return r[1];const o=e.facets?.section?.[0];return o||e.id?.replace(/^docs:page:/,"")||null}export function sectionToFortemiRecord(e,t,r){const o=String(e.group||e.title||"Documentation").split(">").map(e=>e.trim()).filter(Boolean),i=e.module||"",a=i?i.replace(/^\.?\//,"").replace(/^sections\//,"sections/"):`${e.id}.md`,n=Array.from(new Set([...o.map(normalizeFacetValue),e.type?normalizeFacetValue(e.type):null].filter(Boolean))),c=t&&t.trim()?t.trim():`${e.title||""} ${e.summary||""}`.trim();return{schema_version:FORTEMI_RECORD_SCHEMA,id:`docs:page:${e.id}`,type:"docs.page",source:{path:a,repo_relative_path:a,locator:`#/${e.id}`},title:e.title||e.id,text:c,facets:{section:[e.id],group:o.length?o.map(normalizeFacetValue):["documentation"]},tags:e.type?[normalizeFacetValue(e.type)]:[],concepts:n,relationships:[],provenance:[{field:"text",source:a,path:"$.text",confidence:"source",privacy:"public"}],privacy:{classification:"public",pii:!1},updated_at:r}}export function buildFortemiIndexExport(e,t={}){const r=t.repo||"pagenary",o=new Map;for(const{section:t,text:r}of e)t&&t.id&&(o.has(t.id)||o.set(t.id,{section:t,text:r||""}));const i=Array.from(o.values()).sort((e,t)=>`docs:page:${e.section.id}`.localeCompare(`docs:page:${t.section.id}`)),a=stableHash(`${r}${i.map(({section:e,text:t})=>`${e.id}\0${e.title||""}\0${t||""}`).join("")}`),n=Number(BigInt(`0x${a.slice(0,8)}`)),c=new Date(1e3*n).toISOString(),s=i.map(({section:e,text:t})=>sectionToFortemiRecord(e,t,e.date||c));return{index:{schema_version:FORTEMI_INDEX_SCHEMA,generated_at:c,source:{repo:r,privacy:"public",build_hash:a},items:s},buildHash:a,generatedAt:c}}export function computeFacetCounts(e){const t={},r=(e,r)=>{null!=r&&(t[e]||={},t[e][r]=(t[e][r]||0)+1)};for(const t of e)r("type",t.type),r("privacy",t.privacy?.classification),(t.tags||[]).forEach(e=>r("tag",e)),(t.concepts||[]).forEach(e=>r("concept",e)),Object.entries(t.facets||{}).forEach(([e,t])=>(t||[]).forEach(t=>r(e,t)));return t}export function chunkFortemiIndex(e,t={}){const r=Math.max(1,t.partSize||100),o=t.partHref||(e=>`part-${String(e).padStart(4,"0")}.json`),i=e.items||[],a=[],n=[];let c=0,s=0;do{const e=i.slice(c,c+r),t=o(s);a.push({schema_version:FORTEMI_CHUNK_PART_SCHEMA,manifest_schema_version:FORTEMI_CHUNK_MANIFEST_SCHEMA,offset:c,items:e}),n.push({href:t,offset:c,count:e.length}),c+=e.length||r,s+=1}while(c<i.length);return{manifest:{schema_version:FORTEMI_CHUNK_MANIFEST_SCHEMA,generated_at:e.generated_at,source:e.source,total:i.length,part_size:r,facets:computeFacetCounts(i),parts:n},parts:a}}
@@ -1 +1 @@
1
- export function sectionEntry(t,n){const e="string"==typeof t?t:t.id,r=n(e,"string"==typeof t?{}:{title:t.title,summary:t.summary});return{id:e,title:r.title,summary:r.summary,module:`./sections/${e}.js`}}export function groupEntry(t,n){const{id:e,title:r,summary:i,sections:s}=t;return{id:e,title:r,summary:i,subsections:s.map(t=>sectionEntry(t,n))}}export function buildSectionIndex(t){const n=new Map;function e(t,r=null){r&&(t.parentId=r),n.set(t.id,t),Array.isArray(t.subsections)&&t.subsections.forEach(n=>e(n,t.id))}return t.forEach(t=>e(t)),n}export function createFindSection(t){return function(n){return t.get(n)||null}}
1
+ export function sectionEntry(t,n){const e="string"==typeof t?t:t.id,r=n(e,"string"==typeof t?{}:{title:t.title,summary:t.summary});return{id:e,title:r.title,summary:r.summary,module:`/sections/${e}.js`}}export function groupEntry(t,n){const{id:e,title:r,summary:i,sections:s}=t;return{id:e,title:r,summary:i,subsections:s.map(t=>sectionEntry(t,n))}}export function buildSectionIndex(t){const n=new Map;function e(t,r=null){r&&(t.parentId=r),n.set(t.id,t),Array.isArray(t.subsections)&&t.subsections.forEach(n=>e(n,t.id))}return t.forEach(t=>e(t)),n}export function createFindSection(t){return function(n){return t.get(n)||null}}
@@ -1 +1 @@
1
- let t=null,e=null;export function escapeRegExp(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export function flattenManifest(t,e=""){const n=[];for(const r of t){const t=e?`${e} > ${r.title}`:r.title,o=Array.isArray(r.subsections)&&r.subsections.length>0;if(!r.module&&o||n.push({...r,group:e||r.title}),o){const e=flattenManifest(r.subsections,t);n.push(...e)}}return n}export async function buildSearchIndex(n){return t||e||(e=(async()=>{const e=flattenManifest(n),r=await Promise.all(e.map(async t=>{let e="";try{if(t.module){const n=t.module.replace("./","../"),r=await import(n);r.load&&(e=function(t){const e=document.createElement("div");return e.innerHTML=t,e.textContent||e.innerText||""}((await r.load()).html||""))}}catch(t){}return{...t,searchContent:`${t.title||""} ${t.summary||""} ${t.group||""} ${e}`.toLowerCase()}}));return t=r,r})(),e)}export function filterSections(t,e){const n=flattenManifest(t),r=e.trim().toLowerCase();return r?n.filter(t=>`${t.title||""} ${t.summary||""} ${t.group||""}`.toLowerCase().includes(r)):n}export async function searchContent(t,e){const n=await buildSearchIndex(t),r=e.trim().toLowerCase();return r?n.filter(t=>t.searchContent.includes(r)):n}export function parseSearchTerms(t){return t.split(/\s+/).map(t=>t.trim()).filter(Boolean)}export function findPreferredIndex(t,e){const n=t.findIndex(t=>t.id===e);return n>=0?n:0}
1
+ import{queryAiwgFortemiIndex as t,getAiwgFortemiFacets as e,createAiwgIndexController as n,createAiwgFetchChunkLoader as r,aiwgFortemiIndexToCommunityGraph as i,validateAiwgFortemiChunkManifest as o,validateAiwgFortemiIndexExport as a}from"../vendor/fortemi-aiwg-index.js";import{buildFortemiIndexExport as s,recordToSectionId as l}from"./fortemi-corpus.js";export{t as queryAiwgFortemiIndex,e as getAiwgFortemiFacets,n as createAiwgIndexController,r as createAiwgFetchChunkLoader,i as aiwgFortemiIndexToCommunityGraph};let u=null,c=null,f=!1,m=null,p=null,d=null,h=null;export function escapeRegExp(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export function flattenManifest(t,e=""){const n=[];for(const r of t){const t=e?`${e} > ${r.title}`:r.title,i=Array.isArray(r.subsections)&&r.subsections.length>0;if(!r.module&&i||n.push({...r,group:e||r.title}),i){const e=flattenManifest(r.subsections,t);n.push(...e)}}return n}function y(t,e){const n=l(t.item);return n?{...e.get(n)||{id:n,title:t.item.title,summary:""},searchRank:t.rank,searchSnippet:t.snippet,searchMatches:t.matches||[]}:null}export function filterSections(t,e){const n=flattenManifest(t),r=e.trim().toLowerCase();return r?n.filter(t=>`${t.title||""} ${t.summary||""} ${t.group||""}`.toLowerCase().includes(r)):n}export async function searchContentPage(e,i,a={}){const l=Math.max(0,a.offset||0),g=Math.max(1,a.limit||25),x=function(t){if(d&&h===t)return d;d=new Map;for(const e of flattenManifest(t))e&&e.id&&!d.has(e.id)&&d.set(e.id,e);return h=t,d}(e),w={types:["docs.page"],rank:!0,snippets:!0,includeMatches:!0,snippetLength:140,limit:g,offset:l},M=await async function(){if(u)return u;if(f)return null;if(c)return c;c=(async()=>{const t=function(){if("undefined"==typeof document||"undefined"==typeof URL)return null;try{return new URL("search-index/",document.baseURI).toString()}catch{return null}}();if(!t||"function"!=typeof fetch)return null;try{const e=await fetch(new URL("manifest.json",t).toString());if(!e.ok)return null;const i=await e.json();if(!o(i).valid)return null;const a=n();return a.loadChunkedIndex(i,r(t),{maxCachedParts:4}),u=a,a}catch{return null}})();const t=await c;return t||(f=!0),c=null,t}();if(M)try{const t=await M.queryChunked(i,{...w,onProgress:a.onProgress}),e=(t.rankedItems||[]).map(t=>y(t,x)).filter(Boolean);return{items:e,total:t.total,offset:l,limit:g,complete:l+e.length>=t.total,source:"static"}}catch{}const $=await async function(t){return m||p||(p=(async()=>{const e=flattenManifest(t),n=await Promise.all(e.map(async t=>{let e="";try{if(t.module){const n=t.module.replace("./","../"),r=await import(n);r.load&&(e=function(t){if("undefined"==typeof document)return"";const e=document.createElement("div");return e.innerHTML=t,e.textContent||e.innerText||""}((await r.load()).html||""))}}catch{}return{section:t,text:`${t.title||""} ${t.summary||""} ${t.group||""} ${e}`.trim()}})),{index:r}=s(n,{repo:"pagenary"}),i=new Map(e.map(t=>[t.id,t]));return m={index:r,byId:i},m})(),p)}(e);if(!i.trim()){const t=Array.from($.byId.values()),e=t.slice(l,l+g);return{items:e,total:t.length,offset:l,limit:g,complete:l+e.length>=t.length,source:"legacy"}}const C=t($.index,i,w),I=(C.rankedItems||[]).map(t=>y(t,$.byId)).filter(Boolean);return{items:I,total:C.total,offset:l,limit:g,complete:l+I.length>=C.total,source:"legacy"}}export async function searchContent(t,e,n={}){return e.trim()?(await searchContentPage(t,e,{limit:25,...n})).items:(await searchContentPage(t,"",{offset:0,limit:Number.MAX_SAFE_INTEGER,...n})).items}export function buildCommunityGraph(t,e={}){const n=flattenManifest(t).map(t=>({section:t,text:`${t.title||""} ${t.summary||""}`.trim()})),{index:r}=s(n,{repo:"pagenary"});return a(r).valid?i(r,e):{nodes:[],edges:[],communities:[]}}export function parseSearchTerms(t){return t.split(/\s+/).map(t=>t.trim()).filter(Boolean)}export function findPreferredIndex(t,e){const n=t.findIndex(t=>t.id===e);return n>=0?n:0}export function resetSearchState(){u=null,c=null,f=!1,m=null,p=null,d=null,h=null}