@doxbrix/doxloop 0.1.5 → 0.2.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 +1145 -0
- package/README.md +422 -227
- package/assets/doxbrix-preview.css +77 -15
- package/contracts/agent-events-v1.schema.json +1 -0
- package/contracts/coverage-v1.schema.json +59 -0
- package/contracts/drift-v1.schema.json +1 -0
- package/contracts/evaluation-v1.schema.json +62 -0
- package/contracts/quality-codes-v1.json +1 -0
- package/contracts/quality-report-v1.schema.json +1 -0
- package/contracts/validation-v1.schema.json +1 -0
- package/dist/agent-failure.d.ts +36 -0
- package/dist/agent-failure.js +98 -0
- package/dist/agent-isolation.d.ts +17 -0
- package/dist/agent-isolation.js +94 -0
- package/dist/agent-log.d.ts +197 -0
- package/dist/agent-log.js +1094 -0
- package/dist/agent-process.d.ts +63 -0
- package/dist/agent-process.js +128 -0
- package/dist/agent-reply.d.ts +47 -0
- package/dist/agent-reply.js +315 -0
- package/dist/agents.d.ts +24 -5
- package/dist/agents.js +60 -9
- package/dist/api-coverage.d.ts +11 -0
- package/dist/api-coverage.js +72 -0
- package/dist/application-probe.d.ts +28 -0
- package/dist/application-probe.js +93 -0
- package/dist/application-url.d.ts +14 -0
- package/dist/application-url.js +26 -0
- package/dist/args.js +15 -0
- package/dist/artifact-deploy.d.ts +9 -0
- package/dist/artifact-deploy.js +28 -8
- package/dist/assets.d.ts +72 -0
- package/dist/assets.js +382 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +38 -9
- package/dist/author.d.ts +206 -4
- package/dist/author.js +1650 -52
- package/dist/authoring-batches.d.ts +190 -0
- package/dist/authoring-batches.js +489 -0
- package/dist/authoring-postpass.d.ts +79 -0
- package/dist/authoring-postpass.js +1289 -0
- package/dist/authoring-progress.d.ts +95 -0
- package/dist/authoring-progress.js +269 -0
- package/dist/autosync.d.ts +56 -0
- package/dist/autosync.js +442 -0
- package/dist/batch-artifacts.d.ts +81 -0
- package/dist/batch-artifacts.js +412 -0
- package/dist/batch-limits.d.ts +37 -0
- package/dist/batch-limits.js +80 -0
- package/dist/branding.d.ts +41 -0
- package/dist/branding.js +229 -0
- package/dist/bundle-upload.d.ts +26 -0
- package/dist/bundle-upload.js +99 -0
- package/dist/capture-auth.d.ts +95 -0
- package/dist/capture-auth.js +204 -0
- package/dist/capture.d.ts +48 -0
- package/dist/capture.js +20 -1
- package/dist/cli.js +839 -32
- package/dist/content-links.d.ts +10 -0
- package/dist/content-links.js +49 -0
- package/dist/contract-validation.d.ts +4 -0
- package/dist/contract-validation.js +25 -0
- package/dist/coverage-actions.d.ts +8 -0
- package/dist/coverage-actions.js +71 -0
- package/dist/coverage-resolutions.d.ts +16 -0
- package/dist/coverage-resolutions.js +58 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.js +288 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +122 -0
- package/dist/deploy-credentials.d.ts +7 -0
- package/dist/deploy-credentials.js +76 -0
- package/dist/deploy-targets/github-pages.d.ts +3 -0
- package/dist/deploy-targets/github-pages.js +100 -0
- package/dist/deploy-targets/index.d.ts +8 -0
- package/dist/deploy-targets/index.js +78 -0
- package/dist/deploy-targets/netlify.d.ts +3 -0
- package/dist/deploy-targets/netlify.js +49 -0
- package/dist/deploy-targets/types.d.ts +32 -0
- package/dist/deploy-targets/types.js +2 -0
- package/dist/deploy-targets/vercel.d.ts +3 -0
- package/dist/deploy-targets/vercel.js +95 -0
- package/dist/deploy.d.ts +9 -0
- package/dist/deploy.js +87 -13
- package/dist/deterministic-capture.d.ts +90 -0
- package/dist/deterministic-capture.js +435 -0
- package/dist/direct-edit.d.ts +40 -0
- package/dist/direct-edit.js +159 -0
- package/dist/docs-crawl.d.ts +86 -0
- package/dist/docs-crawl.js +536 -0
- package/dist/docs-site.d.ts +49 -0
- package/dist/docs-site.js +233 -0
- package/dist/doctor.js +8 -0
- package/dist/documentation-collections.d.ts +23 -0
- package/dist/documentation-collections.js +191 -0
- package/dist/documentation-plan.d.ts +253 -0
- package/dist/documentation-plan.js +2563 -0
- package/dist/doxbrix-build.d.ts +19 -0
- package/dist/doxbrix-build.js +167 -0
- package/dist/doxbrix-markdown.d.ts +9 -0
- package/dist/doxbrix-markdown.js +115 -14
- package/dist/drift.d.ts +10 -0
- package/dist/drift.js +164 -0
- package/dist/evaluation.d.ts +46 -0
- package/dist/evaluation.js +113 -0
- package/dist/evidence-pack.d.ts +47 -0
- package/dist/evidence-pack.js +358 -0
- package/dist/evidence.d.ts +25 -0
- package/dist/evidence.js +175 -0
- package/dist/fs.d.ts +8 -2
- package/dist/fs.js +41 -11
- package/dist/generator-api.d.ts +89 -0
- package/dist/generator-preflight.d.ts +27 -0
- package/dist/generator-preflight.js +105 -0
- package/dist/generator-runtime.d.ts +7 -0
- package/dist/generator-runtime.js +17 -1
- package/dist/generators.d.ts +24 -2
- package/dist/generators.js +54 -1
- package/dist/git-delivery.d.ts +17 -0
- package/dist/git-delivery.js +123 -0
- package/dist/globs.d.ts +16 -0
- package/dist/globs.js +65 -0
- package/dist/glossary.d.ts +26 -0
- package/dist/glossary.js +179 -0
- package/dist/history.d.ts +106 -0
- package/dist/history.js +600 -0
- package/dist/html-markdown.d.ts +46 -0
- package/dist/html-markdown.js +423 -0
- package/dist/interactive.js +3 -0
- package/dist/job-events.d.ts +74 -0
- package/dist/job-events.js +377 -0
- package/dist/keep-awake.d.ts +50 -0
- package/dist/keep-awake.js +123 -0
- package/dist/local-source-snapshot.d.ts +20 -0
- package/dist/local-source-snapshot.js +61 -0
- package/dist/mintlify-detect.d.ts +3 -0
- package/dist/mintlify-detect.js +18 -0
- package/dist/mintlify-import.d.ts +75 -0
- package/dist/mintlify-import.js +190 -0
- package/dist/navigation.d.ts +98 -0
- package/dist/navigation.js +310 -0
- package/dist/openapi.d.ts +60 -0
- package/dist/openapi.js +439 -0
- package/dist/page-editor-bridge.d.ts +3 -0
- package/dist/page-editor-bridge.js +109 -0
- package/dist/page-editor-preview.d.ts +10 -0
- package/dist/page-editor-preview.js +55 -0
- package/dist/page-extension.d.ts +9 -0
- package/dist/page-extension.js +15 -0
- package/dist/page-metadata.d.ts +28 -0
- package/dist/page-metadata.js +166 -0
- package/dist/page-operations.d.ts +34 -0
- package/dist/page-operations.js +215 -0
- package/dist/page-routes.d.ts +4 -0
- package/dist/page-routes.js +61 -0
- package/dist/pages.d.ts +20 -0
- package/dist/pages.js +184 -0
- package/dist/plan-generator.d.ts +3 -0
- package/dist/plan-generator.js +21 -0
- package/dist/plan-navigation.d.ts +11 -0
- package/dist/plan-navigation.js +30 -0
- package/dist/planning-captures.d.ts +20 -0
- package/dist/planning-captures.js +143 -0
- package/dist/planning-research.d.ts +135 -0
- package/dist/planning-research.js +472 -0
- package/dist/planning-triage.d.ts +23 -0
- package/dist/planning-triage.js +131 -0
- package/dist/preview.d.ts +24 -0
- package/dist/preview.js +280 -29
- package/dist/project-detect.d.ts +36 -0
- package/dist/project-detect.js +251 -0
- package/dist/project-import.d.ts +54 -0
- package/dist/project-import.js +157 -0
- package/dist/project-lock.d.ts +6 -0
- package/dist/project-lock.js +96 -0
- package/dist/project-registry.d.ts +25 -0
- package/dist/project-registry.js +79 -0
- package/dist/project.d.ts +35 -3
- package/dist/project.js +388 -38
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.js +25 -0
- package/dist/proposal-replay.d.ts +32 -0
- package/dist/proposal-replay.js +99 -0
- package/dist/quality-claims.d.ts +8 -0
- package/dist/quality-claims.js +168 -0
- package/dist/quality-config.d.ts +5 -0
- package/dist/quality-config.js +84 -0
- package/dist/quality-contract.d.ts +37 -0
- package/dist/quality-contract.js +46 -0
- package/dist/quality-examples.d.ts +4 -0
- package/dist/quality-examples.js +233 -0
- package/dist/quality-gates.d.ts +16 -0
- package/dist/quality-gates.js +192 -0
- package/dist/quality-links.d.ts +7 -0
- package/dist/quality-links.js +149 -0
- package/dist/quality-lint.d.ts +6 -0
- package/dist/quality-lint.js +124 -0
- package/dist/quality-rendered.d.ts +11 -0
- package/dist/quality-rendered.js +222 -0
- package/dist/quality-schema.d.ts +3 -0
- package/dist/quality-schema.js +71 -0
- package/dist/release-notes.d.ts +44 -0
- package/dist/release-notes.js +183 -0
- package/dist/remote-monitor.d.ts +16 -0
- package/dist/remote-monitor.js +74 -0
- package/dist/remote-source.d.ts +34 -0
- package/dist/remote-source.js +426 -0
- package/dist/review-diff.d.ts +82 -0
- package/dist/review-diff.js +400 -0
- package/dist/review-learning.d.ts +11 -0
- package/dist/review-learning.js +60 -0
- package/dist/review-render.d.ts +38 -0
- package/dist/review-render.js +224 -0
- package/dist/review-report.d.ts +9 -0
- package/dist/review-report.js +89 -0
- package/dist/review-ui.d.ts +14 -0
- package/dist/review-ui.js +1248 -0
- package/dist/schedule.d.ts +78 -0
- package/dist/schedule.js +480 -0
- package/dist/screen-capture-provider.d.ts +52 -0
- package/dist/screen-capture-provider.js +218 -0
- package/dist/screenshot-workflow.d.ts +167 -0
- package/dist/screenshot-workflow.js +1237 -0
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +95 -7
- package/dist/site-export.d.ts +18 -0
- package/dist/site-export.js +87 -0
- package/dist/source-connectors.d.ts +33 -0
- package/dist/source-connectors.js +268 -0
- package/dist/source-discovery.d.ts +132 -0
- package/dist/source-discovery.js +823 -0
- package/dist/source-intelligence.d.ts +9 -0
- package/dist/source-intelligence.js +306 -0
- package/dist/sync-review.d.ts +28 -0
- package/dist/sync-review.js +264 -0
- package/dist/sync-runs.d.ts +192 -0
- package/dist/sync-runs.js +2244 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.js +298 -32
- package/dist/text-diff.d.ts +9 -0
- package/dist/text-diff.js +59 -0
- package/dist/types.d.ts +946 -1
- package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
- package/dist/ui/assets/index-BHBYU2aG.css +1 -0
- package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui-server.d.ts +83 -0
- package/dist/ui-server.js +3532 -0
- package/dist/usage-budget.d.ts +28 -0
- package/dist/usage-budget.js +90 -0
- package/dist/validation.d.ts +25 -1
- package/dist/validation.js +312 -26
- package/dist/workspace-tools.d.ts +54 -0
- package/dist/workspace-tools.js +123 -0
- package/docs/agent-compatibility.md +49 -32
- package/docs/ci-and-automation.md +105 -66
- package/docs/doxbrix-http-api.md +8 -1
- package/docs/existing-documentation.md +80 -0
- package/docs/generation-performance.md +108 -0
- package/docs/generator-authoring.md +68 -5
- package/docs/generator-selection.md +50 -13
- package/docs/mintlify-import.md +71 -0
- package/docs/openapi-security.md +25 -0
- package/docs/project-format.md +331 -36
- package/docs/release-quality.md +158 -0
- package/docs/releasing.md +72 -0
- package/docs/review-workflows.md +51 -0
- package/docs/security-model.md +102 -39
- package/docs/troubleshooting.md +226 -93
- package/package.json +46 -15
- package/scripts/test-auto-screenshot.mjs +172 -0
- package/skills/doxloop-authoring/SKILL.md +242 -361
- package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
- package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
- package/skills/doxloop-authoring/references/page-depth.md +169 -0
- package/skills/doxloop-authoring/references/project-format.md +106 -7
- package/skills/doxloop-authoring/references/quality.md +10 -0
- package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
- package/skills/doxloop-authoring/references/screenshots.md +149 -235
- package/skills/doxloop-authoring/references/workflows.md +76 -0
- package/skills/doxloop-doxbrix/SKILL.md +38 -22
- package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
- package/skills/doxloop-doxbrix/references/components.md +30 -3
- package/skills/doxloop-doxbrix/references/manifest.md +3 -2
- package/vendor/doxbrix-import/LICENSE +202 -0
- package/vendor/doxbrix-import/README.md +17 -0
- package/vendor/doxbrix-import/UPSTREAM.json +47 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
- package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
- package/vendor/doxbrix-import/dist/docs/import.js +147 -0
- package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
- package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
- package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
- package/vendor/doxbrix-import/dist/docs/project.js +77 -0
- package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
- package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
- package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
- package/vendor/doxbrix-import/dist/importer.js +1567 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
- package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
- package/vendor/doxbrix-import/dist/safe-path.js +47 -0
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js.map +0 -1
- package/dist/artifact-deploy.d.ts.map +0 -1
- package/dist/artifact-deploy.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/author.d.ts.map +0 -1
- package/dist/author.js.map +0 -1
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/deploy.d.ts.map +0 -1
- package/dist/deploy.js.map +0 -1
- package/dist/deployment-visibility.d.ts.map +0 -1
- package/dist/deployment-visibility.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doxbrix-markdown.d.ts.map +0 -1
- package/dist/doxbrix-markdown.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/fs.js.map +0 -1
- package/dist/generator-api.d.ts.map +0 -1
- package/dist/generator-api.js.map +0 -1
- package/dist/generator-manager.d.ts.map +0 -1
- package/dist/generator-manager.js.map +0 -1
- package/dist/generator-runtime.d.ts.map +0 -1
- package/dist/generator-runtime.js.map +0 -1
- package/dist/generators.d.ts.map +0 -1
- package/dist/generators.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/preview.d.ts.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js.map +0 -1
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/preview.d.ts
CHANGED
|
@@ -7,16 +7,40 @@ interface PreviewOptions {
|
|
|
7
7
|
open: boolean;
|
|
8
8
|
}
|
|
9
9
|
export declare function startPreview(options: PreviewOptions): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* A stored redirect is followed only when its target is a page the site can
|
|
12
|
+
* serve. Redirects are written when a plan is approved, before the pages are
|
|
13
|
+
* authored, so a target may name a planned path the author never used; falling
|
|
14
|
+
* through to the normal page lookup beats sending readers to "Page not found".
|
|
15
|
+
*/
|
|
16
|
+
export declare function redirectTarget(redirect: string | undefined, site: DoxbrixSiteConfig, pages: Map<string, string>): string | undefined;
|
|
10
17
|
export declare function doxbrixDocument(input: {
|
|
11
18
|
site: DoxbrixSiteConfig;
|
|
12
19
|
title: string;
|
|
13
20
|
description?: string;
|
|
21
|
+
canonical?: string;
|
|
22
|
+
socialImage?: string;
|
|
14
23
|
current: string;
|
|
24
|
+
docVersion?: string;
|
|
15
25
|
rendered: {
|
|
16
26
|
html: string;
|
|
17
27
|
toc: TocEntry[];
|
|
18
28
|
};
|
|
29
|
+
verification?: {
|
|
30
|
+
state: string;
|
|
31
|
+
verifiedOn?: string;
|
|
32
|
+
revisions: Record<string, string>;
|
|
33
|
+
locale: string;
|
|
34
|
+
};
|
|
35
|
+
editorUrl?: string;
|
|
36
|
+
embedded?: boolean;
|
|
37
|
+
workspace?: boolean;
|
|
38
|
+
/** URL prefix used by static exports hosted below an origin, such as GitHub project pages. */
|
|
39
|
+
basePath?: string;
|
|
40
|
+
/** Static builds have no preview event stream. */
|
|
41
|
+
liveReload?: boolean;
|
|
19
42
|
}): string;
|
|
43
|
+
export declare function firstSitePage(site: DoxbrixSiteConfig): string | undefined;
|
|
20
44
|
export declare function previewErrorPage(error: unknown): string;
|
|
21
45
|
export {};
|
|
22
46
|
//# sourceMappingURL=preview.d.ts.map
|
package/dist/preview.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readRedirects } from './page-operations.js';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { readFile } from 'node:fs/promises';
|
|
3
4
|
import { createServer } from 'node:http';
|
|
@@ -5,9 +6,12 @@ import { extname, isAbsolute, relative, resolve } from 'node:path';
|
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import chokidar from 'chokidar';
|
|
7
8
|
import { renderMarkdown } from './doxbrix-markdown.js';
|
|
9
|
+
import { DoxloopError } from './errors.js';
|
|
8
10
|
import { resolveContainedDirectory } from './fs.js';
|
|
11
|
+
import { loadQualityConfig } from './quality-config.js';
|
|
12
|
+
import { reverifyClaims } from './quality-claims.js';
|
|
9
13
|
import { loadGeneratorAdapter } from './generators.js';
|
|
10
|
-
import { loadPages, loadProject, loadSiteConfig, pageId, readPage, siteConfigPath, } from './project.js';
|
|
14
|
+
import { loadPages, loadProject, loadSiteConfig, pageId, readPage, ROOT_CONTENT_IGNORED_DIRECTORIES, siteConfigPath, } from './project.js';
|
|
11
15
|
const PACKAGE_ROOT = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
12
16
|
const DOXBRIX_CSS = resolve(PACKAGE_ROOT, 'assets', 'doxbrix-preview.css');
|
|
13
17
|
const ATLAS_LAYOUT = {
|
|
@@ -44,12 +48,19 @@ export async function startPreview(options) {
|
|
|
44
48
|
await startDoxbrixPreview(options, project);
|
|
45
49
|
}
|
|
46
50
|
async function startDoxbrixPreview(options, project) {
|
|
47
|
-
const contentRoot = await resolveContainedDirectory(options.root, project.contentDir, 'Preview content directory');
|
|
51
|
+
const contentRoot = await resolveContainedDirectory(options.root, project.contentDir, 'Preview content directory', { allowRoot: project.generator === 'doxbrix' });
|
|
48
52
|
const css = await readFile(DOXBRIX_CSS, 'utf8');
|
|
49
53
|
const clients = new Set();
|
|
54
|
+
const ignoredDirectories = contentRoot === resolve(options.root)
|
|
55
|
+
? ROOT_CONTENT_IGNORED_DIRECTORIES
|
|
56
|
+
: undefined;
|
|
50
57
|
const server = createServer(async (request, response) => {
|
|
51
58
|
try {
|
|
52
59
|
const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
|
|
60
|
+
if (url.pathname === '/__doxloop/identity') {
|
|
61
|
+
send(response, 200, 'application/json; charset=utf-8', JSON.stringify({ root: resolve(options.root) }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
53
64
|
if (url.pathname === '/__doxloop/events') {
|
|
54
65
|
response.writeHead(200, {
|
|
55
66
|
'Content-Type': 'text/event-stream',
|
|
@@ -65,7 +76,7 @@ async function startDoxbrixPreview(options, project) {
|
|
|
65
76
|
send(response, 200, 'text/css; charset=utf-8', css);
|
|
66
77
|
return;
|
|
67
78
|
}
|
|
68
|
-
const staticPath = safeStaticPath(contentRoot, url.pathname);
|
|
79
|
+
const staticPath = safeStaticPath(contentRoot, url.pathname, ignoredDirectories);
|
|
69
80
|
if (staticPath && STATIC_TYPES[extname(staticPath).toLowerCase()]) {
|
|
70
81
|
try {
|
|
71
82
|
send(response, 200, STATIC_TYPES[extname(staticPath).toLowerCase()], await readFile(staticPath));
|
|
@@ -75,11 +86,19 @@ async function startDoxbrixPreview(options, project) {
|
|
|
75
86
|
// A missing static asset may still be an extension-bearing documentation route.
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
|
-
const [pages, site] = await Promise.all([
|
|
89
|
+
const [pages, site, qualityConfig, verificationMetadata] = await Promise.all([
|
|
79
90
|
loadPages(options.root, project),
|
|
80
91
|
loadSiteConfig(options.root, project),
|
|
92
|
+
loadQualityConfig(options.root),
|
|
93
|
+
loadQualityConfig(options.root).then((config) => config.readerVerification?.enabled ? reverifyClaims(options.root, project, false).then((result) => result.metadata) : undefined),
|
|
81
94
|
]);
|
|
82
95
|
const pagesById = new Map(pages.map((path) => [pageId(contentRoot, path), path]));
|
|
96
|
+
const redirect = redirectTarget((await readRedirects(options.root))[url.pathname.replace(/\/$/, '') || '/'], site, pagesById);
|
|
97
|
+
if (redirect) {
|
|
98
|
+
response.writeHead(302, { Location: redirect });
|
|
99
|
+
response.end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
83
102
|
if (url.pathname === '/__doxloop/search-index') {
|
|
84
103
|
const entries = await Promise.all([...pagesById].map(async ([id, path]) => {
|
|
85
104
|
const page = await readPage(path);
|
|
@@ -104,8 +123,17 @@ async function startDoxbrixPreview(options, project) {
|
|
|
104
123
|
site,
|
|
105
124
|
title: page.title || labelFromId(requested),
|
|
106
125
|
...(page.description ? { description: page.description } : {}),
|
|
126
|
+
...(page.canonical ? { canonical: page.canonical } : {}),
|
|
127
|
+
...(page.socialImage ? { socialImage: page.socialImage } : {}),
|
|
107
128
|
current: requested,
|
|
129
|
+
...(url.searchParams.get('version') ? { docVersion: url.searchParams.get('version') } : {}),
|
|
130
|
+
...(localEditorUrl(process.env.DOXLOOP_CONTROL_CENTER_URL, relative(options.root, pagesById.get(requested))) ? { editorUrl: localEditorUrl(process.env.DOXLOOP_CONTROL_CENTER_URL, relative(options.root, pagesById.get(requested))) } : {}),
|
|
108
131
|
rendered,
|
|
132
|
+
...(url.searchParams.get('embed') === 'page' ? { embedded: true } : {}),
|
|
133
|
+
...(url.searchParams.get('workspace') === '1' ? { workspace: true } : {}),
|
|
134
|
+
...(qualityConfig.readerVerification?.enabled && verificationMetadata?.pages[relative(options.root, pagesById.get(requested)).replace(/\\/g, '/')]
|
|
135
|
+
? { verification: verificationMetadata.pages[relative(options.root, pagesById.get(requested)).replace(/\\/g, '/')] }
|
|
136
|
+
: {}),
|
|
109
137
|
}));
|
|
110
138
|
}
|
|
111
139
|
catch (error) {
|
|
@@ -121,7 +149,12 @@ async function startDoxbrixPreview(options, project) {
|
|
|
121
149
|
if (options.open)
|
|
122
150
|
openBrowser(url);
|
|
123
151
|
const configPath = await siteConfigPath(options.root, project);
|
|
124
|
-
const watcher = chokidar.watch([contentRoot, configPath], {
|
|
152
|
+
const watcher = chokidar.watch([contentRoot, configPath], {
|
|
153
|
+
ignoreInitial: true,
|
|
154
|
+
...(ignoredDirectories
|
|
155
|
+
? { ignored: (path) => isIgnoredDirectoryPath(contentRoot, path, ignoredDirectories) }
|
|
156
|
+
: {}),
|
|
157
|
+
});
|
|
125
158
|
watcher.on('all', () => {
|
|
126
159
|
for (const client of clients)
|
|
127
160
|
client.write('event: reload\ndata: now\n\n');
|
|
@@ -139,16 +172,40 @@ async function startDoxbrixPreview(options, project) {
|
|
|
139
172
|
process.once('SIGINT', () => void stop());
|
|
140
173
|
process.once('SIGTERM', () => void stop());
|
|
141
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* A stored redirect is followed only when its target is a page the site can
|
|
177
|
+
* serve. Redirects are written when a plan is approved, before the pages are
|
|
178
|
+
* authored, so a target may name a planned path the author never used; falling
|
|
179
|
+
* through to the normal page lookup beats sending readers to "Page not found".
|
|
180
|
+
*/
|
|
181
|
+
export function redirectTarget(redirect, site, pages) {
|
|
182
|
+
if (!redirect)
|
|
183
|
+
return undefined;
|
|
184
|
+
const target = requestedPage(redirect, site, pages);
|
|
185
|
+
return target !== undefined && pages.has(target) ? redirect : undefined;
|
|
186
|
+
}
|
|
142
187
|
function requestedPage(pathname, site, pages) {
|
|
143
188
|
if (pathname === '/' || pathname === '') {
|
|
144
189
|
return firstSitePage(site) ?? pages.keys().next().value;
|
|
145
190
|
}
|
|
146
|
-
|
|
191
|
+
const id = decodeURIComponent(pathname.replace(/^\/+|\/+$/g, '')).replace(/\.(md|mdx)$/i, '');
|
|
192
|
+
return pages.has(id) ? id : pages.has(`${id}/index`) ? `${id}/index` : id;
|
|
147
193
|
}
|
|
148
194
|
export function doxbrixDocument(input) {
|
|
149
|
-
const
|
|
195
|
+
const versions = input.site.versions ?? [];
|
|
196
|
+
const defaultVersion = defaultDocVersion(input.site);
|
|
197
|
+
const contexts = (versions.length ? versions.map((entry) => entry.version) : [undefined]).map((version) => {
|
|
198
|
+
const spaces = input.site.spaces.filter((space) => (!versions.length || (space.version ?? defaultVersion) === version) && hasVisibleNavigation(space.nav));
|
|
199
|
+
return { version, spaces, matching: spaces.find((space) => containsPage(space.nav, input.current)) };
|
|
200
|
+
});
|
|
201
|
+
const context = contexts.find((entry) => entry.version === input.docVersion && entry.matching)
|
|
202
|
+
?? contexts.find((entry) => entry.version === defaultVersion && entry.matching)
|
|
203
|
+
?? contexts.find((entry) => entry.matching)
|
|
204
|
+
?? contexts.find((entry) => entry.version === defaultVersion)
|
|
205
|
+
?? contexts[0];
|
|
206
|
+
const visibleSpaces = context.spaces;
|
|
150
207
|
const showTabs = visibleSpaces.length > 1;
|
|
151
|
-
const activeSpace =
|
|
208
|
+
const activeSpace = context.matching ??
|
|
152
209
|
visibleSpaces[0];
|
|
153
210
|
const primary = themeColor(input.site, 'primaryColor', '#6366f1');
|
|
154
211
|
const primaryLight = themeColor(input.site, 'lightColor', primary);
|
|
@@ -174,14 +231,14 @@ export function doxbrixDocument(input) {
|
|
|
174
231
|
themeString(input.site, 'favicon', '') ||
|
|
175
232
|
themeString(input.site, 'faviconDark', '');
|
|
176
233
|
const siteName = input.site.name?.trim() || 'Documentation';
|
|
177
|
-
const
|
|
234
|
+
const tabsFor = (entry) => entry.spaces
|
|
178
235
|
.map((space) => {
|
|
179
236
|
const first = firstPage(space.nav);
|
|
180
|
-
const active = space ===
|
|
237
|
+
const active = space === (entry.matching ?? entry.spaces[0]) ? ' active' : '';
|
|
181
238
|
const tabIcon = typeof space.icon === 'string' && space.icon.trim()
|
|
182
239
|
? space.icon
|
|
183
240
|
: spaceIcon(space.name);
|
|
184
|
-
const tabTag =
|
|
241
|
+
const tabTag = space.tag
|
|
185
242
|
? `<span class="dxb-atlas-tab-tag">${escapeHtml(space.tag)}</span>`
|
|
186
243
|
: '';
|
|
187
244
|
const label = `<span class="dxb-atlas-tab-icon">${brandIcon(tabIcon, 16)}</span><span>${escapeHtml(space.name)}</span>${tabTag}`;
|
|
@@ -190,11 +247,16 @@ export function doxbrixDocument(input) {
|
|
|
190
247
|
: `<span class="dxb-atlas-tab${active}">${label}</span>`;
|
|
191
248
|
})
|
|
192
249
|
.join('');
|
|
193
|
-
const first = firstSitePage(input.site) ?? '';
|
|
250
|
+
const first = firstPageInVersion(input.site, context.version) ?? firstSitePage(input.site) ?? '';
|
|
194
251
|
const logoHref = safeHref(themeString(input.site, 'logoHref', `/${first}`), `/${first}`);
|
|
195
|
-
const leftnav =
|
|
196
|
-
|
|
197
|
-
|
|
252
|
+
const leftnav = contexts.map((entry) => {
|
|
253
|
+
const space = entry.matching ?? entry.spaces[0];
|
|
254
|
+
if (!space)
|
|
255
|
+
return '';
|
|
256
|
+
const attrs = versions.length ? ` data-doc-version="${escapeAttr(entry.version)}" data-current-version-page="${Boolean(entry.matching)}" data-version-eyebrow="${escapeAttr(groupContainingPage(space.nav, input.current) ?? '')}"` : '';
|
|
257
|
+
return `<aside class="dp-leftnav" aria-label="Documentation navigation"${attrs}${entry !== context ? ' hidden' : ''}>${versionSwitcher(input.site, entry.version)}<div class="dp-nav-tree">${navTree(space.nav, input.current)}</div></aside>`;
|
|
258
|
+
}).join('');
|
|
259
|
+
const tabs = contexts.filter((entry) => entry.spaces.length > 1).map((entry) => `<div class="dxb-atlas-tabs-bar"${versions.length ? ` data-doc-version="${escapeAttr(entry.version)}"` : ''}${entry !== context ? ' hidden' : ''}><nav class="dxb-atlas-tabs" aria-label="Documentation spaces">${tabsFor(entry)}</nav></div>`).join('');
|
|
198
260
|
const toc = tocHtml(input.rendered.toc);
|
|
199
261
|
const groupLabel = activeSpace
|
|
200
262
|
? groupContainingPage(activeSpace.nav, input.current)
|
|
@@ -204,6 +266,11 @@ export function doxbrixDocument(input) {
|
|
|
204
266
|
const faviconLink = favicon
|
|
205
267
|
? `<link rel="icon" href="${escapeAttr(favicon)}">`
|
|
206
268
|
: '';
|
|
269
|
+
const metadata = [
|
|
270
|
+
input.description ? `<meta name="description" content="${escapeAttr(input.description)}">` : '',
|
|
271
|
+
input.canonical ? `<link rel="canonical" href="${escapeAttr(input.canonical)}">` : '',
|
|
272
|
+
input.socialImage ? `<meta property="og:image" content="${escapeAttr(input.socialImage)}">` : '',
|
|
273
|
+
].filter(Boolean).join('\n ');
|
|
207
274
|
const logo = logoLight || logoDark
|
|
208
275
|
? `<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>`
|
|
209
276
|
: `<span class="dp-topnav-logo-mark" aria-hidden="true">${icon('book', 16)}</span><span class="dp-topnav-logo-text">${escapeHtml(siteName)}</span>`;
|
|
@@ -213,12 +280,14 @@ export function doxbrixDocument(input) {
|
|
|
213
280
|
const eyebrow = groupLabel
|
|
214
281
|
? `<div class="dxb-atlas-eyebrow">${escapeHtml(groupLabel)}</div>`
|
|
215
282
|
: '';
|
|
216
|
-
|
|
283
|
+
const verification = input.verification ? verificationBadge(input.verification) : '';
|
|
284
|
+
const document = `<!doctype html>
|
|
217
285
|
<html lang="en">
|
|
218
286
|
<head>
|
|
219
287
|
<meta charset="utf-8">
|
|
220
288
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
221
289
|
<title>${escapeHtml(input.title)} · ${escapeHtml(siteName)}</title>
|
|
290
|
+
${metadata}
|
|
222
291
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
223
292
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
224
293
|
${fontStylesheet}
|
|
@@ -227,6 +296,7 @@ export function doxbrixDocument(input) {
|
|
|
227
296
|
<style>
|
|
228
297
|
${fontFaces}
|
|
229
298
|
body{margin:0}.dp-tab-panel{display:none}.dp-tab-panel.active{display:block}
|
|
299
|
+
.dxb-verification{display:inline-flex;align-items:center;gap:8px;margin-top:12px;padding:6px 9px;border:1px solid #cbd5e1;border-radius:999px;font-size:12px;color:#475569;background:#f8fafc}.dxb-verification strong{font-size:12px}.dxb-verification--verified{border-color:#99e1cb;color:#08745d;background:#effcf8}.dxb-verification--contradicted,.dxb-verification--needs-human{border-color:#fecaca;color:#a61b29;background:#fff5f5}
|
|
230
300
|
.dp-nav-chevron{transition:transform .15s}.dp-nav-chevron:not(.open){transform:rotate(-90deg)}
|
|
231
301
|
.dp-root--published{--project-primary:${escapeAttr(primary)};--project-primary-hover:${escapeAttr(primary)};
|
|
232
302
|
--project-primary-rgb:${hexToRgb(primary)};--primary:${escapeAttr(primary)};--primary-rgb:${hexToRgb(primary)};
|
|
@@ -243,7 +313,7 @@ export function doxbrixDocument(input) {
|
|
|
243
313
|
</style>
|
|
244
314
|
</head>
|
|
245
315
|
<body>
|
|
246
|
-
<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">
|
|
316
|
+
<div class="dp-root dp-root--published${input.embedded ? ' dp-root--embedded' : ''}" data-color-theme="${resolvedMode}" data-project-color-theme="${escapeAttr(mode)}" data-code-theme="${codeTheme}" data-shell-theme="atlas">
|
|
247
317
|
<header class="dxb-atlas-header">
|
|
248
318
|
<div class="dxb-atlas-header-main"><div class="dxb-atlas-header-inner">
|
|
249
319
|
<a class="dp-topnav-logo" href="${escapeAttr(logoHref)}">${logo}</a>
|
|
@@ -262,13 +332,13 @@ export function doxbrixDocument(input) {
|
|
|
262
332
|
<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>
|
|
263
333
|
</div>
|
|
264
334
|
</div></div>
|
|
265
|
-
${
|
|
335
|
+
${tabs}
|
|
266
336
|
</header>
|
|
267
337
|
<div class="dp-body">
|
|
268
338
|
${leftnav}
|
|
269
|
-
<main class="dp-main"><div class="dp-content-wrap">
|
|
339
|
+
<main class="dp-main"><div class="dp-content-wrap">${input.editorUrl ? `<a class="dp-edit-page" href="${escapeHtml(input.editorUrl)}" target="_blank" rel="noopener">Edit this page in Doxloop</a>` : ''}
|
|
270
340
|
<div class="dxb-atlas-title-row">
|
|
271
|
-
<div class="dxb-atlas-title-copy">${eyebrow}<h1 class="dp-page-title">${escapeHtml(input.title)}</h1>${description}</div>
|
|
341
|
+
<div class="dxb-atlas-title-copy">${eyebrow}<h1 class="dp-page-title">${escapeHtml(input.title)}</h1>${description}${verification}</div>
|
|
272
342
|
<button class="dxb-atlas-copy-page" type="button" data-copy-page>${icon('copy', 16)}<span>Copy page</span>${icon('chevron-down', 14)}</button>
|
|
273
343
|
</div>
|
|
274
344
|
<div class="dp-blocks">${input.rendered.html}</div>
|
|
@@ -289,6 +359,14 @@ export function doxbrixDocument(input) {
|
|
|
289
359
|
</div>
|
|
290
360
|
</div>
|
|
291
361
|
<script>
|
|
362
|
+
${input.embedded && input.workspace ? `document.addEventListener('click', function(event) {
|
|
363
|
+
const link = event.target.closest('a[href]');
|
|
364
|
+
if (!link) return;
|
|
365
|
+
const target = new URL(link.href, location.href);
|
|
366
|
+
if (target.origin !== location.origin || (target.pathname === location.pathname && target.hash)) return;
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
parent.postMessage({ type: 'doxloop:preview-navigate', href: target.href }, '*');
|
|
369
|
+
});` : ''}
|
|
292
370
|
const root = document.querySelector('.dp-root--published');
|
|
293
371
|
let colorTheme = root?.dataset.projectColorTheme || 'system';
|
|
294
372
|
try {
|
|
@@ -346,8 +424,21 @@ export function doxbrixDocument(input) {
|
|
|
346
424
|
updateActiveToc();
|
|
347
425
|
addEventListener('scroll', scheduleTocUpdate, { passive: true });
|
|
348
426
|
addEventListener('resize', scheduleTocUpdate);
|
|
349
|
-
const events = new EventSource('/__doxloop/events');
|
|
350
|
-
events
|
|
427
|
+
${input.liveReload === false ? 'const events = null;' : "const events = new EventSource('/__doxloop/events');"}
|
|
428
|
+
events?.addEventListener('reload', () => location.reload());
|
|
429
|
+
function closePreviewEvents() {
|
|
430
|
+
events?.close();
|
|
431
|
+
}
|
|
432
|
+
addEventListener('pagehide', closePreviewEvents, { once: true });
|
|
433
|
+
addEventListener('beforeunload', closePreviewEvents, { once: true });
|
|
434
|
+
document.addEventListener('click', (event) => {
|
|
435
|
+
const link = event.target instanceof Element ? event.target.closest('a[href]') : null;
|
|
436
|
+
if (!link || event.defaultPrevented || link.target === '_blank' || link.hasAttribute('download')) return;
|
|
437
|
+
const target = new URL(link.href, location.href);
|
|
438
|
+
const currentWithoutHash = location.origin + location.pathname + location.search;
|
|
439
|
+
const targetWithoutHash = target.origin + target.pathname + target.search;
|
|
440
|
+
if (target.origin === location.origin && targetWithoutHash !== currentWithoutHash) closePreviewEvents();
|
|
441
|
+
}, { capture: true });
|
|
351
442
|
const previewNotice = document.querySelector('[data-preview-notice]');
|
|
352
443
|
const previewAssistant = document.querySelector('[data-preview-assistant]');
|
|
353
444
|
let previewNoticeTimer;
|
|
@@ -733,13 +824,70 @@ export function doxbrixDocument(input) {
|
|
|
733
824
|
const active = searchResults?.querySelector('.dxb-preview-search-result.active');
|
|
734
825
|
if (active) {
|
|
735
826
|
event.preventDefault();
|
|
827
|
+
closePreviewEvents();
|
|
736
828
|
location.href = active.href;
|
|
737
829
|
}
|
|
738
830
|
}
|
|
739
831
|
});
|
|
740
832
|
</script>
|
|
833
|
+
${MERMAID_PREVIEW_SCRIPT}
|
|
834
|
+
${versions.length ? versionContextScript(layout.tabsHeight) : ''}
|
|
741
835
|
</body>
|
|
742
836
|
</html>`;
|
|
837
|
+
return prefixStaticReferences(document, input.basePath);
|
|
838
|
+
}
|
|
839
|
+
/** Prefix root-relative reader URLs without touching external or fragment links. */
|
|
840
|
+
function prefixStaticReferences(document, rawBasePath) {
|
|
841
|
+
if (rawBasePath === undefined)
|
|
842
|
+
return document;
|
|
843
|
+
const basePath = normalizeBasePath(rawBasePath);
|
|
844
|
+
if (!basePath)
|
|
845
|
+
return document;
|
|
846
|
+
return document
|
|
847
|
+
.replaceAll('href="/', `href="${basePath}/`)
|
|
848
|
+
.replaceAll('src="/', `src="${basePath}/`)
|
|
849
|
+
.replaceAll("fetch('/", `fetch('${basePath}/`)
|
|
850
|
+
.replaceAll("new EventSource('/", `new EventSource('${basePath}/`);
|
|
851
|
+
}
|
|
852
|
+
function normalizeBasePath(value) {
|
|
853
|
+
const trimmed = value.trim();
|
|
854
|
+
if (!trimmed || trimmed === '/')
|
|
855
|
+
return '';
|
|
856
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(trimmed) || trimmed.startsWith('//')) {
|
|
857
|
+
throw new DoxloopError('Static build base path must be an origin-relative URL path.');
|
|
858
|
+
}
|
|
859
|
+
const normalized = `/${trimmed.replace(/^\/+|\/+$/g, '')}`;
|
|
860
|
+
if (normalized.split('/').some((part) => part === '..')) {
|
|
861
|
+
throw new DoxloopError('Static build base path cannot leave its URL root.');
|
|
862
|
+
}
|
|
863
|
+
return normalized;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Render `<Mermaid>` blocks in the local preview. The published site renders
|
|
867
|
+
* diagrams itself; without this the preview showed the diagram source as
|
|
868
|
+
* plain text, so a concept page looked broken before it was ever published.
|
|
869
|
+
* Loaded only when a page contains a diagram, and a failed load (offline)
|
|
870
|
+
* leaves the readable source in place.
|
|
871
|
+
*/
|
|
872
|
+
const MERMAID_PREVIEW_SCRIPT = `<script type="module">
|
|
873
|
+
const diagrams = document.querySelectorAll('pre.mermaid');
|
|
874
|
+
if (diagrams.length > 0) {
|
|
875
|
+
try {
|
|
876
|
+
const { default: mermaid } = await import('https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs');
|
|
877
|
+
const dark = document.querySelector('.dp-root--published')?.dataset.colorTheme === 'dark';
|
|
878
|
+
mermaid.initialize({ startOnLoad: false, theme: dark ? 'dark' : 'neutral', securityLevel: 'strict' });
|
|
879
|
+
await mermaid.run({ nodes: diagrams });
|
|
880
|
+
} catch (error) {
|
|
881
|
+
console.warn('Doxloop preview could not render Mermaid diagrams; the published site renders them.', error);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
</script>`;
|
|
885
|
+
function verificationBadge(verification) {
|
|
886
|
+
const label = verification.state === 'verified' ? 'Verified' : verification.state === 'inferred' ? 'Evidence inferred' : verification.state === 'contradicted' ? 'Contradicted — review required' : 'Needs review';
|
|
887
|
+
const revisions = Object.entries(verification.revisions).map(([source, revision]) => `${source} ${revision.slice(0, 12)}`).join(', ');
|
|
888
|
+
const date = verification.verifiedOn ? new Intl.DateTimeFormat(verification.locale, { dateStyle: 'medium', timeZone: 'UTC' }).format(new Date(verification.verifiedOn)) : '';
|
|
889
|
+
const detail = [date, revisions].filter(Boolean).join(' · ');
|
|
890
|
+
return `<aside class="dxb-verification dxb-verification--${escapeAttr(verification.state)}" aria-label="Documentation verification"><strong>${escapeHtml(label)}</strong>${detail ? `<span>${escapeHtml(detail)}</span>` : ''}</aside>`;
|
|
743
891
|
}
|
|
744
892
|
function navTree(nodes, current, depth = 0) {
|
|
745
893
|
return nodes
|
|
@@ -755,9 +903,12 @@ function navTree(nodes, current, depth = 0) {
|
|
|
755
903
|
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>`;
|
|
756
904
|
}
|
|
757
905
|
if (node.type === 'api') {
|
|
758
|
-
return `<
|
|
906
|
+
return `<a class="dp-nav-item${depthClass(depth)}" href="${escapeAttr(apiSpecHref(node.spec))}" target="_blank" rel="noreferrer">${navIcon(node.icon)}<span class="dp-nav-item-label">${escapeHtml(node.title)}</span></a>`;
|
|
759
907
|
}
|
|
760
908
|
if (node.type === 'group') {
|
|
909
|
+
// An empty header is nothing to click into; hide it rather than render a bare label.
|
|
910
|
+
if (!hasVisibleNavigation(node.items))
|
|
911
|
+
return '';
|
|
761
912
|
const children = navTree(node.items, current, depth + 1);
|
|
762
913
|
const activeBranch = containsPage(node.items, current) ? ' active-branch' : '';
|
|
763
914
|
const groupIcon = navIcon(node.icon);
|
|
@@ -773,8 +924,8 @@ function navTree(nodes, current, depth = 0) {
|
|
|
773
924
|
}
|
|
774
925
|
function tocHtml(entries) {
|
|
775
926
|
if (entries.length === 0)
|
|
776
|
-
return '<aside class="dp-toc"></aside>';
|
|
777
|
-
return `<aside class="dp-toc"><div class="dp-toc-header">${icon('list', 16)} On this page</div>${entries
|
|
927
|
+
return '<aside class="dp-toc" aria-label="On this page"></aside>';
|
|
928
|
+
return `<aside class="dp-toc" aria-label="On this page"><div class="dp-toc-header">${icon('list', 16)} On this page</div>${entries
|
|
778
929
|
.map((entry, index) => `<a class="dp-toc-entry level-${entry.level}${index === 0 ? ' active' : ''}" href="#${escapeAttr(entry.id)}">${escapeHtml(entry.title)}</a>`)
|
|
779
930
|
.join('')}</aside>`;
|
|
780
931
|
}
|
|
@@ -804,7 +955,68 @@ function firstPage(nodes) {
|
|
|
804
955
|
}
|
|
805
956
|
return undefined;
|
|
806
957
|
}
|
|
807
|
-
function
|
|
958
|
+
function defaultDocVersion(site) {
|
|
959
|
+
const versions = site.versions ?? [];
|
|
960
|
+
return (versions.find((entry) => entry.default === true || entry.isDefault === true) ?? versions[0])?.version;
|
|
961
|
+
}
|
|
962
|
+
function firstPageInVersion(site, version) {
|
|
963
|
+
for (const space of site.spaces) {
|
|
964
|
+
if (site.versions?.length && (space.version ?? defaultDocVersion(site)) !== version)
|
|
965
|
+
continue;
|
|
966
|
+
const first = firstPage(space.nav);
|
|
967
|
+
if (first)
|
|
968
|
+
return first;
|
|
969
|
+
}
|
|
970
|
+
return undefined;
|
|
971
|
+
}
|
|
972
|
+
// Mirrors the Doxbrix CLI reader: default/isDefault and a sidebar dropdown.
|
|
973
|
+
function versionSwitcher(site, version) {
|
|
974
|
+
const versions = site.versions ?? [];
|
|
975
|
+
if (versions.length < 2)
|
|
976
|
+
return '';
|
|
977
|
+
const active = versions.find((entry) => entry.version === version) ?? versions[0];
|
|
978
|
+
const label = (entry) => `<span class="dp-version-label">${escapeHtml(entry.label ?? entry.version)}</span>${entry.tag ? `<span class="dp-version-tag">${escapeHtml(entry.tag)}</span>` : ''}`;
|
|
979
|
+
const options = versions.map((entry) => {
|
|
980
|
+
const first = firstPageInVersion(site, entry.version);
|
|
981
|
+
return first
|
|
982
|
+
? `<a class="dp-version-option${entry === active ? ' active' : ''}" href="/${escapeAttr(first)}?version=${escapeAttr(encodeURIComponent(entry.version))}"${entry === active ? ' aria-current="true"' : ''}>${label(entry)}</a>`
|
|
983
|
+
: `<span class="dp-version-option" aria-disabled="true">${label(entry)}</span>`;
|
|
984
|
+
}).join('');
|
|
985
|
+
return `<details class="dp-version-switcher"><summary class="dp-version-trigger" aria-label="Documentation version: ${escapeAttr(active.label ?? active.version)}">${label(active)}${icon('chevron-down', 12)}</summary><nav class="dp-version-menu" aria-label="Documentation versions">${options}</nav></details>`;
|
|
986
|
+
}
|
|
987
|
+
function versionContextScript(tabsHeight) {
|
|
988
|
+
// Shared pages have one static HTML file. Keep the selected version in their URL
|
|
989
|
+
// and select the matching pre-rendered navigation for previews and static hosts.
|
|
990
|
+
return `<script>
|
|
991
|
+
(() => {
|
|
992
|
+
const sides = [...document.querySelectorAll('aside[data-doc-version]')];
|
|
993
|
+
const requested = new URL(location.href).searchParams.get('version');
|
|
994
|
+
const side = sides.find((entry) => entry.dataset.docVersion === requested && entry.dataset.currentVersionPage === 'true') || sides.find((entry) => !entry.hidden);
|
|
995
|
+
if (!side) return;
|
|
996
|
+
const version = side.dataset.docVersion;
|
|
997
|
+
document.querySelectorAll('[data-doc-version]').forEach((entry) => { entry.hidden = entry.dataset.docVersion !== version; });
|
|
998
|
+
const tabs = [...document.querySelectorAll('.dxb-atlas-tabs-bar')].find((entry) => !entry.hidden);
|
|
999
|
+
document.querySelector('.dp-root')?.style.setProperty('--dxb-tabs-height', tabs ? '${tabsHeight}px' : '0px');
|
|
1000
|
+
const eyebrow = document.querySelector('.dxb-atlas-eyebrow');
|
|
1001
|
+
if (eyebrow) eyebrow.textContent = side.dataset.versionEyebrow || '';
|
|
1002
|
+
const logo = document.querySelector('.dp-topnav-logo');
|
|
1003
|
+
const home = side.querySelector('.dp-version-option.active');
|
|
1004
|
+
if (logo && home) logo.href = home.href;
|
|
1005
|
+
const preserve = (link) => {
|
|
1006
|
+
if (!link || link.closest('.dp-version-switcher') || !link.getAttribute('href') || link.getAttribute('href').startsWith('#')) return;
|
|
1007
|
+
const url = new URL(link.href, location.href);
|
|
1008
|
+
if (url.origin !== location.origin || /\\.[a-z0-9]+$/i.test(url.pathname) && !/\\.mdx?$/i.test(url.pathname)) return;
|
|
1009
|
+
if (!url.searchParams.has('version')) { url.searchParams.set('version', version); link.href = url.href; }
|
|
1010
|
+
};
|
|
1011
|
+
document.querySelectorAll('a[href]').forEach(preserve);
|
|
1012
|
+
document.addEventListener('click', (event) => preserve(event.target.closest('a[href]')), true);
|
|
1013
|
+
})();
|
|
1014
|
+
</script>`;
|
|
1015
|
+
}
|
|
1016
|
+
export function firstSitePage(site) {
|
|
1017
|
+
const defaultFirst = firstPageInVersion(site, defaultDocVersion(site));
|
|
1018
|
+
if (defaultFirst)
|
|
1019
|
+
return defaultFirst;
|
|
808
1020
|
for (const space of site.spaces) {
|
|
809
1021
|
const first = firstPage(space.nav);
|
|
810
1022
|
if (first)
|
|
@@ -1014,7 +1226,9 @@ function googleFontStylesheet(families) {
|
|
|
1014
1226
|
return '';
|
|
1015
1227
|
const query = unique
|
|
1016
1228
|
.slice(0, 3)
|
|
1017
|
-
.map((family) =>
|
|
1229
|
+
.map((family) => family.toLowerCase() === 'inter'
|
|
1230
|
+
? 'family=Inter:ital,wght@0,100..900;1,100..900'
|
|
1231
|
+
: `family=${encodeURIComponent(family)}:wght@400;500;600;700;800`)
|
|
1018
1232
|
.join('&');
|
|
1019
1233
|
return `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?${query}&display=swap">`;
|
|
1020
1234
|
}
|
|
@@ -1042,6 +1256,19 @@ function safeNavigationHref(value) {
|
|
|
1042
1256
|
}
|
|
1043
1257
|
return '#';
|
|
1044
1258
|
}
|
|
1259
|
+
function apiSpecHref(value) {
|
|
1260
|
+
const trimmed = value.trim();
|
|
1261
|
+
if (/^https?:\/\//i.test(trimmed))
|
|
1262
|
+
return trimmed;
|
|
1263
|
+
if (trimmed === '' ||
|
|
1264
|
+
trimmed.startsWith('//') ||
|
|
1265
|
+
trimmed.includes('\\') ||
|
|
1266
|
+
trimmed.split(/[/?#]/).includes('..')) {
|
|
1267
|
+
return '#';
|
|
1268
|
+
}
|
|
1269
|
+
const local = trimmed.replace(/^\.\//, '');
|
|
1270
|
+
return local.startsWith('/') ? local : `/${local}`;
|
|
1271
|
+
}
|
|
1045
1272
|
function depthClass(depth) {
|
|
1046
1273
|
return depth === 0 ? '' : ` dp-nav-d${Math.min(depth, 3)}`;
|
|
1047
1274
|
}
|
|
@@ -1052,7 +1279,7 @@ function labelFromId(id) {
|
|
|
1052
1279
|
.map((word) => `${word[0]?.toUpperCase() ?? ''}${word.slice(1)}`)
|
|
1053
1280
|
.join(' ');
|
|
1054
1281
|
}
|
|
1055
|
-
function safeStaticPath(contentRoot, pathname) {
|
|
1282
|
+
function safeStaticPath(contentRoot, pathname, ignoredDirectories) {
|
|
1056
1283
|
let decoded;
|
|
1057
1284
|
try {
|
|
1058
1285
|
decoded = decodeURIComponent(pathname);
|
|
@@ -1062,7 +1289,16 @@ function safeStaticPath(contentRoot, pathname) {
|
|
|
1062
1289
|
}
|
|
1063
1290
|
const path = resolve(contentRoot, decoded.replace(/^\/+/, ''));
|
|
1064
1291
|
const rel = relative(contentRoot, path);
|
|
1065
|
-
return rel === '' || rel.startsWith('..') || isAbsolute(rel)
|
|
1292
|
+
return rel === '' || rel.startsWith('..') || isAbsolute(rel)
|
|
1293
|
+
|| isIgnoredDirectoryPath(contentRoot, path, ignoredDirectories)
|
|
1294
|
+
? undefined
|
|
1295
|
+
: path;
|
|
1296
|
+
}
|
|
1297
|
+
function isIgnoredDirectoryPath(contentRoot, path, ignoredDirectories) {
|
|
1298
|
+
if (!ignoredDirectories)
|
|
1299
|
+
return false;
|
|
1300
|
+
const first = relative(contentRoot, path).split(/[\\/]/)[0];
|
|
1301
|
+
return first !== undefined && ignoredDirectories.has(first);
|
|
1066
1302
|
}
|
|
1067
1303
|
function errorPage(requested, site) {
|
|
1068
1304
|
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>`;
|
|
@@ -1146,4 +1382,19 @@ function openBrowser(url) {
|
|
|
1146
1382
|
const child = spawn(command, args, { detached: true, stdio: 'ignore' });
|
|
1147
1383
|
child.unref();
|
|
1148
1384
|
}
|
|
1385
|
+
function localEditorUrl(origin, path) {
|
|
1386
|
+
if (!origin)
|
|
1387
|
+
return undefined;
|
|
1388
|
+
try {
|
|
1389
|
+
const url = new URL(origin);
|
|
1390
|
+
if (url.protocol !== 'http:' || !['127.0.0.1', 'localhost'].includes(url.hostname) || url.username || url.password)
|
|
1391
|
+
return undefined;
|
|
1392
|
+
url.pathname = '/pages';
|
|
1393
|
+
url.search = new URLSearchParams({ path: path.replace(/\\/g, '/'), edit: '1' }).toString();
|
|
1394
|
+
return url.toString();
|
|
1395
|
+
}
|
|
1396
|
+
catch {
|
|
1397
|
+
return undefined;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1149
1400
|
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { GeneratorName } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* One way the folder could be read. `contentDir` is the directory Doxloop
|
|
4
|
+
* would treat as documentation content, relative to the folder; an empty
|
|
5
|
+
* string means the folder itself, which only Doxbrix allows.
|
|
6
|
+
*/
|
|
7
|
+
export interface GeneratorCandidate {
|
|
8
|
+
generator: GeneratorName;
|
|
9
|
+
contentDir: string;
|
|
10
|
+
markers: string[];
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface GeneratorDetection {
|
|
14
|
+
/** Most specific marker first. Empty when nothing recognizable was found. */
|
|
15
|
+
candidates: GeneratorCandidate[];
|
|
16
|
+
recommended?: GeneratorCandidate;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Page extensions per generator when the adapter package cannot be loaded,
|
|
20
|
+
* so a folder can be inspected before anything is installed.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_PAGE_EXTENSIONS: Record<GeneratorName, readonly string[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Recognize an existing documentation site from the files its generator
|
|
25
|
+
* needs. Detection only reads; it never writes into the folder. Order matters
|
|
26
|
+
* where markers overlap: a Docusaurus or MkDocs site may also carry a Jekyll
|
|
27
|
+
* style `_config.yml`, so the more specific generator is listed first.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectDocumentationGenerator(root: string): Promise<GeneratorDetection>;
|
|
30
|
+
/**
|
|
31
|
+
* Project-relative page paths under a content directory, using the
|
|
32
|
+
* generator's default extensions. Missing directories yield no pages instead
|
|
33
|
+
* of an error so an inspection can report "0 pages" for a wrong guess.
|
|
34
|
+
*/
|
|
35
|
+
export declare function listDocumentationPageFiles(root: string, generator: GeneratorName, contentDir: string): Promise<string[]>;
|
|
36
|
+
//# sourceMappingURL=project-detect.d.ts.map
|