@writedocs/generator 0.9.4 → 0.10.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/astro.config.mjs +7 -0
- package/package.json +1 -1
- package/src/cli/run-pagefind.js +72 -1
- package/src/cli/write-redirects-file.js +33 -0
- package/src/layout/components/MobileMenu.astro +2 -2
- package/src/layout/components/TopBar.astro +1 -1
- package/src/lib/config-schema.js +10 -1
- package/src/lib/config-schema.ts +21 -6
- package/src/lib/config.ts +22 -7
- package/src/lib/content-check.js +14 -0
- package/src/lib/folder-redirects.js +47 -0
- package/src/lib/hidden-sections.js +58 -0
- package/src/lib/json-schema-descriptions.js +6 -0
- package/src/lib/link-check.js +7 -1
- package/src/lib/selector-placement.js +4 -1
- package/src/pages/[...slug].astro +31 -4
- package/src/scripts/search.ts +4 -1
- package/writedocs.schema.json +350 -0
package/astro.config.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import { writedocsTempDir, writedocsBuildStagingDir } from './src/lib/writedocs-
|
|
|
38
38
|
import { stylesAssetFallback } from './src/lib/styles-asset-integration.js';
|
|
39
39
|
import { mcpDevServer } from './src/lib/mcp-dev-integration.js';
|
|
40
40
|
import { canonicalPath } from './src/lib/canonical-path.js';
|
|
41
|
+
import { folderAddresses } from './src/lib/folder-redirects.js';
|
|
41
42
|
import { report } from './src/lib/cli-report.js';
|
|
42
43
|
import {
|
|
43
44
|
loadDocsConfig,
|
|
@@ -152,12 +153,14 @@ function collectNoindexIds(rootContentDir) {
|
|
|
152
153
|
// redirect-only route [...slug].astro synthesizes (noindex, pointing at
|
|
153
154
|
// the first page) - which doesn't belong in the sitemap either.
|
|
154
155
|
let rootIsPage = false;
|
|
156
|
+
const pageIds = [];
|
|
155
157
|
|
|
156
158
|
for (const relativeId of findAllPages(rootContentDir)) {
|
|
157
159
|
const file = path.join(rootContentDir, relativeId);
|
|
158
160
|
const { data } = matter(fs.readFileSync(file, 'utf-8'));
|
|
159
161
|
const pageId = normalizeEntryId(data?.slug ?? relativeId.replace(/\.mdx?$/i, '').replace(/\/index$/, ''));
|
|
160
162
|
if (pageId === 'index') rootIsPage = true;
|
|
163
|
+
pageIds.push(pageId);
|
|
161
164
|
// Top-level `noindex` is Mintlify's spelling, and a Mintlify `hidden`
|
|
162
165
|
// page is noindexed too - content.config.ts folds both into
|
|
163
166
|
// `seo.noindex` the same way (an explicit value wins).
|
|
@@ -172,9 +175,13 @@ function collectNoindexIds(rootContentDir) {
|
|
|
172
175
|
for (const file of walkMdFiles(generatedDocsDir)) {
|
|
173
176
|
const { data } = matter(fs.readFileSync(file, 'utf-8'));
|
|
174
177
|
if (data?.slug && normalizeEntryId(data.slug) === 'index') rootIsPage = true;
|
|
178
|
+
if (data?.slug) pageIds.push(normalizeEntryId(data.slug));
|
|
175
179
|
if ((data?.seo?.noindex ?? data?.noindex ?? data?.hidden === true) && data.slug) ids.add(normalizeEntryId(data.slug));
|
|
176
180
|
}
|
|
177
181
|
if (!rootIsPage) ids.add('index');
|
|
182
|
+
// Folder addresses with no page of their own redirect to one under them
|
|
183
|
+
// (lib/folder-redirects.js) - redirects, so not in the sitemap either.
|
|
184
|
+
for (const folder of folderAddresses(pageIds)) ids.add(folder);
|
|
178
185
|
return ids;
|
|
179
186
|
}
|
|
180
187
|
|
package/package.json
CHANGED
package/src/cli/run-pagefind.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Locates the installed pagefind package by walking up node_modules
|
|
@@ -53,7 +54,77 @@ function resolvePagefindBin(packageRoot) {
|
|
|
53
54
|
* Pagefind's output is captured, not printed: the CLI prints its own step
|
|
54
55
|
* line, and Pagefind's text only when it fails (in the rejected Error).
|
|
55
56
|
*/
|
|
56
|
-
export function runPagefind(siteDir, { packageRoot }) {
|
|
57
|
+
export async function runPagefind(siteDir, { packageRoot }) {
|
|
58
|
+
const groups = searchSpaces(siteDir);
|
|
59
|
+
if (groups) return indexBySpace(siteDir, packageRoot, groups);
|
|
60
|
+
return indexWholeSite(siteDir, packageRoot);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Pages under a hidden navigation item name their search space in their
|
|
64
|
+
// markup ([...slug].astro, lib/hidden-sections.js).
|
|
65
|
+
const SPACE = /data-wd-search-space="([^"]+)"/;
|
|
66
|
+
const SEARCH_PUBLIC = /data-wd-search-public="true"/;
|
|
67
|
+
|
|
68
|
+
/** The site's pages by search space - `{ public, spaces }` - or null when
|
|
69
|
+
* no page is in a hidden space (the whole site is one index, as ever).
|
|
70
|
+
* Only pages Pagefind would index: those with a data-pagefind-body. */
|
|
71
|
+
function searchSpaces(siteDir) {
|
|
72
|
+
const publicPages = [];
|
|
73
|
+
const spaces = new Map();
|
|
74
|
+
const walk = (dir) => {
|
|
75
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
76
|
+
const full = path.join(dir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
if (!['_astro', 'pagefind', 'pagefind-spaces'].includes(entry.name)) walk(full);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!entry.name.endsWith('.html')) continue;
|
|
82
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
83
|
+
if (!content.includes('data-pagefind-body')) continue;
|
|
84
|
+
const page = { rel: path.relative(siteDir, full).split(path.sep).join('/'), content };
|
|
85
|
+
const space = SPACE.exec(content)?.[1];
|
|
86
|
+
if (!space) {
|
|
87
|
+
publicPages.push(page);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const group = spaces.get(space) ?? { searchPublic: false, pages: [] };
|
|
91
|
+
group.searchPublic ||= SEARCH_PUBLIC.test(content);
|
|
92
|
+
group.pages.push(page);
|
|
93
|
+
spaces.set(space, group);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
walk(siteDir);
|
|
97
|
+
return spaces.size ? { public: publicPages, spaces } : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One index for the public pages (dist/pagefind/) and one per hidden
|
|
101
|
+
* space (dist/pagefind-spaces/<id>/) - so the public search never finds a
|
|
102
|
+
* hidden page, and a hidden space's search finds only its own pages, plus
|
|
103
|
+
* the public ones when it sets `searchPublic`. src/scripts/search.ts loads
|
|
104
|
+
* the index of the page's space. */
|
|
105
|
+
async function indexBySpace(siteDir, packageRoot, groups) {
|
|
106
|
+
const pagefind = await import(pathToFileURL(path.join(findPagefindDir(packageRoot), 'lib', 'index.js')).href);
|
|
107
|
+
const check = (result, what) => {
|
|
108
|
+
if (result.errors?.length) throw new Error(`pagefind (${what}): ${result.errors.join('; ')}`);
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
const write = async (pages, outputPath, what) => {
|
|
112
|
+
const { index } = check(await pagefind.createIndex({}), what);
|
|
113
|
+
for (const page of pages) check(await index.addHTMLFile({ sourcePath: page.rel, content: page.content }), `${what}: ${page.rel}`);
|
|
114
|
+
check(await index.writeFiles({ outputPath }), what);
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
await write(groups.public, path.join(siteDir, 'pagefind'), 'public pages');
|
|
118
|
+
for (const [id, space] of groups.spaces) {
|
|
119
|
+
const pages = space.searchPublic ? [...space.pages, ...groups.public] : space.pages;
|
|
120
|
+
await write(pages, path.join(siteDir, 'pagefind-spaces', id), `hidden space ${id}`);
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
await pagefind.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function indexWholeSite(siteDir, packageRoot) {
|
|
57
128
|
const bin = resolvePagefindBin(packageRoot);
|
|
58
129
|
return new Promise((resolve, reject) => {
|
|
59
130
|
const child = spawn(process.execPath, [bin, '--site', siteDir, '--quiet'], {
|
|
@@ -63,6 +63,17 @@ export function writeRedirectsFile(distDir, contentDir) {
|
|
|
63
63
|
if (match) lines.push(`/ ${match[1]} 302`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Folder addresses redirecting to the first page under them
|
|
67
|
+
// (lib/folder-redirects.js). Read back from the built pages like "/"
|
|
68
|
+
// above: Astro writes a temporary (302) redirect's page with a 2-second
|
|
69
|
+
// refresh and a permanent one's with 0 - writedocs.json's own redirects
|
|
70
|
+
// and `url` pages are permanent, so the 2-second ones are exactly the
|
|
71
|
+
// automatic folder redirects. (Read before rewrite-redirect-pages.js makes
|
|
72
|
+
// every redirect page go at once.)
|
|
73
|
+
for (const { url, target } of temporaryRedirectPages(distDir)) {
|
|
74
|
+
lines.push(`${url} ${target} 302`, `${url.replace(/\/$/, '')} ${target} 302`);
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
if (lines.length === 0) return;
|
|
67
78
|
|
|
68
79
|
const generated = [
|
|
@@ -81,3 +92,25 @@ export function writeRedirectsFile(distDir, contentDir) {
|
|
|
81
92
|
const existing = fs.existsSync(redirectsPath) ? fs.readFileSync(redirectsPath, 'utf8').replace(/\s*$/, '\n\n') : '';
|
|
82
93
|
fs.writeFileSync(redirectsPath, existing + generated);
|
|
83
94
|
}
|
|
95
|
+
|
|
96
|
+
/** Astro's pages for temporary redirects below the site's root, as
|
|
97
|
+
* `{ url: "/docs/creator/", target }`. */
|
|
98
|
+
function temporaryRedirectPages(distDir) {
|
|
99
|
+
const found = [];
|
|
100
|
+
const walk = (dir) => {
|
|
101
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
102
|
+
const full = path.join(dir, entry.name);
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
if (!['_astro', 'pagefind', 'pagefind-spaces'].includes(entry.name)) walk(full);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (entry.name !== 'index.html' || dir === distDir) continue;
|
|
108
|
+
const html = fs.readFileSync(full, 'utf8');
|
|
109
|
+
if (!html.includes('<title>Redirecting to:')) continue;
|
|
110
|
+
const match = html.match(/<meta http-equiv="refresh" content="2;\s*url=([^"]+)"/i);
|
|
111
|
+
if (match) found.push({ url: `/${path.relative(distDir, dir).split(path.sep).join('/')}/`, target: match[1] });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
walk(distDir);
|
|
115
|
+
return found.sort((a, b) => a.url.localeCompare(b.url));
|
|
116
|
+
}
|
|
@@ -98,9 +98,9 @@ const showMenu = hasMobileMenu({ sidebar: showSidebarCol, selectors, globalDropd
|
|
|
98
98
|
</button>
|
|
99
99
|
</div>
|
|
100
100
|
<div class="wd-mobile-menu-scroll">
|
|
101
|
-
{(selectors.
|
|
101
|
+
{(selectors.some((sel) => !sel.hidden) || globalDropdowns.length > 0) && (
|
|
102
102
|
<div class="wd-mobile-menu-selectors">
|
|
103
|
-
{selectors.map((sel) => {
|
|
103
|
+
{selectors.filter((sel) => !sel.hidden).map((sel) => {
|
|
104
104
|
const current = sel.options.find((o) => o.active) ?? sel.options[0];
|
|
105
105
|
return (
|
|
106
106
|
<details class="wd-mobile-accordion" name="wd-mobile-accordion">
|
|
@@ -59,7 +59,7 @@ const { config, selectors, globalDropdowns, showSidebarCol, logoLight, logoDark,
|
|
|
59
59
|
const placements = selectorPlacements(selectors, { sidebar: showSidebarCol });
|
|
60
60
|
const showMenuButton = hasMobileMenu({ sidebar: showSidebarCol, selectors, globalDropdowns });
|
|
61
61
|
const switcherSelectors = selectors.filter((_, i) => placements[i] === 'topbar');
|
|
62
|
-
const tabSelectors = selectors.filter((
|
|
62
|
+
const tabSelectors = selectors.filter((_, i) => placements[i] === 'tabs');
|
|
63
63
|
// One row per level of tabs: a tab holding its own tabs gets a second row
|
|
64
64
|
// underneath, with that tab's tabs. The global dropdowns stay in the first
|
|
65
65
|
// row, which renders on its own when there are only those.
|
package/src/lib/config-schema.js
CHANGED
|
@@ -32,7 +32,8 @@ const navLinkSchema = z.object({ label: z.string(), href: z.string() }).strict()
|
|
|
32
32
|
const navItemSchema = z.lazy(
|
|
33
33
|
() => z.union([navPageSchema, navGroupSchema, navLinkSchema])
|
|
34
34
|
);
|
|
35
|
-
function withChildren(
|
|
35
|
+
function withChildren(ownFields) {
|
|
36
|
+
const base = { ...ownFields, hidden: z.boolean().optional(), searchPublic: z.boolean().optional() };
|
|
36
37
|
return z.union([
|
|
37
38
|
z.object({ ...base, pages: z.array(navItemSchema) }).strict(),
|
|
38
39
|
z.object({ ...base, tabs: z.array(tabSchema).min(1) }).strict(),
|
|
@@ -97,6 +98,14 @@ const navigationSchema = z.union([
|
|
|
97
98
|
continue;
|
|
98
99
|
}
|
|
99
100
|
items.forEach((item, i) => {
|
|
101
|
+
const own2 = item;
|
|
102
|
+
if (own2?.searchPublic === true && own2.hidden !== true) {
|
|
103
|
+
ctx.addIssue({
|
|
104
|
+
code: "custom",
|
|
105
|
+
path: [...path, key, i, "searchPublic"],
|
|
106
|
+
message: '`searchPublic` only applies to a hidden item - add "hidden": true, or remove `searchPublic`.'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
100
109
|
const inner = key === "languages" ? String(item?.language ?? "") : language;
|
|
101
110
|
const tab = item?.tab;
|
|
102
111
|
walk(item, [...path, key, i], inner, key === "tabs" ? [...tabs, String(tab ?? "")] : tabs);
|
package/src/lib/config-schema.ts
CHANGED
|
@@ -129,11 +129,17 @@ export type NavChildren =
|
|
|
129
129
|
| { products: ProductItem[] }
|
|
130
130
|
| { href: string };
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
export type
|
|
132
|
+
/** Any navigation item can be hidden: reachable only by its address - no
|
|
133
|
+
* switcher, tab or menu lists it, and its pages search only among
|
|
134
|
+
* themselves (plus the public ones, with `searchPublic`). See
|
|
135
|
+
* lib/hidden-sections.js. */
|
|
136
|
+
export type Hideable = { hidden?: boolean; searchPublic?: boolean };
|
|
137
|
+
|
|
138
|
+
export type TabItem = { tab: string; icon?: string } & Hideable & NavChildren;
|
|
139
|
+
export type VersionItem = { version: string; label?: string; tag?: string; default?: boolean } & Hideable & NavChildren;
|
|
140
|
+
export type LanguageItem = { language: string; label?: string } & Hideable & NavChildren;
|
|
141
|
+
export type DropdownItem = { dropdown: string; icon?: string } & Hideable & NavChildren;
|
|
142
|
+
export type ProductItem = { product: string; icon?: string; description?: string } & Hideable & NavChildren;
|
|
137
143
|
|
|
138
144
|
// `.strict()` on every variant is load-bearing, not decoration: Zod
|
|
139
145
|
// objects silently strip unrecognized keys by default, so without it a
|
|
@@ -142,7 +148,8 @@ export type ProductItem = { product: string; icon?: string; description?: string
|
|
|
142
148
|
// rejected - defeating the entire "exactly one child kind per level"
|
|
143
149
|
// rule this schema exists to enforce (mirroring Mintlify's own "a tab
|
|
144
150
|
// cannot contain both anchors and groups at the same level").
|
|
145
|
-
function withChildren<Base extends z.ZodRawShape>(
|
|
151
|
+
function withChildren<Base extends z.ZodRawShape>(ownFields: Base) {
|
|
152
|
+
const base = { ...ownFields, hidden: z.boolean().optional(), searchPublic: z.boolean().optional() };
|
|
146
153
|
return z.union([
|
|
147
154
|
z.object({ ...base, pages: z.array(navItemSchema) }).strict(),
|
|
148
155
|
z.object({ ...base, tabs: z.array(tabSchema).min(1) }).strict(),
|
|
@@ -247,6 +254,14 @@ const navigationSchema = z.union([
|
|
|
247
254
|
continue;
|
|
248
255
|
}
|
|
249
256
|
items.forEach((item, i) => {
|
|
257
|
+
const own = item as { hidden?: unknown; searchPublic?: unknown };
|
|
258
|
+
if (own?.searchPublic === true && own.hidden !== true) {
|
|
259
|
+
ctx.addIssue({
|
|
260
|
+
code: 'custom',
|
|
261
|
+
path: [...path, key, i, 'searchPublic'],
|
|
262
|
+
message: '`searchPublic` only applies to a hidden item - add "hidden": true, or remove `searchPublic`.',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
250
265
|
const inner = key === 'languages' ? String((item as { language?: unknown })?.language ?? '') : language;
|
|
251
266
|
const tab = (item as { tab?: unknown })?.tab;
|
|
252
267
|
walk(item, [...path, key, i], inner, key === 'tabs' ? [...tabs, String(tab ?? '')] : tabs);
|
package/src/lib/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { EXCLUDED_TOP_LEVEL_DIRS, navPageId } from './pages.js';
|
|
4
|
+
import { isHidden } from './hidden-sections.js';
|
|
4
5
|
import matter from 'gray-matter';
|
|
5
6
|
import { writedocsTempDir } from './writedocs-temp-dir.js';
|
|
6
7
|
import { readableTextOn } from './color.js';
|
|
@@ -787,6 +788,8 @@ export function resolveGlobalDropdowns(navigation: NavigationConfig): DropdownIt
|
|
|
787
788
|
* `{ tab: "Status", href: "..." }`). */
|
|
788
789
|
function firstSlugAmong(items: Container[]): string | null {
|
|
789
790
|
for (const item of items) {
|
|
791
|
+
// A hidden item is reached only by its address - never by default.
|
|
792
|
+
if (isHidden(item)) continue;
|
|
790
793
|
const slug = firstSlugOf(item);
|
|
791
794
|
if (slug !== null) return slug;
|
|
792
795
|
}
|
|
@@ -944,6 +947,11 @@ export interface SelectorOption {
|
|
|
944
947
|
export interface Selector {
|
|
945
948
|
kind: PathSegment['kind'];
|
|
946
949
|
options: SelectorOption[];
|
|
950
|
+
// The page sits inside a hidden item on this level: the level isn't
|
|
951
|
+
// shown (its switcher would list the other items). Kept in the list,
|
|
952
|
+
// not dropped, so the levels after it are placed by their real parent
|
|
953
|
+
// (lib/selector-placement.js).
|
|
954
|
+
hidden?: boolean;
|
|
947
955
|
}
|
|
948
956
|
|
|
949
957
|
/** An option's own `dropdowns` menu, if it has one (see SelectorOption.dropdown
|
|
@@ -958,12 +966,16 @@ function dropdownMenuOf(
|
|
|
958
966
|
activeSegment: PathSegment | undefined
|
|
959
967
|
): SelectorOption[] | undefined {
|
|
960
968
|
if (!('dropdowns' in node)) return undefined;
|
|
961
|
-
return node.dropdowns
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
969
|
+
return node.dropdowns
|
|
970
|
+
.map((d, i) => ({
|
|
971
|
+
label: d.dropdown,
|
|
972
|
+
icon: iconOf(d),
|
|
973
|
+
href: 'href' in d ? d.href : hrefForSlug(firstSlugOf(d) ?? 'index'),
|
|
974
|
+
active: activeSegment?.kind === 'dropdown' && activeSegment.index === i,
|
|
975
|
+
hidden: isHidden(d),
|
|
976
|
+
}))
|
|
977
|
+
.filter((d) => !d.hidden)
|
|
978
|
+
.map(({ hidden: _hidden, ...option }) => option);
|
|
967
979
|
}
|
|
968
980
|
|
|
969
981
|
/** `activePagePosition` is the reader's current page's own index within
|
|
@@ -986,7 +998,10 @@ export function buildSelectors(
|
|
|
986
998
|
const remainingPath = path.slice(segIndex + 1);
|
|
987
999
|
return {
|
|
988
1000
|
kind: segment.kind,
|
|
989
|
-
|
|
1001
|
+
hidden: isHidden(segment.items[segment.index]),
|
|
1002
|
+
options: (segment.items as NamedContainer[]).flatMap((item, i) => {
|
|
1003
|
+
// A hidden item is never offered - not even to its own pages.
|
|
1004
|
+
if (isHidden(item)) return [];
|
|
990
1005
|
const isActive = i === segment.index;
|
|
991
1006
|
const equivalentSlug = preservesPosition
|
|
992
1007
|
? equivalentPageIn(item as Container, remainingPath, activePagePosition)
|
package/src/lib/content-check.js
CHANGED
|
@@ -411,6 +411,20 @@ export async function checkContent(contentDir, configText) {
|
|
|
411
411
|
)
|
|
412
412
|
);
|
|
413
413
|
}
|
|
414
|
+
// Every top-level item hidden, and no home page: `/` never redirects into a
|
|
415
|
+
// hidden item (lib/hidden-sections.js), so the site's address has nothing.
|
|
416
|
+
const nav = config.navigation;
|
|
417
|
+
const topKey = nav && !Array.isArray(nav) ? ['tabs', 'versions', 'languages', 'dropdowns', 'products'].find((k) => Array.isArray(nav[k])) : null;
|
|
418
|
+
if (topKey && nav[topKey].length && nav[topKey].every((item) => item?.hidden === true || 'href' in (item ?? {})) && !ids.has('index')) {
|
|
419
|
+
warnings.push(
|
|
420
|
+
issue(
|
|
421
|
+
'writedocs.json',
|
|
422
|
+
locate(['navigation', topKey])?.line,
|
|
423
|
+
`Every item in navigation.${topKey} is hidden and there's no home page - the site's own address (/) shows "not found".`,
|
|
424
|
+
'Add an index.mdx at the project root for a home page, or leave one item not hidden.'
|
|
425
|
+
)
|
|
426
|
+
);
|
|
427
|
+
}
|
|
414
428
|
// A redirect is one exact path - a Mintlify-style pattern (`/old/:slug`,
|
|
415
429
|
// `/old/*`) becomes a literal page path, and the build fails on it.
|
|
416
430
|
(Array.isArray(config.redirects) ? config.redirects : []).forEach((r, i) => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Folder addresses - `/docs/creator/` when pages live at `/docs/creator/...`
|
|
2
|
+
// but none at `/docs/creator/` itself - redirect to the first page under
|
|
3
|
+
// them, as Mintlify does, instead of showing "not found". The same idea as
|
|
4
|
+
// the automatic `/` redirect, one level down.
|
|
5
|
+
//
|
|
6
|
+
// "First" is the reader's order: [...slug].astro passes the pages in
|
|
7
|
+
// navigation order, the public ones before those under a hidden item
|
|
8
|
+
// (lib/hidden-sections.js) and pages outside the navigation last - so a
|
|
9
|
+
// folder with both public and hidden pages under it goes to a public one.
|
|
10
|
+
// A real page or a writedocs.json redirect at the address always wins.
|
|
11
|
+
//
|
|
12
|
+
// Temporary (302), like `/`: a browser doesn't keep it, so a page added at
|
|
13
|
+
// that address later is seen. Also used by `writedocs broken-links` (a link
|
|
14
|
+
// to a folder address leads somewhere) and the sitemap (which leaves them
|
|
15
|
+
// out, like every redirect).
|
|
16
|
+
//
|
|
17
|
+
// Slugs are page addresses without their slashes: "docs/creator/home".
|
|
18
|
+
|
|
19
|
+
/** First path segments writedocs itself serves - never a folder redirect. */
|
|
20
|
+
export const RESERVED_FOLDERS = new Set(['_astro', 'pagefind', 'pagefind-spaces', 'llms', 'mcp']);
|
|
21
|
+
|
|
22
|
+
/** Every folder above a page: "a/b/c" -> ["a", "a/b"]. */
|
|
23
|
+
export function foldersOf(slug) {
|
|
24
|
+
const parts = slug.split('/').filter(Boolean);
|
|
25
|
+
return parts.slice(0, -1).map((_, i) => parts.slice(0, i + 1).join('/'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The folder redirects for a site: folder -> the page it goes to.
|
|
29
|
+
* `targets` - the pages, in the order to prefer them; `taken` - every
|
|
30
|
+
* address already served (pages, redirects, `url` pages). */
|
|
31
|
+
export function folderRedirects(targets, taken = new Set()) {
|
|
32
|
+
const redirects = new Map();
|
|
33
|
+
for (const slug of targets) {
|
|
34
|
+
if (slug === 'index') continue;
|
|
35
|
+
for (const folder of foldersOf(slug)) {
|
|
36
|
+
if (redirects.has(folder) || taken.has(folder) || RESERVED_FOLDERS.has(folder.split('/')[0])) continue;
|
|
37
|
+
redirects.set(folder, slug);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return redirects;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Just the folder addresses, for checks that don't need the targets. */
|
|
44
|
+
export function folderAddresses(pageSlugs) {
|
|
45
|
+
const pages = new Set(pageSlugs);
|
|
46
|
+
return new Set([...folderRedirects(pageSlugs, pages).keys()]);
|
|
47
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Hidden navigation items (`"hidden": true` on a tab, product, version,
|
|
2
|
+
// language or dropdown): reachable only by their address.
|
|
3
|
+
//
|
|
4
|
+
// - Nothing lists one: its switcher, tab row or menu option is left out
|
|
5
|
+
// everywhere, and `/` never redirects into it (firstSlugAmong() in
|
|
6
|
+
// lib/config.ts skips it).
|
|
7
|
+
// - Inside one, the level it sits on isn't shown - that switcher would
|
|
8
|
+
// list the other items at its level. Everything below it works as
|
|
9
|
+
// usual. (buildSelectors() marks that level `hidden`; the placement in
|
|
10
|
+
// lib/selector-placement.js leaves it out.)
|
|
11
|
+
// - Its pages are a search space of their own: they search only among
|
|
12
|
+
// themselves, and the site's public search never finds them. With
|
|
13
|
+
// `"searchPublic": true` its search finds the public pages too. The
|
|
14
|
+
// build writes one index per space (cli/run-pagefind.js); a page names
|
|
15
|
+
// its space in the markup, and src/scripts/search.ts loads that index.
|
|
16
|
+
//
|
|
17
|
+
// Hidden isn't private: anyone with the address can read the pages. The
|
|
18
|
+
// sitemap, llms.txt and the MCP index still list them unless a page sets
|
|
19
|
+
// `noindex`.
|
|
20
|
+
|
|
21
|
+
/** Whether a navigation item is hidden. */
|
|
22
|
+
export function isHidden(item) {
|
|
23
|
+
return Boolean(item && typeof item === 'object' && item.hidden === true);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const NAME_FIELD = { tab: 'tab', version: 'version', language: 'language', dropdown: 'dropdown', product: 'product' };
|
|
27
|
+
|
|
28
|
+
function slug(text) {
|
|
29
|
+
return String(text ?? '')
|
|
30
|
+
.normalize('NFKD')
|
|
31
|
+
.replace(/[̀-ͯ]/g, '')
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
34
|
+
.replace(/^-+|-+$/g, '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A path's id, from the names of its levels - "product-admin",
|
|
38
|
+
* "tab-api--version-v2". Readable in the index folder's name, and the
|
|
39
|
+
* same on every build. */
|
|
40
|
+
export function spaceIdOf(path) {
|
|
41
|
+
return path
|
|
42
|
+
.map((segment) => {
|
|
43
|
+
const item = segment.items[segment.index] ?? {};
|
|
44
|
+
return `${segment.kind}-${slug(item[NAME_FIELD[segment.kind]]) || segment.index}`;
|
|
45
|
+
})
|
|
46
|
+
.join('--');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The search space a section belongs to: the innermost hidden item on its
|
|
50
|
+
* path - `{ id, searchPublic }` - or null for the public pages. */
|
|
51
|
+
export function spaceOf(path = []) {
|
|
52
|
+
let space = null;
|
|
53
|
+
path.forEach((segment, i) => {
|
|
54
|
+
const item = segment.items[segment.index];
|
|
55
|
+
if (isHidden(item)) space = { id: spaceIdOf(path.slice(0, i + 1)), searchPublic: item.searchPublic === true };
|
|
56
|
+
});
|
|
57
|
+
return space;
|
|
58
|
+
}
|
|
@@ -43,9 +43,15 @@ const CONTAINERS = {
|
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
const HIDDEN = {
|
|
47
|
+
hidden: 'Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn\'t shown. Its pages search only among themselves.',
|
|
48
|
+
searchPublic: 'For a hidden item: its search also finds the site\'s public pages.',
|
|
49
|
+
};
|
|
50
|
+
|
|
46
51
|
const containerEntries = Object.entries(CONTAINERS).flatMap(([type, fields]) => [
|
|
47
52
|
...Object.entries(fields).map(([field, text]) => [`${type}.${field}`, text]),
|
|
48
53
|
...Object.entries(CHILDREN).map(([field, text]) => [`${type}.${field}`, text]),
|
|
54
|
+
...Object.entries(HIDDEN).map(([field, text]) => [`${type}.${field}`, text]),
|
|
49
55
|
]);
|
|
50
56
|
|
|
51
57
|
const logo = (where) => ({
|
package/src/lib/link-check.js
CHANGED
|
@@ -35,6 +35,7 @@ import { visit } from 'unist-util-visit';
|
|
|
35
35
|
import { findAllPages } from './pages.js';
|
|
36
36
|
import { createJsonLocator, contextMenuOptions } from './config-schema.js';
|
|
37
37
|
import { writedocsTempDir } from './writedocs-temp-dir.js';
|
|
38
|
+
import { folderAddresses } from './folder-redirects.js';
|
|
38
39
|
|
|
39
40
|
// The same github-slugger Astro builds page URLs and heading ids with -
|
|
40
41
|
// resolved through Astro itself, so the two can't drift apart.
|
|
@@ -295,6 +296,11 @@ export async function checkLinks(contentDir, configText) {
|
|
|
295
296
|
const special = new Set(['/llms.txt', '/llms-full.txt', '/404.html', '/404/']);
|
|
296
297
|
if (config.domain) ['/sitemap.xml', '/sitemap-index.xml'].forEach((p) => special.add(p));
|
|
297
298
|
const knownUrls = [...pages.keys(), ...generated, ...redirects];
|
|
299
|
+
// Folder addresses with no page of their own redirect to one under them
|
|
300
|
+
// (lib/folder-redirects.js), so a link to one isn't broken.
|
|
301
|
+
const folders = new Set(
|
|
302
|
+
[...folderAddresses([...pages.keys(), ...generated].map((url) => url.replace(/^\/+|\/+$/g, '')))].map((folder) => `/${folder}/`)
|
|
303
|
+
);
|
|
298
304
|
|
|
299
305
|
// Anchors a page can be linked to: its own, plus those of every snippet
|
|
300
306
|
// it imports (they render inside it).
|
|
@@ -398,7 +404,7 @@ export async function checkLinks(contentDir, configText) {
|
|
|
398
404
|
return;
|
|
399
405
|
}
|
|
400
406
|
// "/" always exists: the home page, or a redirect to the first page.
|
|
401
|
-
if (pageKey === '/' || generated.has(pageKey) || redirects.has(pageKey) || special.has(pageKey.replace(/\/$/, '') || '/')) return;
|
|
407
|
+
if (pageKey === '/' || generated.has(pageKey) || redirects.has(pageKey) || folders.has(pageKey) || special.has(pageKey.replace(/\/$/, '') || '/')) return;
|
|
402
408
|
if (special.has(pathname)) return;
|
|
403
409
|
|
|
404
410
|
// Mintlify-style relative link: resolved from the file's folder
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// 'sidebar' the top of the sidebar: products inside a tab or a dropdown,
|
|
7
7
|
// like Mintlify's - they pick what the sidebar shows
|
|
8
8
|
// 'topbar' a switcher next to the site name (everything else)
|
|
9
|
+
// 'none' not shown: the page is inside a hidden item on this level
|
|
10
|
+
// (lib/hidden-sections.js)
|
|
9
11
|
//
|
|
10
12
|
// Without a sidebar on the page (`mode: custom` / `blank`), a 'sidebar'
|
|
11
13
|
// switcher goes to the top bar instead. The mobile menu lists every level
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
|
|
14
16
|
export function selectorPlacements(selectors, { sidebar = true } = {}) {
|
|
15
17
|
return selectors.map((sel, i) => {
|
|
18
|
+
if (sel.hidden) return 'none';
|
|
16
19
|
const parent = selectors[i - 1]?.kind;
|
|
17
20
|
if (sel.kind === 'tab') return 'tabs';
|
|
18
21
|
if (sel.kind === 'dropdown' && parent === 'tab') return 'tab-menu';
|
|
@@ -26,5 +29,5 @@ export function selectorPlacements(selectors, { sidebar = true } = {}) {
|
|
|
26
29
|
* top bar hides the tabs and switchers, so any page with navigation to
|
|
27
30
|
* reach needs it - with a sidebar or without (`mode: custom`). */
|
|
28
31
|
export function hasMobileMenu({ sidebar, selectors = [], globalDropdowns = [] }) {
|
|
29
|
-
return sidebar || selectors.
|
|
32
|
+
return sidebar || selectors.some((sel) => !sel.hidden) || globalDropdowns.length > 0;
|
|
30
33
|
}
|
|
@@ -71,6 +71,8 @@ import ApiPlayground from "../components/ApiPlayground.astro";
|
|
|
71
71
|
import ApiReferencePanel from "../components/ApiReferencePanel.astro";
|
|
72
72
|
import { searchScope, sectionTrail, ALL_SCOPES } from "../lib/search-scope.js";
|
|
73
73
|
import { selectorPlacements } from "../lib/selector-placement.js";
|
|
74
|
+
import { spaceOf } from "../lib/hidden-sections.js";
|
|
75
|
+
import { folderRedirects } from "../lib/folder-redirects.js";
|
|
74
76
|
|
|
75
77
|
// A page is hand-written (the `pages` collection, sourced from anywhere
|
|
76
78
|
// in the project - docs/ has no special status, see findAllPages() in
|
|
@@ -254,12 +256,34 @@ export async function getStaticPaths() {
|
|
|
254
256
|
};
|
|
255
257
|
});
|
|
256
258
|
|
|
257
|
-
|
|
259
|
+
// Folder addresses with pages under them but none of their own
|
|
260
|
+
// (`/docs/creator/`) redirect to the first page under them
|
|
261
|
+
// (lib/folder-redirects.js). Preferred in the reader's order: navigation
|
|
262
|
+
// order, public sections before hidden ones, pages outside the
|
|
263
|
+
// navigation last.
|
|
264
|
+
const slugOf = (route: { params: { slug?: string } }) => route.params.slug ?? "index";
|
|
265
|
+
const inHiddenSection = sections.map((section) => spaceOf(section.path) !== null);
|
|
266
|
+
const contentRoutes = pageRoutes.filter((route) => !("redirectTo" in route.props));
|
|
267
|
+
const folderTargets = [
|
|
268
|
+
...contentRoutes.filter((route) => !inHiddenSection[(route.props as { sectionIndex: number }).sectionIndex]).map(slugOf),
|
|
269
|
+
...contentRoutes.filter((route) => inHiddenSection[(route.props as { sectionIndex: number }).sectionIndex]).map(slugOf),
|
|
270
|
+
...hiddenRoutes.map(slugOf),
|
|
271
|
+
];
|
|
272
|
+
const takenAddresses = new Set([
|
|
273
|
+
...[...pageRoutes, ...hiddenRoutes].map(slugOf),
|
|
274
|
+
...(config.redirects ?? []).map((r) => r.source.replace(/^\/+|\/+$/g, "")),
|
|
275
|
+
]);
|
|
276
|
+
const folderRoutes = [...folderRedirects(folderTargets, takenAddresses)].map(([folder, target]) => ({
|
|
277
|
+
params: { slug: folder },
|
|
278
|
+
props: { redirectTo: `/${target}/`, temporary: true },
|
|
279
|
+
}));
|
|
280
|
+
|
|
281
|
+
return [...rootRedirect, ...pageRoutes, ...hiddenRoutes, ...folderRoutes];
|
|
258
282
|
}
|
|
259
283
|
|
|
260
284
|
interface Props {
|
|
261
285
|
redirectTo?: string;
|
|
262
|
-
// The automatic "/"
|
|
286
|
+
// The automatic "/" and folder redirects: temporary, see below.
|
|
263
287
|
temporary?: boolean;
|
|
264
288
|
entry?: DocsEntry;
|
|
265
289
|
prev?: { slug: string; group: string | null } | null;
|
|
@@ -386,6 +410,9 @@ const selectors = buildSelectors(activeSection.path, hrefForSlug, activePagePosi
|
|
|
386
410
|
// shows the reader's own first. A hidden page belongs to no section.
|
|
387
411
|
const pageSearchScope = isHidden ? { key: ALL_SCOPES, label: "" } : searchScope(activeSection.path);
|
|
388
412
|
const pageSectionTrail = isHidden ? "" : sectionTrail(activeSection.path, breadcrumbs);
|
|
413
|
+
// A page under a hidden navigation item searches its own index, not the
|
|
414
|
+
// site's (lib/hidden-sections.js, cli/run-pagefind.js).
|
|
415
|
+
const pageSearchSpace = isHidden ? null : spaceOf(activeSection.path);
|
|
389
416
|
const languageSegment = isHidden ? undefined : activeSection.path.find((segment) => segment.kind === "language");
|
|
390
417
|
const pageLang = languageSegment ? (languageSegment.items[languageSegment.index] as { language: string }).language : undefined;
|
|
391
418
|
const globalDropdowns = buildGlobalDropdowns(
|
|
@@ -517,7 +544,7 @@ const components = {
|
|
|
517
544
|
{entry.data.openapi && <ApiPlayground operation={entry.data.openapi} contentDir={contentDir} />}
|
|
518
545
|
{/* After the content: a result's excerpt shows the section only when
|
|
519
546
|
that's what matched. */}
|
|
520
|
-
<div hidden data-wd-search-scope={pageSearchScope.key} data-wd-search-scope-label={pageSearchScope.label} data-pagefind-filter={`scope:${pageSearchScope.key}`}>
|
|
547
|
+
<div hidden data-wd-search-scope={pageSearchScope.key} data-wd-search-scope-label={pageSearchScope.label} data-wd-search-space={pageSearchSpace?.id} data-wd-search-public={pageSearchSpace?.searchPublic ? "true" : undefined} data-pagefind-filter={`scope:${pageSearchScope.key}`}>
|
|
521
548
|
{pageSectionTrail && <span data-pagefind-meta="section">{pageSectionTrail}</span>}
|
|
522
549
|
</div>
|
|
523
550
|
</div>
|
|
@@ -543,7 +570,7 @@ const components = {
|
|
|
543
570
|
</div>
|
|
544
571
|
<Content components={components} />
|
|
545
572
|
{entry.data.openapi && <ApiPlayground operation={entry.data.openapi} contentDir={contentDir} />}
|
|
546
|
-
<div hidden data-wd-search-scope={pageSearchScope.key} data-wd-search-scope-label={pageSearchScope.label} data-pagefind-filter={`scope:${pageSearchScope.key}`}>
|
|
573
|
+
<div hidden data-wd-search-scope={pageSearchScope.key} data-wd-search-scope-label={pageSearchScope.label} data-wd-search-space={pageSearchSpace?.id} data-wd-search-public={pageSearchSpace?.searchPublic ? "true" : undefined} data-pagefind-filter={`scope:${pageSearchScope.key}`}>
|
|
547
574
|
{pageSectionTrail && <span data-pagefind-meta="section">{pageSectionTrail}</span>}
|
|
548
575
|
</div>
|
|
549
576
|
{!entry.data.hideFooterPagination && (
|
package/src/scripts/search.ts
CHANGED
|
@@ -53,7 +53,10 @@ export function initSearch(root: ParentNode) {
|
|
|
53
53
|
let loadPromise: Promise<any> | null = null;
|
|
54
54
|
function ensurePagefind() {
|
|
55
55
|
if (!loadPromise) {
|
|
56
|
-
|
|
56
|
+
// A page under a hidden navigation item searches its own space's index
|
|
57
|
+
// (cli/run-pagefind.js, lib/hidden-sections.js); every other page, the site's.
|
|
58
|
+
const space = document.querySelector<HTMLElement>('[data-wd-search-scope]')?.dataset.wdSearchSpace;
|
|
59
|
+
const pagefindUrl = space ? `/pagefind-spaces/${encodeURIComponent(space)}/pagefind.js` : '/pagefind/pagefind.js';
|
|
57
60
|
loadPromise = import(/* @vite-ignore */ pagefindUrl)
|
|
58
61
|
.then((mod: any) => {
|
|
59
62
|
pagefind = mod;
|
package/writedocs.schema.json
CHANGED
|
@@ -1200,6 +1200,16 @@
|
|
|
1200
1200
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1201
1201
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1202
1202
|
},
|
|
1203
|
+
"hidden": {
|
|
1204
|
+
"type": "boolean",
|
|
1205
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1206
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1207
|
+
},
|
|
1208
|
+
"searchPublic": {
|
|
1209
|
+
"type": "boolean",
|
|
1210
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1211
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1212
|
+
},
|
|
1203
1213
|
"pages": {
|
|
1204
1214
|
"type": "array",
|
|
1205
1215
|
"items": {
|
|
@@ -1228,6 +1238,16 @@
|
|
|
1228
1238
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1229
1239
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1230
1240
|
},
|
|
1241
|
+
"hidden": {
|
|
1242
|
+
"type": "boolean",
|
|
1243
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1244
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1245
|
+
},
|
|
1246
|
+
"searchPublic": {
|
|
1247
|
+
"type": "boolean",
|
|
1248
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1249
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1250
|
+
},
|
|
1231
1251
|
"tabs": {
|
|
1232
1252
|
"minItems": 1,
|
|
1233
1253
|
"type": "array",
|
|
@@ -1257,6 +1277,16 @@
|
|
|
1257
1277
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1258
1278
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1259
1279
|
},
|
|
1280
|
+
"hidden": {
|
|
1281
|
+
"type": "boolean",
|
|
1282
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1283
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1284
|
+
},
|
|
1285
|
+
"searchPublic": {
|
|
1286
|
+
"type": "boolean",
|
|
1287
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1288
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1289
|
+
},
|
|
1260
1290
|
"versions": {
|
|
1261
1291
|
"minItems": 1,
|
|
1262
1292
|
"type": "array",
|
|
@@ -1286,6 +1316,16 @@
|
|
|
1286
1316
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1287
1317
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1288
1318
|
},
|
|
1319
|
+
"hidden": {
|
|
1320
|
+
"type": "boolean",
|
|
1321
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1322
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1323
|
+
},
|
|
1324
|
+
"searchPublic": {
|
|
1325
|
+
"type": "boolean",
|
|
1326
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1327
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1328
|
+
},
|
|
1289
1329
|
"languages": {
|
|
1290
1330
|
"minItems": 1,
|
|
1291
1331
|
"type": "array",
|
|
@@ -1315,6 +1355,16 @@
|
|
|
1315
1355
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1316
1356
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1317
1357
|
},
|
|
1358
|
+
"hidden": {
|
|
1359
|
+
"type": "boolean",
|
|
1360
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1361
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1362
|
+
},
|
|
1363
|
+
"searchPublic": {
|
|
1364
|
+
"type": "boolean",
|
|
1365
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1366
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1367
|
+
},
|
|
1318
1368
|
"dropdowns": {
|
|
1319
1369
|
"minItems": 1,
|
|
1320
1370
|
"type": "array",
|
|
@@ -1344,6 +1394,16 @@
|
|
|
1344
1394
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1345
1395
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1346
1396
|
},
|
|
1397
|
+
"hidden": {
|
|
1398
|
+
"type": "boolean",
|
|
1399
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1400
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1401
|
+
},
|
|
1402
|
+
"searchPublic": {
|
|
1403
|
+
"type": "boolean",
|
|
1404
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1405
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1406
|
+
},
|
|
1347
1407
|
"products": {
|
|
1348
1408
|
"minItems": 1,
|
|
1349
1409
|
"type": "array",
|
|
@@ -1373,6 +1433,16 @@
|
|
|
1373
1433
|
"description": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path.",
|
|
1374
1434
|
"markdownDescription": "Icon next to the name: a Lucide name, \"collection:name\", an emoji, or an image path."
|
|
1375
1435
|
},
|
|
1436
|
+
"hidden": {
|
|
1437
|
+
"type": "boolean",
|
|
1438
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1439
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1440
|
+
},
|
|
1441
|
+
"searchPublic": {
|
|
1442
|
+
"type": "boolean",
|
|
1443
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1444
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1445
|
+
},
|
|
1376
1446
|
"href": {
|
|
1377
1447
|
"type": "string",
|
|
1378
1448
|
"description": "Makes this a link to another address instead of a section with pages of its own.",
|
|
@@ -1404,6 +1474,16 @@
|
|
|
1404
1474
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1405
1475
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1406
1476
|
},
|
|
1477
|
+
"hidden": {
|
|
1478
|
+
"type": "boolean",
|
|
1479
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1480
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1481
|
+
},
|
|
1482
|
+
"searchPublic": {
|
|
1483
|
+
"type": "boolean",
|
|
1484
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1485
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1486
|
+
},
|
|
1407
1487
|
"pages": {
|
|
1408
1488
|
"type": "array",
|
|
1409
1489
|
"items": {
|
|
@@ -1432,6 +1512,16 @@
|
|
|
1432
1512
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1433
1513
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1434
1514
|
},
|
|
1515
|
+
"hidden": {
|
|
1516
|
+
"type": "boolean",
|
|
1517
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1518
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1519
|
+
},
|
|
1520
|
+
"searchPublic": {
|
|
1521
|
+
"type": "boolean",
|
|
1522
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1523
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1524
|
+
},
|
|
1435
1525
|
"tabs": {
|
|
1436
1526
|
"minItems": 1,
|
|
1437
1527
|
"type": "array",
|
|
@@ -1461,6 +1551,16 @@
|
|
|
1461
1551
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1462
1552
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1463
1553
|
},
|
|
1554
|
+
"hidden": {
|
|
1555
|
+
"type": "boolean",
|
|
1556
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1557
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1558
|
+
},
|
|
1559
|
+
"searchPublic": {
|
|
1560
|
+
"type": "boolean",
|
|
1561
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1562
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1563
|
+
},
|
|
1464
1564
|
"versions": {
|
|
1465
1565
|
"minItems": 1,
|
|
1466
1566
|
"type": "array",
|
|
@@ -1490,6 +1590,16 @@
|
|
|
1490
1590
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1491
1591
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1492
1592
|
},
|
|
1593
|
+
"hidden": {
|
|
1594
|
+
"type": "boolean",
|
|
1595
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1596
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1597
|
+
},
|
|
1598
|
+
"searchPublic": {
|
|
1599
|
+
"type": "boolean",
|
|
1600
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1601
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1602
|
+
},
|
|
1493
1603
|
"languages": {
|
|
1494
1604
|
"minItems": 1,
|
|
1495
1605
|
"type": "array",
|
|
@@ -1519,6 +1629,16 @@
|
|
|
1519
1629
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1520
1630
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1521
1631
|
},
|
|
1632
|
+
"hidden": {
|
|
1633
|
+
"type": "boolean",
|
|
1634
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1635
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1636
|
+
},
|
|
1637
|
+
"searchPublic": {
|
|
1638
|
+
"type": "boolean",
|
|
1639
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1640
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1641
|
+
},
|
|
1522
1642
|
"dropdowns": {
|
|
1523
1643
|
"minItems": 1,
|
|
1524
1644
|
"type": "array",
|
|
@@ -1548,6 +1668,16 @@
|
|
|
1548
1668
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1549
1669
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1550
1670
|
},
|
|
1671
|
+
"hidden": {
|
|
1672
|
+
"type": "boolean",
|
|
1673
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1674
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1675
|
+
},
|
|
1676
|
+
"searchPublic": {
|
|
1677
|
+
"type": "boolean",
|
|
1678
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1679
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1680
|
+
},
|
|
1551
1681
|
"products": {
|
|
1552
1682
|
"minItems": 1,
|
|
1553
1683
|
"type": "array",
|
|
@@ -1577,6 +1707,16 @@
|
|
|
1577
1707
|
"description": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path.",
|
|
1578
1708
|
"markdownDescription": "Icon next to the name: a Lucide name (\"book-open\"), \"collection:name\" (\"mdi:server\"), an emoji, or an image path."
|
|
1579
1709
|
},
|
|
1710
|
+
"hidden": {
|
|
1711
|
+
"type": "boolean",
|
|
1712
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1713
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1714
|
+
},
|
|
1715
|
+
"searchPublic": {
|
|
1716
|
+
"type": "boolean",
|
|
1717
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1718
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1719
|
+
},
|
|
1580
1720
|
"href": {
|
|
1581
1721
|
"type": "string",
|
|
1582
1722
|
"description": "Makes this a link to another address instead of a section with pages of its own.",
|
|
@@ -1618,6 +1758,16 @@
|
|
|
1618
1758
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1619
1759
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1620
1760
|
},
|
|
1761
|
+
"hidden": {
|
|
1762
|
+
"type": "boolean",
|
|
1763
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1764
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1765
|
+
},
|
|
1766
|
+
"searchPublic": {
|
|
1767
|
+
"type": "boolean",
|
|
1768
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1769
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1770
|
+
},
|
|
1621
1771
|
"pages": {
|
|
1622
1772
|
"type": "array",
|
|
1623
1773
|
"items": {
|
|
@@ -1656,6 +1806,16 @@
|
|
|
1656
1806
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1657
1807
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1658
1808
|
},
|
|
1809
|
+
"hidden": {
|
|
1810
|
+
"type": "boolean",
|
|
1811
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1812
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1813
|
+
},
|
|
1814
|
+
"searchPublic": {
|
|
1815
|
+
"type": "boolean",
|
|
1816
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1817
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1818
|
+
},
|
|
1659
1819
|
"tabs": {
|
|
1660
1820
|
"minItems": 1,
|
|
1661
1821
|
"type": "array",
|
|
@@ -1695,6 +1855,16 @@
|
|
|
1695
1855
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1696
1856
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1697
1857
|
},
|
|
1858
|
+
"hidden": {
|
|
1859
|
+
"type": "boolean",
|
|
1860
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1861
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1862
|
+
},
|
|
1863
|
+
"searchPublic": {
|
|
1864
|
+
"type": "boolean",
|
|
1865
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1866
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1867
|
+
},
|
|
1698
1868
|
"versions": {
|
|
1699
1869
|
"minItems": 1,
|
|
1700
1870
|
"type": "array",
|
|
@@ -1734,6 +1904,16 @@
|
|
|
1734
1904
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1735
1905
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1736
1906
|
},
|
|
1907
|
+
"hidden": {
|
|
1908
|
+
"type": "boolean",
|
|
1909
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1910
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1911
|
+
},
|
|
1912
|
+
"searchPublic": {
|
|
1913
|
+
"type": "boolean",
|
|
1914
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1915
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1916
|
+
},
|
|
1737
1917
|
"languages": {
|
|
1738
1918
|
"minItems": 1,
|
|
1739
1919
|
"type": "array",
|
|
@@ -1773,6 +1953,16 @@
|
|
|
1773
1953
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1774
1954
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1775
1955
|
},
|
|
1956
|
+
"hidden": {
|
|
1957
|
+
"type": "boolean",
|
|
1958
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
1959
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
1960
|
+
},
|
|
1961
|
+
"searchPublic": {
|
|
1962
|
+
"type": "boolean",
|
|
1963
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
1964
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
1965
|
+
},
|
|
1776
1966
|
"dropdowns": {
|
|
1777
1967
|
"minItems": 1,
|
|
1778
1968
|
"type": "array",
|
|
@@ -1812,6 +2002,16 @@
|
|
|
1812
2002
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1813
2003
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1814
2004
|
},
|
|
2005
|
+
"hidden": {
|
|
2006
|
+
"type": "boolean",
|
|
2007
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2008
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2009
|
+
},
|
|
2010
|
+
"searchPublic": {
|
|
2011
|
+
"type": "boolean",
|
|
2012
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2013
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2014
|
+
},
|
|
1815
2015
|
"products": {
|
|
1816
2016
|
"minItems": 1,
|
|
1817
2017
|
"type": "array",
|
|
@@ -1851,6 +2051,16 @@
|
|
|
1851
2051
|
"description": "Open this version first. Without it, the first version is the default.",
|
|
1852
2052
|
"markdownDescription": "Open this version first. Without it, the first version is the default."
|
|
1853
2053
|
},
|
|
2054
|
+
"hidden": {
|
|
2055
|
+
"type": "boolean",
|
|
2056
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2057
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2058
|
+
},
|
|
2059
|
+
"searchPublic": {
|
|
2060
|
+
"type": "boolean",
|
|
2061
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2062
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2063
|
+
},
|
|
1854
2064
|
"href": {
|
|
1855
2065
|
"type": "string",
|
|
1856
2066
|
"description": "Makes this a link to another address instead of a section with pages of its own.",
|
|
@@ -1882,6 +2092,16 @@
|
|
|
1882
2092
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
1883
2093
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
1884
2094
|
},
|
|
2095
|
+
"hidden": {
|
|
2096
|
+
"type": "boolean",
|
|
2097
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2098
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2099
|
+
},
|
|
2100
|
+
"searchPublic": {
|
|
2101
|
+
"type": "boolean",
|
|
2102
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2103
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2104
|
+
},
|
|
1885
2105
|
"pages": {
|
|
1886
2106
|
"type": "array",
|
|
1887
2107
|
"items": {
|
|
@@ -1910,6 +2130,16 @@
|
|
|
1910
2130
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
1911
2131
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
1912
2132
|
},
|
|
2133
|
+
"hidden": {
|
|
2134
|
+
"type": "boolean",
|
|
2135
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2136
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2137
|
+
},
|
|
2138
|
+
"searchPublic": {
|
|
2139
|
+
"type": "boolean",
|
|
2140
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2141
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2142
|
+
},
|
|
1913
2143
|
"tabs": {
|
|
1914
2144
|
"minItems": 1,
|
|
1915
2145
|
"type": "array",
|
|
@@ -1939,6 +2169,16 @@
|
|
|
1939
2169
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
1940
2170
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
1941
2171
|
},
|
|
2172
|
+
"hidden": {
|
|
2173
|
+
"type": "boolean",
|
|
2174
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2175
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2176
|
+
},
|
|
2177
|
+
"searchPublic": {
|
|
2178
|
+
"type": "boolean",
|
|
2179
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2180
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2181
|
+
},
|
|
1942
2182
|
"versions": {
|
|
1943
2183
|
"minItems": 1,
|
|
1944
2184
|
"type": "array",
|
|
@@ -1968,6 +2208,16 @@
|
|
|
1968
2208
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
1969
2209
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
1970
2210
|
},
|
|
2211
|
+
"hidden": {
|
|
2212
|
+
"type": "boolean",
|
|
2213
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2214
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2215
|
+
},
|
|
2216
|
+
"searchPublic": {
|
|
2217
|
+
"type": "boolean",
|
|
2218
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2219
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2220
|
+
},
|
|
1971
2221
|
"languages": {
|
|
1972
2222
|
"minItems": 1,
|
|
1973
2223
|
"type": "array",
|
|
@@ -1997,6 +2247,16 @@
|
|
|
1997
2247
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
1998
2248
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
1999
2249
|
},
|
|
2250
|
+
"hidden": {
|
|
2251
|
+
"type": "boolean",
|
|
2252
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2253
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2254
|
+
},
|
|
2255
|
+
"searchPublic": {
|
|
2256
|
+
"type": "boolean",
|
|
2257
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2258
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2259
|
+
},
|
|
2000
2260
|
"dropdowns": {
|
|
2001
2261
|
"minItems": 1,
|
|
2002
2262
|
"type": "array",
|
|
@@ -2026,6 +2286,16 @@
|
|
|
2026
2286
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
2027
2287
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
2028
2288
|
},
|
|
2289
|
+
"hidden": {
|
|
2290
|
+
"type": "boolean",
|
|
2291
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2292
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2293
|
+
},
|
|
2294
|
+
"searchPublic": {
|
|
2295
|
+
"type": "boolean",
|
|
2296
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2297
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2298
|
+
},
|
|
2029
2299
|
"products": {
|
|
2030
2300
|
"minItems": 1,
|
|
2031
2301
|
"type": "array",
|
|
@@ -2055,6 +2325,16 @@
|
|
|
2055
2325
|
"description": "Text shown in the language picker instead of `language`, like \"Português\".",
|
|
2056
2326
|
"markdownDescription": "Text shown in the language picker instead of `language`, like \"Português\"."
|
|
2057
2327
|
},
|
|
2328
|
+
"hidden": {
|
|
2329
|
+
"type": "boolean",
|
|
2330
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2331
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2332
|
+
},
|
|
2333
|
+
"searchPublic": {
|
|
2334
|
+
"type": "boolean",
|
|
2335
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2336
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2337
|
+
},
|
|
2058
2338
|
"href": {
|
|
2059
2339
|
"type": "string",
|
|
2060
2340
|
"description": "Makes this a link to another address instead of a section with pages of its own.",
|
|
@@ -2091,6 +2371,16 @@
|
|
|
2091
2371
|
"description": "One line under the name in the product picker.",
|
|
2092
2372
|
"markdownDescription": "One line under the name in the product picker."
|
|
2093
2373
|
},
|
|
2374
|
+
"hidden": {
|
|
2375
|
+
"type": "boolean",
|
|
2376
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2377
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2378
|
+
},
|
|
2379
|
+
"searchPublic": {
|
|
2380
|
+
"type": "boolean",
|
|
2381
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2382
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2383
|
+
},
|
|
2094
2384
|
"pages": {
|
|
2095
2385
|
"type": "array",
|
|
2096
2386
|
"items": {
|
|
@@ -2124,6 +2414,16 @@
|
|
|
2124
2414
|
"description": "One line under the name in the product picker.",
|
|
2125
2415
|
"markdownDescription": "One line under the name in the product picker."
|
|
2126
2416
|
},
|
|
2417
|
+
"hidden": {
|
|
2418
|
+
"type": "boolean",
|
|
2419
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2420
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2421
|
+
},
|
|
2422
|
+
"searchPublic": {
|
|
2423
|
+
"type": "boolean",
|
|
2424
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2425
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2426
|
+
},
|
|
2127
2427
|
"tabs": {
|
|
2128
2428
|
"minItems": 1,
|
|
2129
2429
|
"type": "array",
|
|
@@ -2158,6 +2458,16 @@
|
|
|
2158
2458
|
"description": "One line under the name in the product picker.",
|
|
2159
2459
|
"markdownDescription": "One line under the name in the product picker."
|
|
2160
2460
|
},
|
|
2461
|
+
"hidden": {
|
|
2462
|
+
"type": "boolean",
|
|
2463
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2464
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2465
|
+
},
|
|
2466
|
+
"searchPublic": {
|
|
2467
|
+
"type": "boolean",
|
|
2468
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2469
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2470
|
+
},
|
|
2161
2471
|
"versions": {
|
|
2162
2472
|
"minItems": 1,
|
|
2163
2473
|
"type": "array",
|
|
@@ -2192,6 +2502,16 @@
|
|
|
2192
2502
|
"description": "One line under the name in the product picker.",
|
|
2193
2503
|
"markdownDescription": "One line under the name in the product picker."
|
|
2194
2504
|
},
|
|
2505
|
+
"hidden": {
|
|
2506
|
+
"type": "boolean",
|
|
2507
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2508
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2509
|
+
},
|
|
2510
|
+
"searchPublic": {
|
|
2511
|
+
"type": "boolean",
|
|
2512
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2513
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2514
|
+
},
|
|
2195
2515
|
"languages": {
|
|
2196
2516
|
"minItems": 1,
|
|
2197
2517
|
"type": "array",
|
|
@@ -2226,6 +2546,16 @@
|
|
|
2226
2546
|
"description": "One line under the name in the product picker.",
|
|
2227
2547
|
"markdownDescription": "One line under the name in the product picker."
|
|
2228
2548
|
},
|
|
2549
|
+
"hidden": {
|
|
2550
|
+
"type": "boolean",
|
|
2551
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2552
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2553
|
+
},
|
|
2554
|
+
"searchPublic": {
|
|
2555
|
+
"type": "boolean",
|
|
2556
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2557
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2558
|
+
},
|
|
2229
2559
|
"dropdowns": {
|
|
2230
2560
|
"minItems": 1,
|
|
2231
2561
|
"type": "array",
|
|
@@ -2260,6 +2590,16 @@
|
|
|
2260
2590
|
"description": "One line under the name in the product picker.",
|
|
2261
2591
|
"markdownDescription": "One line under the name in the product picker."
|
|
2262
2592
|
},
|
|
2593
|
+
"hidden": {
|
|
2594
|
+
"type": "boolean",
|
|
2595
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2596
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2597
|
+
},
|
|
2598
|
+
"searchPublic": {
|
|
2599
|
+
"type": "boolean",
|
|
2600
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2601
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2602
|
+
},
|
|
2263
2603
|
"products": {
|
|
2264
2604
|
"minItems": 1,
|
|
2265
2605
|
"type": "array",
|
|
@@ -2294,6 +2634,16 @@
|
|
|
2294
2634
|
"description": "One line under the name in the product picker.",
|
|
2295
2635
|
"markdownDescription": "One line under the name in the product picker."
|
|
2296
2636
|
},
|
|
2637
|
+
"hidden": {
|
|
2638
|
+
"type": "boolean",
|
|
2639
|
+
"description": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves.",
|
|
2640
|
+
"markdownDescription": "Reachable only by its address: no tab, switcher or menu lists it, and inside it this level isn't shown. Its pages search only among themselves."
|
|
2641
|
+
},
|
|
2642
|
+
"searchPublic": {
|
|
2643
|
+
"type": "boolean",
|
|
2644
|
+
"description": "For a hidden item: its search also finds the site's public pages.",
|
|
2645
|
+
"markdownDescription": "For a hidden item: its search also finds the site's public pages."
|
|
2646
|
+
},
|
|
2297
2647
|
"href": {
|
|
2298
2648
|
"type": "string",
|
|
2299
2649
|
"description": "Makes this a link to another address instead of a section with pages of its own.",
|