@takazudo/zudo-doc 5.26.5 → 5.28.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 +32 -1
- package/dist/compiled.css +18 -0
- package/dist/config-assertions/index.d.ts +13 -0
- package/dist/config-assertions/index.js +9 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +4 -1
- package/dist/extract-headings/index.d.ts +2 -0
- package/dist/extract-headings/index.js +165 -16
- package/dist/header/header.js +47 -19
- package/dist/header-with-defaults/index.js +5 -1
- package/dist/i18n-defaults/index.js +10 -0
- package/dist/plugins/internal/search-index/collect.js +15 -12
- package/dist/plugins/internal/search-index/types.d.ts +25 -3
- package/dist/plugins/internal/search-index/types.js +1 -1
- package/dist/preset.d.ts +8 -0
- package/dist/preset.js +4 -1
- package/dist/safelist.css +1 -1
- package/dist/search-widget/index.js +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/sidebar-toggle-island/index.d.ts +4 -1
- package/dist/sidebar-toggle-island/index.js +5 -0
- package/dist/sidebar-tree-island/index.d.ts +4 -1
- package/dist/sidebar-tree-island/index.js +5 -5
- package/dist/sidebar-with-defaults/index.js +3 -0
- package/dist/theme/color-scheme-provider.js +40 -10
- package/dist/theme/theme-toggle.d.ts +1 -1
- package/dist/theme-toggle/color-scheme-sync.d.ts +19 -39
- package/dist/theme-toggle/color-scheme-sync.js +81 -6
- package/dist/theme-toggle/index.d.ts +10 -1
- package/dist/theme-toggle/index.js +219 -58
- package/dist/theme-toggle/labels.d.ts +3 -0
- package/dist/theme-toggle/labels.js +12 -0
- package/eject/header/header.tsx +56 -19
- package/eject/sidebar-toggle-island/index.tsx +14 -0
- package/eject/sidebar-tree-island/index.tsx +8 -6
- package/eject/theme-toggle/color-scheme-sync.ts +108 -63
- package/eject/theme-toggle/index.tsx +217 -89
- package/eject/theme-toggle/labels.ts +15 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -8,11 +8,42 @@ The format is based on Keep a Changelog, and release notes are generated from th
|
|
|
8
8
|
|
|
9
9
|
No unreleased changes.
|
|
10
10
|
|
|
11
|
+
## [5.28.0] - 2026-09-27
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- Search now indexes up to 3000 characters per page instead of 300, making longer pages searchable beyond their opening paragraphs. Set `searchMaxBodyLength` to a positive integer to adjust the limit; result snippets retain their existing display length. (332a6184b)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- Heading extraction for the table of contents and link checks ignores heading-like text inside multiline JSX expressions, comments, quoted attributes, and template-literal props, while preserving headings in JSX children. Multiline inline-code spans no longer confuse JSX detection. (b85fe3469, 99ad4252b)
|
|
20
|
+
- Escape closes the mobile drawer's Appearance menu and returns focus to its trigger; a second Escape closes the drawer and focuses the hamburger button. (66359d27c)
|
|
21
|
+
|
|
22
|
+
### Other Changes
|
|
23
|
+
|
|
24
|
+
- Raise the zfb, zfb-runtime, and zfb-md-wasm peer floors to `^2.21.1`, bringing single-Preact-runtime SSR fixes, collection include-filter handling, faster startup, and workspace-package staging and reload fixes. No configuration migration is required. (6686f2da6, e82e57551)
|
|
25
|
+
|
|
26
|
+
## [5.27.0] - 2026-09-24
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
- Added an accessible Appearance menu with Light, Dark, and System choices. System follows device changes, and the selected preference persists across navigation (5bb3d2175, d2146b6e0).
|
|
31
|
+
|
|
32
|
+
### Bug Fixes
|
|
33
|
+
|
|
34
|
+
- Grouped header icons into 40 px controls with no internal gap and 12 px boundaries beside other controls; the Appearance menu also returns keyboard focus to its trigger after closing (4c718aa02).
|
|
35
|
+
- Kept the Appearance menu above the design token panel and within the available viewport (1773a3a21).
|
|
36
|
+
- Corrected heading IDs and table-of-contents links when headings contain escaped punctuation or code spans (59d47f68a).
|
|
37
|
+
|
|
38
|
+
### Other Changes
|
|
39
|
+
|
|
40
|
+
- Raised the minimum supported zfb toolchain peer version to 2.20.2 (e22acafdf).
|
|
41
|
+
|
|
11
42
|
## [5.26.5] - 2026-09-23
|
|
12
43
|
|
|
13
44
|
### Bug Fixes
|
|
14
45
|
|
|
15
|
-
- Fixed package-owned route injection in nested pnpm workspace consumers. Published route sources are staged under the project root so the build shadow can resolve the generated 404, robots, and docs routes (848264b9c).
|
|
46
|
+
- Fixed package-owned route injection in nested pnpm workspace consumers. Published route sources are staged under the project root so the build shadow can resolve the generated 404, robots, and docs routes (848264b9c). Projects scaffolded on 5.26.4 or earlier should add `.zudo-doc/` to their `.gitignore`: the build now writes the staged route sources to `.zudo-doc/routes-src/`, and without the ignore entry `git status` will show it as an untracked directory. New scaffolds already ignore it; see the `create-zudo-doc` changelog.
|
|
16
47
|
|
|
17
48
|
## [5.26.4] - 2026-09-22
|
|
18
49
|
|
package/dist/compiled.css
CHANGED
|
@@ -387,6 +387,9 @@
|
|
|
387
387
|
.z-toolbar {
|
|
388
388
|
z-index: 20;
|
|
389
389
|
}
|
|
390
|
+
.z-tooltip {
|
|
391
|
+
z-index: 80;
|
|
392
|
+
}
|
|
390
393
|
.col-span-full {
|
|
391
394
|
grid-column: 1 / -1;
|
|
392
395
|
}
|
|
@@ -604,6 +607,9 @@
|
|
|
604
607
|
.h-\[14px\] {
|
|
605
608
|
height: 14px;
|
|
606
609
|
}
|
|
610
|
+
.h-\[40px\] {
|
|
611
|
+
height: 40px;
|
|
612
|
+
}
|
|
607
613
|
.h-\[70vh\] {
|
|
608
614
|
height: 70vh;
|
|
609
615
|
}
|
|
@@ -715,6 +721,9 @@
|
|
|
715
721
|
.w-\[18px\] {
|
|
716
722
|
width: 18px;
|
|
717
723
|
}
|
|
724
|
+
.w-\[40px\] {
|
|
725
|
+
width: 40px;
|
|
726
|
+
}
|
|
718
727
|
.w-\[90vw\] {
|
|
719
728
|
width: 90vw;
|
|
720
729
|
}
|
|
@@ -1152,6 +1161,12 @@
|
|
|
1152
1161
|
.bg-accent {
|
|
1153
1162
|
background-color: var(--color-accent);
|
|
1154
1163
|
}
|
|
1164
|
+
.bg-accent\/10 {
|
|
1165
|
+
background-color: var(--color-accent);
|
|
1166
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1167
|
+
background-color: color-mix(in oklab, var(--color-accent) 10%, transparent);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1155
1170
|
.bg-bg {
|
|
1156
1171
|
background-color: var(--color-bg);
|
|
1157
1172
|
}
|
|
@@ -1233,6 +1248,9 @@
|
|
|
1233
1248
|
.p-hsp-xl {
|
|
1234
1249
|
padding: var(--spacing-hsp-xl);
|
|
1235
1250
|
}
|
|
1251
|
+
.p-hsp-xs {
|
|
1252
|
+
padding: var(--spacing-hsp-xs);
|
|
1253
|
+
}
|
|
1236
1254
|
.px-hsp-2xl {
|
|
1237
1255
|
padding-inline: var(--spacing-hsp-2xl);
|
|
1238
1256
|
}
|
|
@@ -68,6 +68,19 @@ export declare function resolvesBundleZdtp(settings: ZdtpBundlingSubject): boole
|
|
|
68
68
|
* in the browser. Fail loudly at config resolution instead (#4261).
|
|
69
69
|
*/
|
|
70
70
|
export declare function assertZdtpBundlingConsistent(settings: ZdtpBundlingSubject): void;
|
|
71
|
+
/**
|
|
72
|
+
* Reject a `searchMaxBodyLength` that is not a positive integer.
|
|
73
|
+
*
|
|
74
|
+
* `undefined` no-ops (the field is optional at this layer — the search-index
|
|
75
|
+
* collector applies its own `MAX_BODY_LENGTH` default). Shared between
|
|
76
|
+
* `zudoDoc()` (`../config.ts`) and `zudoDocPreset()` (same rationale as the
|
|
77
|
+
* other guards above) AND the search-index collector itself
|
|
78
|
+
* (`../plugins/internal/search-index/collect.ts`), which is also reachable
|
|
79
|
+
* directly (tests, or a consumer calling `collectSearchEntries()` outside
|
|
80
|
+
* `zudoDoc()`) — clamping silently there would hide a config typo behind a
|
|
81
|
+
* quietly-wrong index instead of failing loudly (zudolab/zudo-doc#4407).
|
|
82
|
+
*/
|
|
83
|
+
export declare function assertValidSearchMaxBodyLength(searchMaxBodyLength: number | undefined): void;
|
|
71
84
|
/**
|
|
72
85
|
* Warn about `categoryMatch` values that cannot express the intended
|
|
73
86
|
* dropdown grouping.
|
|
@@ -29,6 +29,14 @@ function assertZdtpBundlingConsistent(settings) {
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
function assertValidSearchMaxBodyLength(searchMaxBodyLength) {
|
|
33
|
+
if (searchMaxBodyLength === void 0) return;
|
|
34
|
+
if (!Number.isInteger(searchMaxBodyLength) || searchMaxBodyLength <= 0) {
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
`Invalid searchMaxBodyLength ${JSON.stringify(searchMaxBodyLength)}: must be a positive integer (e.g. 3000).`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
32
40
|
function warnAmbiguousDropdownCategoryMatch(headerNav, logger = console) {
|
|
33
41
|
if (!headerNav) return;
|
|
34
42
|
for (const item of headerNav) {
|
|
@@ -70,6 +78,7 @@ function warnAmbiguousDropdownCategoryMatch(headerNav, logger = console) {
|
|
|
70
78
|
}
|
|
71
79
|
export {
|
|
72
80
|
assertNoEmptyStringFaviconOrLogo,
|
|
81
|
+
assertValidSearchMaxBodyLength,
|
|
73
82
|
assertZdtpBundlingConsistent,
|
|
74
83
|
resolvesBundleZdtp,
|
|
75
84
|
warnAmbiguousDropdownCategoryMatch
|
package/dist/config.d.ts
CHANGED
|
@@ -368,6 +368,20 @@ export interface ZudoDocConfig {
|
|
|
368
368
|
* @default 4
|
|
369
369
|
*/
|
|
370
370
|
tocMaxDepth?: number;
|
|
371
|
+
/**
|
|
372
|
+
* How many characters of each page's plain-text body are indexed for
|
|
373
|
+
* search *matching* (`search-index.json`'s per-entry `body` field). This is
|
|
374
|
+
* a match-depth cap, not a display cap — the search widget already renders
|
|
375
|
+
* only a short match-centred window (`truncate(text, query, 200)`), so
|
|
376
|
+
* raising this value makes long pages searchable past their opening
|
|
377
|
+
* paragraphs without changing what a result snippet shows. Also caps how
|
|
378
|
+
* many bytes are read from a public text asset for its search excerpt (the
|
|
379
|
+
* asset-viewer indexing path in `plugins/internal/search-index/collect.ts`).
|
|
380
|
+
* Must be a positive integer — a non-integer or ≤ 0 value throws at config
|
|
381
|
+
* resolution.
|
|
382
|
+
* @default 3000
|
|
383
|
+
*/
|
|
384
|
+
searchMaxBodyLength?: number;
|
|
371
385
|
/**
|
|
372
386
|
* Enable the draggable sidebar resizer.
|
|
373
387
|
* @default false
|
package/dist/config.js
CHANGED
|
@@ -6,7 +6,8 @@ import { defaultColorSchemes } from "./color-schemes-defaults/index.js";
|
|
|
6
6
|
import { assertNoCommaInVersionSlugs } from "./version-availability/index.js";
|
|
7
7
|
import {
|
|
8
8
|
assertNoEmptyStringFaviconOrLogo,
|
|
9
|
-
assertZdtpBundlingConsistent
|
|
9
|
+
assertZdtpBundlingConsistent,
|
|
10
|
+
assertValidSearchMaxBodyLength
|
|
10
11
|
} from "./config-assertions/index.js";
|
|
11
12
|
import { validateAssetViewerSettings } from "./asset-path/index.js";
|
|
12
13
|
function mergeDefaultTranslations(overrides) {
|
|
@@ -91,6 +92,7 @@ const DEFAULT_SETTINGS = {
|
|
|
91
92
|
designTokenPanel: false,
|
|
92
93
|
tocMinDepth: 2,
|
|
93
94
|
tocMaxDepth: 4,
|
|
95
|
+
searchMaxBodyLength: 3e3,
|
|
94
96
|
sidebarResizer: false,
|
|
95
97
|
sidebarToggle: false,
|
|
96
98
|
tocToggle: false,
|
|
@@ -152,6 +154,7 @@ function zudoDoc(user = {}) {
|
|
|
152
154
|
assertNoCommaInVersionSlugs(settings.versions);
|
|
153
155
|
assertNoEmptyStringFaviconOrLogo(settings);
|
|
154
156
|
assertZdtpBundlingConsistent(settings);
|
|
157
|
+
assertValidSearchMaxBodyLength(settings.searchMaxBodyLength);
|
|
155
158
|
const fragment = zudoDocPreset({
|
|
156
159
|
settings,
|
|
157
160
|
buildDocsSchema: userBuildDocsSchema ?? (() => defaultBuildDocsSchema({
|
|
@@ -38,3 +38,5 @@ export declare function extractHeadings(body: string, opts?: {
|
|
|
38
38
|
tocMinDepth?: number;
|
|
39
39
|
tocMaxDepth?: number;
|
|
40
40
|
}): HeadingItem[];
|
|
41
|
+
/** All rendered h2–h6 IDs, including headings below the TOC depth window. */
|
|
42
|
+
export declare function extractAllHeadingIds(body: string): string[];
|
|
@@ -52,7 +52,39 @@ class SlugAllocator {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
function stripInlineMarkdown(raw) {
|
|
55
|
-
|
|
55
|
+
const protectedText = [];
|
|
56
|
+
const protect = (value) => {
|
|
57
|
+
const index = protectedText.push(value) - 1;
|
|
58
|
+
return `\uE000${index}\uE001`;
|
|
59
|
+
};
|
|
60
|
+
let masked = "";
|
|
61
|
+
for (let i = 0; i < raw.length; ) {
|
|
62
|
+
if (raw[i] === "\\" && i + 1 < raw.length && /[!-/:-@[-`{-~]/.test(raw[i + 1] ?? "")) {
|
|
63
|
+
masked += protect(raw[i + 1] ?? "");
|
|
64
|
+
i += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (raw[i] === "`") {
|
|
68
|
+
const opener = /^`+/.exec(raw.slice(i))?.[0] ?? "`";
|
|
69
|
+
const closer = /`+/g;
|
|
70
|
+
closer.lastIndex = i + opener.length;
|
|
71
|
+
let match;
|
|
72
|
+
while ((match = closer.exec(raw)) !== null && match[0].length !== opener.length) {
|
|
73
|
+
}
|
|
74
|
+
if (match !== null) {
|
|
75
|
+
let code = raw.slice(i + opener.length, match.index).replace(/\n/g, " ");
|
|
76
|
+
if (code.startsWith(" ") && code.endsWith(" ") && /[^ ]/.test(code)) {
|
|
77
|
+
code = code.slice(1, -1);
|
|
78
|
+
}
|
|
79
|
+
masked += protect(code);
|
|
80
|
+
i = match.index + opener.length;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
masked += raw[i];
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
return masked.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(?<![A-Za-z0-9])__([^_]+)__(?![A-Za-z0-9])/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/(?<![A-Za-z0-9])_([^_]+)_(?![A-Za-z0-9])/g, "$1").replace(/\uE000(\d+)\uE001/g, (_match, index) => protectedText[Number(index)] ?? "").trim();
|
|
56
88
|
}
|
|
57
89
|
function resolveDepthWindow(rawMin, rawMax) {
|
|
58
90
|
const min = Math.trunc(Number(rawMin));
|
|
@@ -67,23 +99,18 @@ function extractHeadings(body, opts) {
|
|
|
67
99
|
opts?.tocMinDepth ?? 2,
|
|
68
100
|
opts?.tocMaxDepth ?? 4
|
|
69
101
|
);
|
|
102
|
+
return collectHeadings(body, lo, hi);
|
|
103
|
+
}
|
|
104
|
+
function extractAllHeadingIds(body) {
|
|
105
|
+
return collectHeadings(body, 2, 6).map((heading) => heading.slug).filter(Boolean);
|
|
106
|
+
}
|
|
107
|
+
function collectHeadings(body, lo, hi) {
|
|
70
108
|
const allocator = new SlugAllocator();
|
|
71
109
|
const headings = [];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (fenceMatch) {
|
|
77
|
-
const fence = fenceMatch[1];
|
|
78
|
-
if (fence === void 0) continue;
|
|
79
|
-
if (codeFenceOpener === null) {
|
|
80
|
-
codeFenceOpener = fence;
|
|
81
|
-
} else if (fence[0] === codeFenceOpener[0] && fence.length >= codeFenceOpener.length) {
|
|
82
|
-
codeFenceOpener = null;
|
|
83
|
-
}
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (codeFenceOpener !== null) continue;
|
|
110
|
+
const lines = body.split("\n");
|
|
111
|
+
const candidate = classifyLines(lines);
|
|
112
|
+
for (const [index, line] of lines.entries()) {
|
|
113
|
+
if (candidate[index] !== true) continue;
|
|
87
114
|
const match = /^(#{2,6})[ \t]+(.+)$/.exec(line.trim());
|
|
88
115
|
if (!match) continue;
|
|
89
116
|
const hashes = match[1];
|
|
@@ -98,7 +125,129 @@ function extractHeadings(body, opts) {
|
|
|
98
125
|
}
|
|
99
126
|
return headings;
|
|
100
127
|
}
|
|
128
|
+
const JSX_TAG_CHAR = /[\p{ID_Continue}$\-.:=/>"'{\s]/u;
|
|
129
|
+
const JSX_TAG_START = /[\p{ID_Start}$_/>]/u;
|
|
130
|
+
const ASCII_PUNCTUATION = /[!-/:-@[-`{-~]/;
|
|
131
|
+
const ATX_HEADING = /^#{1,6}(?:[ \t]|$)/;
|
|
132
|
+
function findCodeSpanEnd(lines, from, runLength) {
|
|
133
|
+
for (let lineIndex = from; lineIndex < lines.length; lineIndex++) {
|
|
134
|
+
const line = lines[lineIndex] ?? "";
|
|
135
|
+
const trimmed = line.trimStart();
|
|
136
|
+
if (trimmed.trim() === "" || ATX_HEADING.test(trimmed) || /^(?:`{3,}|~{3,})/.test(trimmed)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
for (const match of line.matchAll(/`+/g)) {
|
|
140
|
+
if (match[0].length === runLength) {
|
|
141
|
+
return { line: lineIndex, col: match.index + runLength };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
function classifyLines(lines) {
|
|
148
|
+
const ignoredOpeners = /* @__PURE__ */ new Set();
|
|
149
|
+
for (; ; ) {
|
|
150
|
+
const result = scanLines(lines, ignoredOpeners);
|
|
151
|
+
if (result.unclosedOpener === null) return result.candidate;
|
|
152
|
+
ignoredOpeners.add(result.unclosedOpener);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function scanLines(lines, ignoredOpeners) {
|
|
156
|
+
const candidate = [];
|
|
157
|
+
let codeFenceOpener = null;
|
|
158
|
+
let exprDepth = 0;
|
|
159
|
+
let tag = null;
|
|
160
|
+
let opener = null;
|
|
161
|
+
let codeSpanEnd = null;
|
|
162
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
163
|
+
let start = 0;
|
|
164
|
+
if (codeSpanEnd !== null) {
|
|
165
|
+
candidate.push(false);
|
|
166
|
+
if (lineIndex < codeSpanEnd.line) continue;
|
|
167
|
+
start = codeSpanEnd.col;
|
|
168
|
+
codeSpanEnd = null;
|
|
169
|
+
}
|
|
170
|
+
const trimmed = line.trimStart();
|
|
171
|
+
if (start === 0 && tag !== null && tag.quote === null && exprDepth === 0) {
|
|
172
|
+
const first = trimmed[0];
|
|
173
|
+
if (first !== void 0 && !JSX_TAG_CHAR.test(first)) tag = null;
|
|
174
|
+
}
|
|
175
|
+
if (start > 0) {
|
|
176
|
+
} else if (exprDepth > 0 || tag !== null) {
|
|
177
|
+
candidate.push(false);
|
|
178
|
+
} else {
|
|
179
|
+
const fence = /^([`~]{3,})/.exec(trimmed)?.[1];
|
|
180
|
+
if (fence !== void 0) {
|
|
181
|
+
if (codeFenceOpener === null) {
|
|
182
|
+
codeFenceOpener = fence;
|
|
183
|
+
} else if (fence[0] === codeFenceOpener[0] && fence.length >= codeFenceOpener.length) {
|
|
184
|
+
codeFenceOpener = null;
|
|
185
|
+
}
|
|
186
|
+
candidate.push(false);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
candidate.push(codeFenceOpener === null);
|
|
190
|
+
if (codeFenceOpener !== null) continue;
|
|
191
|
+
}
|
|
192
|
+
for (let i = start; i < line.length; i++) {
|
|
193
|
+
const ch = line[i] ?? "";
|
|
194
|
+
if (exprDepth > 0) {
|
|
195
|
+
if (ch === "{") exprDepth++;
|
|
196
|
+
else if (ch === "}") exprDepth--;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (tag !== null) {
|
|
200
|
+
if (tag.quote !== null) {
|
|
201
|
+
if (ch === tag.quote) tag.quote = null;
|
|
202
|
+
} else if (ch === '"' || ch === "'") {
|
|
203
|
+
tag.quote = ch;
|
|
204
|
+
} else if (ch === "{") {
|
|
205
|
+
exprDepth = 1;
|
|
206
|
+
} else if (ch === ">") {
|
|
207
|
+
tag = null;
|
|
208
|
+
} else if (!JSX_TAG_CHAR.test(ch)) {
|
|
209
|
+
tag = null;
|
|
210
|
+
i--;
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (ch === "\\" && ASCII_PUNCTUATION.test(line[i + 1] ?? "")) {
|
|
215
|
+
i++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (ch === "`") {
|
|
219
|
+
const run = /^`+/.exec(line.slice(i))?.[0] ?? "`";
|
|
220
|
+
const closer = /`+/g;
|
|
221
|
+
closer.lastIndex = i + run.length;
|
|
222
|
+
let match;
|
|
223
|
+
while ((match = closer.exec(line)) !== null && match[0].length !== run.length) {
|
|
224
|
+
}
|
|
225
|
+
if (match === null && !ATX_HEADING.test(trimmed)) {
|
|
226
|
+
codeSpanEnd = findCodeSpanEnd(lines, lineIndex + 1, run.length);
|
|
227
|
+
if (codeSpanEnd !== null) break;
|
|
228
|
+
}
|
|
229
|
+
i = (match === null ? i : match.index) + run.length - 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const position = `${lineIndex}:${i}`;
|
|
233
|
+
if (ignoredOpeners.has(position)) continue;
|
|
234
|
+
if (ch === "{") {
|
|
235
|
+
exprDepth = 1;
|
|
236
|
+
opener = position;
|
|
237
|
+
} else if (ch === "<" && JSX_TAG_START.test(line[i + 1] ?? "")) {
|
|
238
|
+
tag = { quote: null };
|
|
239
|
+
opener = position;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (exprDepth === 0 && tag === null) opener = null;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
candidate,
|
|
246
|
+
unclosedOpener: exprDepth > 0 || tag !== null ? opener : null
|
|
247
|
+
};
|
|
248
|
+
}
|
|
101
249
|
export {
|
|
250
|
+
extractAllHeadingIds,
|
|
102
251
|
extractHeadings,
|
|
103
252
|
slugify
|
|
104
253
|
};
|
package/dist/header/header.js
CHANGED
|
@@ -49,6 +49,43 @@ function Header(props) {
|
|
|
49
49
|
const matchPath = pathForMatch(pathWithoutVersion, lang, i18n.defaultLocale);
|
|
50
50
|
const activeNavPath = computeActiveNavPath(headerNav, matchPath);
|
|
51
51
|
const rightItemDispatch = createRightItemDispatch(headerRightComponents);
|
|
52
|
+
const rightItems = headerRightItems.map((item, i) => ({
|
|
53
|
+
item,
|
|
54
|
+
node: renderRightItem(
|
|
55
|
+
item,
|
|
56
|
+
i,
|
|
57
|
+
{
|
|
58
|
+
lang,
|
|
59
|
+
githubRepoUrl,
|
|
60
|
+
githubLabel,
|
|
61
|
+
themeToggle,
|
|
62
|
+
languageSwitcher,
|
|
63
|
+
versionSwitcher,
|
|
64
|
+
search,
|
|
65
|
+
colorModeEnabled,
|
|
66
|
+
hasLocales
|
|
67
|
+
},
|
|
68
|
+
rightItemDispatch
|
|
69
|
+
)
|
|
70
|
+
})).filter((entry) => entry.node !== null);
|
|
71
|
+
const rightGroups = [];
|
|
72
|
+
let iconGroup = [];
|
|
73
|
+
const flushIconGroup = () => {
|
|
74
|
+
if (iconGroup.length === 0) return;
|
|
75
|
+
rightGroups.push(
|
|
76
|
+
/* @__PURE__ */ jsx("div", { class: "flex items-center", "data-header-icon-group": true, children: iconGroup })
|
|
77
|
+
);
|
|
78
|
+
iconGroup = [];
|
|
79
|
+
};
|
|
80
|
+
for (const { item, node } of rightItems) {
|
|
81
|
+
if (isHeaderIconItem(item)) {
|
|
82
|
+
iconGroup.push(node);
|
|
83
|
+
} else {
|
|
84
|
+
flushIconGroup();
|
|
85
|
+
rightGroups.push(node);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
flushIconGroup();
|
|
52
89
|
return /* @__PURE__ */ jsxs(
|
|
53
90
|
"header",
|
|
54
91
|
{
|
|
@@ -123,22 +160,7 @@ function Header(props) {
|
|
|
123
160
|
{
|
|
124
161
|
class: "ml-auto flex shrink-0 items-center gap-x-hsp-md",
|
|
125
162
|
"data-header-right": true,
|
|
126
|
-
children:
|
|
127
|
-
item,
|
|
128
|
-
i,
|
|
129
|
-
{
|
|
130
|
-
lang,
|
|
131
|
-
githubRepoUrl,
|
|
132
|
-
githubLabel,
|
|
133
|
-
themeToggle,
|
|
134
|
-
languageSwitcher,
|
|
135
|
-
versionSwitcher,
|
|
136
|
-
search,
|
|
137
|
-
colorModeEnabled,
|
|
138
|
-
hasLocales
|
|
139
|
-
},
|
|
140
|
-
rightItemDispatch
|
|
141
|
-
))
|
|
163
|
+
children: rightGroups
|
|
142
164
|
}
|
|
143
165
|
),
|
|
144
166
|
/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: NAV_OVERFLOW_SCRIPT } }),
|
|
@@ -271,6 +293,12 @@ function stripCurrentVersionPrefix(path, currentVersion) {
|
|
|
271
293
|
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length);
|
|
272
294
|
return path;
|
|
273
295
|
}
|
|
296
|
+
function isHeaderIconItem(item) {
|
|
297
|
+
if (item.type === "trigger") return true;
|
|
298
|
+
if (item.type === "link") return item.icon === "github";
|
|
299
|
+
if (item.type !== "component") return false;
|
|
300
|
+
return ["github-link", "theme-toggle", "search"].includes(item.component);
|
|
301
|
+
}
|
|
274
302
|
function TriggerButton({
|
|
275
303
|
index,
|
|
276
304
|
id,
|
|
@@ -286,7 +314,7 @@ function TriggerButton({
|
|
|
286
314
|
{
|
|
287
315
|
id,
|
|
288
316
|
type: "button",
|
|
289
|
-
class: "flex items-center justify-center text-muted transition-colors hover:text-fg",
|
|
317
|
+
class: "flex h-[40px] w-[40px] shrink-0 items-center justify-center text-muted transition-colors hover:text-fg focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2",
|
|
290
318
|
"aria-label": ariaLabel,
|
|
291
319
|
...inlineOnclick,
|
|
292
320
|
children
|
|
@@ -373,7 +401,7 @@ const BASE_RIGHT_ITEM_DISPATCH = {
|
|
|
373
401
|
href: ctx.githubRepoUrl,
|
|
374
402
|
target: "_blank",
|
|
375
403
|
rel: "noopener noreferrer",
|
|
376
|
-
class: "flex items-center justify-center text-muted transition-colors hover:text-fg",
|
|
404
|
+
class: "flex h-[40px] w-[40px] shrink-0 items-center justify-center text-muted transition-colors hover:text-fg focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2",
|
|
377
405
|
"aria-label": ctx.githubLabel,
|
|
378
406
|
title: ctx.githubLabel,
|
|
379
407
|
children: [
|
|
@@ -403,7 +431,7 @@ const BASE_RIGHT_ITEM_DISPATCH = {
|
|
|
403
431
|
href: item.href,
|
|
404
432
|
target: isExternal ? "_blank" : void 0,
|
|
405
433
|
rel: isExternal ? "noopener noreferrer" : void 0,
|
|
406
|
-
class: "flex items-center justify-center text-muted transition-colors hover:text-fg",
|
|
434
|
+
class: item.icon === "github" ? "flex h-[40px] w-[40px] shrink-0 items-center justify-center text-muted transition-colors hover:text-fg focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2" : "flex items-center justify-center text-muted transition-colors hover:text-fg",
|
|
407
435
|
"aria-label": item.ariaLabel,
|
|
408
436
|
title: label,
|
|
409
437
|
children: item.icon === "github" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
VersionSwitcher
|
|
7
7
|
} from "../i18n-version/index.js";
|
|
8
8
|
import { ThemeToggle } from "../theme-toggle/index.js";
|
|
9
|
+
import { themeToggleLabels } from "../theme-toggle/labels.js";
|
|
9
10
|
import { SidebarToggle } from "../sidebar-toggle-island/index.js";
|
|
10
11
|
import { buildGitHubRepoUrl as buildGitHubRepoUrlBase } from "../github-helpers/index.js";
|
|
11
12
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
@@ -47,6 +48,7 @@ function createHeaderWithDefaults(ctx) {
|
|
|
47
48
|
const backToMenuLabel = t("nav.backToMenu", lang);
|
|
48
49
|
const localeLinks = buildLocaleLinksForNav(currentPath, lang, locales.length);
|
|
49
50
|
const themeDefaultMode = getThemeDefaultMode();
|
|
51
|
+
const themeRespectSystem = (ctx.settings.colorMode && ctx.settings.colorMode.respectPrefersColorScheme) ?? true;
|
|
50
52
|
const buildDefaultSidebarNodes = () => buildSidebarNodes(lang, navSection, currentVersion);
|
|
51
53
|
const sidebarNodes = sidebarNodesProp === void 0 ? buildDefaultSidebarNodes() : typeof sidebarNodesProp === "function" ? sidebarNodesProp({
|
|
52
54
|
lang,
|
|
@@ -66,13 +68,15 @@ function createHeaderWithDefaults(ctx) {
|
|
|
66
68
|
locale: lang,
|
|
67
69
|
localeLinks,
|
|
68
70
|
themeDefaultMode,
|
|
71
|
+
themeLabels: themeToggleLabels(t, lang),
|
|
72
|
+
themeRespectSystem,
|
|
69
73
|
dateFormats: dateFormatsFor(lang)
|
|
70
74
|
}
|
|
71
75
|
)
|
|
72
76
|
});
|
|
73
77
|
const themeToggle = Island({
|
|
74
78
|
when: "load",
|
|
75
|
-
children: /* @__PURE__ */ jsx(ThemeToggle, { defaultMode: themeDefaultMode, pendingUntilHydrated: true })
|
|
79
|
+
children: /* @__PURE__ */ jsx(ThemeToggle, { defaultMode: themeDefaultMode, labels: themeToggleLabels(t, lang), respectPrefersColorScheme: themeRespectSystem, pendingUntilHydrated: true })
|
|
76
80
|
});
|
|
77
81
|
const searchWidget = /* @__PURE__ */ jsx(
|
|
78
82
|
SearchWidget,
|
|
@@ -37,6 +37,11 @@ const defaultTranslations = {
|
|
|
37
37
|
"doc.editPage": "Edit this page",
|
|
38
38
|
"doc.viewSource": "View source on GitHub",
|
|
39
39
|
"header.github": "GitHub repository",
|
|
40
|
+
"appearance.title": "Appearance",
|
|
41
|
+
"appearance.light": "Light",
|
|
42
|
+
"appearance.dark": "Dark",
|
|
43
|
+
"appearance.system": "System",
|
|
44
|
+
"appearance.systemHelper": "Follows device \xB7 currently {mode}",
|
|
40
45
|
"language.switcher.label": "Language",
|
|
41
46
|
"doc.tags": "Tags",
|
|
42
47
|
"doc.taggedWith": "Pages tagged with",
|
|
@@ -167,6 +172,11 @@ const defaultTranslations = {
|
|
|
167
172
|
"doc.editPage": "\u3053\u306E\u30DA\u30FC\u30B8\u3092\u7DE8\u96C6",
|
|
168
173
|
"doc.viewSource": "GitHub \u3067\u30BD\u30FC\u30B9\u3092\u898B\u308B",
|
|
169
174
|
"header.github": "GitHub \u30EA\u30DD\u30B8\u30C8\u30EA",
|
|
175
|
+
"appearance.title": "\u5916\u89B3",
|
|
176
|
+
"appearance.light": "\u30E9\u30A4\u30C8",
|
|
177
|
+
"appearance.dark": "\u30C0\u30FC\u30AF",
|
|
178
|
+
"appearance.system": "\u30B7\u30B9\u30C6\u30E0",
|
|
179
|
+
"appearance.systemHelper": "\u30C7\u30D0\u30A4\u30B9\u306E\u8A2D\u5B9A\u306B\u5F93\u3044\u307E\u3059 \xB7 \u73FE\u5728\u306F{mode}",
|
|
170
180
|
"language.switcher.label": "\u8A00\u8A9E",
|
|
171
181
|
"doc.tags": "\u30BF\u30B0",
|
|
172
182
|
"doc.taggedWith": "\u30BF\u30B0\u4ED8\u304D\u30DA\u30FC\u30B8",
|
|
@@ -8,14 +8,15 @@ import {
|
|
|
8
8
|
stripMarkdown
|
|
9
9
|
} from "../../../md-utils/index.js";
|
|
10
10
|
import { collectAssetPageDescriptors } from "../asset-viewer/asset-pages.js";
|
|
11
|
+
import { assertValidSearchMaxBodyLength } from "../../../config-assertions/index.js";
|
|
11
12
|
import {
|
|
12
13
|
MAX_BODY_LENGTH
|
|
13
14
|
} from "./types.js";
|
|
14
|
-
function truncateBody(text) {
|
|
15
|
-
return text.length >
|
|
15
|
+
function truncateBody(text, maxBodyLength) {
|
|
16
|
+
return text.length > maxBodyLength ? text.substring(0, maxBodyLength) : text;
|
|
16
17
|
}
|
|
17
|
-
function readAssetExcerpt(filePath) {
|
|
18
|
-
const sample = Buffer.allocUnsafe(
|
|
18
|
+
function readAssetExcerpt(filePath, maxBodyLength) {
|
|
19
|
+
const sample = Buffer.allocUnsafe(maxBodyLength * 4);
|
|
19
20
|
const fd = openSync(filePath, "r");
|
|
20
21
|
try {
|
|
21
22
|
let offset = 0;
|
|
@@ -30,12 +31,12 @@ function readAssetExcerpt(filePath) {
|
|
|
30
31
|
if (bytesRead === 0) break;
|
|
31
32
|
offset += bytesRead;
|
|
32
33
|
}
|
|
33
|
-
return truncateBody(sample.subarray(0, offset).toString("utf8"));
|
|
34
|
+
return truncateBody(sample.subarray(0, offset).toString("utf8"), maxBodyLength);
|
|
34
35
|
} finally {
|
|
35
36
|
closeSync(fd);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
|
-
function buildEntries(contentDir, locale, base) {
|
|
39
|
+
function buildEntries(contentDir, locale, base, maxBodyLength) {
|
|
39
40
|
const absDir = resolve(contentDir);
|
|
40
41
|
const files = collectMdFiles(absDir);
|
|
41
42
|
const entries = [];
|
|
@@ -49,14 +50,14 @@ function buildEntries(contentDir, locale, base) {
|
|
|
49
50
|
entries.push({
|
|
50
51
|
id,
|
|
51
52
|
title: data.title ?? slug,
|
|
52
|
-
body: truncateBody(stripMarkdown(content)),
|
|
53
|
+
body: truncateBody(stripMarkdown(content), maxBodyLength),
|
|
53
54
|
url: slugToUrl(slug, locale, base),
|
|
54
55
|
description: data.description ?? ""
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
return entries;
|
|
58
59
|
}
|
|
59
|
-
function buildAssetEntries(config) {
|
|
60
|
+
function buildAssetEntries(config, maxBodyLength) {
|
|
60
61
|
const { assetScan, projectRoot } = config;
|
|
61
62
|
if (assetScan === void 0 || projectRoot === void 0) return [];
|
|
62
63
|
const descriptors = collectAssetPageDescriptors({
|
|
@@ -67,7 +68,7 @@ function buildAssetEntries(config) {
|
|
|
67
68
|
const assetRoot = resolve(projectRoot, "public", assetScan.assetViewerDir);
|
|
68
69
|
return descriptors.map((descriptor) => {
|
|
69
70
|
const localePrefix = descriptor.locale === void 0 ? "" : `${descriptor.locale}/`;
|
|
70
|
-
const body = descriptor.isText ? readAssetExcerpt(resolve(assetRoot, descriptor.path)) : "";
|
|
71
|
+
const body = descriptor.isText ? readAssetExcerpt(resolve(assetRoot, descriptor.path), maxBodyLength) : "";
|
|
71
72
|
return {
|
|
72
73
|
id: `asset:${localePrefix}${assetScan.assetViewerRoutePrefix}/${descriptor.path}`,
|
|
73
74
|
title: basename(descriptor.path),
|
|
@@ -78,15 +79,17 @@ function buildAssetEntries(config) {
|
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
function collectSearchEntries(config) {
|
|
82
|
+
assertValidSearchMaxBodyLength(config.maxBodyLength);
|
|
83
|
+
const maxBodyLength = config.maxBodyLength ?? MAX_BODY_LENGTH;
|
|
81
84
|
const base = config.base ?? "";
|
|
82
85
|
const entries = [];
|
|
83
|
-
entries.push(...buildEntries(config.docsDir, null, base));
|
|
86
|
+
entries.push(...buildEntries(config.docsDir, null, base, maxBodyLength));
|
|
84
87
|
if (config.locales) {
|
|
85
88
|
for (const [code, locale] of Object.entries(config.locales)) {
|
|
86
|
-
entries.push(...buildEntries(locale.dir, code, base));
|
|
89
|
+
entries.push(...buildEntries(locale.dir, code, base, maxBodyLength));
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
|
-
entries.push(...buildAssetEntries(config));
|
|
92
|
+
entries.push(...buildAssetEntries(config, maxBodyLength));
|
|
90
93
|
return entries;
|
|
91
94
|
}
|
|
92
95
|
export {
|