@doxbrix/doxloop 0.1.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/CHANGELOG.md +34 -0
- package/LICENSE +93 -0
- package/README.md +576 -0
- package/SECURITY.md +15 -0
- package/assets/brand/README.md +8 -0
- package/assets/brand/doxloop-favicon.png +0 -0
- package/assets/brand/doxloop-logo-dark.png +0 -0
- package/assets/brand/doxloop-logo-light.png +0 -0
- package/assets/doxbrix-preview.css +6672 -0
- package/dist/agents.d.ts +26 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +258 -0
- package/dist/agents.js.map +1 -0
- package/dist/args.d.ts +8 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +95 -0
- package/dist/args.js.map +1 -0
- package/dist/artifact-deploy.d.ts +8 -0
- package/dist/artifact-deploy.d.ts.map +1 -0
- package/dist/artifact-deploy.js +240 -0
- package/dist/artifact-deploy.js.map +1 -0
- package/dist/auth.d.ts +17 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +245 -0
- package/dist/auth.js.map +1 -0
- package/dist/author.d.ts +30 -0
- package/dist/author.d.ts.map +1 -0
- package/dist/author.js +195 -0
- package/dist/author.js.map +1 -0
- package/dist/capture.d.ts +10 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +299 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +558 -0
- package/dist/cli.js.map +1 -0
- package/dist/deploy.d.ts +22 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +136 -0
- package/dist/deploy.js.map +1 -0
- package/dist/doctor.d.ts +18 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +222 -0
- package/dist/doctor.js.map +1 -0
- package/dist/doxbrix-markdown.d.ts +20 -0
- package/dist/doxbrix-markdown.d.ts.map +1 -0
- package/dist/doxbrix-markdown.js +873 -0
- package/dist/doxbrix-markdown.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -0
- package/dist/fs.d.ts +6 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +77 -0
- package/dist/fs.js.map +1 -0
- package/dist/generator-api.d.ts +69 -0
- package/dist/generator-api.d.ts.map +1 -0
- package/dist/generator-api.js +6 -0
- package/dist/generator-api.js.map +1 -0
- package/dist/generator-manager.d.ts +13 -0
- package/dist/generator-manager.d.ts.map +1 -0
- package/dist/generator-manager.js +155 -0
- package/dist/generator-manager.js.map +1 -0
- package/dist/generator-runtime.d.ts +24 -0
- package/dist/generator-runtime.d.ts.map +1 -0
- package/dist/generator-runtime.js +265 -0
- package/dist/generator-runtime.js.map +1 -0
- package/dist/generators.d.ts +21 -0
- package/dist/generators.d.ts.map +1 -0
- package/dist/generators.js +202 -0
- package/dist/generators.js.map +1 -0
- package/dist/preview.d.ts +21 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +691 -0
- package/dist/preview.js.map +1 -0
- package/dist/progress.d.ts +19 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +70 -0
- package/dist/progress.js.map +1 -0
- package/dist/project.d.ts +37 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +499 -0
- package/dist/project.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +221 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +141 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +478 -0
- package/dist/validation.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/docs/agent-compatibility.md +41 -0
- package/docs/ci-and-automation.md +56 -0
- package/docs/doxbrix-http-api.md +182 -0
- package/docs/generator-authoring.md +59 -0
- package/docs/generator-selection.md +25 -0
- package/docs/project-format.md +97 -0
- package/docs/security-model.md +90 -0
- package/docs/troubleshooting.md +103 -0
- package/package.json +87 -0
- package/skills/doxloop-authoring/SKILL.md +381 -0
- package/skills/doxloop-authoring/agents/openai.yaml +4 -0
- package/skills/doxloop-authoring/assets/screenshot-highlight.js +83 -0
- package/skills/doxloop-authoring/references/accessibility.md +54 -0
- package/skills/doxloop-authoring/references/audience-flavors.md +93 -0
- package/skills/doxloop-authoring/references/branding.md +50 -0
- package/skills/doxloop-authoring/references/documentation-types.md +236 -0
- package/skills/doxloop-authoring/references/domain-ai-ml.md +67 -0
- package/skills/doxloop-authoring/references/domain-api-platform.md +68 -0
- package/skills/doxloop-authoring/references/domain-cli-tool.md +65 -0
- package/skills/doxloop-authoring/references/domain-data-platform.md +65 -0
- package/skills/doxloop-authoring/references/domain-developer-library.md +63 -0
- package/skills/doxloop-authoring/references/domain-ecommerce.md +63 -0
- package/skills/doxloop-authoring/references/domain-infrastructure-devops.md +67 -0
- package/skills/doxloop-authoring/references/domain-payments-fintech.md +69 -0
- package/skills/doxloop-authoring/references/domain-saas.md +71 -0
- package/skills/doxloop-authoring/references/domain-security-identity.md +65 -0
- package/skills/doxloop-authoring/references/editorial-style.md +105 -0
- package/skills/doxloop-authoring/references/examples-and-evidence.md +71 -0
- package/skills/doxloop-authoring/references/navigation-architecture.md +152 -0
- package/skills/doxloop-authoring/references/project-format.md +119 -0
- package/skills/doxloop-authoring/references/quality.md +122 -0
- package/skills/doxloop-authoring/references/reference-sites.md +207 -0
- package/skills/doxloop-authoring/references/screenshots.md +251 -0
- package/skills/doxloop-authoring/references/template-routing.md +94 -0
- package/skills/doxloop-authoring/references/type-administrator-guide.md +59 -0
- package/skills/doxloop-authoring/references/type-api-reference.md +66 -0
- package/skills/doxloop-authoring/references/type-architecture-concepts.md +61 -0
- package/skills/doxloop-authoring/references/type-cli-manual.md +61 -0
- package/skills/doxloop-authoring/references/type-deployment-operations.md +64 -0
- package/skills/doxloop-authoring/references/type-developer-portal.md +69 -0
- package/skills/doxloop-authoring/references/type-getting-started.md +59 -0
- package/skills/doxloop-authoring/references/type-integration-guide.md +59 -0
- package/skills/doxloop-authoring/references/type-migration-release.md +66 -0
- package/skills/doxloop-authoring/references/type-sdk-guide.md +63 -0
- package/skills/doxloop-authoring/references/type-troubleshooting-kb.md +63 -0
- package/skills/doxloop-authoring/references/type-user-guide.md +59 -0
- package/skills/doxloop-doxbrix/SKILL.md +83 -0
- package/skills/doxloop-doxbrix/agents/openai.yaml +4 -0
- package/skills/doxloop-doxbrix/references/api-endpoints.md +128 -0
- package/skills/doxloop-doxbrix/references/components.md +137 -0
- package/skills/doxloop-doxbrix/references/manifest.md +117 -0
package/dist/preview.js
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { extname, isAbsolute, relative, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import chokidar from 'chokidar';
|
|
7
|
+
import { renderMarkdown } from './doxbrix-markdown.js';
|
|
8
|
+
import { resolveContainedDirectory } from './fs.js';
|
|
9
|
+
import { loadGeneratorAdapter } from './generators.js';
|
|
10
|
+
import { loadPages, loadProject, loadSiteConfig, pageId, readPage, siteConfigPath, } from './project.js';
|
|
11
|
+
const PACKAGE_ROOT = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
12
|
+
const DOXBRIX_CSS = resolve(PACKAGE_ROOT, 'assets', 'doxbrix-preview.css');
|
|
13
|
+
const ATLAS_LAYOUT = {
|
|
14
|
+
headerHeight: 64,
|
|
15
|
+
tabsHeight: 48,
|
|
16
|
+
shellMaxWidth: 1400,
|
|
17
|
+
sidebarWidth: 256,
|
|
18
|
+
tocWidth: 248,
|
|
19
|
+
contentMaxWidth: 800,
|
|
20
|
+
contentPadding: 4,
|
|
21
|
+
navPadding: 24,
|
|
22
|
+
};
|
|
23
|
+
const STATIC_TYPES = {
|
|
24
|
+
'.avif': 'image/avif',
|
|
25
|
+
'.gif': 'image/gif',
|
|
26
|
+
'.ico': 'image/x-icon',
|
|
27
|
+
'.jpeg': 'image/jpeg',
|
|
28
|
+
'.jpg': 'image/jpeg',
|
|
29
|
+
'.pdf': 'application/pdf',
|
|
30
|
+
'.png': 'image/png',
|
|
31
|
+
'.svg': 'image/svg+xml',
|
|
32
|
+
'.ttf': 'font/ttf',
|
|
33
|
+
'.otf': 'font/otf',
|
|
34
|
+
'.webp': 'image/webp',
|
|
35
|
+
'.woff': 'font/woff',
|
|
36
|
+
'.woff2': 'font/woff2',
|
|
37
|
+
};
|
|
38
|
+
export async function startPreview(options) {
|
|
39
|
+
const project = await loadProject(options.root);
|
|
40
|
+
if (project.generator !== 'doxbrix') {
|
|
41
|
+
await (await loadGeneratorAdapter(options.root, project)).preview(options);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
await startDoxbrixPreview(options, project);
|
|
45
|
+
}
|
|
46
|
+
async function startDoxbrixPreview(options, project) {
|
|
47
|
+
const contentRoot = await resolveContainedDirectory(options.root, project.contentDir, 'Preview content directory');
|
|
48
|
+
const css = await readFile(DOXBRIX_CSS, 'utf8');
|
|
49
|
+
const clients = new Set();
|
|
50
|
+
const server = createServer(async (request, response) => {
|
|
51
|
+
try {
|
|
52
|
+
const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
|
|
53
|
+
if (url.pathname === '/__doxloop/events') {
|
|
54
|
+
response.writeHead(200, {
|
|
55
|
+
'Content-Type': 'text/event-stream',
|
|
56
|
+
'Cache-Control': 'no-cache',
|
|
57
|
+
Connection: 'keep-alive',
|
|
58
|
+
});
|
|
59
|
+
response.write(': connected\n\n');
|
|
60
|
+
clients.add(response);
|
|
61
|
+
request.once('close', () => clients.delete(response));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (url.pathname === '/__doxloop/doxbrix.css') {
|
|
65
|
+
send(response, 200, 'text/css; charset=utf-8', css);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const staticPath = safeStaticPath(contentRoot, url.pathname);
|
|
69
|
+
if (staticPath && STATIC_TYPES[extname(staticPath).toLowerCase()]) {
|
|
70
|
+
try {
|
|
71
|
+
send(response, 200, STATIC_TYPES[extname(staticPath).toLowerCase()], await readFile(staticPath));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// A missing static asset may still be an extension-bearing documentation route.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const [pages, site] = await Promise.all([
|
|
79
|
+
loadPages(options.root, project),
|
|
80
|
+
loadSiteConfig(options.root, project),
|
|
81
|
+
]);
|
|
82
|
+
const pagesById = new Map(pages.map((path) => [pageId(contentRoot, path), path]));
|
|
83
|
+
const requested = requestedPage(url.pathname, site, pagesById);
|
|
84
|
+
if (requested === undefined || !pagesById.has(requested)) {
|
|
85
|
+
send(response, 404, 'text/html; charset=utf-8', errorPage(requested ?? '', site));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const page = await readPage(pagesById.get(requested));
|
|
89
|
+
const rendered = renderMarkdown(page.body);
|
|
90
|
+
send(response, 200, 'text/html; charset=utf-8', doxbrixDocument({
|
|
91
|
+
site,
|
|
92
|
+
title: page.title || labelFromId(requested),
|
|
93
|
+
...(page.description ? { description: page.description } : {}),
|
|
94
|
+
current: requested,
|
|
95
|
+
rendered,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
send(response, 500, 'text/plain; charset=utf-8', error instanceof Error ? error.message : String(error));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
await new Promise((resolveListen, reject) => {
|
|
103
|
+
server.once('error', reject);
|
|
104
|
+
server.listen(options.port, options.host, resolveListen);
|
|
105
|
+
});
|
|
106
|
+
const url = `http://${shownHost(options.host)}:${options.port}`;
|
|
107
|
+
process.stdout.write(`Doxbrix preview: ${url}\nPress Ctrl+C to stop.\n`);
|
|
108
|
+
if (options.open)
|
|
109
|
+
openBrowser(url);
|
|
110
|
+
const configPath = await siteConfigPath(options.root, project);
|
|
111
|
+
const watcher = chokidar.watch([contentRoot, configPath], { ignoreInitial: true });
|
|
112
|
+
watcher.on('all', () => {
|
|
113
|
+
for (const client of clients)
|
|
114
|
+
client.write('event: reload\ndata: now\n\n');
|
|
115
|
+
});
|
|
116
|
+
let stopping = false;
|
|
117
|
+
const stop = async () => {
|
|
118
|
+
if (stopping)
|
|
119
|
+
return;
|
|
120
|
+
stopping = true;
|
|
121
|
+
await watcher.close();
|
|
122
|
+
for (const client of clients)
|
|
123
|
+
client.end();
|
|
124
|
+
await new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
125
|
+
};
|
|
126
|
+
process.once('SIGINT', () => void stop());
|
|
127
|
+
process.once('SIGTERM', () => void stop());
|
|
128
|
+
}
|
|
129
|
+
function requestedPage(pathname, site, pages) {
|
|
130
|
+
if (pathname === '/' || pathname === '') {
|
|
131
|
+
return firstSitePage(site) ?? pages.keys().next().value;
|
|
132
|
+
}
|
|
133
|
+
return decodeURIComponent(pathname.replace(/^\/+|\/+$/g, '')).replace(/\.(md|mdx)$/i, '');
|
|
134
|
+
}
|
|
135
|
+
export function doxbrixDocument(input) {
|
|
136
|
+
const visibleSpaces = input.site.spaces.filter((space) => hasVisibleNavigation(space.nav));
|
|
137
|
+
const showTabs = visibleSpaces.length > 1;
|
|
138
|
+
const activeSpace = visibleSpaces.find((space) => containsPage(space.nav, input.current)) ??
|
|
139
|
+
visibleSpaces[0];
|
|
140
|
+
const primary = themeColor(input.site, 'primaryColor', '#6366f1');
|
|
141
|
+
const primaryLight = themeColor(input.site, 'lightColor', primary);
|
|
142
|
+
const primaryDark = themeColor(input.site, 'darkColor', primary);
|
|
143
|
+
const layout = themeLayout(input.site);
|
|
144
|
+
const configuredMode = typeof input.site.theme === 'string'
|
|
145
|
+
? input.site.theme
|
|
146
|
+
: themeString(input.site, 'mode', 'system');
|
|
147
|
+
const mode = ['light', 'dark', 'system'].includes(configuredMode)
|
|
148
|
+
? configuredMode
|
|
149
|
+
: 'system';
|
|
150
|
+
const resolvedMode = mode === 'dark' ? 'dark' : 'light';
|
|
151
|
+
const font = themeString(input.site, 'font', 'Inter');
|
|
152
|
+
const headingFont = themeString(input.site, 'headingFont', font);
|
|
153
|
+
const codeFont = themeString(input.site, 'codeFont', 'ui-monospace');
|
|
154
|
+
const backgroundLight = themeColor(input.site, 'backgroundColorLight', '#ffffff');
|
|
155
|
+
const backgroundDark = themeColor(input.site, 'backgroundColorDark', '#12131a');
|
|
156
|
+
const backgroundImage = themeString(input.site, 'backgroundImage', '');
|
|
157
|
+
const logoLight = themeString(input.site, 'logoLight', '');
|
|
158
|
+
const logoDark = themeString(input.site, 'logoDark', '');
|
|
159
|
+
const codeTheme = resolveCodeTheme(themeString(input.site, 'codeTheme', 'auto'));
|
|
160
|
+
const favicon = themeString(input.site, 'faviconLight', '') ||
|
|
161
|
+
themeString(input.site, 'favicon', '') ||
|
|
162
|
+
themeString(input.site, 'faviconDark', '');
|
|
163
|
+
const siteName = input.site.name?.trim() || 'Documentation';
|
|
164
|
+
const tabs = visibleSpaces
|
|
165
|
+
.map((space) => {
|
|
166
|
+
const first = firstPage(space.nav);
|
|
167
|
+
const active = space === activeSpace ? ' active' : '';
|
|
168
|
+
const tabIcon = typeof space.icon === 'string' && space.icon.trim()
|
|
169
|
+
? space.icon
|
|
170
|
+
: spaceIcon(space.name);
|
|
171
|
+
const tabTag = typeof space.tag === 'string' && space.tag
|
|
172
|
+
? `<span class="dxb-atlas-tab-tag">${escapeHtml(space.tag)}</span>`
|
|
173
|
+
: '';
|
|
174
|
+
const label = `<span class="dxb-atlas-tab-icon">${brandIcon(tabIcon, 16)}</span><span>${escapeHtml(space.name)}</span>${tabTag}`;
|
|
175
|
+
return first
|
|
176
|
+
? `<a class="dxb-atlas-tab${active}" href="/${escapeAttr(first)}">${label}</a>`
|
|
177
|
+
: `<span class="dxb-atlas-tab${active}">${label}</span>`;
|
|
178
|
+
})
|
|
179
|
+
.join('');
|
|
180
|
+
const first = firstSitePage(input.site) ?? '';
|
|
181
|
+
const logoHref = safeHref(themeString(input.site, 'logoHref', `/${first}`), `/${first}`);
|
|
182
|
+
const leftnav = activeSpace
|
|
183
|
+
? `<aside class="dp-leftnav"><div class="dp-nav-tree">${navTree(activeSpace.nav, input.current)}</div></aside>`
|
|
184
|
+
: '';
|
|
185
|
+
const toc = tocHtml(input.rendered.toc);
|
|
186
|
+
const groupLabel = activeSpace
|
|
187
|
+
? groupContainingPage(activeSpace.nav, input.current)
|
|
188
|
+
: undefined;
|
|
189
|
+
const fontFaces = fontSourceCss(input.site);
|
|
190
|
+
const fontStylesheet = googleFontStylesheet([font, headingFont]);
|
|
191
|
+
const faviconLink = favicon
|
|
192
|
+
? `<link rel="icon" href="${escapeAttr(favicon)}">`
|
|
193
|
+
: '';
|
|
194
|
+
const logo = logoLight || logoDark
|
|
195
|
+
? `<span class="dp-topnav-logo-img-wrap">${logoLight ? `<img class="dp-topnav-logo-img dp-topnav-logo-img--light${logoDark ? ' has-dark' : ''}" src="${escapeAttr(logoLight)}" alt="${escapeAttr(siteName)}">` : ''}${logoDark ? `<img class="dp-topnav-logo-img dp-topnav-logo-img--dark${logoLight ? ' has-light' : ''}" src="${escapeAttr(logoDark)}" alt="${escapeAttr(siteName)}">` : ''}</span>`
|
|
196
|
+
: `<span class="dp-topnav-logo-mark" aria-hidden="true">${icon('book', 16)}</span><span class="dp-topnav-logo-text">${escapeHtml(siteName)}</span>`;
|
|
197
|
+
const description = input.description
|
|
198
|
+
? `<p class="dxb-atlas-description">${escapeHtml(input.description)}</p>`
|
|
199
|
+
: '';
|
|
200
|
+
const eyebrow = groupLabel
|
|
201
|
+
? `<div class="dxb-atlas-eyebrow">${escapeHtml(groupLabel)}</div>`
|
|
202
|
+
: '';
|
|
203
|
+
return `<!doctype html>
|
|
204
|
+
<html lang="en">
|
|
205
|
+
<head>
|
|
206
|
+
<meta charset="utf-8">
|
|
207
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
208
|
+
<title>${escapeHtml(input.title)} · ${escapeHtml(siteName)}</title>
|
|
209
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
210
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
211
|
+
${fontStylesheet}
|
|
212
|
+
${faviconLink}
|
|
213
|
+
<link rel="stylesheet" href="/__doxloop/doxbrix.css">
|
|
214
|
+
<style>
|
|
215
|
+
${fontFaces}
|
|
216
|
+
body{margin:0}.dp-tab-panel{display:none}.dp-tab-panel.active{display:block}
|
|
217
|
+
.dp-nav-chevron{transition:transform .15s}.dp-nav-chevron:not(.open){transform:rotate(-90deg)}
|
|
218
|
+
.dp-root--published{--project-primary:${escapeAttr(primary)};--project-primary-hover:${escapeAttr(primary)};
|
|
219
|
+
--project-primary-rgb:${hexToRgb(primary)};--primary:${escapeAttr(primary)};--primary-rgb:${hexToRgb(primary)};
|
|
220
|
+
--dxb-primary-light:${escapeAttr(primaryLight)};
|
|
221
|
+
--dxb-primary-dark:${escapeAttr(primaryDark)};--project-font-family:${cssFontFamily(font)},system-ui,sans-serif;
|
|
222
|
+
--project-heading-font-family:${cssFontFamily(headingFont)},system-ui,sans-serif;
|
|
223
|
+
--project-code-font-family:${cssFontFamily(codeFont)},SFMono-Regular,Menlo,Monaco,Consolas,monospace;
|
|
224
|
+
--dxb-background-light:${escapeAttr(backgroundLight)};--dxb-background-dark:${escapeAttr(backgroundDark)};
|
|
225
|
+
--dxb-background-image:${backgroundImage ? `url('${cssString(backgroundImage)}')` : 'none'};
|
|
226
|
+
--dxb-banner-height:0px;--dxb-header-height:${layout.headerHeight}px;--dxb-tabs-height:${showTabs ? layout.tabsHeight : 0}px;
|
|
227
|
+
--dxb-shell-max-width:${layout.shellMaxWidth}px;--dxb-sidebar-width:${layout.sidebarWidth}px;
|
|
228
|
+
--dxb-toc-width:${layout.tocWidth}px;--dxb-content-max-width:${layout.contentMaxWidth}px;
|
|
229
|
+
--dxb-content-padding:${layout.contentPadding}px;--dxb-nav-padding:${layout.navPadding}px}
|
|
230
|
+
</style>
|
|
231
|
+
</head>
|
|
232
|
+
<body>
|
|
233
|
+
<div class="dp-root dp-root--published" data-color-theme="${resolvedMode}" data-project-color-theme="${escapeAttr(mode)}" data-code-theme="${codeTheme}" data-shell-theme="atlas">
|
|
234
|
+
<header class="dxb-atlas-header">
|
|
235
|
+
<div class="dxb-atlas-header-main"><div class="dxb-atlas-header-inner">
|
|
236
|
+
<a class="dp-topnav-logo" href="${escapeAttr(logoHref)}">${logo}</a>
|
|
237
|
+
<div class="dxb-atlas-search-cluster">
|
|
238
|
+
<button class="dp-topnav-search" type="button" aria-disabled="true">${icon('search', 17)}<span>Search...</span><kbd>⌘K</kbd></button>
|
|
239
|
+
<button class="dxb-atlas-assistant" type="button" aria-disabled="true">${icon('sparkle', 16)}<span>Ask Assistant</span></button>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="dxb-atlas-header-actions">
|
|
242
|
+
<button class="dp-theme-toggle" type="button" aria-label="Switch color theme" aria-pressed="${resolvedMode === 'dark'}" data-theme-toggle>${icon(resolvedMode === 'dark' ? 'sun' : 'moon', 17)}</button>
|
|
243
|
+
</div>
|
|
244
|
+
</div></div>
|
|
245
|
+
${showTabs ? `<div class="dxb-atlas-tabs-bar"><nav class="dxb-atlas-tabs" aria-label="Documentation spaces">${tabs}</nav></div>` : ''}
|
|
246
|
+
</header>
|
|
247
|
+
<div class="dp-body">
|
|
248
|
+
${leftnav}
|
|
249
|
+
<main class="dp-main"><div class="dp-content-wrap">
|
|
250
|
+
<div class="dxb-atlas-title-row">
|
|
251
|
+
<div class="dxb-atlas-title-copy">${eyebrow}<h1 class="dp-page-title">${escapeHtml(input.title)}</h1>${description}</div>
|
|
252
|
+
<button class="dxb-atlas-copy-page" type="button" data-copy-page>${icon('copy', 16)}<span>Copy page</span>${icon('chevron-down', 14)}</button>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="dp-blocks">${input.rendered.html}</div>
|
|
255
|
+
</div></main>
|
|
256
|
+
${toc}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
<script>
|
|
260
|
+
const root = document.querySelector('.dp-root--published');
|
|
261
|
+
let colorTheme = root?.dataset.projectColorTheme || 'system';
|
|
262
|
+
try {
|
|
263
|
+
const stored = localStorage.getItem('docflow:docs:color-theme:v1');
|
|
264
|
+
if (stored === 'light' || stored === 'dark') colorTheme = stored;
|
|
265
|
+
} catch {}
|
|
266
|
+
if (colorTheme === 'system') {
|
|
267
|
+
colorTheme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
268
|
+
}
|
|
269
|
+
if (root) root.dataset.colorTheme = colorTheme;
|
|
270
|
+
const themeToggle = document.querySelector('[data-theme-toggle]');
|
|
271
|
+
function updateThemeToggle() {
|
|
272
|
+
if (!themeToggle || !root) return;
|
|
273
|
+
const dark = root.dataset.colorTheme === 'dark';
|
|
274
|
+
themeToggle.setAttribute('aria-label', 'Switch to ' + (dark ? 'light' : 'dark') + ' theme');
|
|
275
|
+
themeToggle.setAttribute('aria-pressed', String(dark));
|
|
276
|
+
themeToggle.innerHTML = dark ? '${icon('sun', 17)}' : '${icon('moon', 17)}';
|
|
277
|
+
}
|
|
278
|
+
updateThemeToggle();
|
|
279
|
+
const events = new EventSource('/__doxloop/events');
|
|
280
|
+
events.addEventListener('reload', () => location.reload());
|
|
281
|
+
document.addEventListener('click', (event) => {
|
|
282
|
+
const toggle = event.target.closest('[data-theme-toggle]');
|
|
283
|
+
if (toggle && root) {
|
|
284
|
+
root.dataset.colorTheme = root.dataset.colorTheme === 'dark' ? 'light' : 'dark';
|
|
285
|
+
try { localStorage.setItem('docflow:docs:color-theme:v1', root.dataset.colorTheme); } catch {}
|
|
286
|
+
updateThemeToggle();
|
|
287
|
+
}
|
|
288
|
+
const copyPage = event.target.closest('[data-copy-page]');
|
|
289
|
+
if (copyPage && navigator.clipboard) {
|
|
290
|
+
navigator.clipboard.writeText(location.href);
|
|
291
|
+
}
|
|
292
|
+
const tab = event.target.closest('.dp-code-group-tab');
|
|
293
|
+
if (tab) {
|
|
294
|
+
const box = tab.closest('.dp-tabs,.dp-code-group');
|
|
295
|
+
const id = tab.getAttribute('data-tab');
|
|
296
|
+
box.querySelectorAll('.dp-code-group-tab').forEach((item) => item.classList.toggle('active', item === tab));
|
|
297
|
+
box.querySelectorAll('.dp-tab-panel').forEach((panel) => panel.classList.toggle('active', panel.id === id));
|
|
298
|
+
}
|
|
299
|
+
const copy = event.target.closest('.dp-code-copy-btn');
|
|
300
|
+
if (copy) {
|
|
301
|
+
const code = copy.closest('.dp-code-block')?.querySelector('pre');
|
|
302
|
+
if (code && navigator.clipboard) navigator.clipboard.writeText(code.innerText);
|
|
303
|
+
}
|
|
304
|
+
const apiCopy = event.target.closest('.dp-api-code-copy[type="button"],.dp-api-light-copy');
|
|
305
|
+
if (apiCopy) {
|
|
306
|
+
const card = apiCopy.closest('.dp-api-code-card,.dp-api-resp-card');
|
|
307
|
+
const code = card?.querySelector('pre:not([hidden])');
|
|
308
|
+
if (code && navigator.clipboard) navigator.clipboard.writeText(code.innerText);
|
|
309
|
+
}
|
|
310
|
+
const responseTab = event.target.closest('[data-api-response-tab]');
|
|
311
|
+
if (responseTab) {
|
|
312
|
+
const card = responseTab.closest('.dp-api-resp-card');
|
|
313
|
+
const target = responseTab.getAttribute('data-api-response-tab');
|
|
314
|
+
card?.querySelectorAll('[data-api-response-tab]').forEach((item) => item.classList.toggle('active', item === responseTab));
|
|
315
|
+
card?.querySelectorAll('[data-api-response-panel]').forEach((panel) => {
|
|
316
|
+
panel.hidden = panel.id !== target;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const apiSection = event.target.closest('.dp-api-section-btn:not(.dp-api-section-btn--static)');
|
|
320
|
+
if (apiSection) {
|
|
321
|
+
const body = apiSection.nextElementSibling;
|
|
322
|
+
if (body) {
|
|
323
|
+
const open = apiSection.getAttribute('aria-expanded') !== 'false';
|
|
324
|
+
apiSection.setAttribute('aria-expanded', String(!open));
|
|
325
|
+
body.hidden = open;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const group = event.target.closest('[data-nav-toggle]');
|
|
329
|
+
if (group) {
|
|
330
|
+
const children = group.parentElement?.querySelector(':scope > .dp-nav-children');
|
|
331
|
+
if (children) {
|
|
332
|
+
const open = children.style.display !== 'none';
|
|
333
|
+
children.style.display = open ? 'none' : '';
|
|
334
|
+
group.setAttribute('aria-expanded', String(!open));
|
|
335
|
+
group.querySelector('.dp-nav-chevron')?.classList.toggle('open', !open);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
</script>
|
|
340
|
+
</body>
|
|
341
|
+
</html>`;
|
|
342
|
+
}
|
|
343
|
+
function navTree(nodes, current, depth = 0) {
|
|
344
|
+
return nodes
|
|
345
|
+
.map((node) => {
|
|
346
|
+
if ('hidden' in node && node.hidden)
|
|
347
|
+
return '';
|
|
348
|
+
if (node.type === 'label') {
|
|
349
|
+
return `<div class="dp-nav-section-title"><span class="dp-nav-item-label">${escapeHtml(node.text)}</span></div>`;
|
|
350
|
+
}
|
|
351
|
+
if (node.type === 'divider')
|
|
352
|
+
return '<div class="dp-nav-divider"></div>';
|
|
353
|
+
if (node.type === 'link') {
|
|
354
|
+
return `<a class="dp-nav-item${depthClass(depth)}" href="${escapeAttr(safeNavigationHref(node.href))}">${navIcon(node.icon)}<span class="dp-nav-item-label">${escapeHtml(node.title)}</span></a>`;
|
|
355
|
+
}
|
|
356
|
+
if (node.type === 'api') {
|
|
357
|
+
return `<div class="dp-nav-item${depthClass(depth)}">${navIcon(node.icon)}<span class="dp-nav-item-label">${escapeHtml(node.title)}</span></div>`;
|
|
358
|
+
}
|
|
359
|
+
if (node.type === 'group') {
|
|
360
|
+
const children = navTree(node.items, current, depth + 1);
|
|
361
|
+
const activeBranch = containsPage(node.items, current) ? ' active-branch' : '';
|
|
362
|
+
const groupIcon = navIcon(node.icon);
|
|
363
|
+
if (depth === 0) {
|
|
364
|
+
return `<div class="dp-nav-root-block${activeBranch}"><div class="dp-nav-section-title">${groupIcon}<span class="dp-nav-item-label">${escapeHtml(node.label)}</span></div><div class="dp-nav-children dp-nav-children--root">${children}</div></div>`;
|
|
365
|
+
}
|
|
366
|
+
return `<div class="dp-nav-branch${activeBranch}"><button class="dp-nav-item dp-nav-group${activeBranch}${depthClass(depth)}" data-nav-toggle aria-expanded="true">${groupIcon}<span class="dp-nav-item-label">${escapeHtml(node.label)}</span>${chevron(true)}</button><div class="dp-nav-children">${children}</div></div>`;
|
|
367
|
+
}
|
|
368
|
+
const active = normalizePage(node.file) === current ? ' active' : '';
|
|
369
|
+
return `<a class="dp-nav-item${active}${depthClass(depth)}" href="/${escapeAttr(normalizePage(node.file))}">${navIcon(node.icon)}<span class="dp-nav-item-label">${escapeHtml(node.title || labelFromId(node.file))}</span></a>`;
|
|
370
|
+
})
|
|
371
|
+
.join('');
|
|
372
|
+
}
|
|
373
|
+
function tocHtml(entries) {
|
|
374
|
+
if (entries.length === 0)
|
|
375
|
+
return '<aside class="dp-toc"></aside>';
|
|
376
|
+
return `<aside class="dp-toc"><div class="dp-toc-header">${icon('list', 16)} On this page</div>${entries
|
|
377
|
+
.map((entry, index) => `<a class="dp-toc-entry level-${entry.level}${index === 0 ? ' active' : ''}" href="#${escapeAttr(entry.id)}">${escapeHtml(entry.title)}</a>`)
|
|
378
|
+
.join('')}</aside>`;
|
|
379
|
+
}
|
|
380
|
+
function firstPage(nodes) {
|
|
381
|
+
for (const node of nodes) {
|
|
382
|
+
if ('hidden' in node && node.hidden)
|
|
383
|
+
continue;
|
|
384
|
+
if (node.type === 'page')
|
|
385
|
+
return normalizePage(node.file);
|
|
386
|
+
if (node.type === 'group') {
|
|
387
|
+
const found = firstPage(node.items);
|
|
388
|
+
if (found)
|
|
389
|
+
return found;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
function firstSitePage(site) {
|
|
395
|
+
for (const space of site.spaces) {
|
|
396
|
+
const first = firstPage(space.nav);
|
|
397
|
+
if (first)
|
|
398
|
+
return first;
|
|
399
|
+
}
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
function containsPage(nodes, current) {
|
|
403
|
+
return nodes.some((node) => (node.type === 'page' && normalizePage(node.file) === current) ||
|
|
404
|
+
(node.type === 'group' && containsPage(node.items, current)));
|
|
405
|
+
}
|
|
406
|
+
function hasVisibleNavigation(nodes) {
|
|
407
|
+
return nodes.some((node) => {
|
|
408
|
+
if ('hidden' in node && node.hidden)
|
|
409
|
+
return false;
|
|
410
|
+
if (node.type === 'group')
|
|
411
|
+
return hasVisibleNavigation(node.items);
|
|
412
|
+
return true;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function groupContainingPage(nodes, current, parent) {
|
|
416
|
+
for (const node of nodes) {
|
|
417
|
+
if (node.type === 'page' && normalizePage(node.file) === current)
|
|
418
|
+
return parent;
|
|
419
|
+
if (node.type === 'group') {
|
|
420
|
+
const found = groupContainingPage(node.items, current, node.label);
|
|
421
|
+
if (found !== undefined)
|
|
422
|
+
return found;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
function normalizePage(value) {
|
|
428
|
+
return value.replace(/^\/+/, '').replace(/\.(md|mdx)$/i, '');
|
|
429
|
+
}
|
|
430
|
+
function themeString(site, key, fallback) {
|
|
431
|
+
if (!site.theme || typeof site.theme !== 'object')
|
|
432
|
+
return fallback;
|
|
433
|
+
const value = site.theme[key];
|
|
434
|
+
return typeof value === 'string' && value.trim() ? value : fallback;
|
|
435
|
+
}
|
|
436
|
+
function themeColor(site, key, fallback) {
|
|
437
|
+
const value = themeString(site, key, fallback);
|
|
438
|
+
return /^#[0-9a-f]{6}$/i.test(value) ? value : fallback;
|
|
439
|
+
}
|
|
440
|
+
function themeLayout(site) {
|
|
441
|
+
const theme = site.theme && typeof site.theme === 'object'
|
|
442
|
+
? site.theme
|
|
443
|
+
: undefined;
|
|
444
|
+
const raw = theme?.layout && typeof theme.layout === 'object' && !Array.isArray(theme.layout)
|
|
445
|
+
? theme.layout
|
|
446
|
+
: {};
|
|
447
|
+
return {
|
|
448
|
+
headerHeight: boundedNumber(raw.headerHeight, 48, 96, ATLAS_LAYOUT.headerHeight),
|
|
449
|
+
tabsHeight: boundedNumber(raw.tabsHeight, 36, 72, ATLAS_LAYOUT.tabsHeight),
|
|
450
|
+
shellMaxWidth: boundedNumber(raw.shellMaxWidth, 960, 1800, ATLAS_LAYOUT.shellMaxWidth),
|
|
451
|
+
sidebarWidth: boundedNumber(raw.sidebarWidth, 240, 380, ATLAS_LAYOUT.sidebarWidth),
|
|
452
|
+
tocWidth: boundedNumber(raw.tocWidth, 220, 360, ATLAS_LAYOUT.tocWidth),
|
|
453
|
+
contentMaxWidth: boundedNumber(raw.contentMaxWidth, 640, 1200, ATLAS_LAYOUT.contentMaxWidth),
|
|
454
|
+
contentPadding: boundedNumber(raw.contentPadding, 0, 80, ATLAS_LAYOUT.contentPadding),
|
|
455
|
+
navPadding: boundedNumber(raw.navPadding, 8, 48, ATLAS_LAYOUT.navPadding),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function boundedNumber(value, minimum, maximum, fallback) {
|
|
459
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
460
|
+
return fallback;
|
|
461
|
+
return Math.round(Math.max(minimum, Math.min(maximum, value)));
|
|
462
|
+
}
|
|
463
|
+
function resolveCodeTheme(value) {
|
|
464
|
+
const normalized = value.toLowerCase();
|
|
465
|
+
if (!normalized || normalized === 'system' || normalized === 'auto')
|
|
466
|
+
return 'auto';
|
|
467
|
+
if (normalized.includes('light') || normalized === 'day')
|
|
468
|
+
return 'light';
|
|
469
|
+
return 'dark';
|
|
470
|
+
}
|
|
471
|
+
const ICON_PATHS = {
|
|
472
|
+
book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>',
|
|
473
|
+
'book-open': '<path d="M2 4.5A2.5 2.5 0 0 1 4.5 2H11a1 1 0 0 1 1 1v18a1 1 0 0 0-1-1H4.5A2.5 2.5 0 0 0 2 22z"/><path d="M22 4.5A2.5 2.5 0 0 0 19.5 2H13a1 1 0 0 0-1 1v18a1 1 0 0 1 1-1h6.5A2.5 2.5 0 0 1 22 22z"/>',
|
|
474
|
+
file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>',
|
|
475
|
+
'file-text': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/>',
|
|
476
|
+
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>',
|
|
477
|
+
sparkle: '<path d="M12 3l1.9 5.8L20 11l-6.1 2.2L12 19l-1.9-5.8L4 11l6.1-2.2z"/>',
|
|
478
|
+
home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
|
|
479
|
+
house: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
|
|
480
|
+
rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.87 12.87 0 0 1 22 2c0 2.72-.78 7.5-6.05 11a22.35 22.35 0 0 1-3.95 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
|
|
481
|
+
bolt: '<path d="m13 2-9 12h8l-1 8 9-12h-8z"/>',
|
|
482
|
+
zap: '<path d="m13 2-9 12h8l-1 8 9-12h-8z"/>',
|
|
483
|
+
terminal: '<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>',
|
|
484
|
+
code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
|
|
485
|
+
braces: '<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h1"/><path d="M16 21h1a2 2 0 0 0 2-2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/>',
|
|
486
|
+
plug: '<path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v3a6 6 0 0 1-12 0V8z"/>',
|
|
487
|
+
settings: '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.72l.15-.1a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
|
|
488
|
+
wrench: '<path d="M14.7 6.3a4 4 0 0 0-5-5L7 4l3 3-2.7 2.7a4 4 0 0 0 5 5L21 6l-3-3z"/><path d="m5 16-3 3 3 3 3-3"/>',
|
|
489
|
+
'shield-check': '<path d="M20 13c0 5-3.5 7.5-8 9-4.5-1.5-8-4-8-9V5l8-3 8 3z"/><path d="m9 12 2 2 4-4"/>',
|
|
490
|
+
key: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15 7 2 2"/><path d="m18 4 2 2"/>',
|
|
491
|
+
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>',
|
|
492
|
+
server: '<rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>',
|
|
493
|
+
cloud: '<path d="M17.5 19H9a7 7 0 1 1 6.7-9h1.8a4.5 4.5 0 1 1 0 9Z"/>',
|
|
494
|
+
package: '<path d="m16.5 9.4-9-5.2"/><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><polyline points="3.3 7 12 12 20.7 7"/><line x1="12" y1="22" x2="12" y2="12"/>',
|
|
495
|
+
workflow: '<rect x="3" y="3" width="6" height="6" rx="1"/><rect x="15" y="15" width="6" height="6" rx="1"/><path d="M9 6h4a4 4 0 0 1 4 4v5"/><path d="m14 12 3 3 3-3"/>',
|
|
496
|
+
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
497
|
+
compass: '<circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>',
|
|
498
|
+
globe: '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 0 20"/><path d="M12 2a15.3 15.3 0 0 0 0 20"/>',
|
|
499
|
+
list: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
|
|
500
|
+
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
|
|
501
|
+
github: '<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3.3-.4 6.8-1.6 6.8-7A5.4 5.4 0 0 0 19.4 4 5 5 0 0 0 19.3.5S18.2.1 15 1.8a13.4 13.4 0 0 0-7 0C4.8.1 3.7.5 3.7.5A5 5 0 0 0 3.6 4a5.4 5.4 0 0 0-1.4 3.7c0 5.4 3.5 6.6 6.8 7A4.8 4.8 0 0 0 8 18v4"/><path d="M8 19c-3 .9-3-1.5-4-2"/>',
|
|
502
|
+
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
503
|
+
history: '<path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l3 2"/>',
|
|
504
|
+
moon: '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>',
|
|
505
|
+
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.66 6.34l1.41-1.41"/>',
|
|
506
|
+
copy: '<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
|
|
507
|
+
chevron: '<polyline points="6 9 12 15 18 9"/>',
|
|
508
|
+
'chevron-down': '<polyline points="6 9 12 15 18 9"/>',
|
|
509
|
+
};
|
|
510
|
+
function icon(name, size = 16) {
|
|
511
|
+
const paths = ICON_PATHS[name] ?? ICON_PATHS.file;
|
|
512
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
|
|
513
|
+
}
|
|
514
|
+
function brandIcon(value, size = 16) {
|
|
515
|
+
const trimmed = value.trim();
|
|
516
|
+
if (!trimmed)
|
|
517
|
+
return icon('file', size);
|
|
518
|
+
const image = iconImageSource(trimmed);
|
|
519
|
+
if (image) {
|
|
520
|
+
return `<img src="${escapeAttr(image)}" alt="" width="${size}" height="${size}">`;
|
|
521
|
+
}
|
|
522
|
+
if (!/^[a-z0-9][a-z0-9 _-]*$/i.test(trimmed))
|
|
523
|
+
return escapeHtml(trimmed);
|
|
524
|
+
const normalized = trimmed.toLowerCase().replace(/[_\s]+/g, '-');
|
|
525
|
+
if (normalized === 'js' || normalized.includes('javascript') || normalized.includes('typescript')) {
|
|
526
|
+
return `<span class="dxb-atlas-letter-icon" style="width:${size}px;height:${size}px">JS</span>`;
|
|
527
|
+
}
|
|
528
|
+
if (normalized === 'py' || normalized.includes('python')) {
|
|
529
|
+
return `<span class="dxb-atlas-letter-icon dxb-atlas-letter-icon--python" style="width:${size}px;height:${size}px">Py</span>`;
|
|
530
|
+
}
|
|
531
|
+
const aliases = {
|
|
532
|
+
api: 'braces',
|
|
533
|
+
changelog: 'history',
|
|
534
|
+
cli: 'terminal',
|
|
535
|
+
documentation: 'book-open',
|
|
536
|
+
'get-started': 'rocket',
|
|
537
|
+
overview: 'house',
|
|
538
|
+
};
|
|
539
|
+
return icon(aliases[normalized] ?? normalized, size);
|
|
540
|
+
}
|
|
541
|
+
function navIcon(value) {
|
|
542
|
+
if (typeof value !== 'string' || !value.trim())
|
|
543
|
+
return '';
|
|
544
|
+
return `<span class="dp-nav-item-icon dxb-atlas-nav-icon" aria-hidden="true">${brandIcon(value, 15)}</span>`;
|
|
545
|
+
}
|
|
546
|
+
function iconImageSource(value) {
|
|
547
|
+
if (/^data:image\/(?:avif|gif|jpeg|png|svg\+xml|webp)[;,]/i.test(value))
|
|
548
|
+
return value;
|
|
549
|
+
if (!/\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#].*)?$/i.test(value))
|
|
550
|
+
return undefined;
|
|
551
|
+
if (value.startsWith('/') || /^https:\/\//i.test(value))
|
|
552
|
+
return value;
|
|
553
|
+
if (!/^[a-z0-9_./-]+(?:[?#][a-z0-9_=&.-]*)?$/i.test(value))
|
|
554
|
+
return undefined;
|
|
555
|
+
return `/${value.replace(/^\.?\//, '')}`;
|
|
556
|
+
}
|
|
557
|
+
function spaceIcon(name) {
|
|
558
|
+
const normalized = name.toLowerCase();
|
|
559
|
+
if (normalized.includes('python'))
|
|
560
|
+
return 'python';
|
|
561
|
+
if (normalized.includes('typescript') || normalized.includes('javascript'))
|
|
562
|
+
return 'js';
|
|
563
|
+
if (normalized === 'cli' || normalized.includes('command'))
|
|
564
|
+
return 'terminal';
|
|
565
|
+
return 'rocket';
|
|
566
|
+
}
|
|
567
|
+
function chevron(open) {
|
|
568
|
+
return `<svg class="dp-nav-chevron${open ? ' open' : ''}" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICON_PATHS.chevron}</svg>`;
|
|
569
|
+
}
|
|
570
|
+
function fontSourceCss(site) {
|
|
571
|
+
const sources = site.theme && typeof site.theme === 'object'
|
|
572
|
+
? site.theme.fontSources
|
|
573
|
+
: undefined;
|
|
574
|
+
if (!Array.isArray(sources))
|
|
575
|
+
return '';
|
|
576
|
+
return sources
|
|
577
|
+
.flatMap((entry) => {
|
|
578
|
+
if (!entry || typeof entry !== 'object')
|
|
579
|
+
return [];
|
|
580
|
+
const source = entry;
|
|
581
|
+
const family = typeof source.family === 'string' ? source.family.trim() : '';
|
|
582
|
+
const path = typeof source.source === 'string' ? source.source.trim() : '';
|
|
583
|
+
if (!family || !path)
|
|
584
|
+
return [];
|
|
585
|
+
const format = typeof source.format === 'string' && /^[a-z0-9-]+$/i.test(source.format)
|
|
586
|
+
? source.format
|
|
587
|
+
: 'woff2';
|
|
588
|
+
const weight = typeof source.weight === 'number' && Number.isFinite(source.weight)
|
|
589
|
+
? Math.max(100, Math.min(900, Math.round(source.weight)))
|
|
590
|
+
: 400;
|
|
591
|
+
const style = source.style === 'italic' ? 'italic' : 'normal';
|
|
592
|
+
return [
|
|
593
|
+
`@font-face{font-family:${cssFontFamily(family)};src:url('${cssString(path)}') format('${format}');font-weight:${weight};font-style:${style};font-display:swap}`,
|
|
594
|
+
];
|
|
595
|
+
})
|
|
596
|
+
.join('');
|
|
597
|
+
}
|
|
598
|
+
function googleFontStylesheet(families) {
|
|
599
|
+
const unique = [...new Set(families.map((family) => family.trim()).filter(Boolean))];
|
|
600
|
+
if (unique.length === 0)
|
|
601
|
+
return '';
|
|
602
|
+
const query = unique
|
|
603
|
+
.slice(0, 3)
|
|
604
|
+
.map((family) => `family=${encodeURIComponent(family)}:wght@400;500;600;700;800`)
|
|
605
|
+
.join('&');
|
|
606
|
+
return `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?${query}&display=swap">`;
|
|
607
|
+
}
|
|
608
|
+
function cssFontFamily(value) {
|
|
609
|
+
return `'${cssString(value.replaceAll(',', ' '))}'`;
|
|
610
|
+
}
|
|
611
|
+
function cssString(value) {
|
|
612
|
+
return value
|
|
613
|
+
.replaceAll('\\', '\\\\')
|
|
614
|
+
.replaceAll("'", "\\'")
|
|
615
|
+
.replaceAll('<', '\\3C ')
|
|
616
|
+
.replaceAll('>', '\\3E ')
|
|
617
|
+
.replaceAll('\n', '')
|
|
618
|
+
.replaceAll('\r', '');
|
|
619
|
+
}
|
|
620
|
+
function safeHref(value, fallback) {
|
|
621
|
+
return value.startsWith('/') || /^https:\/\//i.test(value) ? value : fallback;
|
|
622
|
+
}
|
|
623
|
+
function safeNavigationHref(value) {
|
|
624
|
+
const trimmed = value.trim();
|
|
625
|
+
if ((trimmed.startsWith('/') && !trimmed.startsWith('//')) ||
|
|
626
|
+
trimmed.startsWith('#') ||
|
|
627
|
+
/^(?:https?|mailto|tel):/i.test(trimmed)) {
|
|
628
|
+
return trimmed;
|
|
629
|
+
}
|
|
630
|
+
return '#';
|
|
631
|
+
}
|
|
632
|
+
function depthClass(depth) {
|
|
633
|
+
return depth === 0 ? '' : ` dp-nav-d${Math.min(depth, 3)}`;
|
|
634
|
+
}
|
|
635
|
+
function labelFromId(id) {
|
|
636
|
+
const last = normalizePage(id).split('/').at(-1) ?? id;
|
|
637
|
+
return last
|
|
638
|
+
.split(/[-_]+/)
|
|
639
|
+
.map((word) => `${word[0]?.toUpperCase() ?? ''}${word.slice(1)}`)
|
|
640
|
+
.join(' ');
|
|
641
|
+
}
|
|
642
|
+
function safeStaticPath(contentRoot, pathname) {
|
|
643
|
+
let decoded;
|
|
644
|
+
try {
|
|
645
|
+
decoded = decodeURIComponent(pathname);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
const path = resolve(contentRoot, decoded.replace(/^\/+/, ''));
|
|
651
|
+
const rel = relative(contentRoot, path);
|
|
652
|
+
return rel === '' || rel.startsWith('..') || isAbsolute(rel) ? undefined : path;
|
|
653
|
+
}
|
|
654
|
+
function errorPage(requested, site) {
|
|
655
|
+
return `<!doctype html><meta charset="utf-8"><title>Page not found</title><body style="font-family:system-ui;padding:40px"><h1>Page not found</h1><p>No Doxbrix page matches <code>/${escapeHtml(requested)}</code> in ${escapeHtml(site.name ?? 'this project')}.</p></body>`;
|
|
656
|
+
}
|
|
657
|
+
function send(response, status, type, body) {
|
|
658
|
+
response.writeHead(status, {
|
|
659
|
+
'Content-Type': type,
|
|
660
|
+
'Cache-Control': type.startsWith('text/html') ? 'no-store' : 'public, max-age=60',
|
|
661
|
+
});
|
|
662
|
+
response.end(body);
|
|
663
|
+
}
|
|
664
|
+
function shownHost(host) {
|
|
665
|
+
return host === '0.0.0.0' ? 'localhost' : host;
|
|
666
|
+
}
|
|
667
|
+
function hexToRgb(value) {
|
|
668
|
+
const match = /^#([0-9a-f]{6})$/i.exec(value);
|
|
669
|
+
if (!match)
|
|
670
|
+
return '99 102 241';
|
|
671
|
+
const hex = match[1];
|
|
672
|
+
return `${Number.parseInt(hex.slice(0, 2), 16)} ${Number.parseInt(hex.slice(2, 4), 16)} ${Number.parseInt(hex.slice(4, 6), 16)}`;
|
|
673
|
+
}
|
|
674
|
+
function escapeHtml(value) {
|
|
675
|
+
return value
|
|
676
|
+
.replaceAll('&', '&')
|
|
677
|
+
.replaceAll('<', '<')
|
|
678
|
+
.replaceAll('>', '>')
|
|
679
|
+
.replaceAll('"', '"')
|
|
680
|
+
.replaceAll("'", ''');
|
|
681
|
+
}
|
|
682
|
+
function escapeAttr(value) {
|
|
683
|
+
return escapeHtml(value);
|
|
684
|
+
}
|
|
685
|
+
function openBrowser(url) {
|
|
686
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
687
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
688
|
+
const child = spawn(command, args, { detached: true, stdio: 'ignore' });
|
|
689
|
+
child.unref();
|
|
690
|
+
}
|
|
691
|
+
//# sourceMappingURL=preview.js.map
|