@pagenary/publisher 2026.5.0
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/LICENSE +661 -0
- package/README.md +337 -0
- package/bin/pagenary.mjs +116 -0
- package/build.config.json +5 -0
- package/package.json +66 -0
- package/scripts/build-site.js +87 -0
- package/scripts/build-tenants.js +3569 -0
- package/scripts/build.js +99 -0
- package/scripts/generate-sections.js +41 -0
- package/scripts/lib/seo-generator.js +558 -0
- package/scripts/lint-content.js +62 -0
- package/scripts/seo-smoke.js +94 -0
- package/scripts/serve.js +142 -0
- package/site/app.js +1 -0
- package/site/index.html +57 -0
- package/site/lib/categories.js +1 -0
- package/site/lib/export.js +1 -0
- package/site/lib/manifest-utils.js +1 -0
- package/site/lib/router.js +1 -0
- package/site/lib/search.js +1 -0
- package/site/llms.txt +22 -0
- package/site/manifest.js +132 -0
- package/site/mermaid-init.js +1 -0
- package/site/pages/api.html +339 -0
- package/site/pages/architecture.html +303 -0
- package/site/pages/deployment.html +282 -0
- package/site/pages/developer-guide.html +157 -0
- package/site/pages/extending.html +135 -0
- package/site/pages/quickstart.html +318 -0
- package/site/pages/seo-strategy.html +121 -0
- package/site/pages/tenant-config.html +519 -0
- package/site/pages/welcome.html +116 -0
- package/site/robots.txt +10 -0
- package/site/sections/api.js +3 -0
- package/site/sections/architecture.js +3 -0
- package/site/sections/deployment.js +3 -0
- package/site/sections/developer-guide.js +3 -0
- package/site/sections/extending.js +3 -0
- package/site/sections/quickstart.js +3 -0
- package/site/sections/section-templates.js +1 -0
- package/site/sections/seo-strategy.js +3 -0
- package/site/sections/tenant-config.js +3 -0
- package/site/sections/welcome.js +3 -0
- package/site/seo.js +1 -0
- package/site/sitemap.xml +63 -0
- package/site/styles.css +1982 -0
- package/site/syntax-highlight.js +1 -0
- package/src/app.js +988 -0
- package/src/index.html +56 -0
- package/src/lib/categories.js +55 -0
- package/src/lib/export.js +195 -0
- package/src/lib/manifest-utils.js +69 -0
- package/src/lib/router.js +44 -0
- package/src/lib/search.js +151 -0
- package/src/manifest.js +246 -0
- package/src/mermaid-init.js +207 -0
- package/src/sections/archive-future-roadmap.js +7 -0
- package/src/sections/archive-initiative-alpha.js +7 -0
- package/src/sections/archive-milestone-records.js +7 -0
- package/src/sections/archive-timeline-overview.js +7 -0
- package/src/sections/core-technology-compliance-frameworks.js +7 -0
- package/src/sections/core-technology-coordination-model.js +7 -0
- package/src/sections/core-technology-data-definitions.js +7 -0
- package/src/sections/core-technology-hardware-integration.js +7 -0
- package/src/sections/core-technology-integrity-controls.js +7 -0
- package/src/sections/core-technology-network-topology.js +7 -0
- package/src/sections/core-technology-operator-requirements.js +7 -0
- package/src/sections/core-technology-overview.js +7 -0
- package/src/sections/core-technology-service-interfaces.js +7 -0
- package/src/sections/core-technology-synchronization-strategy.js +7 -0
- package/src/sections/core-technology-system-foundation.js +7 -0
- package/src/sections/developers-api-credentials.js +7 -0
- package/src/sections/developers-api-operations.js +7 -0
- package/src/sections/developers-api-reference.js +7 -0
- package/src/sections/developers-api-websocket.js +7 -0
- package/src/sections/developers-automation-blueprints.js +7 -0
- package/src/sections/developers-automation-modules.js +7 -0
- package/src/sections/developers-automation-patterns.js +7 -0
- package/src/sections/developers-deployment-playbook.js +7 -0
- package/src/sections/developers-overview.js +7 -0
- package/src/sections/developers-scheduling-patterns.js +7 -0
- package/src/sections/developers-sdk-go.js +7 -0
- package/src/sections/developers-sdk-javascript.js +7 -0
- package/src/sections/developers-sdk-python.js +7 -0
- package/src/sections/developers-sdk-rust.js +7 -0
- package/src/sections/developers-sdks.js +7 -0
- package/src/sections/developers-solution-examples.js +7 -0
- package/src/sections/developers-testing-framework.js +7 -0
- package/src/sections/getting-started-architecture-basics.js +7 -0
- package/src/sections/getting-started-introduction.js +7 -0
- package/src/sections/getting-started-performance-overview.js +7 -0
- package/src/sections/governance-community-initiatives.js +7 -0
- package/src/sections/governance-dao-overview.js +7 -0
- package/src/sections/governance-multi-token.js +7 -0
- package/src/sections/governance-overview.js +7 -0
- package/src/sections/governance-proposal-process.js +7 -0
- package/src/sections/governance-proposals.js +7 -0
- package/src/sections/governance-structure.js +7 -0
- package/src/sections/governance-token-distribution.js +7 -0
- package/src/sections/governance-treasury.js +7 -0
- package/src/sections/operations-environment-prep.js +7 -0
- package/src/sections/operations-getting-started.js +7 -0
- package/src/sections/operations-incentives-guide.js +7 -0
- package/src/sections/operations-incentives-strategies.js +7 -0
- package/src/sections/operations-incentives.js +7 -0
- package/src/sections/operations-infrastructure.js +7 -0
- package/src/sections/operations-monitoring.js +7 -0
- package/src/sections/operations-overview.js +7 -0
- package/src/sections/operations-performance.js +7 -0
- package/src/sections/operations-power-infrastructure.js +7 -0
- package/src/sections/operations-setup-guide.js +7 -0
- package/src/sections/operations-sync-setup.js +7 -0
- package/src/sections/products-flagship-solution.js +7 -0
- package/src/sections/products-solution-library.js +7 -0
- package/src/sections/resources-brand-assets.js +7 -0
- package/src/sections/resources-faq.js +7 -0
- package/src/sections/resources-glossary.js +7 -0
- package/src/sections/resources-research-papers.js +7 -0
- package/src/sections/section-templates.js +873 -0
- package/src/sections/security-audits.js +7 -0
- package/src/sections/security-best-practices.js +7 -0
- package/src/sections/security-bug-bounty.js +7 -0
- package/src/sections/security-incident-response.js +7 -0
- package/src/sections/security-overview.js +7 -0
- package/src/sections/technical-architecture.js +7 -0
- package/src/sections/technical-whitepaper.js +7 -0
- package/src/sections/tutorial-automation-bot.js +7 -0
- package/src/sections/tutorial-build-first-integration.js +7 -0
- package/src/sections/tutorial-deploy-automation.js +7 -0
- package/src/sections/tutorial-event-driven-experience.js +7 -0
- package/src/sections/tutorial-operations-onboarding.js +7 -0
- package/src/sections/tutorial-systems-integration.js +7 -0
- package/src/sections/tutorials-overview.js +7 -0
- package/src/sections/use-case-connected-devices.js +7 -0
- package/src/sections/use-case-digital-auctions.js +7 -0
- package/src/sections/use-case-financial-automation.js +7 -0
- package/src/sections/use-case-interactive-media.js +7 -0
- package/src/sections/use-case-realtime-execution.js +7 -0
- package/src/sections/use-case-research-analytics.js +7 -0
- package/src/sections/use-case-supply-operations.js +7 -0
- package/src/sections/use-cases-overview.js +7 -0
- package/src/sections/welcome-overview.js +7 -0
- package/src/seo.js +90 -0
- package/src/styles.css +1982 -0
- package/src/syntax-highlight.js +90 -0
- package/tenants.json.example +68 -0
- package/tenants.schema.json +231 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight content linting for Docs Toolkit.
|
|
4
|
+
* - scans source and docs for trailing whitespace or tab characters
|
|
5
|
+
* - ensures files end with a newline
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
const ROOT = process.cwd();
|
|
11
|
+
const INCLUDE_DIRS = ['src', 'scripts', 'docs'];
|
|
12
|
+
const EXTENSIONS = new Set(['.js', '.mjs', '.css', '.html', '.md', '.json']);
|
|
13
|
+
const IGNORED_FILES = new Set(['scripts/lint-content.js', 'scripts/seo-smoke.js']);
|
|
14
|
+
|
|
15
|
+
const issues = [];
|
|
16
|
+
|
|
17
|
+
function walk(dir) {
|
|
18
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.name.startsWith('.')) continue;
|
|
21
|
+
const fullPath = path.join(dir, entry.name);
|
|
22
|
+
const relPath = path.relative(ROOT, fullPath);
|
|
23
|
+
if (IGNORED_FILES.has(relPath)) continue;
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
walk(fullPath);
|
|
26
|
+
} else {
|
|
27
|
+
const ext = path.extname(entry.name);
|
|
28
|
+
if (!EXTENSIONS.has(ext)) continue;
|
|
29
|
+
lintFile(fullPath, relPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function lintFile(fullPath, relPath) {
|
|
35
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
36
|
+
const lines = content.split(/\r?\n/);
|
|
37
|
+
lines.forEach((line, index) => {
|
|
38
|
+
if (/\s$/.test(line)) {
|
|
39
|
+
issues.push(`${relPath}:${index + 1} trailing whitespace`);
|
|
40
|
+
}
|
|
41
|
+
if (/\t/.test(line)) {
|
|
42
|
+
issues.push(`${relPath}:${index + 1} tab character found`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (!content.endsWith('\n')) {
|
|
46
|
+
issues.push(`${relPath} missing terminating newline`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const dir of INCLUDE_DIRS) {
|
|
51
|
+
const target = path.join(ROOT, dir);
|
|
52
|
+
if (!fs.existsSync(target)) continue;
|
|
53
|
+
walk(target);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (issues.length) {
|
|
57
|
+
console.error('Lint issues detected:\n');
|
|
58
|
+
issues.forEach((issue) => console.error(` • ${issue}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
} else {
|
|
61
|
+
console.log('Lint check passed.');
|
|
62
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SEO smoke checks for Docs Toolkit.
|
|
4
|
+
* Verifies dist/index.html contains core metadata and that manifest entries
|
|
5
|
+
* include summaries and modules for shareability.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { pathToFileURL } from 'url';
|
|
10
|
+
|
|
11
|
+
const ROOT = process.cwd();
|
|
12
|
+
const DIST = path.join(ROOT, 'dist');
|
|
13
|
+
const INDEX = path.join(DIST, 'index.html');
|
|
14
|
+
|
|
15
|
+
function fail(message) {
|
|
16
|
+
console.error(`✖ ${message}`);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pass(message) {
|
|
21
|
+
console.log(`✔ ${message}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(DIST)) {
|
|
25
|
+
fail('dist/ not found. Run "npm run build" first.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(INDEX)) {
|
|
30
|
+
fail('dist/index.html missing. Build may have failed.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const html = fs.readFileSync(INDEX, 'utf8');
|
|
35
|
+
|
|
36
|
+
const requiredPatterns = [
|
|
37
|
+
{ label: 'Meta description', pattern: /<meta\s+name="description"\s+content="[^"]+"/i },
|
|
38
|
+
{ label: 'Viewport tag', pattern: /<meta\s+name="viewport"\s+content="[^"]+"/i },
|
|
39
|
+
{ label: 'Document title', pattern: /<title>\s*[^<]+\s*<\/title>/i },
|
|
40
|
+
{ label: 'Stylesheet link', pattern: /<link\s+rel="stylesheet"\s+href="\.[^"]*styles\.css"/i }
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const { label, pattern } of requiredPatterns) {
|
|
44
|
+
if (pattern.test(html)) pass(`${label} present`);
|
|
45
|
+
else fail(`${label} missing in dist/index.html`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function loadManifest() {
|
|
49
|
+
const manifestUrl = pathToFileURL(path.join(ROOT, 'src', 'manifest.js')).href;
|
|
50
|
+
try {
|
|
51
|
+
const mod = await import(manifestUrl);
|
|
52
|
+
if (!mod || !Array.isArray(mod.MANIFEST)) {
|
|
53
|
+
fail('Manifest did not export an array.');
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return mod.MANIFEST;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
fail(`Failed to import manifest: ${err.message}`);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ensureSummary(entity, label) {
|
|
64
|
+
if (!entity.summary || !String(entity.summary).trim()) {
|
|
65
|
+
fail(`${label} missing summary`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ensureModule(entity, label) {
|
|
70
|
+
if (!entity.module || !String(entity.module).trim()) {
|
|
71
|
+
fail(`${label} missing module path`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
(async function run() {
|
|
76
|
+
const manifest = await loadManifest();
|
|
77
|
+
if (!manifest.length) {
|
|
78
|
+
fail('Manifest has no sections.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
pass(`Manifest contains ${manifest.length} top-level entr${manifest.length === 1 ? 'y' : 'ies'}`);
|
|
82
|
+
|
|
83
|
+
manifest.forEach((section) => {
|
|
84
|
+
ensureSummary(section, `Section "${section.title}"`);
|
|
85
|
+
if (section.subsections && Array.isArray(section.subsections)) {
|
|
86
|
+
section.subsections.forEach((subsection) => {
|
|
87
|
+
ensureSummary(subsection, `Subsection "${subsection.title}"`);
|
|
88
|
+
ensureModule(subsection, `Subsection "${subsection.title}"`);
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
ensureModule(section, `Section "${section.title}"`);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
})();
|
package/scripts/serve.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* Lightweight static server for dist with tenant routing */
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import url from 'url';
|
|
7
|
+
|
|
8
|
+
const root = path.join(process.cwd(), 'dist');
|
|
9
|
+
const port = Number(process.env.PORT || 5173);
|
|
10
|
+
const host = process.env.HOST || '0.0.0.0';
|
|
11
|
+
const DEV = process.argv.includes('--dev') || process.env.NODE_ENV === 'development';
|
|
12
|
+
const DEFAULT_TENANT = process.env.DEFAULT_TENANT || 'tenant-default';
|
|
13
|
+
|
|
14
|
+
const MIME = {
|
|
15
|
+
'.html': 'text/html; charset=utf-8',
|
|
16
|
+
'.css': 'text/css; charset=utf-8',
|
|
17
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
18
|
+
'.mjs': 'application/javascript; charset=utf-8',
|
|
19
|
+
'.json': 'application/json; charset=utf-8',
|
|
20
|
+
'.svg': 'image/svg+xml',
|
|
21
|
+
'.png': 'image/png'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Detect tenant directories at startup
|
|
25
|
+
// Tenants can use tenant-* prefix or any custom name (e.g., aiwg-docs)
|
|
26
|
+
const tenantDirs = new Set();
|
|
27
|
+
const NON_TENANT_DIRS = new Set(['lib', 'sections', 'pages', 'assets', 'node_modules']);
|
|
28
|
+
try {
|
|
29
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.isDirectory() && !NON_TENANT_DIRS.has(entry.name)) {
|
|
32
|
+
tenantDirs.add(entry.name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// dist may not exist yet
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse tenant from path. Returns { tenant, localPath }
|
|
41
|
+
* e.g., /tenant-alpha/app.js -> { tenant: 'tenant-alpha', localPath: '/app.js' }
|
|
42
|
+
* /aiwg-docs/app.js -> { tenant: 'aiwg-docs', localPath: '/app.js' }
|
|
43
|
+
* /app.js -> { tenant: null, localPath: '/app.js' }
|
|
44
|
+
*/
|
|
45
|
+
function parseTenantPath(pathname) {
|
|
46
|
+
const match = pathname.match(/^\/([^/]+)(\/.*)?$/);
|
|
47
|
+
if (match && tenantDirs.has(match[1])) {
|
|
48
|
+
return {
|
|
49
|
+
tenant: match[1],
|
|
50
|
+
localPath: match[2] || '/'
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { tenant: null, localPath: pathname };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const server = http.createServer((req, res) => {
|
|
57
|
+
const parsed = url.parse(req.url);
|
|
58
|
+
let pathname = decodeURIComponent(parsed.pathname || '/');
|
|
59
|
+
|
|
60
|
+
// Redirect root to default tenant
|
|
61
|
+
if (pathname === '/' && tenantDirs.has(DEFAULT_TENANT)) {
|
|
62
|
+
res.writeHead(302, { Location: `/${DEFAULT_TENANT}/` });
|
|
63
|
+
res.end();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Redirect tenant path without trailing slash to add it (for correct relative path resolution)
|
|
68
|
+
const tenantOnlyMatch = pathname.match(/^\/([^/]+)$/);
|
|
69
|
+
if (tenantOnlyMatch && tenantDirs.has(tenantOnlyMatch[1])) {
|
|
70
|
+
res.writeHead(302, { Location: `${pathname}/` });
|
|
71
|
+
res.end();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Parse tenant from path
|
|
76
|
+
const { tenant, localPath } = parseTenantPath(pathname);
|
|
77
|
+
|
|
78
|
+
// If no tenant specified and not a root redirect, serve 404
|
|
79
|
+
if (!tenant) {
|
|
80
|
+
res.writeHead(404).end('Not Found - Please specify a tenant');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Determine the root directory for this request
|
|
85
|
+
const requestRoot = path.join(root, tenant);
|
|
86
|
+
|
|
87
|
+
// Resolve the local path within the tenant/root directory
|
|
88
|
+
let resolvedPath = localPath === '/' ? '/index.html' : localPath;
|
|
89
|
+
let filePath = path.join(requestRoot, resolvedPath);
|
|
90
|
+
|
|
91
|
+
// Security check
|
|
92
|
+
if (!filePath.startsWith(requestRoot)) {
|
|
93
|
+
res.writeHead(403).end('Forbidden');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fs.stat(filePath, (err, stat) => {
|
|
98
|
+
if (!err && stat.isDirectory()) {
|
|
99
|
+
filePath = path.join(filePath, 'index.html');
|
|
100
|
+
fs.stat(filePath, (idxErr, idxStat) => {
|
|
101
|
+
if (idxErr || !idxStat.isFile()) {
|
|
102
|
+
res.writeHead(404).end('Not Found');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
return streamFile(filePath, res);
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (err || !stat.isFile()) {
|
|
111
|
+
const noExt = !path.extname(filePath);
|
|
112
|
+
if (noExt) {
|
|
113
|
+
const htmlFallback = `${filePath}.html`;
|
|
114
|
+
return fs.stat(htmlFallback, (htmlErr, htmlStat) => {
|
|
115
|
+
if (htmlErr || !htmlStat.isFile()) {
|
|
116
|
+
res.writeHead(404).end('Not Found');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
streamFile(htmlFallback, res);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
res.writeHead(404).end('Not Found');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
streamFile(filePath, res);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
function streamFile(target, res) {
|
|
130
|
+
const ext = path.extname(target).toLowerCase();
|
|
131
|
+
const headers = { 'Content-Type': MIME[ext] || 'application/octet-stream' };
|
|
132
|
+
headers['Cache-Control'] = DEV ? 'no-store' : (ext === '.html' ? 'no-cache' : 'public, max-age=31536000, immutable');
|
|
133
|
+
res.writeHead(200, headers);
|
|
134
|
+
fs.createReadStream(target).pipe(res);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
server.listen(port, host, () => {
|
|
138
|
+
console.log(`Serving dist at http://${host}:${port} ${DEV ? '(dev cache disabled)' : ''}`);
|
|
139
|
+
if (tenantDirs.size > 0) {
|
|
140
|
+
console.log(`Tenants: ${[...tenantDirs].join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
});
|
package/site/app.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MANIFEST as e,DEFAULT_SECTION as t,findSection as n,getAdjacentSections as a,SITE_CONFIG as s,EXPORT_CONFIG as o}from"./manifest.js";import{updateMetaTags as i}from"./seo.js";import{escapeRegExp as l,searchContent as c,flattenManifest as r,findPreferredIndex as d}from"./lib/search.js";import{resolveTarget as m,resolveEntry as p}from"./lib/router.js";import{composeExportDocument as u,collectExportableSections as v}from"./lib/export.js";import{renderMermaidBlocks as h}from"./mermaid-init.js";import{highlightCodeBlocks as f}from"./syntax-highlight.js";const y=document.getElementById("app"),b=document.getElementById("nav"),g=document.getElementById("year"),E=document.getElementById("exportBtn"),L=document.getElementById("commandToggle"),T=document.getElementById("commandPalette"),x=document.getElementById("commandInput"),N=document.getElementById("commandList"),w=document.getElementById("mobileMenuToggle"),C=document.querySelector(".sidebar"),k="docs-toolkit-command-query",$=new Map,H=new Map,M=new Map,I=new Set;let A=[],S=0,P=!1,q=(localStorage.getItem(k)||"").trim(),B=!1;function R(e,t){const n=document.createElement("a");return n.href=e.url,n.target="_blank",n.rel="noopener noreferrer",n.className=`${t} nav-external`,n.title=e.summary||e.title,n.innerHTML=`\n <span class="nav-title">${e.title}<span class="nav-external-icon" aria-label="(opens in new tab)">↗</span></span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,n}function F(e,t={}){const{scrollToHighlight:a=!1}=t,{targetId:s,parentId:o}=function(e){return m(e,n)}(e);P&&_(),o&&j(o,!0),B=a||Boolean(q),location.hash.replace("#","")===s?O():location.hash=`#${s}`}function D(){return location.hash.replace("#","")||t}async function O(){const e=D(),t=p(e,n);if(!t)return;const{entry:o,targetId:l,parentId:c}=t;l===e?(c&&j(c,!0),function(e,t=null){H.forEach(e=>{e.setAttribute("aria-current","false")});const n=H.get(e);if(n&&n.setAttribute("aria-current","page"),t){const e=H.get(t);e&&e.setAttribute("aria-current","page")}}(o.id,c),await async function(e){if(!e)return;const t=await import(e.module),n=t.load||t.default;if("function"!=typeof n)return void(y.innerHTML='<article class="section"><p>Section failed to load.</p></article>');const o=await n();y.innerHTML=o.html||"",await h(y),await f(y),function(e){const t=y.querySelector(".bottom-nav");if(t&&t.remove(),"never"===s.bottomNav)return;const n=(s.bottomNavSections||[]).some(t=>e.startsWith(t)),o="mobile"===s.bottomNav&&!n,{prev:i,next:l}=a(e);if(!i&&!l)return;const c=document.createElement("nav");if(c.className="bottom-nav",o&&c.classList.add("mobile-only"),i){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-prev",e.innerHTML='<span class="bottom-nav-chevron">‹</span>';const t=document.createElement("a");t.href=`#${i.id}`,t.className="bottom-nav-link",t.title=`Previous: ${i.title}`,t.textContent=i.title,t.addEventListener("click",e=>{e.preventDefault(),F(i.id)}),e.appendChild(t),c.appendChild(e)}else{const e=document.createElement("div");e.className="bottom-nav-spacer",c.appendChild(e)}if(l){const e=document.createElement("div");e.className="bottom-nav-item bottom-nav-next";const t=document.createElement("a");t.href=`#${l.id}`,t.className="bottom-nav-link",t.title=`Next: ${l.title}`,t.textContent=l.title,t.addEventListener("click",e=>{e.preventDefault(),F(l.id)}),e.appendChild(t),e.innerHTML+='<span class="bottom-nav-chevron">›</span>',c.appendChild(e)}(y.querySelector("section")||y).appendChild(c)}(e.id),y.scrollTop=0,window.scrollTo(0,0),"function"==typeof o.afterRender&&o.afterRender(y),i({title:e.title,description:e.summary,siteTitle:s.siteTitle,siteUrl:s.siteUrl,sectionId:e.id}),$.set(e.id,Date.now());const l=B;B=!1,K(l),requestAnimationFrame(()=>y.focus())}(o)):location.replace(`#${l}`)}function j(e,t){if(!e)return;const n=M.get(e);t?(I.add(e),n&&n.group.classList.add("expanded")):(I.delete(e),n&&n.group.classList.remove("expanded"))}function W(){if(!T||!x)return;P=!0,T.hidden=!1;const e=q;x.value=e,z(e),requestAnimationFrame(()=>{x.focus(),e&&x.select()})}function _(){T&&x&&(P=!1,T.hidden=!0,x.blur())}!function(){b.innerHTML="",H.clear(),M.clear();let t=I.size>0;e.forEach((e,n)=>{if(e.url){const t=R(e,"nav-leaf");return void b.appendChild(t)}if(e.subsections&&e.subsections.length){const n=document.createElement("div");n.className="nav-group";const a=Boolean(e.module),s=document.createElement("button");s.type="button",s.className="nav-parent"+(a?" nav-parent-with-content":""),s.dataset.section=e.id,s.title=e.summary,a?(s.innerHTML=`\n <span class="nav-title-link">${e.title}</span>\n <span class="nav-expand-toggle" aria-label="Expand"></span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,s.querySelector(".nav-title-link").addEventListener("click",t=>{t.stopPropagation(),F(e.id,{scrollToHighlight:Boolean(q)})}),s.querySelector(".nav-expand-toggle").addEventListener("click",t=>{t.stopPropagation();const n=!I.has(e.id);j(e.id,n)}),s.addEventListener("click",t=>{if(t.target===s){const t=!I.has(e.id);j(e.id,t)}})):(s.innerHTML=`\n <span class="nav-title">${e.title}</span>\n ${e.summary?`<span class="nav-summary">${e.summary}</span>`:""}\n `,s.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)}));const o=document.createElement("div");o.className="nav-sublist",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void o.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-nested";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-nested",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-nested",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void a.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-deep";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-deep",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)});const s=document.createElement("div");s.className="nav-sublist nav-sublist-deep",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void s.appendChild(t)}if(e.subsections&&e.subsections.length){const t=document.createElement("div");t.className="nav-group nav-group-ultra";const n=document.createElement("button");n.type="button",n.className="nav-parent nav-parent-ultra",n.dataset.section=e.id,n.title=e.summary||e.title,n.innerHTML=`<span class="nav-title">${e.title}</span>`,n.addEventListener("click",()=>{const t=!I.has(e.id);j(e.id,t)});const a=document.createElement("div");a.className="nav-sublist nav-sublist-ultra",e.subsections.forEach(e=>{if(e.url){const t=R(e,"nav-item");return void a.appendChild(t)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-ultra"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),s.appendChild(t),H.set(e.id,n),M.set(e.id,{group:t,button:n,list:a});const o=I.has(e.id)&&!e.collapsed;return void j(e.id,o)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-deep"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),s.appendChild(t),H.set(e.id,t)}),t.append(n,s),a.appendChild(t),H.set(e.id,n),M.set(e.id,{group:t,button:n,list:s});const o=I.has(e.id)&&!e.collapsed;return void j(e.id,o)}const t=document.createElement("button");t.type="button",t.className="nav-item nav-item-nested"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary||e.title,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary||""}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),a.appendChild(t),H.set(e.id,t)}),t.append(n,a),o.appendChild(t),H.set(e.id,n),M.set(e.id,{group:t,button:n,list:a});const s=I.has(e.id)&&!e.collapsed;return void j(e.id,s)}const t=document.createElement("button");t.type="button",t.className="nav-item"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),o.appendChild(t),H.set(e.id,t)}),n.append(s,o),b.appendChild(n),H.set(e.id,s),M.set(e.id,{group:n,button:s,list:o});const i=!e.collapsed&&(I.has(e.id)||!t&&!I.size);j(e.id,i),i&&(t=!0)}else{const t=document.createElement("button");t.type="button",t.className="nav-leaf"+(e.type?` nav-type-${e.type}`:""),t.dataset.section=e.id,t.title=e.summary,t.innerHTML=`\n <span class="nav-title">${e.title}${"press-release"===e.type?'<span class="nav-type-icon" aria-label="Press Release"></span>':""}</span>\n <span class="nav-summary">${e.summary}</span>\n `,t.addEventListener("click",()=>F(e.id,{scrollToHighlight:Boolean(q)})),b.appendChild(t),H.set(e.id,t)}})}(),q&&(B=!0),window.addEventListener("hashchange",()=>{q&&(B=!0),O()}),g.textContent=(new Date).getFullYear(),O(),L&&T&&x&&N&&(L.addEventListener("click",()=>{P?_():W()}),x.addEventListener("input",()=>{const e=x.value;J(e,!0),z(e)}),x.addEventListener("keydown",e=>{const t=A.length-1;if("ArrowDown"===e.key)e.preventDefault(),S=Math.min(t,S+1),X();else if("ArrowUp"===e.key)e.preventDefault(),S=Math.max(0,S-1),X();else if("Enter"===e.key){e.preventDefault();const t=A[S];t&&(J(x.value,!0),F(t.id,{scrollToHighlight:!0}),_())}else"Escape"===e.key&&(e.preventDefault(),_())}),N.addEventListener("click",e=>{const t=e.target.closest("[data-section]");if(!t)return;const n=t.dataset.section;n&&(J(x.value,!0),F(n,{scrollToHighlight:!0}),_())}),T.addEventListener("click",e=>{e.target===T&&_()}),window.addEventListener("keydown",e=>{const t=e.target,n=t&&("INPUT"===t.tagName||"TEXTAREA"===t.tagName||t.isContentEditable),a=e.metaKey||e.ctrlKey;"k"===e.key.toLowerCase()&&a||"/"===e.key&&!n?(e.preventDefault(),P?_():W()):"Escape"===e.key&&P&&(e.preventDefault(),_())}));let V=null,U=!1;async function z(t){N&&(!U&&t.trim()&&(U=!0,N.innerHTML='<li class="cmd-item cmd-loading">Indexing content...</li>'),clearTimeout(V),V=setTimeout(async()=>{A=await c(e,t);const n=D();S=d(A,n),function(){if(N){if(N.innerHTML="",!A.length){const e=document.createElement("li");return e.className="cmd-item",e.setAttribute("aria-selected","false"),e.textContent="No matches.",void N.appendChild(e)}A.forEach(e=>{const t=document.createElement("li");t.className="cmd-item",t.dataset.section=e.id,t.setAttribute("role","option");const n=document.createElement("span");if(n.className="cmd-item-title",n.textContent=e.title,e.group){const t=document.createElement("span");t.className="cmd-item-group",t.textContent=e.group,n.prepend(t)}const a=document.createElement("span");a.className="cmd-item-summary",a.textContent=e.summary||"",t.append(n,a),N.appendChild(t)})}}(),X(),U=!1},t.trim()?150:0))}function X(){N&&Array.from(N.children).forEach((e,t)=>{const n=t===S&&A.length;e.setAttribute("aria-selected",n?"true":"false"),n&&e.scrollIntoView({block:"nearest"})})}function G(e){const t=document.createElement("div");t.innerHTML=e,t.querySelectorAll("script").forEach(e=>e.remove()),t.querySelectorAll("button").forEach(e=>e.removeAttribute("onclick")),t.querySelectorAll("mark.hl").forEach(e=>{const t=document.createTextNode(e.textContent||"");e.replaceWith(t)});const n=t.querySelector("section");return n?n.innerHTML:t.innerHTML}function J(e,t=!1){q=e.trim(),t&&(q?localStorage.setItem(k,q):localStorage.removeItem(k)),K()}function K(e=!1){y&&function(e,t,{scrollToFirst:n=!1}={}){if(!e)return;if(function(e){e&&e.querySelectorAll("mark.hl").forEach(e=>{const t=document.createTextNode(e.textContent||"");e.replaceWith(t)})}(e),!t)return;const a=t.split(/\s+/).map(e=>e.trim()).filter(Boolean);if(!a.length)return;const s=a.map(e=>e.toLowerCase()),o=new Set(["SCRIPT","STYLE","CODE","PRE"]),i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode(e){if(!e.nodeValue||!e.nodeValue.trim())return NodeFilter.FILTER_REJECT;const t=e.parentNode;return t&&o.has(t.tagName)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),c=[];let r;for(;r=i.nextNode();){const e=r.nodeValue.toLowerCase();s.some(t=>e.includes(t))&&c.push(r)}const d=new RegExp(`(${a.map(l).join("|")})`,"gi");c.forEach(e=>{const t=e.nodeValue,n=[];let a=0;t.replace(d,(e,s,o)=>{o>a&&n.push(document.createTextNode(t.slice(a,o)));const i=document.createElement("mark");return i.className="hl",i.textContent=e,n.push(i),a=o+e.length,e}),a<t.length&&n.push(document.createTextNode(t.slice(a)));const s=document.createDocumentFragment();n.forEach(e=>s.appendChild(e)),e.parentNode.replaceChild(s,e)}),n&&requestAnimationFrame(()=>{const t=e.querySelector("mark.hl");t&&t.scrollIntoView({behavior:"smooth",block:"center"})})}(y,q,{scrollToFirst:e})}E&&E.addEventListener("click",function(){const t=document.createElement("div");t.className="export-options-overlay",t.innerHTML='\n <div class="export-options-modal">\n <div class="export-options-header">EXPORT OPTIONS</div>\n <div class="export-options-buttons">\n <button type="button" class="export-option-btn" data-scope="page">\n <span class="export-option-title">Current Page</span>\n <span class="export-option-desc">Export only this section</span>\n </button>\n <button type="button" class="export-option-btn" data-scope="site">\n <span class="export-option-title">Entire Site</span>\n <span class="export-option-desc">Export all documentation</span>\n </button>\n </div>\n <button type="button" class="export-cancel-btn">Cancel</button>\n </div>\n ',document.body.appendChild(t),setTimeout(()=>t.classList.add("active"),10);const n=()=>{t.classList.remove("active"),setTimeout(()=>t.remove(),200)};t.querySelector(".export-cancel-btn").addEventListener("click",n),t.addEventListener("click",e=>{e.target===t&&n()}),t.querySelectorAll(".export-option-btn").forEach(t=>{t.addEventListener("click",()=>{const a=t.dataset.scope;n(),async function(t="site"){if(!E)return;const n=E.innerHTML,a=window.open("","_blank","width=1,height=1,left=0,top=0");if(!a||a.closed||void 0===a.closed)return void confirm("Pop-ups are blocked. Please allow pop-ups for this site to export the document.\n\nWould you like to try again after enabling pop-ups?");a.close(),E.disabled=!0;const s=document.createElement("div");s.className="export-loading-overlay",s.innerHTML='\n <div class="export-loading-modal">\n <div class="export-loading-header">\n <div class="export-loading-title">COMPILING DOCUMENTATION</div>\n <div class="export-loading-subtitle">Assembling all sections into unified document</div>\n </div>\n <div class="export-loading-progress">\n <div class="export-loading-bar">\n <div class="export-loading-fill"></div>\n </div>\n <div class="export-loading-status-container">\n <div class="export-loading-status">Initializing...</div>\n </div>\n </div>\n <div class="export-loading-scanner">\n <div class="scanner-line"></div>\n </div>\n </div>\n ',document.body.appendChild(s),setTimeout(()=>s.classList.add("active"),10);const i=s.querySelector(".export-loading-fill"),l=s.querySelector(".export-loading-status");try{let n;if("page"===t){const t=D(),a=v(e).find(e=>e.id===t);n=a?[a]:[]}else n=v(e);if(0===n.length)return alert("No content available to export."),s.remove(),void(E.disabled=!1);const a=[],c=n.length;let r=0;for(const e of n){r++;const n=r/c*100;i.style.width=`${n}%`,l.textContent="page"===t?`Exporting: ${e.title}`:`Processing section ${r} of ${c}: ${e.title}`,await new Promise(e=>setTimeout(e,50));try{const t=await import(e.module),n=t.load||t.default;if("function"!=typeof n)continue;const s=G((await n()).html||"");a.push({section:e,html:s})}catch(t){console.error("Failed to include section in export",e.id,t)}}l.textContent="Generating document...",await new Promise(e=>setTimeout(e,200));const d=u(a,o);l.textContent="Opening document viewer...",await new Promise(e=>setTimeout(e,100));const m=window.open("","_blank","width=900,height=860,scrollbars=yes,resizable=yes");if(!m)return alert("Please allow pop-ups to export the document."),void s.remove();m.document.open(),m.document.write(d),m.document.close(),m.focus(),s.classList.remove("active"),setTimeout(()=>s.remove(),300)}catch(e){console.error("Export failed",e),alert("Export failed. Check console for details."),s.remove()}finally{E.disabled=!1,E.innerHTML=n}}(a)})})}),w&&C&&(w.addEventListener("click",()=>{C.classList.contains("mobile-open")?(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false")):(C.classList.add("mobile-open"),document.body.classList.add("menu-open"),w.setAttribute("aria-expanded","true"))}),b.addEventListener("click",e=>{if(window.innerWidth<=960){const t=e.target.closest(".nav-item, .nav-leaf, .nav-parent");t&&(t.classList.contains("nav-item")||t.classList.contains("nav-leaf"))&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false"))}}),document.addEventListener("click",e=>{window.innerWidth<=960&&C.classList.contains("mobile-open")&&!C.contains(e.target)&&!w.contains(e.target)&&(C.classList.remove("mobile-open"),document.body.classList.remove("menu-open"),w.setAttribute("aria-expanded","false"))}));
|
package/site/index.html
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>Pagenary Docs</title>
|
|
7
|
+
<meta name="description" content="Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself." />
|
|
8
|
+
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
9
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
10
|
+
<meta name="x-build" content="2026-05-26T00:23:59.591Z" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<a class="skip-link" href="#app">Skip to content</a>
|
|
14
|
+
<header class="shell topbar">
|
|
15
|
+
<button type="button" id="mobileMenuToggle" class="mobile-menu-toggle" aria-label="Toggle navigation" aria-expanded="false">
|
|
16
|
+
<span class="menu-icon"></span>
|
|
17
|
+
</button>
|
|
18
|
+
<div class="brand">
|
|
19
|
+
<span class="brand-mark">Pagenary</span>
|
|
20
|
+
<span class="brand-sub">Docs</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="top-actions">
|
|
23
|
+
<button type="button" id="commandToggle" class="ghost-button" aria-haspopup="dialog" aria-controls="commandPalette">
|
|
24
|
+
<span class="ghost-icon">⌘K</span>
|
|
25
|
+
<span class="ghost-label">Quick Find</span>
|
|
26
|
+
</button>
|
|
27
|
+
<button type="button" id="exportBtn" class="ghost-button">
|
|
28
|
+
<span class="ghost-icon">⇣</span>
|
|
29
|
+
<span class="ghost-label">Export</span>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</header>
|
|
33
|
+
<div class="layout">
|
|
34
|
+
<aside class="sidebar" aria-label="Section navigation">
|
|
35
|
+
<div class="sidebar-inner">
|
|
36
|
+
<nav id="nav" class="nav" aria-label="Primary"></nav>
|
|
37
|
+
</div>
|
|
38
|
+
</aside>
|
|
39
|
+
<main id="app" class="canvas" tabindex="-1" aria-live="polite"></main>
|
|
40
|
+
</div>
|
|
41
|
+
<footer class="shell footnote">
|
|
42
|
+
<span>© <span id="year"></span> Pagenary</span>
|
|
43
|
+
<span class="divider"></span>
|
|
44
|
+
<span>Where documentation takes shape.</span>
|
|
45
|
+
</footer>
|
|
46
|
+
<div id="commandPalette" class="cmd" role="dialog" aria-modal="true" aria-labelledby="commandLabel" hidden>
|
|
47
|
+
<div class="cmd-surface" role="document">
|
|
48
|
+
<div class="cmd-header">
|
|
49
|
+
<label id="commandLabel" class="cmd-title" for="commandInput">Jump to section</label>
|
|
50
|
+
<input id="commandInput" class="cmd-input" type="search" name="query" autocomplete="off" placeholder="Start typing…" />
|
|
51
|
+
</div>
|
|
52
|
+
<ul id="commandList" class="cmd-list" role="listbox"></ul>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<script type="module" src="./app.js"></script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WORD_OVERRIDES={api:"API",apis:"APIs",faq:"FAQ",rpc:"RPC",sdk:"SDK",sdks:"SDKs",ui:"UI"};export const CATEGORY_RULES=[{category:"welcome",test:t=>t.startsWith("welcome")},{category:"guide",test:t=>t.startsWith("getting-started")},{category:"reference",test:t=>t.startsWith("core-technology")},{category:"technical",test:t=>t.startsWith("technical")},{category:"developer",test:t=>t.startsWith("developers")},{category:"tutorial-overview",test:t=>"tutorials-overview"===t},{category:"tutorial",test:t=>t.startsWith("tutorial-")},{category:"tutorial",test:t=>t.startsWith("tutorials-")},{category:"use-case",test:t=>t.startsWith("use-case")},{category:"product",test:t=>t.startsWith("products")},{category:"governance",test:t=>t.startsWith("governance")},{category:"resource",test:t=>t.startsWith("resources")},{category:"security",test:t=>t.startsWith("security")},{category:"operations",test:t=>t.startsWith("operations")},{category:"archive",test:t=>t.startsWith("archive")}];export function formatWord(t){return t?WORD_OVERRIDES[t]?WORD_OVERRIDES[t]:/^\d+$/.test(t)?t:t.charAt(0).toUpperCase()+t.slice(1):""}export function normalizeId(t){return t.trim()}export function inferCategory(t){const e=normalizeId(t),r=CATEGORY_RULES.find(t=>t.test(e));return r?r.category:"default"}export function titleFromId(t){return normalizeId(t).replace(/_/g,"-").split("-").filter(Boolean).map(t=>formatWord(t)).join(" ")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function composeExportDocument(e,t={}){const{title:n="Documentation Export",brandMark:o="Docs",brandSub:r="Toolkit",tagline:a="",logo:i=null,showTagline:s=!0,showDate:l=!0}=t,m=(new Date).toLocaleString();return`<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>${n}</title>\n <style>\n :root { color-scheme: light; font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }\n body { margin: 0 auto; padding: 2.5rem; max-width: 820px; color: #111; line-height: 1.6; }\n /* Export header styling */\n header { text-align: center; margin-bottom: 3rem; }\n .export-brand { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 0.5rem; }\n .export-header--logo-text .export-brand { flex-direction: row; }\n .export-header--text-only .export-brand { flex-direction: column; gap: 0; }\n .export-logo { max-height: 48px; width: auto; }\n h1 { font-size: 2.2rem; letter-spacing: 0.1em; text-transform: uppercase; margin: 0; line-height: 1; }\n .brand-mark { font-weight: 700; }\n .brand-sub { font-weight: 400; opacity: 0.85; }\n .tagline { color: #666; font-size: 0.95rem; margin: 0.5rem 0 0 0; font-style: italic; }\n .meta { color: #666; font-size: 0.9rem; margin: 0.5rem 0 0 0; }\n .toc { border: 1px solid rgba(0,0,0,0.1); padding: 1.5rem; margin-bottom: 2.5rem; background: rgba(0,0,0,0.02); }\n .toc h2 { margin-top: 0; letter-spacing: 0.12em; text-transform: uppercase; font-size: 0.95rem; }\n .toc ul { margin: 0; padding-left: 1.2rem; }\n .toc li { margin: 0.4rem 0; }\n section { page-break-inside: avoid; margin-bottom: 2.75rem; }\n h2 { font-size: 1.4rem; letter-spacing: 0.08em; text-transform: uppercase; border-bottom: 1px solid rgba(0,0,0,0.12); padding-bottom: 0.5rem; margin-bottom: 1rem; }\n .section-summary { color: #5a5a5a; font-size: 0.95rem; margin-top: 0; }\n .section-body { border-left: 1px solid rgba(0,0,0,0.1); padding-left: 1.25rem; }\n .card, .card-grid, .card-grid > *, .section-body > * { break-inside: avoid; }\n pre { background: rgba(0,0,0,0.05); padding: 1rem; border-radius: 4px; overflow-x: auto; }\n code { font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n /* Table styling */\n table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }\n th, td { padding: 0.75rem 1rem; text-align: left; border: 1px solid rgba(0,0,0,0.15); }\n th { background: rgba(0,0,0,0.05); font-weight: 600; }\n tbody tr:nth-child(even) { background: rgba(0,0,0,0.02); }\n /* Spec table styling (legacy) */\n .spec-table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }\n .spec-table th, .spec-table td { padding: 0.75rem 1rem; text-align: left; border: 1px solid rgba(0,0,0,0.15); }\n .spec-table th { background: rgba(0,0,0,0.05); font-weight: 600; }\n .spec-table tr:nth-child(even) { background: rgba(0,0,0,0.02); }\n .spec-table td:first-child { width: 40%; }\n /* Layer stack styling */\n .layer-stack { display: flex; flex-direction: column; margin: 1.5rem 0; border: 2px solid #111; border-radius: 6px; overflow: hidden; }\n .layer-stack .layer { padding: 1rem 1.25rem; text-align: center; border-bottom: 1px solid rgba(0,0,0,0.15); }\n .layer-stack .layer:last-child { border-bottom: none; }\n .layer-stack .layer:first-child { background: rgba(0,0,0,0.02); }\n .layer-stack .layer:nth-child(2) { background: rgba(0,0,0,0.04); }\n .layer-stack .layer:nth-child(3) { background: rgba(0,0,0,0.06); }\n .layer-stack .layer-title { font-weight: 600; font-size: 1rem; margin-bottom: 0.25rem; }\n .layer-stack .layer-desc, .layer-stack .layer-detail { font-size: 0.85rem; color: #666; }\n /* HTML block container */\n .html-block { margin: 1.5rem 0; }\n /* Box diagram styling */\n .box-diagram { border: 2px solid #111; border-radius: 6px; padding: 1.25rem; margin: 1.5rem 0; background: rgba(0,0,0,0.02); font-family: 'IBM Plex Mono', monospace; font-size: 0.85rem; white-space: pre-wrap; }\n .box-diagram .box-title { font-weight: 700; font-size: 1rem; margin-bottom: 0.75rem; padding-bottom: 0.5rem; border-bottom: 1px solid rgba(0,0,0,0.15); }\n /* Syntax highlighting tokens */\n .token.comment, .token.prolog, .token.doctype, .token.cdata { color: #6a737d; font-style: italic; }\n .token.punctuation { color: #24292e; }\n .token.property, .token.tag, .token.constant, .token.symbol, .token.deleted { color: #d73a49; }\n .token.boolean, .token.number { color: #005cc5; }\n .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #22863a; }\n .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #d73a49; }\n .token.atrule, .token.attr-value, .token.keyword { color: #d73a49; }\n .token.function, .token.class-name { color: #6f42c1; }\n .token.regex, .token.important, .token.variable { color: #e36209; }\n .token.important, .token.bold { font-weight: bold; }\n .token.italic { font-style: italic; }\n @media print {\n @page { size: A4; margin: 1in; }\n body { box-shadow: none; }\n header, .toc { break-after: avoid; }\n .export-logo { max-height: 36px; }\n }\n </style>\n </head>\n <body>\n <header class="${i?"export-header--logo-text":"export-header--text-only"}">\n <div class="export-brand">\n ${i?`<img src="${i}" alt="" class="export-logo" aria-hidden="true" />`:""}\n <h1>${r?`<span class="brand-mark">${o}</span><span class="brand-sub">${r}</span>`:o}</h1>\n </div>\n ${s&&a?`<p class="tagline">${a}</p>`:""}\n ${l?`<p class="meta">Generated ${m}</p>`:""}\n </header>\n <div class="toc">\n <h2>Table of Contents</h2>\n <ul>${e.map((e,t)=>`\n <li>${t+1}. ${e.section.title}</li>`).join("")||"<li>No sections</li>"}</ul>\n </div>\n${e.map((e,t)=>`\n <section>\n <h2>${t+1}. ${e.section.title}</h2>\n <p class="section-summary">${e.section.summary||""}</p>\n <div class="section-body">\n${e.html}\n </div>\n </section>`).join("\n")||"<p>No sections available.</p>"}\n <script type="module">\n import Prism from 'https://esm.sh/prismjs@1.29.0';\n await Promise.all([\n import('https://esm.sh/prismjs@1.29.0/components/prism-c'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-json'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-typescript'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-python'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-rust'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-go'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-bash'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-yaml'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-sql'),\n import('https://esm.sh/prismjs@1.29.0/components/prism-solidity'),\n ]);\n Prism.highlightAll();\n window.focus();\n <\/script>\n </body>\n</html>`}export function collectExportableSections(e){const t=[];for(const n of e)if(n.module&&t.push(n),n.subsections)for(const e of n.subsections)e.module&&t.push(e);return t}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function sectionEntry(t,n){const e="string"==typeof t?t:t.id,r=n(e,"string"==typeof t?{}:{title:t.title,summary:t.summary});return{id:e,title:r.title,summary:r.summary,module:`./sections/${e}.js`}}export function groupEntry(t,n){const{id:e,title:r,summary:i,sections:s}=t;return{id:e,title:r,summary:i,subsections:s.map(t=>sectionEntry(t,n))}}export function buildSectionIndex(t){const n=new Map;function e(t,r=null){r&&(t.parentId=r),n.set(t.id,t),Array.isArray(t.subsections)&&t.subsections.forEach(n=>e(n,t.id))}return t.forEach(t=>e(t)),n}export function createFindSection(t){return function(n){return t.get(n)||null}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function currentSectionId(t,e){return t.replace("#","")||e}export function resolveTarget(t,e){const r=e(t);return r?!r.module&&r.subsections&&r.subsections.length?{targetId:r.subsections[0].id,parentId:r.id}:{targetId:r.id,parentId:r.parentId||null}:{targetId:t,parentId:null}}export function resolveEntry(t,e){const{targetId:r,parentId:n}=resolveTarget(t,e),d=e(r);return d?{entry:d,targetId:r,parentId:n}:null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let t=null,e=null;export function escapeRegExp(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export function flattenManifest(t,e=""){const n=[];for(const r of t){const t=e?`${e} > ${r.title}`:r.title,o=Array.isArray(r.subsections)&&r.subsections.length>0;if(!r.module&&o||n.push({...r,group:e||r.title}),o){const e=flattenManifest(r.subsections,t);n.push(...e)}}return n}export async function buildSearchIndex(n){return t||e||(e=(async()=>{const e=flattenManifest(n),r=await Promise.all(e.map(async t=>{let e="";try{if(t.module){const n=t.module.replace("./","../"),r=await import(n);r.load&&(e=function(t){const e=document.createElement("div");return e.innerHTML=t,e.textContent||e.innerText||""}((await r.load()).html||""))}}catch(t){}return{...t,searchContent:`${t.title||""} ${t.summary||""} ${t.group||""} ${e}`.toLowerCase()}}));return t=r,r})(),e)}export function filterSections(t,e){const n=flattenManifest(t),r=e.trim().toLowerCase();return r?n.filter(t=>`${t.title||""} ${t.summary||""} ${t.group||""}`.toLowerCase().includes(r)):n}export async function searchContent(t,e){const n=await buildSearchIndex(t),r=e.trim().toLowerCase();return r?n.filter(t=>t.searchContent.includes(r)):n}export function parseSearchTerms(t){return t.split(/\s+/).map(t=>t.trim()).filter(Boolean)}export function findPreferredIndex(t,e){const n=t.findIndex(t=>t.id===e);return n>=0?n:0}
|
package/site/llms.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Pagenary Docs
|
|
2
|
+
|
|
3
|
+
> Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself.
|
|
4
|
+
|
|
5
|
+
- [Welcome](/pages/welcome.html): What Pagenary is and how this dogfooded portal is built.
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
- [Quickstart](/pages/quickstart.html): Install, build the default bundle, and serve it locally.
|
|
10
|
+
|
|
11
|
+
## Guides
|
|
12
|
+
|
|
13
|
+
- [Developer Guide](/pages/developer-guide.html): Project layout, scripts, and the content authoring workflow.
|
|
14
|
+
- [Tenant Configuration](/pages/tenant-config.html): Every config.json option: branding, theming, SEO, and export.
|
|
15
|
+
- [Extending](/pages/extending.html): Add section templates, content types, and build behaviors.
|
|
16
|
+
|
|
17
|
+
## Reference
|
|
18
|
+
|
|
19
|
+
- [Architecture](/pages/architecture.html): The static SPA pattern, build pipeline, and tenant content model.
|
|
20
|
+
- [API Reference](/pages/api.html): Module-level documentation for the publisher internals.
|
|
21
|
+
- [Deployment](/pages/deployment.html): Hosting the static output and multi-tenant domain routing.
|
|
22
|
+
- [SEO Strategy](/pages/seo-strategy.html): Metadata, hash-routing considerations, and discoverability.
|
package/site/manifest.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
export const MANIFEST = [
|
|
2
|
+
{
|
|
3
|
+
"id": "welcome",
|
|
4
|
+
"title": "Welcome",
|
|
5
|
+
"summary": "What Pagenary is and how this dogfooded portal is built.",
|
|
6
|
+
"module": "./sections/welcome.js"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "getting-started",
|
|
10
|
+
"title": "Getting Started",
|
|
11
|
+
"summary": "Build your first bundle and stand up a tenant.",
|
|
12
|
+
"subsections": [
|
|
13
|
+
{
|
|
14
|
+
"id": "quickstart",
|
|
15
|
+
"title": "Quickstart",
|
|
16
|
+
"summary": "Install, build the default bundle, and serve it locally.",
|
|
17
|
+
"module": "./sections/quickstart.js"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "guides",
|
|
23
|
+
"title": "Guides",
|
|
24
|
+
"summary": "Day-to-day workflows for authors and maintainers.",
|
|
25
|
+
"subsections": [
|
|
26
|
+
{
|
|
27
|
+
"id": "developer-guide",
|
|
28
|
+
"title": "Developer Guide",
|
|
29
|
+
"summary": "Project layout, scripts, and the content authoring workflow.",
|
|
30
|
+
"module": "./sections/developer-guide.js"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "tenant-config",
|
|
34
|
+
"title": "Tenant Configuration",
|
|
35
|
+
"summary": "Every config.json option: branding, theming, SEO, and export.",
|
|
36
|
+
"module": "./sections/tenant-config.js"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "extending",
|
|
40
|
+
"title": "Extending",
|
|
41
|
+
"summary": "Add section templates, content types, and build behaviors.",
|
|
42
|
+
"module": "./sections/extending.js"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "reference",
|
|
48
|
+
"title": "Reference",
|
|
49
|
+
"summary": "Architecture, APIs, deployment, and SEO reference material.",
|
|
50
|
+
"subsections": [
|
|
51
|
+
{
|
|
52
|
+
"id": "architecture",
|
|
53
|
+
"title": "Architecture",
|
|
54
|
+
"summary": "The static SPA pattern, build pipeline, and tenant content model.",
|
|
55
|
+
"module": "./sections/architecture.js"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "api",
|
|
59
|
+
"title": "API Reference",
|
|
60
|
+
"summary": "Module-level documentation for the publisher internals.",
|
|
61
|
+
"module": "./sections/api.js"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "deployment",
|
|
65
|
+
"title": "Deployment",
|
|
66
|
+
"summary": "Hosting the static output and multi-tenant domain routing.",
|
|
67
|
+
"module": "./sections/deployment.js"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "seo-strategy",
|
|
71
|
+
"title": "SEO Strategy",
|
|
72
|
+
"summary": "Metadata, hash-routing considerations, and discoverability.",
|
|
73
|
+
"module": "./sections/seo-strategy.js"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
export const DEFAULT_SECTION = "welcome";
|
|
79
|
+
|
|
80
|
+
const SECTION_INDEX = new Map();
|
|
81
|
+
|
|
82
|
+
function registerEntry(entry, parentId = null) {
|
|
83
|
+
if (parentId) entry.parentId = parentId;
|
|
84
|
+
SECTION_INDEX.set(entry.id, entry);
|
|
85
|
+
if (Array.isArray(entry.subsections)) {
|
|
86
|
+
entry.subsections.forEach((child) => registerEntry(child, entry.id));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
MANIFEST.forEach((entry) => registerEntry(entry));
|
|
91
|
+
|
|
92
|
+
export function findSection(id) {
|
|
93
|
+
return SECTION_INDEX.get(id) || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Build flat list of navigable sections for prev/next navigation
|
|
97
|
+
function buildFlatNav() {
|
|
98
|
+
const flat = [];
|
|
99
|
+
MANIFEST.forEach((entry) => {
|
|
100
|
+
if (entry.subsections && entry.subsections.length) {
|
|
101
|
+
entry.subsections.forEach((sub) => flat.push(sub));
|
|
102
|
+
} else {
|
|
103
|
+
flat.push(entry);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return flat;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const FLAT_NAV = buildFlatNav();
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get previous and next sections for bottom navigation
|
|
113
|
+
* @param {string} currentId - Current section ID
|
|
114
|
+
* @returns {{ prev: object|null, next: object|null }}
|
|
115
|
+
*/
|
|
116
|
+
export function getAdjacentSections(currentId) {
|
|
117
|
+
const index = FLAT_NAV.findIndex((s) => s.id === currentId);
|
|
118
|
+
if (index === -1) return { prev: null, next: null };
|
|
119
|
+
return {
|
|
120
|
+
prev: index > 0 ? FLAT_NAV[index - 1] : null,
|
|
121
|
+
next: index < FLAT_NAV.length - 1 ? FLAT_NAV[index + 1] : null
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Site configuration (from tenant _manifest.json)
|
|
126
|
+
export const SITE_CONFIG = {
|
|
127
|
+
"bottomNav": "mobile",
|
|
128
|
+
"bottomNavSections": []
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Export document branding configuration
|
|
132
|
+
export const EXPORT_CONFIG = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=null;function t(){return"mermaid-"+Math.random().toString(36).slice(2,10)}export async function renderMermaidBlocks(o){const s=o.querySelectorAll("code.language-mermaid");if(0===s.length)return;const a=await async function(){return e||(e=(async()=>{const{default:e}=await import("https://esm.sh/mermaid@10/dist/mermaid.esm.min.mjs");return e.initialize({startOnLoad:!1,theme:"neutral",securityLevel:"strict",flowchart:{htmlLabels:!0,curve:"basis",nodeSpacing:30,rankSpacing:50,padding:15,useMaxWidth:!0},themeVariables:{fontFamily:"system-ui, -apple-system, sans-serif",fontSize:"14px"}}),e})(),e)}();for(const e of s){const o=e.parentElement,s=e.textContent,r=t();try{const{svg:e}=await a.render(r,s),t=document.createElement("div");t.className="mermaid-diagram";const n=document.createElement("div");n.className="mermaid-controls",n.innerHTML='\n <button type="button" class="mermaid-btn mermaid-zoom-out" aria-label="Zoom out">−</button>\n <button type="button" class="mermaid-btn mermaid-zoom-reset" aria-label="Reset zoom">⊙</button>\n <button type="button" class="mermaid-btn mermaid-zoom-in" aria-label="Zoom in">+</button>\n ';const c=document.createElement("div");c.className="mermaid-content",c.innerHTML=e,t.appendChild(n),t.appendChild(c);let i=1;const l=c.querySelector("svg");let m,d,u,p,f=!1;const h=()=>{l&&(l.style.transform=`scale(${i})`,l.style.transformOrigin="top left")};n.querySelector(".mermaid-zoom-in").addEventListener("click",()=>{i=Math.min(i+.25,3),h()}),n.querySelector(".mermaid-zoom-out").addEventListener("click",()=>{i=Math.max(i-.25,.5),h()}),n.querySelector(".mermaid-zoom-reset").addEventListener("click",()=>{i=1,h(),c.scrollLeft=0,c.scrollTop=0}),c.addEventListener("mousedown",e=>{f=!0,c.style.cursor="grabbing",m=e.pageX-c.offsetLeft,d=e.pageY-c.offsetTop,u=c.scrollLeft,p=c.scrollTop}),c.addEventListener("mouseleave",()=>{f=!1,c.style.cursor="grab"}),c.addEventListener("mouseup",()=>{f=!1,c.style.cursor="grab"}),c.addEventListener("mousemove",e=>{if(!f)return;e.preventDefault();const t=e.pageX-c.offsetLeft,o=e.pageY-c.offsetTop;c.scrollLeft=u-(t-m),c.scrollTop=p-(o-d)});let g=0;c.addEventListener("touchstart",e=>{1===e.touches.length?(f=!0,m=e.touches[0].pageX-c.offsetLeft,d=e.touches[0].pageY-c.offsetTop,u=c.scrollLeft,p=c.scrollTop):2===e.touches.length&&(g=Math.hypot(e.touches[0].pageX-e.touches[1].pageX,e.touches[0].pageY-e.touches[1].pageY))},{passive:!0}),c.addEventListener("touchmove",e=>{if(1===e.touches.length&&f){const t=e.touches[0].pageX-c.offsetLeft,o=e.touches[0].pageY-c.offsetTop;c.scrollLeft=u-(t-m),c.scrollTop=p-(o-d)}else if(2===e.touches.length){const t=Math.hypot(e.touches[0].pageX-e.touches[1].pageX,e.touches[0].pageY-e.touches[1].pageY);if(g>0){const e=t-g;Math.abs(e)>10&&(i=Math.max(.5,Math.min(3,i+(e>0?.1:-.1))),h(),g=t)}}},{passive:!0}),c.addEventListener("touchend",()=>{f=!1,g=0},{passive:!0}),c.style.cursor="grab",o.replaceWith(t)}catch(e){console.error("Mermaid render error:",e),o.classList.add("mermaid-error");const t=document.createElement("div");t.className="mermaid-error-message",t.textContent="Diagram failed to render: "+e.message,o.insertAdjacentElement("beforebegin",t)}}}
|