@dogsbay/docs-layout 0.2.0-beta.13 → 0.2.0-beta.130
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/package.json +9 -5
- package/src/BlogIndex.astro +179 -0
- package/src/DocsFooter.astro +79 -15
- package/src/DocsLayout.astro +649 -44
- package/src/DocsNavClient.astro +113 -0
- package/src/DocsToc.astro +1 -1
- package/src/SearchDialog.astro +317 -33
- package/src/TagList.astro +24 -3
- package/src/VersionSwitcher.astro +7 -1
- package/src/docs-nav-client.ts +422 -0
- package/src/json-ld.ts +4 -53
- package/src/link-icons.ts +54 -0
- package/src/markdown-negotiation.ts +38 -2
- package/src/nav-filter.ts +68 -127
- package/src/pagefind-base-url.ts +59 -0
- package/src/search-facets.ts +511 -9
- package/src/switcher.ts +93 -4
- package/src/tag-list-data.ts +26 -3
- package/src/toc-kind.css +10 -10
- package/src/toc-placement.ts +71 -0
- package/src/version-redirect.ts +17 -4
package/src/tag-list-data.ts
CHANGED
|
@@ -33,6 +33,29 @@ export interface BuildChipsOptions {
|
|
|
33
33
|
* text. URL still uses the slug.
|
|
34
34
|
*/
|
|
35
35
|
labels?: Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Color chips whose prefix has no `color` with a palette color
|
|
38
|
+
* hashed from the first segment (`autoTagColor`). Default false.
|
|
39
|
+
*/
|
|
40
|
+
autoColor?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Palette order for automatic colors. */
|
|
44
|
+
const AUTO_PALETTE: readonly TagPaletteName[] = ["blue", "amber", "emerald", "violet", "rose", "slate"];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A stable palette color for a tag segment: FNV-1a hash of the
|
|
48
|
+
* lowercased segment, modulo the palette. The same segment gets the
|
|
49
|
+
* same color on every page and every build; with more distinct tags
|
|
50
|
+
* than colors, colors repeat.
|
|
51
|
+
*/
|
|
52
|
+
export function autoTagColor(segment: string): TagPaletteName {
|
|
53
|
+
let hash = 0x811c9dc5;
|
|
54
|
+
for (const ch of segment.toLowerCase()) {
|
|
55
|
+
hash ^= ch.codePointAt(0)!;
|
|
56
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
57
|
+
}
|
|
58
|
+
return AUTO_PALETTE[hash % AUTO_PALETTE.length];
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
export interface ChipModel {
|
|
@@ -164,7 +187,7 @@ export function buildChips(
|
|
|
164
187
|
options: BuildChipsOptions,
|
|
165
188
|
): ChipModel[] {
|
|
166
189
|
const safe = filterTags(tags);
|
|
167
|
-
const { indexPath, prefixes, labels } = options;
|
|
190
|
+
const { indexPath, prefixes, labels, autoColor = false } = options;
|
|
168
191
|
|
|
169
192
|
return safe.map((tag): ChipModel => {
|
|
170
193
|
const href = `${indexPath}/${tag}/`;
|
|
@@ -182,7 +205,7 @@ export function buildChips(
|
|
|
182
205
|
href,
|
|
183
206
|
prefixLabel: null,
|
|
184
207
|
leafLabel: overrideLeaf ?? tag,
|
|
185
|
-
color: null,
|
|
208
|
+
color: autoColor ? autoTagColor(firstSegment) : null,
|
|
186
209
|
};
|
|
187
210
|
}
|
|
188
211
|
|
|
@@ -196,7 +219,7 @@ export function buildChips(
|
|
|
196
219
|
href,
|
|
197
220
|
prefixLabel: prefixEntry.label ?? null,
|
|
198
221
|
leafLabel: leafLabel.length > 0 ? leafLabel : tag,
|
|
199
|
-
color: prefixEntry.color ?? null,
|
|
222
|
+
color: prefixEntry.color ?? (autoColor ? autoTagColor(firstSegment) : null),
|
|
200
223
|
};
|
|
201
224
|
});
|
|
202
225
|
}
|
package/src/toc-kind.css
CHANGED
|
@@ -11,40 +11,40 @@
|
|
|
11
11
|
line-height: 1.4;
|
|
12
12
|
}
|
|
13
13
|
.toc-kind-class {
|
|
14
|
-
background-color: oklch(0.
|
|
15
|
-
color: oklch(0.
|
|
14
|
+
background-color: oklch(0.44 0.18 265 / 0.15);
|
|
15
|
+
color: oklch(0.44 0.18 265);
|
|
16
16
|
}
|
|
17
17
|
.dark .toc-kind-class {
|
|
18
18
|
background-color: oklch(0.7 0.15 265 / 0.2);
|
|
19
19
|
color: oklch(0.7 0.15 265);
|
|
20
20
|
}
|
|
21
21
|
.toc-kind-meth {
|
|
22
|
-
background-color: oklch(0.
|
|
23
|
-
color: oklch(0.
|
|
22
|
+
background-color: oklch(0.44 0.15 155 / 0.15);
|
|
23
|
+
color: oklch(0.44 0.15 155);
|
|
24
24
|
}
|
|
25
25
|
.dark .toc-kind-meth {
|
|
26
26
|
background-color: oklch(0.7 0.15 155 / 0.2);
|
|
27
27
|
color: oklch(0.7 0.15 155);
|
|
28
28
|
}
|
|
29
29
|
.toc-kind-attr {
|
|
30
|
-
background-color: oklch(0.
|
|
31
|
-
color: oklch(0.
|
|
30
|
+
background-color: oklch(0.44 0.15 55 / 0.15);
|
|
31
|
+
color: oklch(0.44 0.15 55);
|
|
32
32
|
}
|
|
33
33
|
.dark .toc-kind-attr {
|
|
34
34
|
background-color: oklch(0.75 0.15 55 / 0.2);
|
|
35
35
|
color: oklch(0.75 0.15 55);
|
|
36
36
|
}
|
|
37
37
|
.toc-kind-func {
|
|
38
|
-
background-color: oklch(0.
|
|
39
|
-
color: oklch(0.
|
|
38
|
+
background-color: oklch(0.44 0.18 330 / 0.15);
|
|
39
|
+
color: oklch(0.44 0.18 330);
|
|
40
40
|
}
|
|
41
41
|
.dark .toc-kind-func {
|
|
42
42
|
background-color: oklch(0.7 0.15 330 / 0.2);
|
|
43
43
|
color: oklch(0.7 0.15 330);
|
|
44
44
|
}
|
|
45
45
|
.toc-kind-module {
|
|
46
|
-
background-color: oklch(0.
|
|
47
|
-
color: oklch(0.
|
|
46
|
+
background-color: oklch(0.44 0.12 200 / 0.15);
|
|
47
|
+
color: oklch(0.44 0.12 200);
|
|
48
48
|
}
|
|
49
49
|
.dark .toc-kind-module {
|
|
50
50
|
background-color: oklch(0.65 0.12 200 / 0.2);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide which TOC container(s) and right-rail region `DocsLayout` renders,
|
|
3
|
+
* given the `toc` placement mode and per-page facts. Factored out of
|
|
4
|
+
* `DocsLayout.astro` so the branching logic is unit-testable (the .astro
|
|
5
|
+
* file just consumes the result) — same pattern as `nav-filter.ts` etc.
|
|
6
|
+
*
|
|
7
|
+
* See plans/ask-branch1-placement-toc.md.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type TocMode = "top" | "popover" | "rail" | "off";
|
|
11
|
+
|
|
12
|
+
export interface TocPlacement {
|
|
13
|
+
/** Classic right-hand TOC sidebar (the pre-`toc`-option layout). */
|
|
14
|
+
railToc: boolean;
|
|
15
|
+
/** Expandable "On this page" disclosure at the top of the article. */
|
|
16
|
+
topToc: boolean;
|
|
17
|
+
/** "On this page" dropdown in the header. */
|
|
18
|
+
popoverToc: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The right-rail plugin region (host for the `right-rail` named slot, e.g.
|
|
21
|
+
* an Ask AI panel). Present whenever the rail isn't the classic TOC's, and
|
|
22
|
+
* deliberately NOT gated on headings — so a plugin can dock on heading-less
|
|
23
|
+
* pages. Never shown on wide/API pages (no rail there).
|
|
24
|
+
*/
|
|
25
|
+
regionRail: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether a page has enough displayable headings to warrant a TOC. The TOC
|
|
30
|
+
* (`DocsToc`) only lists depth `minDepth`–`maxDepth` (2–3 by default), so the
|
|
31
|
+
* page H1 never counts; and a TOC of a single entry isn't useful, so require
|
|
32
|
+
* `min` (2) by default. Pages like a landing/welcome page — H1 + prose, no
|
|
33
|
+
* sub-sections — therefore get no "On this page".
|
|
34
|
+
*/
|
|
35
|
+
export function hasDisplayableToc(
|
|
36
|
+
headings: { depth: number }[],
|
|
37
|
+
opts: { minDepth?: number; maxDepth?: number; min?: number } = {},
|
|
38
|
+
): boolean {
|
|
39
|
+
const { minDepth = 2, maxDepth = 3, min = 2 } = opts;
|
|
40
|
+
let count = 0;
|
|
41
|
+
for (const h of headings) {
|
|
42
|
+
if (h.depth >= minDepth && h.depth <= maxDepth) count++;
|
|
43
|
+
if (count >= min) return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface TocPlacementInput {
|
|
49
|
+
/** Page has at least one heading to list. */
|
|
50
|
+
hasHeadings: boolean;
|
|
51
|
+
/** Wide/API layout — composes its own columns, so no right rail. */
|
|
52
|
+
wideLayout: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the placement flags. A TOC container only renders when there are
|
|
57
|
+
* headings to show; the region rail is independent of headings. `rail` and
|
|
58
|
+
* the region rail are mutually exclusive (they compete for the same column),
|
|
59
|
+
* and both are suppressed on wide pages.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveTocPlacement(
|
|
62
|
+
toc: TocMode,
|
|
63
|
+
{ hasHeadings, wideLayout }: TocPlacementInput,
|
|
64
|
+
): TocPlacement {
|
|
65
|
+
return {
|
|
66
|
+
railToc: toc === "rail" && hasHeadings && !wideLayout,
|
|
67
|
+
topToc: toc === "top" && hasHeadings,
|
|
68
|
+
popoverToc: toc === "popover" && hasHeadings,
|
|
69
|
+
regionRail: toc !== "rail" && !wideLayout,
|
|
70
|
+
};
|
|
71
|
+
}
|
package/src/version-redirect.ts
CHANGED
|
@@ -31,6 +31,15 @@ export interface AxisRedirectConfig {
|
|
|
31
31
|
defaultVersion?: string;
|
|
32
32
|
/** Full set of declared version ids. Empty/undefined → axis inactive. */
|
|
33
33
|
knownVersions?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* The version served at the site root (`versions[].path: ""`, the
|
|
36
|
+
* default version). A URL with no version segment is one of its pages,
|
|
37
|
+
* so the version is never injected; a URL starting with another known
|
|
38
|
+
* version's segment is that version's (the longest matching prefix
|
|
39
|
+
* wins). Only the locale redirect remains. format-astro emits no version
|
|
40
|
+
* config at all in that case; this keeps the helper right when given one.
|
|
41
|
+
*/
|
|
42
|
+
rootVersion?: string;
|
|
34
43
|
/** Default locale id; required when locale axis is active. */
|
|
35
44
|
defaultLocale?: string;
|
|
36
45
|
/** Full set of declared locale ids. Empty/undefined → axis inactive. */
|
|
@@ -126,7 +135,8 @@ export function shouldRedirectToDefaultVersion(
|
|
|
126
135
|
if (
|
|
127
136
|
versionAxisActive &&
|
|
128
137
|
cursor < segments.length &&
|
|
129
|
-
knownVersions.has(segments[cursor])
|
|
138
|
+
knownVersions.has(segments[cursor]) &&
|
|
139
|
+
segments[cursor] !== config.rootVersion
|
|
130
140
|
) {
|
|
131
141
|
presentVersion = segments[cursor];
|
|
132
142
|
cursor += 1;
|
|
@@ -134,13 +144,16 @@ export function shouldRedirectToDefaultVersion(
|
|
|
134
144
|
|
|
135
145
|
// What's the rest of the path beyond the recognised axis prefixes?
|
|
136
146
|
const tailSegments = segments.slice(cursor);
|
|
147
|
+
// With a root version, a URL without a version segment is already complete.
|
|
148
|
+
const versionMissing =
|
|
149
|
+
versionAxisActive && presentVersion === undefined && !config.rootVersion;
|
|
137
150
|
if (tailSegments.length === 0) {
|
|
138
151
|
// The path is JUST a known axis prefix (e.g. /docs/en/ or
|
|
139
152
|
// /docs/v1/). Treat as the axis's landing page; no redirect
|
|
140
153
|
// needed unless other axes are MISSING from the URL too.
|
|
141
154
|
if (
|
|
142
155
|
(localeAxisActive && presentLocale === undefined) ||
|
|
143
|
-
|
|
156
|
+
versionMissing
|
|
144
157
|
) {
|
|
145
158
|
// Some axis still missing — fall through to redirect.
|
|
146
159
|
} else {
|
|
@@ -150,7 +163,7 @@ export function shouldRedirectToDefaultVersion(
|
|
|
150
163
|
|
|
151
164
|
// If all required-active axes are present, no redirect needed.
|
|
152
165
|
const localeNeeded = localeAxisActive && presentLocale === undefined;
|
|
153
|
-
const versionNeeded =
|
|
166
|
+
const versionNeeded = versionMissing;
|
|
154
167
|
if (!localeNeeded && !versionNeeded) return null;
|
|
155
168
|
|
|
156
169
|
// Compose the redirect target with missing segments injected
|
|
@@ -159,7 +172,7 @@ export function shouldRedirectToDefaultVersion(
|
|
|
159
172
|
if (localeAxisActive) {
|
|
160
173
|
out.push(presentLocale ?? config.defaultLocale!);
|
|
161
174
|
}
|
|
162
|
-
if (versionAxisActive) {
|
|
175
|
+
if (versionAxisActive && (presentVersion !== undefined || !config.rootVersion)) {
|
|
163
176
|
out.push(presentVersion ?? config.defaultVersion!);
|
|
164
177
|
}
|
|
165
178
|
out.push(...tailSegments);
|