@pagenary/publisher 2026.6.12 → 2026.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/package.json +2 -2
- package/scripts/build-tenants.js +42 -6
- package/scripts/lib/search-index-generator.js +16 -2
- package/site/app.js +1 -1
- package/site/index.html +1 -1
- package/site/lib/docs-map.js +1 -1
- package/site/lib/fortemi-corpus.js +1 -1
- package/site/lib/search.js +1 -1
- package/site/pages/api.html +9 -3
- package/site/pages/architecture.html +1 -1
- package/site/pages/deployment.html +1 -1
- package/site/pages/developer-guide.html +1 -1
- package/site/pages/extending.html +1 -1
- package/site/pages/quickstart.html +1 -1
- package/site/pages/seo-strategy.html +1 -1
- package/site/pages/tenant-config.html +4 -4
- package/site/pages/theming-recipes.html +4 -4
- package/site/pages/welcome.html +1 -1
- package/site/robots.txt +1 -1
- package/site/search-index/manifest.json +37 -2
- package/site/search-index/metadata.json +1541 -0
- package/site/search-index/part-0000.json +1182 -32
- package/site/sections/api.js +1 -1
- package/site/sections/tenant-config.js +1 -1
- package/site/sections/theming-recipes.js +1 -1
- package/site/sitemap.xml +11 -11
- package/site/styles.css +286 -3
- package/site/vendor/fortemi-aiwg-index.d.ts +41 -1
- package/site/vendor/fortemi-aiwg-index.js +1 -1
- package/src/app.js +182 -2
- package/src/lib/docs-map.js +293 -14
- package/src/lib/fortemi-corpus.js +142 -4
- package/src/lib/search.js +73 -0
- package/src/styles.css +286 -3
- package/src/vendor/fortemi-aiwg-index.d.ts +41 -1
- package/src/vendor/fortemi-aiwg-index.js +61 -6
- package/tenants.schema.json +26 -0
package/README.md
CHANGED
|
@@ -92,6 +92,9 @@ inspect, build, or run the AIWG project from `~/dev/aiwg`.
|
|
|
92
92
|
The engine is *vendored* (`@fortemi/core`) — see `docs/VENDORING.md` for the
|
|
93
93
|
process, `docs/ARCHITECTURE.md`, and
|
|
94
94
|
`.aiwg/architecture/adr/ADR-015-fortemi-core-search-adapter.md`.
|
|
95
|
+
- **Docs Map** — opt-in static graph view over the same Fortemi corpus, with
|
|
96
|
+
weighted relationships, zoom/pan controls, neighbor highlighting, and node
|
|
97
|
+
metadata popups.
|
|
95
98
|
- **Manifest-Driven Nav** — declarative navigation structure
|
|
96
99
|
- **Keyboard Navigation** — arrow keys, Enter to select
|
|
97
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagenary/publisher",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-tenant static publishing component for Pagenary platform.",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"terser": "^5.48.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@fortemi/core": "2026.6.
|
|
70
|
+
"@fortemi/core": "2026.6.8",
|
|
71
71
|
"jest": "^30.4.2"
|
|
72
72
|
}
|
|
73
73
|
}
|
package/scripts/build-tenants.js
CHANGED
|
@@ -1267,6 +1267,7 @@ async function applyThemePicker(distDir, config, tenantId) {
|
|
|
1267
1267
|
async function applyDocsMap(distDir, config, tenantId) {
|
|
1268
1268
|
const docsMap = config.docsMap;
|
|
1269
1269
|
if (!docsMap || !docsMap.enabled) return;
|
|
1270
|
+
const renderer = normalizeDocsMapRenderer(docsMap.renderer);
|
|
1270
1271
|
|
|
1271
1272
|
const manifestPath = path.join(distDir, 'manifest.js');
|
|
1272
1273
|
if (!(await pathExists(manifestPath))) return;
|
|
@@ -1282,21 +1283,23 @@ async function applyDocsMap(distDir, config, tenantId) {
|
|
|
1282
1283
|
await fsp.writeFile(
|
|
1283
1284
|
path.join(distDir, 'docs-map-data.js'),
|
|
1284
1285
|
`export const DOCS_MAP_GRAPH = ${JSON.stringify(graphArtifact.graph)};\n` +
|
|
1285
|
-
`export const DOCS_MAP_LABELS = ${JSON.stringify(graphArtifact.labels)};\n
|
|
1286
|
+
`export const DOCS_MAP_LABELS = ${JSON.stringify(graphArtifact.labels)};\n` +
|
|
1287
|
+
`export const DOCS_MAP_METADATA = ${JSON.stringify(graphArtifact.metadata)};\n`,
|
|
1286
1288
|
'utf8'
|
|
1287
1289
|
);
|
|
1288
1290
|
await fsp.writeFile(
|
|
1289
1291
|
path.join(sectionsDir, 'docs-map.js'),
|
|
1290
|
-
"import
|
|
1292
|
+
"import * as DOCS_MAP_DATA from '../docs-map-data.js';\n" +
|
|
1291
1293
|
"import { loadDocsMap } from '../lib/docs-map.js';\n" +
|
|
1292
|
-
|
|
1294
|
+
`export const load = () => loadDocsMap(DOCS_MAP_DATA.DOCS_MAP_GRAPH, DOCS_MAP_DATA.DOCS_MAP_LABELS, { renderer: ${JSON.stringify(renderer)}, metadata: DOCS_MAP_DATA.DOCS_MAP_METADATA });\n`,
|
|
1293
1295
|
'utf8'
|
|
1294
1296
|
);
|
|
1295
1297
|
} else {
|
|
1296
1298
|
// No content to analyze — fall back to the runtime (MANIFEST-derived) graph.
|
|
1297
1299
|
await fsp.writeFile(
|
|
1298
1300
|
path.join(sectionsDir, 'docs-map.js'),
|
|
1299
|
-
"
|
|
1301
|
+
"import { loadManifestDocsMap } from '../lib/docs-map.js';\n" +
|
|
1302
|
+
`export const load = () => loadManifestDocsMap({ renderer: ${JSON.stringify(renderer)} });\n`,
|
|
1300
1303
|
'utf8'
|
|
1301
1304
|
);
|
|
1302
1305
|
}
|
|
@@ -1321,7 +1324,12 @@ async function applyDocsMap(distDir, config, tenantId) {
|
|
|
1321
1324
|
js = updated;
|
|
1322
1325
|
}
|
|
1323
1326
|
await fsp.writeFile(manifestPath, js, 'utf8');
|
|
1324
|
-
console.log(` ↳ wired docs-map view for ${tenantId}`);
|
|
1327
|
+
console.log(` ↳ wired docs-map view for ${tenantId} (${renderer} renderer)`);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function normalizeDocsMapRenderer(value) {
|
|
1331
|
+
const renderer = String(value || 'svg').trim().toLowerCase();
|
|
1332
|
+
return renderer === 'cytoscape' ? 'cytoscape' : 'svg';
|
|
1325
1333
|
}
|
|
1326
1334
|
|
|
1327
1335
|
/**
|
|
@@ -1395,12 +1403,40 @@ async function buildDocsMapGraph(distDir, tenantId) {
|
|
|
1395
1403
|
const graph = aiwgFortemiIndexToCommunityGraph(index, { communityFacet: 'group' });
|
|
1396
1404
|
if (!graph.nodes || graph.nodes.length < 2) return null;
|
|
1397
1405
|
|
|
1406
|
+
const recordIds = new Set(index.items.map((item) => item.id));
|
|
1407
|
+
const nodeMetadata = index.items.map((item) => [item.id, {
|
|
1408
|
+
title: item.title,
|
|
1409
|
+
section_id: String(item.id).replace(/^docs:page:/, ''),
|
|
1410
|
+
concepts: item.concepts || [],
|
|
1411
|
+
skos_concepts: item.skos_concepts || [],
|
|
1412
|
+
source: item.source || null,
|
|
1413
|
+
privacy: item.privacy || null,
|
|
1414
|
+
updated_at: item.updated_at || null
|
|
1415
|
+
}]);
|
|
1416
|
+
const edgeMetadata = [];
|
|
1417
|
+
for (const item of index.items) {
|
|
1418
|
+
for (const relationship of item.relationships || []) {
|
|
1419
|
+
if (!recordIds.has(relationship.target_id)) continue;
|
|
1420
|
+
const key = `${item.id}\0${relationship.target_id}\0${relationship.type}`;
|
|
1421
|
+
edgeMetadata.push([key, {
|
|
1422
|
+
label: relationship.label || relationship.type,
|
|
1423
|
+
confidence: relationship.confidence ?? null,
|
|
1424
|
+
privacy: relationship.privacy || null,
|
|
1425
|
+
shared_concepts: Array.isArray(relationship.metadata?.shared_concepts)
|
|
1426
|
+
? relationship.metadata.shared_concepts
|
|
1427
|
+
: [],
|
|
1428
|
+
source_path: relationship.source_path || null
|
|
1429
|
+
}]);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1398
1433
|
const labels = sections.map((s) => [s.id, s.title]);
|
|
1434
|
+
const metadata = { nodes: nodeMetadata, edges: edgeMetadata };
|
|
1399
1435
|
console.log(
|
|
1400
1436
|
` ↳ docs-map graph for ${tenantId}: ${graph.nodes.length} nodes, ` +
|
|
1401
1437
|
`${graph.edges.length} edges, ${graph.communities.length} communities`
|
|
1402
1438
|
);
|
|
1403
|
-
return { graph, labels };
|
|
1439
|
+
return { graph, labels, metadata };
|
|
1404
1440
|
}
|
|
1405
1441
|
|
|
1406
1442
|
const NAV_POSITIONS = new Set(['left', 'right', 'top', 'bottom', 'hybrid']);
|
|
@@ -20,6 +20,7 @@ import path from 'path';
|
|
|
20
20
|
import { pathToFileURL } from 'node:url';
|
|
21
21
|
import {
|
|
22
22
|
buildFortemiIndexExport,
|
|
23
|
+
buildFortemiMetadataExport,
|
|
23
24
|
chunkFortemiIndex,
|
|
24
25
|
stripHtml,
|
|
25
26
|
DEFAULT_PART_SIZE
|
|
@@ -66,7 +67,10 @@ async function extractSectionText(section, distDir) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
|
-
* Generate and write the chunked Fortemi index for one tenant bundle.
|
|
70
|
+
* Generate and write the chunked Fortemi index for one tenant bundle. The
|
|
71
|
+
* static search artifact is also the page-metadata source, so it opts into
|
|
72
|
+
* content concepts and concept-derived relationships for richer graph/metadata
|
|
73
|
+
* UX while preserving the lower-level corpus builder's bare default.
|
|
70
74
|
* @param {string} distDir - Tenant output directory (contains manifest.js, sections/)
|
|
71
75
|
* @param {Array} processedManifest - The nested manifest array written to manifest.js
|
|
72
76
|
* @param {object} [options]
|
|
@@ -88,8 +92,13 @@ export async function generateSearchIndex(distDir, processedManifest, options =
|
|
|
88
92
|
entries.push({ section, text });
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
const { index, buildHash } = buildFortemiIndexExport(entries, {
|
|
95
|
+
const { index, buildHash } = buildFortemiIndexExport(entries, {
|
|
96
|
+
repo: tenantId,
|
|
97
|
+
extractConcepts: true,
|
|
98
|
+
relateByConcept: true
|
|
99
|
+
});
|
|
92
100
|
const { manifest, parts } = chunkFortemiIndex(index, { partSize });
|
|
101
|
+
const metadata = buildFortemiMetadataExport(index);
|
|
93
102
|
|
|
94
103
|
// Build-time validation gate (#25): assert the emitted artifact against the
|
|
95
104
|
// vendored @fortemi/core contract so an invalid index fails the build clearly,
|
|
@@ -114,6 +123,11 @@ export async function generateSearchIndex(distDir, processedManifest, options =
|
|
|
114
123
|
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
115
124
|
'utf8'
|
|
116
125
|
);
|
|
126
|
+
await fsp.writeFile(
|
|
127
|
+
path.join(outDir, 'metadata.json'),
|
|
128
|
+
`${JSON.stringify(metadata, null, 2)}\n`,
|
|
129
|
+
'utf8'
|
|
130
|
+
);
|
|
117
131
|
await Promise.all(parts.map((part, i) =>
|
|
118
132
|
fsp.writeFile(
|
|
119
133
|
path.join(outDir, manifest.parts[i].href),
|
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,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"),w=document.getElementById("commandList"),N=document.getElementById("mobileMenuToggle"),C=document.querySelector(".sidebar"),k="docs-toolkit-command-query",$=new Map,S=new Map,I=new Map,H=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&&j(o,!0),R=a||Boolean(P),location.hash.replace("#","")===s?O():location.hash=`#${s}`}function F(){return location.hash.replace("#","")||t}async function O(){const e=F(),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){S.forEach(e=>{e.setAttribute("aria-current","false")});const n=S.get(e);if(n&&n.setAttribute("aria-current","page"),t){const e=S.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 j(e,t){if(!e)return;const n=I.get(e);t?(H.add(e),n&&n.group.classList.add("expanded")):(H.delete(e),n&&n.group.classList.remove("expanded"))}function _(){if(!T||!x)return;q=!0,T.hidden=!1;const e=P;x.value=e,X(e),requestAnimationFrame(()=>{x.focus(),e&&x.select()})}function W(){T&&x&&(q=!1,T.hidden=!0,x.blur())}!function(){y.innerHTML="",S.clear(),I.clear();let t=H.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=!H.has(e.id);j(e.id,n)}),s.addEventListener("click",t=>{if(t.target===s){const t=!H.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=!H.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=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=!H.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=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=!H.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=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=!H.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=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),S.set(e.id,t)}),t.append(n,a),s.appendChild(t),S.set(e.id,n),I.set(e.id,{group:t,button:n,list:a});const o=H.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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),s.appendChild(t),S.set(e.id,t)}),t.append(n,s),a.appendChild(t),S.set(e.id,n),I.set(e.id,{group:t,button:n,list:s});const o=H.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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),a.appendChild(t),S.set(e.id,t)}),t.append(n,a),o.appendChild(t),S.set(e.id,n),I.set(e.id,{group:t,button:n,list:a});const s=H.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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),o.appendChild(t),S.set(e.id,t)}),n.append(s,o),y.appendChild(n),S.set(e.id,s),I.set(e.id,{group:n,button:s,list:o});const i=!e.collapsed&&(H.has(e.id)||!t&&!H.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",()=>D(e.id,{scrollToHighlight:Boolean(P)})),y.appendChild(t),S.set(e.id,t)}})}(),function(){const e=document.getElementById("themePicker"),t=document.getElementById("themeStylesheet");if(!e||!t)return;let n;try{n=JSON.parse(e.dataset.themes||"[]")}catch{return}if(!Array.isArray(n)||0===n.length)return;const a="pagenary:theme",s=e=>{const t=n.find(t=>t.name===e);return t?t.file:null};function o(n){const a=s(n);a&&(t.setAttribute("href",a),e.value=n)}let i=null;try{i=localStorage.getItem(a)}catch{i=null}const l=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches;o(i&&s(i)?i:l&&s("dark")?"dark":e.dataset.default&&s(e.dataset.default)?e.dataset.default:n[0].name),e.addEventListener("change",()=>{o(e.value);try{localStorage.setItem(a,e.value)}catch{}})}(),P&&(R=!0),window.addEventListener("hashchange",()=>{P&&(R=!0),O()}),b.textContent=(new Date).getFullYear(),O(),L&&T&&x&&w&&(L.addEventListener("click",()=>{q?W():_()}),x.addEventListener("input",()=>{const e=x.value;Z(e,!0),X(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())}),w.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()}),w.addEventListener("scroll",()=>{J.loading||J.complete||w.scrollTop+w.clientHeight>=w.scrollHeight-48&&G(!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 J={query:"",offset:0,total:0,complete:!0,loading:!1};async function X(e){w&&(J={query:e,offset:0,total:0,complete:!1,loading:!1},M=[],!V&&e.trim()&&(V=!0,w.innerHTML='<li class="cmd-item cmd-loading">Indexing content...</li>'),clearTimeout(U),U=setTimeout(async()=>{await G(!0);const e=F();A=d(M,e),K(),V=!1},e.trim()?150:0))}async function G(t=!1){if(J.loading)return;if(!t&&J.complete)return;const n=J.query;let a;J.loading=!0;try{a=await c(e,n,{offset:J.offset,limit:z})}catch{return void(J.loading=!1)}n===J.query?(M=t?a.items:M.concat(a.items),J.offset=M.length,J.total=a.total,J.complete=a.complete||0===a.items.length,J.loading=!1,function(){if(w){if(w.innerHTML="",!M.length){const e=document.createElement("li");return e.className="cmd-item",e.setAttribute("aria-selected","false"),e.textContent="No matches.",void w.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)}w.appendChild(t)}),!J.complete&&J.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 ${J.total} — scroll for more`,w.appendChild(e)}}}()):J.loading=!1}function K(){w&&Array.from(w.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)})})}),N&&C&&(N.addEventListener("click",()=>{C.classList.contains("mobile-open")?(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),N.setAttribute("aria-expanded","false")):(C.classList.add("mobile-open"),document.body.classList.add("menu-open"),N.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"),N.setAttribute("aria-expanded","false"))}}),document.addEventListener("click",e=>{window.innerWidth<=960&&C.classList.contains("mobile-open")&&!C.contains(e.target)&&!N.contains(e.target)&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),N.setAttribute("aria-expanded","false"))}));
|
|
1
|
+
import{MANIFEST as e,DEFAULT_SECTION as t,findSection as n,getAdjacentSections as a,SITE_CONFIG as o,EXPORT_CONFIG as s}from"./manifest.js";import{updateMetaTags as i}from"./seo.js";import{escapeRegExp as c,searchContentPage as r,flattenManifest as l,findPreferredIndex as d,resolveSectionMetadata as m}from"./lib/search.js";import{resolveTarget as p,resolveEntry as u}from"./lib/router.js";import{composeExportDocument as v,collectExportableSections as h}from"./lib/export.js";import{renderMermaidBlocks as f}from"./mermaid-init.js";import{highlightCodeBlocks as g}from"./syntax-highlight.js";const y=document.getElementById("app"),b=document.getElementById("nav"),E=document.getElementById("year"),x=document.getElementById("exportBtn"),L=document.getElementById("commandToggle"),T=document.getElementById("commandPalette"),C=document.getElementById("commandInput"),N=document.getElementById("commandList"),k=document.getElementById("mobileMenuToggle"),w=document.querySelector(".sidebar"),$="docs-toolkit-command-query",S=new Map,A=new Map,I=new Map,M=new Set;let H=[],_=0,P=!1,q=(localStorage.getItem($)||"").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:o,parentId:s}=function(e){return p(e,n)}(e);P&&X(),s&&O(s,!0),B=a||Boolean(q),location.hash.replace("#","")===o?j():location.hash=`#${o}`}function D(){return location.hash.replace("#","")||t}async function j(){const t=D(),s=u(t,n);if(!s)return;const{entry:c,targetId:r,parentId:l}=s;r===t?(l&&O(l,!0),function(e,t=null){A.forEach(e=>{e.setAttribute("aria-current","false")});const n=A.get(e);if(n&&n.setAttribute("aria-current","page"),t){const e=A.get(t);e&&e.setAttribute("aria-current","page")}}(c.id,l),await async function(t){if(!t)return;const n=await import(t.module),s=n.load||n.default;if("function"!=typeof s)return void(y.innerHTML='<article class="section"><p>Section failed to load.</p></article>');const c=await s();y.innerHTML=c.html||"",function(t){if(!t)return;const n=(y.querySelector(".doc-content")||y.querySelector("article, section")||y).querySelector("h1");if(!n)return;const a=[];t.showDate&&t.date&&a.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)}(t.date)),t.showReadingTime&&t.reading_time&&a.push(`${t.reading_time} min read`);let o=n;if(a.length>0){const e=document.createElement("p");e.className="doc-meta",e.textContent=a.join(" · "),n.after(e),o=e}if(t.showSummary&&t.summary){const e=document.createElement("p");e.className="doc-summary",e.textContent=t.summary,o.after(e),o=e}!async function(t,n){const a=t.id,o=n.closest(".doc-content")||n.parentElement;let s=null;try{s=await m(e,a)}catch{return}if(D()!==a||!function(e){return!!e&&Boolean(e.concepts&&e.concepts.length||e.skos_concepts&&e.skos_concepts.length||e.relationships&&e.relationships.length||e.provenance&&e.provenance.length||e.provenance_events&&e.provenance_events.length||e.source||e.privacy)}(s))return;const i=document.createElement("div");i.className="doc-fortemi-tools";const c=`docFortemiPanel-${a}`,r=document.createElement("div");r.id=c,r.className="doc-fortemi-panel",r.hidden=!0,r.setAttribute("role","region"),r.setAttribute("aria-label","Page metadata"),function(e,t){e.textContent="",z(e,"Concepts",e=>{const n=t.skos_concepts&&t.skos_concepts.length?t.skos_concepts:t.concepts||[];n.length?function(e,t){const n=document.createElement("div");n.className="doc-fortemi-chips",t.forEach(e=>{const t=document.createElement("span");t.className="doc-fortemi-chip",t.textContent=U(e),"object"==typeof e&&e&&e.definition&&(t.title=e.definition),n.appendChild(t)}),e.appendChild(n)}(e,n):V(e,"concepts","none"),(t.skos_relations||[]).forEach(t=>{const n=U({id:t.source_id||t.source}),a=U({id:t.target_id||t.target});V(e,t.type||"related",`${W(n)} -> ${W(a)}`)})}),z(e,"Source and Provenance",e=>{V(e,"source",t.source?.repo_relative_path||t.source?.path),V(e,"locator",t.source?.locator),V(e,"updated",t.updated_at),V(e,"privacy",t.privacy?.classification),(t.provenance||[]).forEach(t=>{V(e,t.field||"field",`${t.source||"source"} (${t.confidence||"unknown"})`)}),(t.provenance_events||[]).forEach(t=>{const n=[t.agent||"unknown agent",t.source,t.started_at||t.ended_at].filter(Boolean).join(" · ");V(e,t.activity||"activity",n)})}),z(e,"Related Pages",e=>{const n=t.relationships||[];if(!n.length)return void V(e,"related","none");const a=document.createElement("ul");a.className="doc-fortemi-links",n.forEach(e=>{const t=document.createElement("li"),n=document.createElement("button");n.type="button",n.className="doc-fortemi-link";const o=function(e){return String(e?.target_id||e?.target||"").replace(/^docs:page:/,"")}(e),s=function(e){const t=Number(e);return Number.isFinite(t)?`${Math.round(100*Math.max(0,Math.min(1,t)))}%`:null}(e.confidence),i=e.label||e.type||"related",c=Array.isArray(e.metadata?.shared_concepts)?e.metadata.shared_concepts.map(W).join(", "):null;n.textContent=`${i}: ${W(o)}${s?` (${s})`:""}`,c&&(n.title=`Shared concepts: ${c}`),n.addEventListener("click",()=>{o&&F(o)}),t.appendChild(n),a.appendChild(t)}),e.appendChild(a)})}(r,s);const l=document.createElement("button");l.type="button",l.className="doc-fortemi-button",l.setAttribute("aria-expanded","false"),l.setAttribute("aria-controls",c),l.title="Show page metadata",l.innerHTML='<span aria-hidden="true">i</span><span class="sr-only">Show page metadata</span>',l.addEventListener("click",()=>{const e=r.hidden;r.hidden=!e,l.setAttribute("aria-expanded",String(e))}),i.appendChild(l),o.append(i,r)}(t,o)}(t),await f(y),await g(y),function(e){const t=y.querySelector(".bottom-nav");if(t&&t.remove(),"never"===o.bottomNav)return;const n=(o.bottomNavSections||[]).some(t=>e.startsWith(t)),s="mobile"===o.bottomNav&&!n,{prev:i,next:c}=a(e);if(!i&&!c)return;const r=document.createElement("nav");if(r.className="bottom-nav",s&&r.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),r.appendChild(e)}else{const e=document.createElement("div");e.className="bottom-nav-spacer",r.appendChild(e)}if(c){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-next";const t=document.createElement("a");t.href=`#${c.id}`,t.className="bottom-nav-link",t.title=`Next: ${c.title}`,t.textContent=c.title,t.addEventListener("click",e=>{e.preventDefault(),F(c.id)}),e.appendChild(t),e.innerHTML+='<span class="bottom-nav-chevron">›</span>',r.appendChild(e)}(y.querySelector("section")||y).appendChild(r)}(t.id),y.scrollTop=0,window.scrollTo(0,0),"function"==typeof c.afterRender&&c.afterRender(y),i({title:t.title,description:t.summary,siteTitle:o.siteTitle,siteUrl:o.siteUrl,sectionId:t.id,ogImage:t.ogImage||o.ogImage}),S.set(t.id,Date.now());const r=B;B=!1,oe(r),requestAnimationFrame(()=>y.focus())}(c)):location.replace(`#${r}`)}function O(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(e){return String(e||"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim()}function U(e){return"string"==typeof e?W(e):e.prefLabel||e.pref_label||e.label||e.id||"concept"}function V(e,t,n){if(null==n||""===n)return;const a=document.createElement("div");a.className="doc-fortemi-row";const o=document.createElement("span");o.className="doc-fortemi-key",o.textContent=t;const s=document.createElement("span");s.textContent=String(n),a.append(o,s),e.appendChild(a)}function z(e,t,n){const a=document.createElement("section");a.className="doc-fortemi-section";const o=document.createElement("h2");o.textContent=t,a.appendChild(o),n(a),e.appendChild(a)}function J(){if(!T||!C)return;P=!0,T.hidden=!1;const e=q;C.value=e,Q(e),requestAnimationFrame(()=>{C.focus(),e&&C.select()})}function X(){T&&C&&(P=!1,T.hidden=!0,C.blur())}!function(){b.innerHTML="",A.clear(),I.clear();let t=M.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),o=document.createElement("button");o.type="button",o.className="nav-parent"+(a?" nav-parent-with-content":""),o.dataset.section=e.id,o.title=e.summary,a?(o.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 `,o.querySelector(".nav-title-link").addEventListener("click",t=>{t.stopPropagation(),F(e.id,{scrollToHighlight:Boolean(q)})}),o.querySelector(".nav-expand-toggle").addEventListener("click",t=>{t.stopPropagation();const n=!M.has(e.id);O(e.id,n)}),o.addEventListener("click",t=>{if(t.target===o){const t=!M.has(e.id);O(e.id,t)}})):(o.innerHTML=`\n <span class="nav-title">${e.title}</span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,o.addEventListener("click",()=>{const t=!M.has(e.id);O(e.id,t)}));const s=document.createElement("div");s.className="nav-sublist",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-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);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=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);O(e.id,t)});const o=document.createElement("div");o.className="nav-sublist nav-sublist-deep",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-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);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=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),A.set(e.id,t)}),t.append(n,a),o.appendChild(t),A.set(e.id,n),I.set(e.id,{group:t,button:n,list:a});const s=M.has(e.id)&&!e.collapsed;return void O(e.id,s)}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)})),o.appendChild(t),A.set(e.id,t)}),t.append(n,o),a.appendChild(t),A.set(e.id,n),I.set(e.id,{group:t,button:n,list:o});const s=M.has(e.id)&&!e.collapsed;return void O(e.id,s)}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),A.set(e.id,t)}),t.append(n,a),s.appendChild(t),A.set(e.id,n),I.set(e.id,{group:t,button:n,list:a});const o=M.has(e.id)&&!e.collapsed;return void O(e.id,o)}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)})),s.appendChild(t),A.set(e.id,t)}),n.append(o,s),b.appendChild(n),A.set(e.id,o),I.set(e.id,{group:n,button:o,list:s});const i=!e.collapsed&&(M.has(e.id)||!t&&!M.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",()=>F(e.id,{scrollToHighlight:Boolean(q)})),b.appendChild(t),A.set(e.id,t)}})}(),function(){const e=document.getElementById("themePicker"),t=document.getElementById("themeStylesheet");if(!e||!t)return;let n;try{n=JSON.parse(e.dataset.themes||"[]")}catch{return}if(!Array.isArray(n)||0===n.length)return;const a="pagenary:theme",o=e=>{const t=n.find(t=>t.name===e);return t?t.file:null};function s(n){const a=o(n);a&&(t.setAttribute("href",a),e.value=n)}let i=null;try{i=localStorage.getItem(a)}catch{i=null}const c=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches;s(i&&o(i)?i:c&&o("dark")?"dark":e.dataset.default&&o(e.dataset.default)?e.dataset.default:n[0].name),e.addEventListener("change",()=>{s(e.value);try{localStorage.setItem(a,e.value)}catch{}})}(),q&&(B=!0),window.addEventListener("hashchange",()=>{q&&(B=!0),j()}),E.textContent=(new Date).getFullYear(),j(),L&&T&&C&&N&&(L.addEventListener("click",()=>{P?X():J()}),C.addEventListener("input",()=>{const e=C.value;ae(e,!0),Q(e)}),C.addEventListener("keydown",e=>{const t=H.length-1;if("ArrowDown"===e.key)e.preventDefault(),_=Math.min(t,_+1),te();else if("ArrowUp"===e.key)e.preventDefault(),_=Math.max(0,_-1),te();else if("Enter"===e.key){e.preventDefault();const t=H[_];t&&(ae(C.value,!0),F(t.id,{scrollToHighlight:!0}),X())}else"Escape"===e.key&&(e.preventDefault(),X())}),N.addEventListener("click",e=>{const t=e.target.closest("[data-section]");if(!t)return;const n=t.dataset.section;n&&(ae(C.value,!0),F(n,{scrollToHighlight:!0}),X())}),T.addEventListener("click",e=>{e.target===T&&X()}),N.addEventListener("scroll",()=>{Z.loading||Z.complete||N.scrollTop+N.clientHeight>=N.scrollHeight-48&&ee(!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(),P?X():J()):"Escape"===e.key&&P&&(e.preventDefault(),X())}));let G=null,K=!1;const Y=25;let Z={query:"",offset:0,total:0,complete:!0,loading:!1};async function Q(e){N&&(Z={query:e,offset:0,total:0,complete:!1,loading:!1},H=[],!K&&e.trim()&&(K=!0,N.innerHTML='<li class="cmd-item cmd-loading">Indexing content...</li>'),clearTimeout(G),G=setTimeout(async()=>{await ee(!0);const e=D();_=d(H,e),te(),K=!1},e.trim()?150:0))}async function ee(t=!1){if(Z.loading)return;if(!t&&Z.complete)return;const n=Z.query;let a;Z.loading=!0;try{a=await r(e,n,{offset:Z.offset,limit:Y})}catch{return void(Z.loading=!1)}n===Z.query?(H=t?a.items:H.concat(a.items),Z.offset=H.length,Z.total=a.total,Z.complete=a.complete||0===a.items.length,Z.loading=!1,function(){if(N){if(N.innerHTML="",!H.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(H.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)}),!Z.complete&&Z.total>H.length){const e=document.createElement("li");e.className="cmd-item cmd-more",e.setAttribute("aria-selected","false"),e.setAttribute("role","presentation"),e.textContent=`Showing ${H.length} of ${Z.total} — scroll for more`,N.appendChild(e)}}}()):Z.loading=!1}function te(){N&&Array.from(N.children).forEach((e,t)=>{const n=t===_&&H.length;e.setAttribute("aria-selected",n?"true":"false"),n&&e.scrollIntoView({block:"nearest"})})}function ne(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 ae(e,t=!1){q=e.trim(),t&&(q?localStorage.setItem($,q):localStorage.removeItem($)),oe()}function oe(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 o=a.map(e=>e.toLowerCase()),s=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&&s.has(t.tagName)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),r=[];let l;for(;l=i.nextNode();){const e=l.nodeValue.toLowerCase();o.some(t=>e.includes(t))&&r.push(l)}const d=new RegExp(`(${a.map(c).join("|")})`,"gi");r.forEach(e=>{const t=e.nodeValue,n=[];let a=0;t.replace(d,(e,o,s)=>{s>a&&n.push(document.createTextNode(t.slice(a,s)));const i=document.createElement("mark");return i.className="hl",i.textContent=e,n.push(i),a=s+e.length,e}),a<t.length&&n.push(document.createTextNode(t.slice(a)));const o=document.createDocumentFragment();n.forEach(e=>o.appendChild(e)),e.parentNode.replaceChild(o,e)}),n&&requestAnimationFrame(()=>{const t=e.querySelector("mark.hl");t&&t.scrollIntoView({behavior:"smooth",block:"center"})})}(y,q,{scrollToFirst:e})}x&&x.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(!x)return;const n=x.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(),x.disabled=!0;const o=document.createElement("div");o.className="export-loading-overlay",o.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(o),setTimeout(()=>o.classList.add("active"),10);const i=o.querySelector(".export-loading-fill"),c=o.querySelector(".export-loading-status");try{let n;if("page"===t){const t=D(),a=h(e).find(e=>e.id===t);n=a?[a]:[]}else n=h(e);if(0===n.length)return alert("No content available to export."),o.remove(),void(x.disabled=!1);const a=[],r=n.length;let l=0;for(const e of n){l++;const n=l/r*100;i.style.width=`${n}%`,c.textContent="page"===t?`Exporting: ${e.title}`:`Processing section ${l} of ${r}: ${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 o=ne((await n()).html||"");a.push({section:e,html:o})}catch(t){console.error("Failed to include section in export",e.id,t)}}c.textContent="Generating document...",await new Promise(e=>setTimeout(e,200));const d=v(a,s);c.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 o.remove();m.document.open(),m.document.write(d),m.document.close(),m.focus(),o.classList.remove("active"),setTimeout(()=>o.remove(),300)}catch(e){console.error("Export failed",e),alert("Export failed. Check console for details."),o.remove()}finally{x.disabled=!1,x.innerHTML=n}}(a)})})}),k&&w&&(k.addEventListener("click",()=>{w.classList.contains("mobile-open")?(w.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),k.setAttribute("aria-expanded","false")):(w.classList.add("mobile-open"),document.body.classList.add("menu-open"),k.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"))&&(w.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),k.setAttribute("aria-expanded","false"))}}),document.addEventListener("click",e=>{window.innerWidth<=960&&w.classList.contains("mobile-open")&&!w.contains(e.target)&&!k.contains(e.target)&&(w.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),k.setAttribute("aria-expanded","false"))}));
|
package/site/index.html
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</script>
|
|
22
22
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
23
23
|
<link rel="stylesheet" href="./styles.css" />
|
|
24
|
-
<meta name="x-build" content="2026-06-
|
|
24
|
+
<meta name="x-build" content="2026-06-21T02:21:08.456Z" />
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<a class="skip-link" href="#app">Skip to content</a>
|
package/site/lib/docs-map.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildCommunityGraph as
|
|
1
|
+
import{buildCommunityGraph as e,flattenManifest as t}from"./search.js";import{MANIFEST as n}from"../manifest.js";const a="http://www.w3.org/2000/svg",s=1e3;function o(e){return String(e).replace(/^docs:page:/,"")}function r(e){return[e.source??e.from??(Array.isArray(e)?e[0]:null),e.target??e.to??(Array.isArray(e)?e[1]:null)]}function c(e){return e instanceof Map?e:new Map(Array.isArray(e)?e:Object.entries(e||{}))}function i(e){return String(e||"").replace(/^concept:/,"").replace(/^docs:page:/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim()}function d(e,t,n){return`${e}\0${t}\0${n||"related"}`}function l(e){const t=Number(e);return Number.isFinite(t)?`${Math.round(100*Math.max(0,Math.min(1,t)))}%`:null}function p(e){return e.filter(Boolean).join("\n")}function u(e,t,n){const a=document.createElement("button");return a.type="button",a.className="docs-map-control",a.textContent=e,a.title=t,a.setAttribute("aria-label",t),a.addEventListener("click",n),a}export function renderDocsMap(e,t,n={}){if(!e)return;e.textContent="";const m=t&&t.nodes||[];if(m.length<2){const t=document.createElement("p");return t.className="docs-map-empty",t.textContent="Not enough pages to map yet — add a few more sections to see how they relate.",void e.appendChild(t)}const h=n.labelFor||(e=>o(e)),g=n.onNavigate||(()=>{}),f=c(n.metadata?.nodes),y=c(n.metadata?.edges),{positions:b,anchors:x,communityCount:v}=function(e){const t=new Map,n=e.communities&&e.communities.length?e.communities:[{id:"all",nodes:e.nodes.map(e=>e.id)}],a=.34*Math.min(s,700),o=.12*Math.min(s,700),r=[];return n.forEach((e,s)=>{const c=1===n.length?{x:500,y:350}:{x:500+a*Math.cos(2*Math.PI*s/n.length-Math.PI/2),y:350+a*Math.sin(2*Math.PI*s/n.length-Math.PI/2)};r.push({id:e.id,x:c.x,y:c.y,index:s});const i=e.nodes||[];i.forEach((e,n)=>{if(1===i.length)t.set(e,{x:c.x,y:c.y,community:s});else{const a=2*Math.PI*n/i.length;t.set(e,{x:c.x+o*Math.cos(a),y:c.y+o*Math.sin(a),community:s})}})}),e.nodes.forEach((e,n)=>{t.has(e.id)||t.set(e.id,{x:500+n%5*12-24,y:350,community:-1})}),{positions:t,anchors:r,communityCount:n.length}}(t),A=e=>e<0?0:Math.round(360*e/Math.max(1,v)),M=new Map;for(const e of t.edges||[]){const[t,n]=r(e);t&&n&&(M.has(t)||M.set(t,new Set),M.has(n)||M.set(n,new Set),M.get(t).add(n),M.get(n).add(t))}let E=1;const C={x:0,y:0};let w=!1,N=null;const S=document.createElement("div");S.className="docs-map-controls";const L=document.createElementNS(a,"svg");L.setAttribute("class","docs-map-svg"),L.setAttribute("viewBox","0 0 1000 700"),L.setAttribute("role","img"),L.setAttribute("aria-label",`Relationship map of ${m.length} documentation pages`);const k=document.createElementNS(a,"g");k.setAttribute("class","docs-map-viewport"),L.appendChild(k);const $=()=>{k.setAttribute("transform",`translate(${C.x} ${C.y}) scale(${E})`)},D=e=>{E=Math.max(.55,Math.min(2.8,e)),$()},I=(e,t)=>{C.x+=e,C.y+=t,$()};S.append(u("+ Zoom","Zoom in",()=>D(1.2*E)),u("- Zoom","Zoom out",()=>D(E/1.2)),u("←","Pan left",()=>I(48,0)),u("↑","Pan up",()=>I(0,48)),u("↓","Pan down",()=>I(0,-48)),u("→","Pan right",()=>I(-48,0)),u("Reset","Reset view",()=>{E=1,C.x=0,C.y=0,$()}));const P=function(e){const t=document.createElement("aside");t.className="docs-map-popup",t.hidden=!0,t.dataset.pinned="false",t.setAttribute("aria-live","polite");const n=document.createElement("button");return n.type="button",n.className="docs-map-popup-close",n.textContent="x",n.setAttribute("aria-label","Close graph details"),n.addEventListener("click",()=>{t.hidden=!0,t.dataset.pinned="false"}),t.appendChild(n),t.renderDetails=(n,a=!1)=>{t.dataset.pinned=String(a),t.hidden=!1,t.querySelectorAll(":scope > :not(.docs-map-popup-close)").forEach(e=>e.remove());const s=document.createElement("h2");if(s.textContent=n.title,t.appendChild(s),n.source){const e=document.createElement("p");e.className="docs-map-popup-source",e.textContent=n.source,t.appendChild(e)}if(n.concepts.length){const e=document.createElement("div");e.className="docs-map-popup-chips",n.concepts.slice(0,8).forEach(t=>{const n=document.createElement("span");n.textContent=t,e.appendChild(n)}),t.appendChild(e)}const o=document.createElement("button");o.type="button",o.className="docs-map-popup-open",o.textContent="Open page",o.addEventListener("click",()=>e(n.sectionId)),t.appendChild(o)},t}(g);L.addEventListener("wheel",e=>{e.preventDefault(),D(E*(e.deltaY<0?1.12:.88))},{passive:!1}),L.addEventListener("pointerdown",e=>{(function(e){let t=e;for(;t&&1===t.nodeType;){if(t.classList&&t.classList.contains("docs-map-node"))return t;t=t.parentNode}return null})(e.target)||(w=!0,N={x:e.clientX,y:e.clientY,panX:C.x,panY:C.y},L.setPointerCapture(e.pointerId))}),L.addEventListener("pointermove",e=>{w&&N&&(C.x=N.panX+(e.clientX-N.x),C.y=N.panY+(e.clientY-N.y),$())}),L.addEventListener("pointerup",e=>{w=!1,N=null,L.hasPointerCapture(e.pointerId)&&L.releasePointerCapture(e.pointerId)}),L.addEventListener("pointercancel",()=>{w=!1,N=null});for(const e of t.edges||[]){const[t,n]=r(e),s=b.get(t),o=b.get(n);if(!s||!o)continue;const c=y.get(d(t,n,e.kind)),u=l(c?.confidence),m=Array.isArray(c?.shared_concepts)&&c.shared_concepts.length?c.shared_concepts.map(i).join(", "):null,h=document.createElementNS(a,"line");h.setAttribute("x1",s.x),h.setAttribute("y1",s.y),h.setAttribute("x2",o.x),h.setAttribute("y2",o.y),h.setAttribute("class","docs-map-edge"),h.setAttribute("data-kind",e.kind||"related"),h.dataset.source=t,h.dataset.target=n,h.setAttribute("stroke-width",String(Math.max(1,Math.min(5,1+.6*Number(e.weight||1))))),h.setAttribute("opacity",String(Math.max(.28,Math.min(.82,.34+.08*Number(e.weight||1)))));const g=document.createElementNS(a,"title");g.textContent=p([c?.label||e.kind||"related",u?`Confidence: ${u}`:null,m?`Shared concepts: ${m}`:null]),h.appendChild(g),k.appendChild(h)}x.forEach(e=>{if("all"===e.id)return;const t=document.createElementNS(a,"text");t.setAttribute("x",e.x),t.setAttribute("y",e.y-.12*Math.min(s,700)-10),t.setAttribute("text-anchor","middle"),t.setAttribute("class","docs-map-community"),t.setAttribute("fill",`hsl(${A(e.index)} 60% 55%)`),t.textContent=String(e.id).replace(/^[a-z]+:/,"").replace(/[-_]/g," "),k.appendChild(t)});for(const e of m){const t=b.get(e.id);if(!t)continue;const n=o(e.id),s=h(e.id),r=f.get(e.id),c=Array.isArray(r?.skos_concepts)&&r.skos_concepts.length?r.skos_concepts.map(e=>e.prefLabel||e.id):(r?.concepts||[]).map(i),d={sectionId:n,title:s,concepts:c,source:r?.source?.repo_relative_path||r?.source?.path||""},l=document.createElementNS(a,"g");l.setAttribute("class","docs-map-node"),l.dataset.nodeId=e.id,l.setAttribute("tabindex","0"),l.setAttribute("role","button"),l.setAttribute("aria-label",s);const u=document.createElementNS(a,"title");u.textContent=p([s,c.length?`Concepts: ${c.slice(0,8).join(", ")}`:null,r?.source?.repo_relative_path||r?.source?.path]),l.appendChild(u);const m=document.createElementNS(a,"circle");m.setAttribute("cx",t.x),m.setAttribute("cy",t.y),m.setAttribute("r",8),m.setAttribute("fill",`hsl(${A(t.community)} 60% 55%)`),l.appendChild(m);const g=document.createElementNS(a,"text");g.setAttribute("x",t.x+12),g.setAttribute("y",t.y+4),g.setAttribute("class","docs-map-label"),g.textContent=s,l.appendChild(g);const y=()=>{const t=M.get(e.id)||new Set;k.querySelectorAll(".docs-map-node").forEach(n=>{const a=n.dataset.nodeId;n.classList.toggle("is-active",a===e.id),n.classList.toggle("is-neighbor",t.has(a)),n.classList.toggle("is-dimmed",a!==e.id&&!t.has(a))}),k.querySelectorAll(".docs-map-edge").forEach(t=>{const n=t.dataset.source===e.id||t.dataset.target===e.id;t.classList.toggle("is-active",n),t.classList.toggle("is-dimmed",!n)})},x=()=>{k.querySelectorAll(".is-active, .is-neighbor, .is-dimmed").forEach(e=>{e.classList.remove("is-active","is-neighbor","is-dimmed")})};l.addEventListener("mouseenter",()=>{y(),"true"!==P.dataset.pinned&&P.renderDetails(d,!1)}),l.addEventListener("mouseleave",()=>{"true"!==P.dataset.pinned&&x(),"true"!==P.dataset.pinned&&(P.hidden=!0)}),l.addEventListener("click",e=>{e.preventDefault(),y(),P.renderDetails(d,!0)}),l.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),P.renderDetails(d,!0))}),k.appendChild(l)}$(),e.append(S,L,P)}const m=['<section class="section doc">',' <div class="doc-content">'," <header>",' <p class="eyebrow">Overview</p>'," <h1>Docs Map</h1>",' <p class="lead">How these pages cluster and relate.</p>'," </header>",' <div id="docsMapRoot" class="docs-map"></div>'," </div>","</section>"].join("\n");function h(e,t,n={}){const a=(s=n.renderer,"cytoscape"===String(s||"svg").trim().toLowerCase()?"cytoscape":"svg");var s;"svg"!==a?(e.dataset.docsMapRenderer=a,e.dataset.docsMapFallback="svg"):(e.dataset.docsMapRenderer="svg",delete e.dataset.docsMapFallback),renderDocsMap(e,t,n)}export function loadDocsMap(e,t,n={}){const a=t instanceof Map?t:new Map(Array.isArray(t)?t:Object.entries(t||{}));return{html:m,afterRender(t){const s=t.querySelector("#docsMapRoot");s&&h(s,e||{nodes:[],edges:[],communities:[]},{renderer:n.renderer,metadata:n.metadata,labelFor:e=>a.get(o(e))||o(e),onNavigate:e=>{location.hash=`#${e}`}})}}}export async function loadManifestDocsMap(a={}){return{html:m,afterRender(s){const r=s.querySelector("#docsMapRoot");if(!r)return;let c;try{c=e(n)}catch{c={nodes:[],edges:[],communities:[]}}const i=function(e){const n=new Map;for(const a of t(e))a&&a.id&&n.set(a.id,a.title||a.id);return n}(n);h(r,c,{renderer:a.renderer,labelFor:e=>i.get(o(e))||o(e),onNavigate:e=>{location.hash=`#${e}`}})}}}export async function load(){return loadManifestDocsMap()}
|
|
@@ -1 +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 o=0;o<e.length;o+=1)t^=BigInt(255&e.charCodeAt(o)),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(/ /gi," ").replace(/&/gi,"&").replace(/</gi,"<").replace(/>/gi,">").replace(/"/gi,'"').replace(/'/gi,"'").replace(/—/gi,"—").replace(/…/gi,"…").replace(/\s+/g," ").trim():""}export function normalizeFacetValue(e){return String(e||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"general"}const e=new Set(["the","and","for","are","but","not","you","all","any","can","her","was","one","our","out","has","have","his","how","its","may","new","now","old","see","two","who","did","get","use","used","uses","using","with","this","that","they","them","then","than","from","into","your","yours","each","once","more","most","some","such","only","over","under","when","what","which","while","where","will","would","should","could","also","about","after","before","these","those","their","there","here","been","being","were","because","between","both","same","every","either","back","down","make","made","need","needs","keep","read","reads","call","calls","set","sets","follow","follows","three","plus","step","steps","shape","shapes","related","example","examples","docs","page","pages","http","https","www","com","json","returns","return","inspect","describe","described"]);export function extractConcepts(t,o={}){const
|
|
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 FORTEMI_METADATA_SCHEMA="pagenary.fortemi.metadata.v1";export const DEFAULT_PART_SIZE=100;export function stableHash(e){let t=0xcbf29ce484222325n;for(let o=0;o<e.length;o+=1)t^=BigInt(255&e.charCodeAt(o)),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(/ /gi," ").replace(/&/gi,"&").replace(/</gi,"<").replace(/>/gi,">").replace(/"/gi,'"').replace(/'/gi,"'").replace(/—/gi,"—").replace(/…/gi,"…").replace(/\s+/g," ").trim():""}export function normalizeFacetValue(e){return String(e||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"general"}const e=new Set(["the","and","for","are","but","not","you","all","any","can","her","was","one","our","out","has","have","his","how","its","may","new","now","old","see","two","who","did","get","use","used","uses","using","with","this","that","they","them","then","than","from","into","your","yours","each","once","more","most","some","such","only","over","under","when","what","which","while","where","will","would","should","could","also","about","after","before","these","those","their","there","here","been","being","were","because","between","both","same","every","either","back","down","make","made","need","needs","keep","read","reads","call","calls","set","sets","follow","follows","three","plus","step","steps","shape","shapes","related","example","examples","docs","page","pages","http","https","www","com","json","returns","return","inspect","describe","described"]);export function extractConcepts(t,o={}){const n=Number.isFinite(o.max)?o.max:6,r=Number.isFinite(o.minLength)?o.minLength:4;if(!t)return[];const a=new Map,s=String(t).toLowerCase().match(/[a-z][a-z0-9-]+/g)||[];for(const t of s){const o=t.replace(/^-+|-+$/g,"");o.length<r||e.has(o)||a.set(o,(a.get(o)||0)+1)}const c=Array.from(a.entries()).sort((e,t)=>t[1]-e[1]||e[0].localeCompare(t[0])).slice(0,Math.max(0,n)).map(([e])=>normalizeFacetValue(e)).filter(Boolean);return Array.from(new Set(c)).sort()}export function addConceptRelationships(e,t,o={}){const n=Number.isFinite(o.maxRelations)?o.maxRelations:4,r=Number.isFinite(o.minShared)?o.minShared:1,a=t.map(e=>new Set(e||[]));for(let t=0;t<e.length;t+=1){const o=a[t];if(!o||0===o.size)continue;const s=[];for(let n=0;n<e.length;n+=1){if(n===t)continue;const c=[];for(const e of o)a[n].has(e)&&c.push(e);const i=c.length;i>=r&&s.push({id:e[n].id,shared:i,sharedConcepts:c.sort()})}s.sort((e,t)=>t.shared-e.shared||e.id.localeCompare(t.id));const c=new Set(e[t].relationships.map(e=>e.target_id));for(const r of s.slice(0,Math.max(0,n)))c.has(r.id)||(e[t].relationships.push({target_id:r.id,type:"related",label:`Shares ${r.shared} concept${1===r.shared?"":"s"}`,confidence:Math.min(1,r.shared/Math.max(1,o.size)),privacy:"public",metadata:{shared_concepts:r.sharedConcepts}}),c.add(r.id))}}export function recordToSectionId(e){if(!e)return null;const t=e.source?.locator||"",o=/^#\/?(.+)$/.exec(t);if(o)return o[1];const n=e.facets?.section?.[0];return n||e.id?.replace(/^docs:page:/,"")||null}function t(e,t){return null==e?t:JSON.parse(JSON.stringify(e))}function o(e,o){for(const n of o)if(e&&null!=e[n])return t(e[n],null);return null}function n(e){return String(e||"").split(/[-_]+/).filter(Boolean).map(e=>e?e[0].toUpperCase()+e.slice(1):e).join(" ")}function r(e){return{id:`concept:${e}`,prefLabel:n(e),notation:e,uri:`urn:pagenary:concept:${e}`}}export function fortemiRecordToPageMetadata(e){const n=recordToSectionId(e);if(!e||!n)return null;const r={section_id:n,record_id:e.id,type:e.type,title:e.title||n,source:t(e.source,null),facets:t(e.facets||{},{}),tags:t(e.tags||[],[]),concepts:t(e.concepts||[],[]),relationships:t(e.relationships||[],[]),provenance:t(e.provenance||[],[]),privacy:t(e.privacy||null,null),updated_at:e.updated_at||null},a=o(e,["skos_concepts","skosConcepts","concept_details","conceptDetails"]);a&&(r.skos_concepts=a);const s=o(e,["skos_relations","skosRelations","concept_relations","conceptRelations"]);s&&(r.skos_relations=s);const c=o(e,["provenance_events","provenanceEvents","prov","prov_events","provEvents"]);return c&&(r.provenance_events=c),r}export function buildFortemiMetadataExport(e){const o=(e?.items||[]).map(e=>fortemiRecordToPageMetadata(e)).filter(Boolean).sort((e,t)=>e.section_id.localeCompare(t.section_id));return{schema_version:FORTEMI_METADATA_SCHEMA,generated_at:e?.generated_at||null,source:t(e?.source||null,null),pages:o}}export function sectionToFortemiRecord(e,t,o){const n=String(e.group||e.title||"Documentation").split(">").map(e=>e.trim()).filter(Boolean),r=e.module||"",a=r?r.replace(/^\.?\//,"").replace(/^sections\//,"sections/"):`${e.id}.md`,s=Array.from(new Set([...n.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:n.length?n.map(normalizeFacetValue):["documentation"]},tags:e.type?[normalizeFacetValue(e.type)]:[],concepts:s,relationships:[],provenance:[{field:"text",source:a,path:"$.text",confidence:"source",privacy:"public"}],privacy:{classification:"public",pii:!1},updated_at:o}}export function buildFortemiIndexExport(e,t={}){const o=t.repo||"pagenary",n=new Map;for(const{section:t,text:o}of e)t&&t.id&&(n.has(t.id)||n.set(t.id,{section:t,text:o||""}));const a=Array.from(n.values()).sort((e,t)=>`docs:page:${e.section.id}`.localeCompare(`docs:page:${t.section.id}`)),s=stableHash(`${o}${a.map(({section:e,text:t})=>`${e.id}\0${e.title||""}\0${t||""}`).join("")}`),c=Number(BigInt(`0x${s.slice(0,8)}`)),i=new Date(1e3*c).toISOString(),p=a.map(({section:e,text:t})=>sectionToFortemiRecord(e,t,e.date||i));if(t.extractConcepts||t.relateByConcept){const e=a.map(({text:e})=>extractConcepts(e,t.conceptOptions));t.extractConcepts&&p.forEach((t,o)=>{t.concepts=Array.from(new Set([...t.concepts,...e[o]])).sort()}),t.relateByConcept&&addConceptRelationships(p,e,t.relationOptions)}return function(e){for(const t of e)t.skos_concepts=Array.from(new Set(t.concepts||[])).sort().map(r),t.skos_relations=[],t.provenance_events=[{activity:"built",agent:"pagenary",source:t.source?.repo_relative_path||t.source?.path,path:"$",confidence:"source",privacy:t.privacy?.classification||"public",attributes:{record_id:t.id,section_id:recordToSectionId(t)}}]}(p),{index:{schema_version:FORTEMI_INDEX_SCHEMA,generated_at:i,source:{repo:o,privacy:"public",build_hash:s},items:p},buildHash:s,generatedAt:i}}export function computeFacetCounts(e){const t={},o=(e,o)=>{null!=o&&(t[e]||={},t[e][o]=(t[e][o]||0)+1)};for(const t of e)o("type",t.type),o("privacy",t.privacy?.classification),(t.tags||[]).forEach(e=>o("tag",e)),(t.concepts||[]).forEach(e=>o("concept",e)),Object.entries(t.facets||{}).forEach(([e,t])=>(t||[]).forEach(t=>o(e,t)));return t}export function chunkFortemiIndex(e,t={}){const o=Math.max(1,t.partSize||100),n=t.partHref||(e=>`part-${String(e).padStart(4,"0")}.json`),r=e.items||[],a=[],s=[];let c=0,i=0;do{const e=r.slice(c,c+o),t=n(i);a.push({schema_version:FORTEMI_CHUNK_PART_SCHEMA,manifest_schema_version:FORTEMI_CHUNK_MANIFEST_SCHEMA,offset:c,items:e}),s.push({href:t,offset:c,count:e.length}),c+=e.length||o,i+=1}while(c<r.length);return{manifest:{schema_version:FORTEMI_CHUNK_MANIFEST_SCHEMA,generated_at:e.generated_at,source:e.source,total:r.length,part_size:o,facets:computeFacetCounts(r),parts:s},parts:a}}
|
package/site/lib/search.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{queryAiwgFortemiIndex as t,getAiwgFortemiFacets as e,createAiwgIndexController as n,createAiwgFetchChunkLoader as r,aiwgFortemiIndexToCommunityGraph as i,validateAiwgFortemiChunkManifest as
|
|
1
|
+
import{queryAiwgFortemiIndex as t,getAiwgFortemiFacets as e,createAiwgIndexController as n,createAiwgFetchChunkLoader as r,aiwgFortemiIndexToCommunityGraph as i,validateAiwgFortemiChunkManifest as a,validateAiwgFortemiIndexExport as o}from"../vendor/fortemi-aiwg-index.js";import{buildFortemiMetadataExport as s,buildFortemiIndexExport as l,recordToSectionId as u}from"./fortemi-corpus.js";export{t as queryAiwgFortemiIndex,e as getAiwgFortemiFacets,n as createAiwgIndexController,r as createAiwgFetchChunkLoader,i as aiwgFortemiIndexToCommunityGraph};let c=null,f=null,m=!1,p=null,d=null,y=null,h=null,g=!1,x=null,w=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 M(){if("undefined"==typeof document||"undefined"==typeof URL)return null;try{return new URL("search-index/",document.baseURI).toString()}catch{return null}}async function $(){if(y)return y;if(g)return null;if(h)return h;h=(async()=>{const t=M();if(!t||"function"!=typeof fetch)return null;try{const e=await fetch(new URL("metadata.json",t).toString());if(!e.ok)return null;const n=await e.json();return"pagenary.fortemi.metadata.v1"===n?.schema_version&&Array.isArray(n.pages)?(y=n,n):null}catch{return null}})();const t=await h;return t||(g=!0),h=null,t}async function C(t){return p||d||(d=(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}=l(n,{repo:"pagenary"}),i=new Map(e.map(t=>[t.id,t]));return p={index:r,byId:i},p})(),d)}function S(t,e){const n=u(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,o={}){const s=Math.max(0,o.offset||0),l=Math.max(1,o.limit||25),u=function(t){if(x&&w===t)return x;x=new Map;for(const e of flattenManifest(t))e&&e.id&&!x.has(e.id)&&x.set(e.id,e);return w=t,x}(e),p={types:["docs.page"],rank:!0,snippets:!0,includeMatches:!0,snippetLength:140,limit:l,offset:s},d=await async function(){if(c)return c;if(m)return null;if(f)return f;f=(async()=>{const t=M();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(!a(i).valid)return null;const o=n();return o.loadChunkedIndex(i,r(t),{maxCachedParts:4}),c=o,o}catch{return null}})();const t=await f;return t||(m=!0),f=null,t}();if(d)try{const t=await d.queryChunked(i,{...p,onProgress:o.onProgress}),e=(t.rankedItems||[]).map(t=>S(t,u)).filter(Boolean);return{items:e,total:t.total,offset:s,limit:l,complete:s+e.length>=t.total,source:"static"}}catch{}const y=await C(e);if(!i.trim()){const t=Array.from(y.byId.values()),e=t.slice(s,s+l);return{items:e,total:t.length,offset:s,limit:l,complete:s+e.length>=t.length,source:"legacy"}}const h=t(y.index,i,p),g=(h.rankedItems||[]).map(t=>S(t,y.byId)).filter(Boolean);return{items:g,total:h.total,offset:s,limit:l,complete:s+g.length>=h.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}=l(n,{repo:"pagenary"});return o(r).valid?i(r,e):{nodes:[],edges:[],communities:[]}}export async function resolveSectionMetadata(t,e){if(!e)return null;const n=await $();if(n){const t=n.pages.find(t=>t.section_id===e);if(t)return t}const r=await C(t);return s(r.index).pages.find(t=>t.section_id===e)||null}export async function resolveSectionMetadataMap(t){const e=await $()||s((await C(t)).index);return new Map((e.pages||[]).map(t=>[t.section_id,t]))}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(){c=null,f=null,m=!1,p=null,d=null,y=null,h=null,g=!1,x=null,w=null}
|
package/site/pages/api.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "API Reference",
|
|
28
28
|
"description": "Module-level documentation for the publisher internals.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/api.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/api.html"
|
|
@@ -165,7 +165,7 @@ await loadSection(section);</code></pre>
|
|
|
165
165
|
<hr>
|
|
166
166
|
<h2 id="library-modules">Library Modules</h2>
|
|
167
167
|
<h3 id="libsearchjs-fortemi-backed-search">lib/search.js - Fortemi-backed search</h3>
|
|
168
|
-
<p>Search runs on the <strong>real, vendored `@fortemi/core` static-index engine</strong> (`src/vendor/fortemi-aiwg-index.js`). At build time, `scripts/build-tenants.js` emits a deterministic <strong>chunked</strong> index per tenant under `dist/<tenant>/search-index/` (`manifest.json` + `part-NNNN.json`, the `aiwg.fortemi.index.*.v1` contract). At runtime the adapter loads that index through an index controller + fetch chunk-loader: parts are fetched lazily and cached (<strong>precache</strong>), results are ranked with snippets, and pages are returned by offset for <strong>infinite scroll</strong>. If the static index is missing or invalid, the adapter falls back to an in-browser index built from section modules — same ranking engine, same result shape. See `.aiwg/architecture/adr/ADR-015-fortemi-core-search-adapter.md`.</p>
|
|
168
|
+
<p>Search runs on the <strong>real, vendored `@fortemi/core` static-index engine</strong> (`src/vendor/fortemi-aiwg-index.js`). At build time, `scripts/build-tenants.js` emits a deterministic <strong>chunked</strong> index per tenant under `dist/<tenant>/search-index/` (`manifest.json` + `part-NNNN.json`, the `aiwg.fortemi.index.*.v1` contract) plus compact `metadata.json` (`pagenary.fortemi.metadata.v1`) for page-addressable Fortemi metadata without duplicating full document text. At runtime the adapter loads that index through an index controller + fetch chunk-loader: parts are fetched lazily and cached (<strong>precache</strong>), results are ranked with snippets, and pages are returned by offset for <strong>infinite scroll</strong>. If the static index is missing or invalid, the adapter falls back to an in-browser index built from section modules — same ranking engine, same result shape. See `.aiwg/architecture/adr/ADR-015-fortemi-core-search-adapter.md`.</p>
|
|
169
169
|
<p>Build-time and fallback share the deterministic corpus builder in `lib/fortemi-corpus.js`.</p>
|
|
170
170
|
<h4 id="functions-3">Functions</h4>
|
|
171
171
|
<p><strong>`escapeRegExp(value: string): string`</strong></p>
|
|
@@ -189,12 +189,18 @@ await loadSection(section);</code></pre>
|
|
|
189
189
|
<p><strong>`buildCommunityGraph(manifest, options?): { nodes, edges, communities }`</strong></p>
|
|
190
190
|
<p>Project the corpus into a Fortemi community graph (relationships/facets) — the "graph" capability, no full-text required.</p>
|
|
191
191
|
<pre><code class="language-javascript">const graph = buildCommunityGraph(MANIFEST);</code></pre>
|
|
192
|
+
<p><strong>`resolveSectionMetadata(manifest, sectionId): Promise<object|null>`</strong></p>
|
|
193
|
+
<p>Resolve compact Fortemi metadata for one page. The static artifact is preferred; the browser fallback derives the same shape from the in-browser Fortemi index.</p>
|
|
194
|
+
<pre><code class="language-javascript">const metadata = await resolveSectionMetadata(MANIFEST, 'developers');
|
|
195
|
+
// metadata.source, facets, concepts, relationships, provenance, privacy</code></pre>
|
|
196
|
+
<p><strong>`resolveSectionMetadataMap(manifest): Promise<Map<string, object>>`</strong></p>
|
|
197
|
+
<p>Resolve all compact Fortemi metadata keyed by section id for page tools, graph node details, or tenant-specific integrations.</p>
|
|
192
198
|
<p><strong>`findPreferredIndex(entries: Section[], currentId: string): number`</strong></p>
|
|
193
199
|
<p>Find index of current section in filtered results.</p>
|
|
194
200
|
<p>Re-exported from the vendored engine for advanced use: `queryAiwgFortemiIndex`, `getAiwgFortemiFacets`, `createAiwgIndexController`, `createAiwgFetchChunkLoader`, `aiwgFortemiIndexToCommunityGraph`.</p>
|
|
195
201
|
<hr>
|
|
196
202
|
<h3 id="libfortemi-corpusjs-deterministic-corpus-builder">lib/fortemi-corpus.js - Deterministic corpus builder</h3>
|
|
197
|
-
<p>Pure, DOM-free, `Date.now()`-free helpers shared by the build-time generator and the runtime fallback: `buildFortemiIndexExport(entries, { repo })` (records sorted by id, deduped, content-hashed `generated_at` + `source.build_hash`), `chunkFortemiIndex(index, { partSize })`, `sectionToFortemiRecord`, `stripHtml`, `recordToSectionId`, `stableHash`.</p>
|
|
203
|
+
<p>Pure, DOM-free, `Date.now()`-free helpers shared by the build-time generator and the runtime fallback: `buildFortemiIndexExport(entries, { repo })` (records sorted by id, deduped, content-hashed `generated_at` + `source.build_hash`), `chunkFortemiIndex(index, { partSize })`, `sectionToFortemiRecord`, `stripHtml`, `recordToSectionId`, `fortemiRecordToPageMetadata`, `buildFortemiMetadataExport`, `stableHash`.</p>
|
|
198
204
|
<hr>
|
|
199
205
|
<h3 id="librouterjs-hash-routing">lib/router.js - Hash Routing</h3>
|
|
200
206
|
<p>URL hash parsing and resolution.</p>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "Architecture",
|
|
28
28
|
"description": "The static SPA pattern, build pipeline, and tenant content model.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/architecture.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/architecture.html"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "Deployment",
|
|
28
28
|
"description": "Hosting the static output and multi-tenant domain routing.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/deployment.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/deployment.html"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "Developer Guide",
|
|
28
28
|
"description": "Project layout, scripts, and the content authoring workflow.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/developer-guide.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/developer-guide.html"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "Extending",
|
|
28
28
|
"description": "Add section templates, content types, and build behaviors.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/extending.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/extending.html"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "Quickstart",
|
|
28
28
|
"description": "Install, build the default bundle, and serve it locally.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/quickstart.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/quickstart.html"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "SEO Strategy",
|
|
28
28
|
"description": "Metadata, hash-routing considerations, and discoverability.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/seo-strategy.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/seo-strategy.html"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"headline": "Tenant Configuration",
|
|
28
28
|
"description": "Every config.json option: branding, theming, SEO, and export.",
|
|
29
29
|
"url": "https://docs.pagenary.com/pages/tenant-config.html",
|
|
30
|
-
"dateModified": "2026-06-
|
|
30
|
+
"dateModified": "2026-06-21",
|
|
31
31
|
"mainEntityOfPage": {
|
|
32
32
|
"@type": "WebPage",
|
|
33
33
|
"@id": "https://docs.pagenary.com/pages/tenant-config.html"
|
|
@@ -175,9 +175,9 @@
|
|
|
175
175
|
<p>The build emits one full stylesheet per selectable theme (`theme-<name>.css`) and swaps the active `<link>` at runtime, so switching is instant and pixel-correct (including code blocks and tables). When disabled, no control, script behavior, or extra stylesheets are emitted.</p>
|
|
176
176
|
<h4 id="docs-map-relationship-view">Docs map (relationship view)</h4>
|
|
177
177
|
<p>Opt-in `docsMap` adds a standalone <strong>Docs Map</strong> page — a framework-free SVG view that clusters your pages by concept (from the same index that powers search) so readers can see how the docs relate. It appears in the nav and at `#docs-map`.</p>
|
|
178
|
-
<pre><code class="language-json">{ "docsMap": { "enabled": true } }</code></pre>
|
|
179
|
-
<table><thead><tr><th style="text-align: left">Property</th><th style="text-align: left">Type</th><th style="text-align: left">Default</th><th style="text-align: left">Description</th></tr></thead><tbody><tr><td style="text-align: left">`docsMap.enabled`</td><td style="text-align: left">boolean</td><td style="text-align: left">`false`</td><td style="text-align: left">Add the Docs Map page + nav entry</td></tr><tr><td style="text-align: left">`docsMap.title`</td><td style="text-align: left">string</td><td style="text-align: left">`"Docs Map"`</td><td style="text-align: left">Nav/heading label</td></tr></tbody></table>
|
|
180
|
-
<p>The graph is computed <strong>at build time</strong> from your actual page content. Each page's body is run through
|
|
178
|
+
<pre><code class="language-json">{ "docsMap": { "enabled": true, "renderer": "svg" } }</code></pre>
|
|
179
|
+
<table><thead><tr><th style="text-align: left">Property</th><th style="text-align: left">Type</th><th style="text-align: left">Default</th><th style="text-align: left">Description</th></tr></thead><tbody><tr><td style="text-align: left">`docsMap.enabled`</td><td style="text-align: left">boolean</td><td style="text-align: left">`false`</td><td style="text-align: left">Add the Docs Map page + nav entry</td></tr><tr><td style="text-align: left">`docsMap.title`</td><td style="text-align: left">string</td><td style="text-align: left">`"Docs Map"`</td><td style="text-align: left">Nav/heading label</td></tr><tr><td style="text-align: left">`docsMap.renderer`</td><td style="text-align: left">`"svg"` or `"cytoscape"`</td><td style="text-align: left">`"svg"`</td><td style="text-align: left">Graph renderer. `svg` is the current renderer and fallback; `cytoscape` reserves the opt-in richer JS renderer path.</td></tr></tbody></table>
|
|
180
|
+
<p>The graph is computed <strong>at build time</strong> from your actual page content using the vendored Fortemi graph adapter. Each page's body is run through the Fortemi concept procedure: pages cluster into communities by nav group, and pages that share salient concepts are linked with weighted `related` edges. The build embeds the Fortemi graph plus compact node/relationship metadata as `docs-map-data.js`; the SVG renderer uses that metadata for subtle hover titles, edge weight, confidence, shared-concept details, zoom/pan controls, neighbor highlighting, and pinned node popups. The default renderer is the framework-free SVG view; optional renderers must fall back to SVG if they cannot initialize. Tenants with too little content fall back to a lightweight manifest-derived graph, and small or empty corpora render a friendly placeholder. When disabled, nothing is emitted.</p>
|
|
181
181
|
<blockquote>
|
|
182
182
|
<p>See `examples/docs-map-corpus/` (the <strong>docs-map</strong> recipe) for a fully</p>
|
|
183
183
|
<p>cross-linked sample corpus — 14 interconnected pages that produce a graph of</p>
|