jamdesk 1.1.35 → 1.1.38
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/dist/__tests__/integration/init.integration.test.js +41 -0
- package/dist/__tests__/integration/init.integration.test.js.map +1 -1
- package/dist/__tests__/unit/docs-config.test.js +17 -0
- package/dist/__tests__/unit/docs-config.test.js.map +1 -1
- package/dist/__tests__/unit/init.test.js +2 -1
- package/dist/__tests__/unit/init.test.js.map +1 -1
- package/dist/lib/docs-config.d.ts +4 -1
- package/dist/lib/docs-config.d.ts.map +1 -1
- package/dist/lib/docs-config.js +27 -23
- package/dist/lib/docs-config.js.map +1 -1
- package/package.json +1 -1
- package/templates/api-reference/openapi-example.mdx +55 -0
- package/templates/api-reference/request-response-examples.mdx +210 -0
- package/templates/docs.json +27 -0
- package/templates/openapi/example-api.yaml +185 -0
- package/vendored/app/[[...slug]]/page.tsx +26 -8
- package/vendored/app/api/chat/[project]/route.ts +53 -3
- package/vendored/app/api/docs-search/[project]/search/route.ts +83 -3
- package/vendored/app/layout.tsx +26 -3
- package/vendored/components/HtmlLangSync.tsx +38 -0
- package/vendored/components/mdx/OpenApiEndpoint.tsx +2 -1
- package/vendored/components/navigation/LanguageSelector.tsx +18 -21
- package/vendored/components/navigation/TableOfContents.tsx +18 -3
- package/vendored/components/search/SearchModal.tsx +7 -14
- package/vendored/hooks/useChat.ts +22 -4
- package/vendored/lib/chat-prompt.ts +1 -1
- package/vendored/lib/chat-tools.ts +3 -0
- package/vendored/lib/embedding-chunker.ts +18 -2
- package/vendored/lib/language-codes.json +27 -0
- package/vendored/lib/language-utils.ts +98 -6
- package/vendored/lib/link-rewriter.ts +67 -0
- package/vendored/lib/locale-helpers.ts +62 -0
- package/vendored/lib/middleware-helpers.ts +57 -2
- package/vendored/lib/openapi/code-examples.ts +5 -6
- package/vendored/lib/openapi/derive-auth.ts +46 -0
- package/vendored/lib/openapi/index.ts +7 -0
- package/vendored/lib/openapi/parser.ts +7 -2
- package/vendored/lib/openapi/resolve-server-url.ts +14 -0
- package/vendored/lib/openapi/types.ts +2 -0
- package/vendored/lib/page-isr-helpers.ts +20 -0
- package/vendored/lib/path-safety.ts +96 -0
- package/vendored/lib/search-client.ts +67 -10
- package/vendored/lib/seo.ts +80 -13
- package/vendored/lib/static-artifacts.ts +25 -1
- package/vendored/lib/vector-store.ts +70 -17
- package/vendored/scripts/build-search-index.cjs +59 -0
- package/vendored/scripts/validate-links.cjs +21 -66
- package/vendored/themes/base.css +5 -0
- package/vendored/workspace-package-lock.json +16 -16
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
import type { NavigationConfig } from './docs-types.js';
|
|
9
9
|
import { RECURSE_KEYS } from './enhance-navigation.js';
|
|
10
10
|
import { filterVisibility } from './visibility-filter.js';
|
|
11
|
+
import {
|
|
12
|
+
buildLoweredLocaleSet,
|
|
13
|
+
resolveLocaleFromPath,
|
|
14
|
+
resolveLocaleWithLoweredSet,
|
|
15
|
+
} from './language-utils.js';
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Page metadata for artifact generation.
|
|
@@ -585,6 +590,8 @@ export interface SearchDocument {
|
|
|
585
590
|
slug: string;
|
|
586
591
|
section?: string;
|
|
587
592
|
type: string;
|
|
593
|
+
/** Language of the document. Empty string for default-language pages. */
|
|
594
|
+
locale: string;
|
|
588
595
|
}
|
|
589
596
|
|
|
590
597
|
/**
|
|
@@ -602,6 +609,14 @@ export interface SearchPageInfo {
|
|
|
602
609
|
};
|
|
603
610
|
}
|
|
604
611
|
|
|
612
|
+
/** Whitelist-gated alias of `resolveLocaleFromPath`, named for the search-index domain. */
|
|
613
|
+
export function detectLocaleFromSlug(
|
|
614
|
+
slug: string,
|
|
615
|
+
projectLanguages: readonly string[],
|
|
616
|
+
): string {
|
|
617
|
+
return resolveLocaleFromPath(slug, projectLanguages);
|
|
618
|
+
}
|
|
619
|
+
|
|
605
620
|
/**
|
|
606
621
|
* Infer page type from slug path.
|
|
607
622
|
*/
|
|
@@ -668,15 +683,23 @@ export function extractSections(content: string): Array<{ heading: string; conte
|
|
|
668
683
|
* Generate search data from page content.
|
|
669
684
|
*
|
|
670
685
|
* @param pages - Array of page info with content
|
|
686
|
+
* @param projectLanguages - Language codes declared in docs.json.navigation.languages.
|
|
687
|
+
* Used as the locale whitelist; slugs whose prefix is not in this list are
|
|
688
|
+
* tagged as default-language (locale='').
|
|
671
689
|
* @returns JSON string of search documents
|
|
672
690
|
*/
|
|
673
|
-
export function generateSearchData(
|
|
691
|
+
export function generateSearchData(
|
|
692
|
+
pages: SearchPageInfo[],
|
|
693
|
+
projectLanguages: readonly string[],
|
|
694
|
+
): string {
|
|
674
695
|
const documents: SearchDocument[] = [];
|
|
696
|
+
const loweredLanguages = buildLoweredLocaleSet(projectLanguages);
|
|
675
697
|
|
|
676
698
|
for (const page of pages) {
|
|
677
699
|
const pathWithoutExt = page.path.replace(/\.mdx?$/, '');
|
|
678
700
|
const slug = pathWithoutExt.replace(/\\/g, '/');
|
|
679
701
|
const pageType = inferPageType(slug);
|
|
702
|
+
const locale = resolveLocaleWithLoweredSet(slug, loweredLanguages);
|
|
680
703
|
// Filter for="agents" content out of the search index — the site
|
|
681
704
|
// search is a human-facing surface, so agent-only content must not
|
|
682
705
|
// leak into autocomplete.
|
|
@@ -694,6 +717,7 @@ export function generateSearchData(pages: SearchPageInfo[]): string {
|
|
|
694
717
|
slug,
|
|
695
718
|
section: section.heading || undefined,
|
|
696
719
|
type: pageType,
|
|
720
|
+
locale,
|
|
697
721
|
});
|
|
698
722
|
}
|
|
699
723
|
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { Index, FusionAlgorithm, WeightingStrategy, QueryMode } from '@upstash/vector';
|
|
13
13
|
import type { EmbeddingChunk } from './embedding-chunker.js';
|
|
14
|
+
import { logger } from '../shared/logger';
|
|
14
15
|
|
|
15
16
|
export interface ChunkMetadata {
|
|
16
17
|
[key: string]: unknown;
|
|
@@ -18,6 +19,10 @@ export interface ChunkMetadata {
|
|
|
18
19
|
sectionHeading: string;
|
|
19
20
|
pageTitle: string;
|
|
20
21
|
content: string;
|
|
22
|
+
/** Locale code (lowercased, e.g. "en", "es", "pt-br"). Absent for chunks
|
|
23
|
+
* written by single-language projects (no `i18n.languages` config) — the
|
|
24
|
+
* filter is gated per-project so unfiltered queries return all chunks. */
|
|
25
|
+
locale?: string;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/** Upstash limit per upsert call */
|
|
@@ -78,20 +83,24 @@ export async function upsertChunks(
|
|
|
78
83
|
for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
|
|
79
84
|
const batch = chunks.slice(i, i + BATCH_SIZE);
|
|
80
85
|
await ns.upsert(
|
|
81
|
-
batch.map(c =>
|
|
82
|
-
|
|
83
|
-
// Prefix + body goes to Upstash for embedding/BM25; metadata.content
|
|
84
|
-
// stays prefix-free so consumers display clean body text.
|
|
85
|
-
data: c.prefix + c.content,
|
|
86
|
-
metadata: {
|
|
86
|
+
batch.map(c => {
|
|
87
|
+
const metadata: ChunkMetadata = {
|
|
87
88
|
pageSlug: c.pageSlug,
|
|
88
89
|
sectionHeading: c.sectionHeading,
|
|
89
90
|
pageTitle: c.pageTitle,
|
|
90
91
|
content: c.content.length > MAX_METADATA_CONTENT_CHARS
|
|
91
92
|
? c.content.slice(0, MAX_METADATA_CONTENT_CHARS) + '...'
|
|
92
93
|
: c.content,
|
|
93
|
-
}
|
|
94
|
-
|
|
94
|
+
};
|
|
95
|
+
if (c.locale) metadata.locale = c.locale; // omit when null
|
|
96
|
+
return {
|
|
97
|
+
id: c.id,
|
|
98
|
+
// Prefix + body goes to Upstash for embedding/BM25; metadata.content
|
|
99
|
+
// stays prefix-free so consumers display clean body text.
|
|
100
|
+
data: c.prefix + c.content,
|
|
101
|
+
metadata,
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
95
104
|
);
|
|
96
105
|
}
|
|
97
106
|
}
|
|
@@ -130,13 +139,12 @@ export function extractTopicQuery(queryText: string): string | null {
|
|
|
130
139
|
/** Query with hybrid mode, falling back to dense-only if hybrid is not supported */
|
|
131
140
|
async function queryWithFallback(
|
|
132
141
|
ns: ReturnType<typeof getNamespace>,
|
|
133
|
-
params: { data: string; topK: number; includeMetadata: true },
|
|
142
|
+
params: { data: string; topK: number; includeMetadata: true; filter?: string },
|
|
134
143
|
): Promise<Array<{ id: string | number; score: number; metadata?: ChunkMetadata }>> {
|
|
135
144
|
try {
|
|
136
145
|
return await ns.query<ChunkMetadata>({ ...params, ...HYBRID_QUERY_OPTS });
|
|
137
146
|
} catch (err) {
|
|
138
|
-
|
|
139
|
-
console.warn('[vector-store] Hybrid query failed, falling back to dense-only:', String(err));
|
|
147
|
+
logger.warn('vector-store: hybrid query failed, falling back to dense-only', { error: String(err) });
|
|
140
148
|
return await ns.query<ChunkMetadata>({ ...params, queryMode: QueryMode.DENSE });
|
|
141
149
|
}
|
|
142
150
|
}
|
|
@@ -184,6 +192,10 @@ function filterAndMerge(
|
|
|
184
192
|
* request-pattern words (e.g. "give me a javascript example") from diluting
|
|
185
193
|
* the topic signal in the embedding.
|
|
186
194
|
*
|
|
195
|
+
* When `options.locale` is set, a strict Upstash filter expression is applied
|
|
196
|
+
* so only chunks for that locale are returned. No retry-without-filter on 0
|
|
197
|
+
* results — migration safety lives in the per-project gate (Task A5).
|
|
198
|
+
*
|
|
187
199
|
* Returns up to `topK` results with their similarity scores,
|
|
188
200
|
* filtering out any results with missing metadata.
|
|
189
201
|
*/
|
|
@@ -191,25 +203,66 @@ export async function querySimilarChunks(
|
|
|
191
203
|
projectId: string,
|
|
192
204
|
queryText: string,
|
|
193
205
|
topK = 5,
|
|
206
|
+
options: { locale?: string } = {},
|
|
194
207
|
): Promise<Array<ChunkMetadata & { score: number }>> {
|
|
195
208
|
const ns = getNamespace(projectId);
|
|
196
|
-
const
|
|
209
|
+
const locale = options.locale ? normalizeLocaleForFilter(options.locale) : undefined;
|
|
210
|
+
// Defense-in-depth: A5 rejects malformed locales at the API boundary. If a
|
|
211
|
+
// truthy locale here normalizes to empty, A5's guard was bypassed (test or
|
|
212
|
+
// internal caller) — surface it loudly rather than silently dropping the filter.
|
|
213
|
+
if (options.locale && !locale) {
|
|
214
|
+
logger.warn('vector-store: locale normalized to empty — filter skipped', { rawLocale: options.locale });
|
|
215
|
+
}
|
|
216
|
+
const filter = locale ? buildLocaleFilter(locale) : undefined;
|
|
217
|
+
// When filtering, raise effective topK by ~33% so we still get ~topK chunks
|
|
218
|
+
// back from a mixed-language namespace where filtering cuts the candidate set.
|
|
219
|
+
const effectiveTopK = filter ? Math.ceil(topK * 1.33) : topK;
|
|
220
|
+
const queryParams = { topK: effectiveTopK, includeMetadata: true as const, filter };
|
|
197
221
|
|
|
198
222
|
const topicQuery = extractTopicQuery(queryText);
|
|
199
223
|
|
|
200
224
|
// Dual-query: topic query is the PRIMARY source (better topical relevance);
|
|
201
225
|
// the full query fills remaining slots with unique results only.
|
|
226
|
+
let merged: Array<ChunkMetadata & { score: number }>;
|
|
202
227
|
if (topicQuery) {
|
|
203
228
|
const [fullResults, topicResults] = await Promise.all([
|
|
204
229
|
queryWithFallback(ns, { data: queryText, ...queryParams }),
|
|
205
230
|
queryWithFallback(ns, { data: topicQuery, ...queryParams }),
|
|
206
231
|
]);
|
|
232
|
+
merged = filterAndMerge([topicResults, fullResults], topK);
|
|
233
|
+
} else {
|
|
234
|
+
const results = await queryWithFallback(ns, { data: queryText, ...queryParams });
|
|
235
|
+
merged = filterAndMerge([results], topK);
|
|
236
|
+
}
|
|
207
237
|
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
// Telemetry: a filtered query that returns materially fewer chunks than
|
|
239
|
+
// requested signals the locale filter is hurting recall (project skewed away
|
|
240
|
+
// from the requested locale, or filter syntax matched a near-empty subset).
|
|
241
|
+
// Surface it so we can decide whether to widen the carve-out, increase the
|
|
242
|
+
// 1.33× boost, or rebuild the project's index.
|
|
243
|
+
if (filter && merged.length < Math.ceil(topK / 2)) {
|
|
244
|
+
logger.warn('vector-store: locale filter under-fills topK', {
|
|
245
|
+
projectId, locale, returned: merged.length, requested: topK,
|
|
246
|
+
});
|
|
210
247
|
}
|
|
211
248
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
return merged;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Validate + normalize a locale code for use inside an Upstash filter
|
|
254
|
+
* expression. We allow [a-z0-9_-] (covers `en`, `pt-br`, `zh-Hans` after
|
|
255
|
+
* lowercasing) and lowercase to match how chunks were stored. Any other
|
|
256
|
+
* character is dropped — defense against filter injection from a malformed
|
|
257
|
+
* client request. The route layer rejects bad locales at the API boundary
|
|
258
|
+
* (Task A5), so this strip is belt-and-suspenders.
|
|
259
|
+
*/
|
|
260
|
+
function normalizeLocaleForFilter(value: string): string {
|
|
261
|
+
return value.toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Single source of truth for the Upstash filter expression. The smoke-test
|
|
265
|
+
* script reuses this so production and verification stay in sync. */
|
|
266
|
+
export function buildLocaleFilter(locale: string): string {
|
|
267
|
+
return `locale = "${locale}"`;
|
|
215
268
|
}
|
|
@@ -25,6 +25,56 @@ const { create, insertMultiple } = require('@orama/orama');
|
|
|
25
25
|
const { persist } = require('@orama/plugin-data-persistence');
|
|
26
26
|
const { filterVisibility } = require('./visibility-filter.cjs');
|
|
27
27
|
|
|
28
|
+
// Shared canonical list of language codes — keep in sync with
|
|
29
|
+
// lib/language-utils.ts. The sync test in
|
|
30
|
+
// __tests__/lib/language-codes-sync.test.ts catches drift.
|
|
31
|
+
const LANGUAGE_CODES = require('../lib/language-codes.json');
|
|
32
|
+
|
|
33
|
+
const LANGUAGE_CODE_BY_LOWER = (() => {
|
|
34
|
+
const m = new Map();
|
|
35
|
+
for (const k of LANGUAGE_CODES) {
|
|
36
|
+
const lower = k.toLowerCase();
|
|
37
|
+
if (!m.has(lower)) m.set(lower, k);
|
|
38
|
+
}
|
|
39
|
+
return m;
|
|
40
|
+
})();
|
|
41
|
+
|
|
42
|
+
/** Mirrors lib/language-utils.ts:extractLanguageFromPath. */
|
|
43
|
+
function extractLanguageFromPath(pathname) {
|
|
44
|
+
if (!pathname) return undefined;
|
|
45
|
+
const parts = pathname.replace(/^\/docs\/?/, '').replace(/^\//, '').split('/').filter(Boolean);
|
|
46
|
+
if (parts.length === 0) return undefined;
|
|
47
|
+
return LANGUAGE_CODE_BY_LOWER.get(parts[0].toLowerCase());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Mirrors lib/language-utils.ts:resolveLocaleFromPath. */
|
|
51
|
+
function resolveLocaleFromPath(pathname, projectLanguages) {
|
|
52
|
+
const candidate = extractLanguageFromPath(pathname);
|
|
53
|
+
if (!candidate) return '';
|
|
54
|
+
const lowered = new Set(projectLanguages.map((l) => l.toLowerCase()));
|
|
55
|
+
return lowered.has(candidate.toLowerCase()) ? candidate : '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read the project's declared languages from its docs.json. Returns [] when
|
|
60
|
+
* the file is missing or malformed — the script proceeds with no whitelist,
|
|
61
|
+
* which means every doc gets locale='' (matches single-language behavior).
|
|
62
|
+
*/
|
|
63
|
+
function readProjectLanguages(contentDir) {
|
|
64
|
+
try {
|
|
65
|
+
const docsJsonPath = path.join(contentDir, 'docs.json');
|
|
66
|
+
if (!fs.existsSync(docsJsonPath)) return [];
|
|
67
|
+
const config = JSON.parse(fs.readFileSync(docsJsonPath, 'utf-8'));
|
|
68
|
+
const langs = config?.navigation?.languages ?? [];
|
|
69
|
+
return langs
|
|
70
|
+
.map((l) => (l && typeof l.language === 'string' ? l.language : null))
|
|
71
|
+
.filter((c) => typeof c === 'string');
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.warn(`Could not read languages from ${contentDir}/docs.json: ${err.message}`);
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
28
78
|
// Concurrency control for parallel file processing
|
|
29
79
|
const CONCURRENCY = parseInt(process.env.SEARCH_INDEX_CONCURRENCY || '') || os.cpus().length * 2;
|
|
30
80
|
|
|
@@ -214,6 +264,11 @@ async function buildSearchIndex() {
|
|
|
214
264
|
process.exit(1);
|
|
215
265
|
}
|
|
216
266
|
|
|
267
|
+
const projectLanguages = readProjectLanguages(contentDir);
|
|
268
|
+
if (projectLanguages.length > 0) {
|
|
269
|
+
console.log(`Project languages: ${projectLanguages.join(', ')}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
217
272
|
// First, collect all MDX file paths
|
|
218
273
|
const mdxFiles = [];
|
|
219
274
|
|
|
@@ -264,6 +319,7 @@ async function buildSearchIndex() {
|
|
|
264
319
|
const docs = [];
|
|
265
320
|
const normalizedSlug = slug.replace(/\\/g, '/');
|
|
266
321
|
const pageType = inferPageType(normalizedSlug);
|
|
322
|
+
const locale = resolveLocaleFromPath(normalizedSlug, projectLanguages);
|
|
267
323
|
sections.forEach((section, idx) => {
|
|
268
324
|
const cleanContent = stripMarkdown(section.content);
|
|
269
325
|
if (cleanContent.trim()) {
|
|
@@ -275,6 +331,7 @@ async function buildSearchIndex() {
|
|
|
275
331
|
slug: normalizedSlug,
|
|
276
332
|
section: section.heading || undefined,
|
|
277
333
|
type: pageType,
|
|
334
|
+
locale,
|
|
278
335
|
});
|
|
279
336
|
}
|
|
280
337
|
});
|
|
@@ -318,6 +375,7 @@ async function buildOramaIndex(searchData, outputDir) {
|
|
|
318
375
|
slug: 'string',
|
|
319
376
|
section: 'string',
|
|
320
377
|
type: 'string',
|
|
378
|
+
locale: 'enum',
|
|
321
379
|
},
|
|
322
380
|
});
|
|
323
381
|
|
|
@@ -330,6 +388,7 @@ async function buildOramaIndex(searchData, outputDir) {
|
|
|
330
388
|
slug: item.slug,
|
|
331
389
|
section: item.section || '',
|
|
332
390
|
type: item.type || 'guide',
|
|
391
|
+
locale: item.locale || '',
|
|
333
392
|
}));
|
|
334
393
|
|
|
335
394
|
await insertMultiple(db, normalizedData);
|
|
@@ -443,79 +443,34 @@ function getNavigationPages(contentDir) {
|
|
|
443
443
|
|
|
444
444
|
const pages = new Set();
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
*/
|
|
449
|
-
function getPagePath(entry) {
|
|
450
|
-
if (typeof entry === 'string') return entry;
|
|
451
|
-
if (entry && typeof entry === 'object' && entry.page) return entry.page;
|
|
452
|
-
return null;
|
|
446
|
+
function addPage(value) {
|
|
447
|
+
if (typeof value === 'string') pages.add(value);
|
|
453
448
|
}
|
|
454
449
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
function
|
|
459
|
-
if (!
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (entry.pages) collectPages(entry.pages);
|
|
469
|
-
if (entry.groups) {
|
|
470
|
-
for (const group of entry.groups) {
|
|
471
|
-
if (group.pages) collectPages(group.pages);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Collect pages from tabs
|
|
480
|
-
*/
|
|
481
|
-
function collectFromTabs(tabs) {
|
|
482
|
-
if (!Array.isArray(tabs)) return;
|
|
483
|
-
|
|
484
|
-
for (const tab of tabs) {
|
|
485
|
-
if (tab.pages) collectPages(tab.pages);
|
|
486
|
-
if (tab.groups) {
|
|
487
|
-
for (const group of tab.groups) {
|
|
488
|
-
if (group.pages) collectPages(group.pages);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
450
|
+
// Mutual recursion: walkContainer dispatches on the known container fields,
|
|
451
|
+
// walkItems handles arrays of strings (page ids) or nested containers.
|
|
452
|
+
// Covers pages/groups/tabs/anchors/dropdowns/languages/global at any depth.
|
|
453
|
+
function walkContainer(c) {
|
|
454
|
+
if (!c || typeof c !== 'object') return;
|
|
455
|
+
if ('page' in c) addPage(c.page);
|
|
456
|
+
walkItems(c.pages);
|
|
457
|
+
walkItems(c.groups);
|
|
458
|
+
walkItems(c.tabs);
|
|
459
|
+
walkItems(c.anchors);
|
|
460
|
+
walkItems(c.dropdowns);
|
|
461
|
+
walkItems(c.languages);
|
|
462
|
+
if (c.global) walkContainer(c.global);
|
|
492
463
|
}
|
|
493
464
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
for (const group of navigation.groups) {
|
|
500
|
-
if (group.pages) collectPages(group.pages);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Collect from tabs
|
|
505
|
-
if (navigation.tabs) collectFromTabs(navigation.tabs);
|
|
506
|
-
|
|
507
|
-
// Collect from anchors
|
|
508
|
-
if (navigation.anchors) {
|
|
509
|
-
for (const anchor of navigation.anchors) {
|
|
510
|
-
if (anchor.pages) collectPages(anchor.pages);
|
|
511
|
-
if (anchor.groups) {
|
|
512
|
-
for (const group of anchor.groups) {
|
|
513
|
-
if (group.pages) collectPages(group.pages);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
465
|
+
function walkItems(items) {
|
|
466
|
+
if (!Array.isArray(items)) return;
|
|
467
|
+
for (const item of items) {
|
|
468
|
+
if (typeof item === 'string') addPage(item);
|
|
469
|
+
else if (item && typeof item === 'object') walkContainer(item);
|
|
516
470
|
}
|
|
517
471
|
}
|
|
518
472
|
|
|
473
|
+
walkContainer(navigation);
|
|
519
474
|
return pages;
|
|
520
475
|
}
|
|
521
476
|
|
package/vendored/themes/base.css
CHANGED
|
@@ -378,6 +378,11 @@
|
|
|
378
378
|
text-decoration: none;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
/* Restore spacing between stacked .not-prose blocks — prose sibling rules skip them. */
|
|
382
|
+
.prose > .not-prose + .not-prose {
|
|
383
|
+
margin-top: 1.5rem;
|
|
384
|
+
}
|
|
385
|
+
|
|
381
386
|
html {
|
|
382
387
|
/* Prevent horizontal overflow on mobile */
|
|
383
388
|
overflow-x: hidden;
|
|
@@ -294,14 +294,14 @@
|
|
|
294
294
|
"license": "MIT"
|
|
295
295
|
},
|
|
296
296
|
"node_modules/@iconify/utils": {
|
|
297
|
-
"version": "3.1.
|
|
298
|
-
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.
|
|
299
|
-
"integrity": "sha512-
|
|
297
|
+
"version": "3.1.1",
|
|
298
|
+
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.1.tgz",
|
|
299
|
+
"integrity": "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==",
|
|
300
300
|
"license": "MIT",
|
|
301
301
|
"dependencies": {
|
|
302
302
|
"@antfu/install-pkg": "^1.1.0",
|
|
303
303
|
"@iconify/types": "^2.0.0",
|
|
304
|
-
"mlly": "^1.8.
|
|
304
|
+
"mlly": "^1.8.2"
|
|
305
305
|
}
|
|
306
306
|
},
|
|
307
307
|
"node_modules/@img/colour": {
|
|
@@ -2025,9 +2025,9 @@
|
|
|
2025
2025
|
}
|
|
2026
2026
|
},
|
|
2027
2027
|
"node_modules/ajv": {
|
|
2028
|
-
"version": "8.
|
|
2029
|
-
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.
|
|
2030
|
-
"integrity": "sha512-
|
|
2028
|
+
"version": "8.20.0",
|
|
2029
|
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
|
2030
|
+
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
|
2031
2031
|
"license": "MIT",
|
|
2032
2032
|
"dependencies": {
|
|
2033
2033
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -2152,9 +2152,9 @@
|
|
|
2152
2152
|
}
|
|
2153
2153
|
},
|
|
2154
2154
|
"node_modules/baseline-browser-mapping": {
|
|
2155
|
-
"version": "2.10.
|
|
2156
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.
|
|
2157
|
-
"integrity": "sha512-
|
|
2155
|
+
"version": "2.10.23",
|
|
2156
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz",
|
|
2157
|
+
"integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==",
|
|
2158
2158
|
"license": "Apache-2.0",
|
|
2159
2159
|
"bin": {
|
|
2160
2160
|
"baseline-browser-mapping": "dist/cli.cjs"
|
|
@@ -2215,9 +2215,9 @@
|
|
|
2215
2215
|
"license": "MIT"
|
|
2216
2216
|
},
|
|
2217
2217
|
"node_modules/caniuse-lite": {
|
|
2218
|
-
"version": "1.0.
|
|
2219
|
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.
|
|
2220
|
-
"integrity": "sha512-
|
|
2218
|
+
"version": "1.0.30001791",
|
|
2219
|
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz",
|
|
2220
|
+
"integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==",
|
|
2221
2221
|
"funding": [
|
|
2222
2222
|
{
|
|
2223
2223
|
"type": "opencollective",
|
|
@@ -5586,9 +5586,9 @@
|
|
|
5586
5586
|
}
|
|
5587
5587
|
},
|
|
5588
5588
|
"node_modules/postcss": {
|
|
5589
|
-
"version": "8.5.
|
|
5590
|
-
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.
|
|
5591
|
-
"integrity": "sha512-
|
|
5589
|
+
"version": "8.5.12",
|
|
5590
|
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
|
|
5591
|
+
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
|
|
5592
5592
|
"funding": [
|
|
5593
5593
|
{
|
|
5594
5594
|
"type": "opencollective",
|