jamdesk 1.1.38 → 1.1.39
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 +14 -11
- package/dist/__tests__/integration/init.integration.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/components/callouts.mdx +56 -0
- package/templates/components/cards.mdx +80 -0
- package/templates/components/steps.mdx +39 -0
- package/templates/components/tabs-and-accordions.mdx +65 -0
- package/templates/docs.json +21 -0
- package/templates/introduction.mdx +40 -10
- package/templates/quickstart.mdx +98 -9
- package/templates/writing/code-blocks.mdx +80 -0
- package/templates/writing/components.mdx +78 -0
- package/templates/writing/pages.mdx +59 -0
- package/vendored/app/api/chat/[project]/route.ts +2 -2
- package/vendored/app/api/docs-search/[project]/search/route.ts +15 -50
- package/vendored/app/layout.tsx +4 -4
- package/vendored/components/navigation/Sidebar.tsx +9 -4
- package/vendored/components/search/SearchModal.tsx +6 -6
- package/vendored/lib/language-utils.ts +6 -0
- package/vendored/lib/search-client.ts +72 -24
- package/vendored/lib/static-file-route.ts +13 -0
- package/vendored/scripts/build-search-index.cjs +34 -26
|
@@ -70,30 +70,29 @@ describe('init command integration', () => {
|
|
|
70
70
|
const { init } = await import('../../commands/init.js');
|
|
71
71
|
await init('api-project');
|
|
72
72
|
const targetDir = path.join(tmpDir, 'api-project');
|
|
73
|
-
// Files exist
|
|
74
73
|
const openapiPagePath = path.join(targetDir, 'api-reference/openapi-example.mdx');
|
|
75
74
|
const examplesPagePath = path.join(targetDir, 'api-reference/request-response-examples.mdx');
|
|
76
75
|
const specPath = path.join(targetDir, 'openapi/example-api.yaml');
|
|
77
|
-
expect(fs.existsSync(openapiPagePath)).toBe(true);
|
|
78
|
-
expect(fs.existsSync(examplesPagePath)).toBe(true);
|
|
79
|
-
expect(fs.existsSync(specPath)).toBe(true);
|
|
80
|
-
// Page content is non-empty and uses an openapi frontmatter directive.
|
|
81
76
|
// Regex avoids brittle coupling to the exact spec path/method.
|
|
82
77
|
const openapiPage = await fs.readFile(openapiPagePath, 'utf8');
|
|
83
78
|
expect(openapiPage).toMatch(/^openapi:\s*\/openapi\/example-api\.yaml\s+\w+\s+\/\S+/m);
|
|
84
79
|
const examplesPage = await fs.readFile(examplesPagePath, 'utf8');
|
|
85
80
|
expect(examplesPage).toContain('<RequestExample>');
|
|
86
81
|
expect(examplesPage).toContain('<ResponseExample>');
|
|
87
|
-
// YAML spec is non-empty and references the /tickets path
|
|
88
82
|
const spec = await fs.readFile(specPath, 'utf8');
|
|
89
83
|
expect(spec).toMatch(/^openapi:\s*3\./m);
|
|
90
84
|
expect(spec).toMatch(/\/tickets:/);
|
|
91
|
-
|
|
92
|
-
const docsJson = await fs.readJson(
|
|
85
|
+
const docsJsonPath = path.join(targetDir, 'docs.json');
|
|
86
|
+
const docsJson = await fs.readJson(docsJsonPath);
|
|
93
87
|
expect(docsJson.api?.openapi).toContain('/openapi/example-api.yaml');
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
const languages = docsJson.api?.examples?.languages;
|
|
89
|
+
expect(Array.isArray(languages)).toBe(true);
|
|
90
|
+
// arrayContaining so adding a 10th language to the starter doesn't break CLI CI.
|
|
91
|
+
expect(languages).toEqual(expect.arrayContaining(['curl', 'python', 'javascript', 'go', 'ruby', 'csharp', 'java', 'rust', 'php']));
|
|
92
|
+
// Validate against the live schema — catches drift in the api block (unknown
|
|
93
|
+
// language enum, missing required fields) that arrayContaining wouldn't.
|
|
94
|
+
const validation = await validateConfig(docsJsonPath);
|
|
95
|
+
expect(validation.valid).toBe(true);
|
|
97
96
|
const nav = docsJson.navigation;
|
|
98
97
|
const allGroups = [
|
|
99
98
|
...(nav.groups ?? []),
|
|
@@ -114,6 +113,10 @@ describe('init command integration', () => {
|
|
|
114
113
|
expect(fs.existsSync(path.join(targetDir, 'docs.json'))).toBe(true);
|
|
115
114
|
expect(fs.existsSync(path.join(targetDir, 'introduction.mdx'))).toBe(true);
|
|
116
115
|
expect(fs.existsSync(path.join(targetDir, 'quickstart.mdx'))).toBe(true);
|
|
116
|
+
// Bundled fallback ships api-reference + openapi too — guard against vendor drift.
|
|
117
|
+
expect(fs.existsSync(path.join(targetDir, 'api-reference/openapi-example.mdx'))).toBe(true);
|
|
118
|
+
expect(fs.existsSync(path.join(targetDir, 'api-reference/request-response-examples.mdx'))).toBe(true);
|
|
119
|
+
expect(fs.existsSync(path.join(targetDir, 'openapi/example-api.yaml'))).toBe(true);
|
|
117
120
|
const docsJson = await fs.readJson(path.join(targetDir, 'docs.json'));
|
|
118
121
|
expect(docsJson.name).toBe('Fallback Project');
|
|
119
122
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.integration.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/init.integration.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"init.integration.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/init.integration.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.39",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Callouts
|
|
3
|
+
description: "Draw attention to important information with Note, Tip, Warning, Danger, and Check callouts. Supports custom titles and rich Markdown content."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Callouts draw attention to key information.
|
|
7
|
+
|
|
8
|
+
## Types
|
|
9
|
+
|
|
10
|
+
<Note>
|
|
11
|
+
Helpful context or additional information.
|
|
12
|
+
</Note>
|
|
13
|
+
|
|
14
|
+
<Info>
|
|
15
|
+
Neutral information or supplementary facts.
|
|
16
|
+
</Info>
|
|
17
|
+
|
|
18
|
+
<Tip>
|
|
19
|
+
Best practices or optimization suggestions.
|
|
20
|
+
</Tip>
|
|
21
|
+
|
|
22
|
+
<Warning>
|
|
23
|
+
Important caveats or requirements.
|
|
24
|
+
</Warning>
|
|
25
|
+
|
|
26
|
+
<Danger>
|
|
27
|
+
Critical warnings — actions that could cause data loss.
|
|
28
|
+
</Danger>
|
|
29
|
+
|
|
30
|
+
<Check>
|
|
31
|
+
Success confirmations or completed steps.
|
|
32
|
+
</Check>
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```mdx
|
|
37
|
+
<Note>
|
|
38
|
+
Helpful context or additional information.
|
|
39
|
+
</Note>
|
|
40
|
+
|
|
41
|
+
<Warning>
|
|
42
|
+
Important caveats or requirements.
|
|
43
|
+
</Warning>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## With custom title
|
|
47
|
+
|
|
48
|
+
<Note title="Did you know?">
|
|
49
|
+
You can use **Markdown** inside callouts, including `code` and [links](/introduction).
|
|
50
|
+
</Note>
|
|
51
|
+
|
|
52
|
+
```mdx
|
|
53
|
+
<Note title="Did you know?">
|
|
54
|
+
You can use **Markdown** inside callouts.
|
|
55
|
+
</Note>
|
|
56
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cards
|
|
3
|
+
description: "Create linked feature grids and navigation panels with Card and Columns components. Supports icons, descriptions, and click-through links."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Basic card
|
|
7
|
+
|
|
8
|
+
<Card title="Getting Started">
|
|
9
|
+
A simple card with a title and content.
|
|
10
|
+
</Card>
|
|
11
|
+
|
|
12
|
+
```mdx
|
|
13
|
+
<Card title="Getting Started">
|
|
14
|
+
A simple card with a title and content.
|
|
15
|
+
</Card>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Card with icon
|
|
19
|
+
|
|
20
|
+
<Card title="Documentation" icon="book">
|
|
21
|
+
Add an icon for visual emphasis.
|
|
22
|
+
</Card>
|
|
23
|
+
|
|
24
|
+
```mdx
|
|
25
|
+
<Card title="Documentation" icon="book">
|
|
26
|
+
Add an icon for visual emphasis.
|
|
27
|
+
</Card>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Card with link
|
|
31
|
+
|
|
32
|
+
<Card title="Quickstart Guide" icon="play" href="/quickstart">
|
|
33
|
+
Cards can link to other pages.
|
|
34
|
+
</Card>
|
|
35
|
+
|
|
36
|
+
```mdx
|
|
37
|
+
<Card title="Quickstart Guide" icon="play" href="/quickstart">
|
|
38
|
+
Cards can link to other pages.
|
|
39
|
+
</Card>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Card group
|
|
43
|
+
|
|
44
|
+
Use `Columns` to arrange cards in a grid:
|
|
45
|
+
|
|
46
|
+
<Columns cols={2}>
|
|
47
|
+
<Card title="Docs" icon="book">
|
|
48
|
+
Documentation
|
|
49
|
+
</Card>
|
|
50
|
+
<Card title="API" icon="code">
|
|
51
|
+
API Reference
|
|
52
|
+
</Card>
|
|
53
|
+
<Card title="Guides" icon="graduation-cap">
|
|
54
|
+
Tutorials
|
|
55
|
+
</Card>
|
|
56
|
+
<Card title="Support" icon="life-ring">
|
|
57
|
+
Get help
|
|
58
|
+
</Card>
|
|
59
|
+
</Columns>
|
|
60
|
+
|
|
61
|
+
```mdx
|
|
62
|
+
<Columns cols={2}>
|
|
63
|
+
<Card title="Docs" icon="book">Documentation</Card>
|
|
64
|
+
<Card title="API" icon="code">API Reference</Card>
|
|
65
|
+
</Columns>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Properties
|
|
69
|
+
|
|
70
|
+
<ParamField name="title" type="string" required>
|
|
71
|
+
The title displayed at the top of the card.
|
|
72
|
+
</ParamField>
|
|
73
|
+
|
|
74
|
+
<ParamField name="icon" type="string">
|
|
75
|
+
Font Awesome icon name (e.g., "book", "code", "rocket").
|
|
76
|
+
</ParamField>
|
|
77
|
+
|
|
78
|
+
<ParamField name="href" type="string">
|
|
79
|
+
URL the card links to.
|
|
80
|
+
</ParamField>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Steps
|
|
3
|
+
description: "Break multi-step processes into numbered instructions with Steps and Step components. Each step gets a title and supports rich content."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use Steps for ordered instructions:
|
|
7
|
+
|
|
8
|
+
<Steps>
|
|
9
|
+
<Step title="Install the CLI">
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g jamdesk
|
|
12
|
+
```
|
|
13
|
+
</Step>
|
|
14
|
+
<Step title="Initialize your project">
|
|
15
|
+
```bash
|
|
16
|
+
jamdesk init
|
|
17
|
+
```
|
|
18
|
+
</Step>
|
|
19
|
+
<Step title="Start writing">
|
|
20
|
+
Open your project and create `.mdx` files. Each file becomes a page in your docs.
|
|
21
|
+
</Step>
|
|
22
|
+
</Steps>
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```mdx
|
|
27
|
+
<Steps>
|
|
28
|
+
<Step title="First step">
|
|
29
|
+
Description of what to do.
|
|
30
|
+
</Step>
|
|
31
|
+
<Step title="Second step">
|
|
32
|
+
Next instruction.
|
|
33
|
+
</Step>
|
|
34
|
+
</Steps>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
<Tip>
|
|
38
|
+
Steps work well for setup guides, tutorials, and getting-started flows.
|
|
39
|
+
</Tip>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tabs & Accordions
|
|
3
|
+
description: "Organize content with Tabs for language or platform variants and AccordionGroup for collapsible FAQ-style sections that save vertical space."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Tabs
|
|
7
|
+
|
|
8
|
+
Show content in switchable panels:
|
|
9
|
+
|
|
10
|
+
<Tabs>
|
|
11
|
+
<Tab title="React">
|
|
12
|
+
```jsx
|
|
13
|
+
function App() {
|
|
14
|
+
return <h1>Hello React</h1>;
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
</Tab>
|
|
18
|
+
<Tab title="Vue">
|
|
19
|
+
```html
|
|
20
|
+
<template>
|
|
21
|
+
<h1>Hello Vue</h1>
|
|
22
|
+
</template>
|
|
23
|
+
```
|
|
24
|
+
</Tab>
|
|
25
|
+
<Tab title="Svelte">
|
|
26
|
+
```html
|
|
27
|
+
<h1>Hello Svelte</h1>
|
|
28
|
+
```
|
|
29
|
+
</Tab>
|
|
30
|
+
</Tabs>
|
|
31
|
+
|
|
32
|
+
```mdx
|
|
33
|
+
<Tabs>
|
|
34
|
+
<Tab title="React">
|
|
35
|
+
Content for the React tab.
|
|
36
|
+
</Tab>
|
|
37
|
+
<Tab title="Vue">
|
|
38
|
+
Content for the Vue tab.
|
|
39
|
+
</Tab>
|
|
40
|
+
</Tabs>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Accordions
|
|
44
|
+
|
|
45
|
+
Collapsible sections for optional details:
|
|
46
|
+
|
|
47
|
+
<AccordionGroup>
|
|
48
|
+
<Accordion title="What is Jamdesk?">
|
|
49
|
+
Jamdesk is a documentation platform that builds and hosts your docs from MDX files in a GitHub repository.
|
|
50
|
+
</Accordion>
|
|
51
|
+
<Accordion title="How do I deploy?">
|
|
52
|
+
Push to GitHub. Jamdesk builds and deploys automatically on every push.
|
|
53
|
+
</Accordion>
|
|
54
|
+
<Accordion title="Can I use a custom domain?">
|
|
55
|
+
Yes. Configure your custom domain in the Jamdesk dashboard under project settings.
|
|
56
|
+
</Accordion>
|
|
57
|
+
</AccordionGroup>
|
|
58
|
+
|
|
59
|
+
```mdx
|
|
60
|
+
<AccordionGroup>
|
|
61
|
+
<Accordion title="Question here">
|
|
62
|
+
Answer here.
|
|
63
|
+
</Accordion>
|
|
64
|
+
</AccordionGroup>
|
|
65
|
+
```
|
package/templates/docs.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "{{PROJECT_NAME}}",
|
|
3
3
|
"theme": "jam",
|
|
4
|
+
"colors": {
|
|
5
|
+
"primary": "#635BFF",
|
|
6
|
+
"light": "#7C75FF",
|
|
7
|
+
"dark": "#4F46E5"
|
|
8
|
+
},
|
|
4
9
|
"api": {
|
|
5
10
|
"openapi": [
|
|
6
11
|
"/openapi/example-api.yaml"
|
|
@@ -23,8 +28,24 @@
|
|
|
23
28
|
"groups": [
|
|
24
29
|
{
|
|
25
30
|
"group": "Getting Started",
|
|
31
|
+
"icon": "rocket",
|
|
26
32
|
"pages": ["introduction", "quickstart"]
|
|
27
33
|
},
|
|
34
|
+
{
|
|
35
|
+
"group": "Writing Content",
|
|
36
|
+
"icon": "pen",
|
|
37
|
+
"pages": ["writing/pages", "writing/components", "writing/code-blocks"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"group": "Built-In Components",
|
|
41
|
+
"icon": "puzzle-piece",
|
|
42
|
+
"pages": [
|
|
43
|
+
"components/cards",
|
|
44
|
+
"components/callouts",
|
|
45
|
+
"components/tabs-and-accordions",
|
|
46
|
+
"components/steps"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
28
49
|
{
|
|
29
50
|
"group": "API Pages",
|
|
30
51
|
"icon": "plug",
|
|
@@ -1,19 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Introduction
|
|
3
|
-
description:
|
|
3
|
+
description: "Starter project with example pages, components, and API reference. Edit MDX, customize your theme, and ship docs in minutes — preview locally or auto-deploy from GitHub."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Welcome to your Jamdesk starter project. Everything you need to ship a polished documentation site is in this repo — edit the MDX files, tweak `docs.json`, and you're live.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
<Tip>
|
|
9
|
+
**Built-in AI search.** Click the sparkles button in the top-right corner of the header — chat is grounded on this site's pages and works out of the box.
|
|
10
|
+
</Tip>
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## What's included
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
<Columns cols={2}>
|
|
15
|
+
<Card title="Ready-made pages" icon="file-lines">
|
|
16
|
+
Example pages for getting started, writing content, and API references.
|
|
17
|
+
</Card>
|
|
18
|
+
<Card title="50+ components" icon="puzzle-piece" href="/components/cards">
|
|
19
|
+
Cards, callouts, tabs, steps, code groups, accordions — no imports needed.
|
|
20
|
+
</Card>
|
|
21
|
+
<Card title="OpenAPI rendering" icon="plug" href="/api-reference/openapi-example">
|
|
22
|
+
Auto-generate endpoint pages from a YAML or JSON spec.
|
|
23
|
+
</Card>
|
|
24
|
+
<Card title="Auto-deploy" icon="rocket">
|
|
25
|
+
Push to GitHub and your docs build and deploy in seconds.
|
|
26
|
+
</Card>
|
|
27
|
+
</Columns>
|
|
13
28
|
|
|
14
|
-
##
|
|
29
|
+
## A minimal Jamdesk project
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
```text
|
|
32
|
+
my-docs/
|
|
33
|
+
├── docs.json # Site config: theme, colors, navigation
|
|
34
|
+
├── introduction.mdx # This page
|
|
35
|
+
└── quickstart.mdx # Your getting-started guide
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That's all you need. Add more `.mdx` files to add more pages — the file path becomes the URL.
|
|
39
|
+
|
|
40
|
+
## Next steps
|
|
41
|
+
|
|
42
|
+
<Columns cols={2}>
|
|
43
|
+
<Card title="Quickstart" icon="play" href="/quickstart">
|
|
44
|
+
Edit pages locally, customize, and deploy.
|
|
45
|
+
</Card>
|
|
46
|
+
<Card title="Components" icon="puzzle-piece" href="/components/cards">
|
|
47
|
+
Browse every built-in component with live examples.
|
|
48
|
+
</Card>
|
|
49
|
+
</Columns>
|
package/templates/quickstart.mdx
CHANGED
|
@@ -1,20 +1,109 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Quickstart
|
|
3
|
-
description:
|
|
3
|
+
description: "Edit MDX files, preview locally with the Jamdesk CLI, then connect a GitHub repo for automatic deploys. Customize colors, branding, and navigation in docs.json."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Your docs are built from MDX files in this repository. Edit them locally with the CLI, then connect to Jamdesk for automatic builds on every push.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## 1. Preview locally
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Install the Jamdesk CLI:
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
<CodeGroup>
|
|
13
|
+
```bash npm
|
|
14
|
+
npm install -g jamdesk
|
|
15
|
+
```
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
```bash brew
|
|
18
|
+
brew install jamdesk/tap/jamdesk
|
|
19
|
+
```
|
|
20
|
+
</CodeGroup>
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
Start the dev server with hot reload:
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
```bash
|
|
25
|
+
jamdesk dev
|
|
26
|
+
```
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
Open [http://localhost:3000](http://localhost:3000). Edits to MDX files appear instantly.
|
|
29
|
+
|
|
30
|
+
## 2. Edit a page
|
|
31
|
+
|
|
32
|
+
Open any `.mdx` file and start writing. MDX supports standard Markdown plus Jamdesk components.
|
|
33
|
+
|
|
34
|
+
```mdx
|
|
35
|
+
---
|
|
36
|
+
title: My Page
|
|
37
|
+
description: A brief description for SEO
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# Heading
|
|
41
|
+
|
|
42
|
+
Regular markdown works — **bold**, *italic*, `code`, [links](https://example.com).
|
|
43
|
+
|
|
44
|
+
<Tip>
|
|
45
|
+
Jamdesk components like this Tip drop in without imports.
|
|
46
|
+
</Tip>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 3. Add a new page
|
|
50
|
+
|
|
51
|
+
<Steps>
|
|
52
|
+
<Step title="Create an MDX file">
|
|
53
|
+
Add a new `.mdx` file anywhere in your project, for example `guides/deployment.mdx`.
|
|
54
|
+
</Step>
|
|
55
|
+
<Step title="Add it to navigation">
|
|
56
|
+
Open `docs.json` and add the page path to the `navigation` section:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"group": "Guides",
|
|
61
|
+
"pages": ["guides/deployment"]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
</Step>
|
|
65
|
+
</Steps>
|
|
66
|
+
|
|
67
|
+
## 4. Customize your site
|
|
68
|
+
|
|
69
|
+
Everything is configured in `docs.json`:
|
|
70
|
+
|
|
71
|
+
| Setting | What it does |
|
|
72
|
+
|---------|-------------|
|
|
73
|
+
| `name` | Site name shown in the header |
|
|
74
|
+
| `colors` | Primary, light, and dark accent colors |
|
|
75
|
+
| `logo` | Light and dark mode logo images |
|
|
76
|
+
| `theme` | Visual theme (`jam`, `nebula`, or `pulsar`) |
|
|
77
|
+
| `navigation` | Sidebar tabs, groups, and page order |
|
|
78
|
+
| `navbar` | Top navigation links and buttons |
|
|
79
|
+
|
|
80
|
+
<Tip>
|
|
81
|
+
See the full configuration reference at [jamdesk.com/docs/config/docs-json-reference](https://jamdesk.com/docs/config/docs-json-reference).
|
|
82
|
+
</Tip>
|
|
83
|
+
|
|
84
|
+
## 5. Connect GitHub for auto-deploy
|
|
85
|
+
|
|
86
|
+
Once your docs look right locally, hand off building and hosting to Jamdesk:
|
|
87
|
+
|
|
88
|
+
<Steps>
|
|
89
|
+
<Step title="Push your code to GitHub">
|
|
90
|
+
Create a repository and push your project.
|
|
91
|
+
</Step>
|
|
92
|
+
<Step title="Connect on the dashboard">
|
|
93
|
+
Sign in at [dashboard.jamdesk.com](https://dashboard.jamdesk.com), create a project, and connect your repository.
|
|
94
|
+
</Step>
|
|
95
|
+
<Step title="Push changes">
|
|
96
|
+
Every push triggers an automatic build. Your site is live in seconds at `<slug>.jamdesk.app` or your custom domain.
|
|
97
|
+
</Step>
|
|
98
|
+
</Steps>
|
|
99
|
+
|
|
100
|
+
## What's next
|
|
101
|
+
|
|
102
|
+
<Columns cols={2}>
|
|
103
|
+
<Card title="Components" icon="puzzle-piece" href="/components/cards">
|
|
104
|
+
Cards, callouts, tabs, steps, and more — all ready to use.
|
|
105
|
+
</Card>
|
|
106
|
+
<Card title="API Pages" icon="plug" href="/api-reference/openapi-example">
|
|
107
|
+
Render endpoint pages from an OpenAPI spec, or hand-author with components.
|
|
108
|
+
</Card>
|
|
109
|
+
</Columns>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Code Blocks
|
|
3
|
+
description: "Fenced code blocks with automatic syntax highlighting, optional file titles, and CodeGroup for multi-language examples side by side."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Fenced code blocks are automatically syntax-highlighted.
|
|
7
|
+
|
|
8
|
+
## Basic code block
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
function greet(name) {
|
|
12
|
+
return `Hello, ${name}!`;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Add a language identifier after the opening fence for syntax highlighting:
|
|
17
|
+
|
|
18
|
+
````mdx
|
|
19
|
+
```javascript
|
|
20
|
+
function greet(name) {
|
|
21
|
+
return `Hello, ${name}!`;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
````
|
|
25
|
+
|
|
26
|
+
## With title
|
|
27
|
+
|
|
28
|
+
Add a title after the language:
|
|
29
|
+
|
|
30
|
+
```javascript server.js
|
|
31
|
+
const express = require('express');
|
|
32
|
+
const app = express();
|
|
33
|
+
|
|
34
|
+
app.get('/', (req, res) => {
|
|
35
|
+
res.send('Hello World');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.listen(3000);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
````mdx
|
|
42
|
+
```javascript server.js
|
|
43
|
+
const express = require('express');
|
|
44
|
+
const app = express();
|
|
45
|
+
app.listen(3000);
|
|
46
|
+
```
|
|
47
|
+
````
|
|
48
|
+
|
|
49
|
+
## Code groups
|
|
50
|
+
|
|
51
|
+
Show the same example in multiple languages:
|
|
52
|
+
|
|
53
|
+
<CodeGroup>
|
|
54
|
+
```javascript Node.js
|
|
55
|
+
const response = await fetch('https://api.example.com/data');
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```python Python
|
|
60
|
+
import requests
|
|
61
|
+
response = requests.get('https://api.example.com/data')
|
|
62
|
+
data = response.json()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```bash cURL
|
|
66
|
+
curl -X GET https://api.example.com/data
|
|
67
|
+
```
|
|
68
|
+
</CodeGroup>
|
|
69
|
+
|
|
70
|
+
````mdx
|
|
71
|
+
<CodeGroup>
|
|
72
|
+
```javascript Node.js
|
|
73
|
+
const response = await fetch('https://api.example.com/data');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python Python
|
|
77
|
+
response = requests.get('https://api.example.com/data')
|
|
78
|
+
```
|
|
79
|
+
</CodeGroup>
|
|
80
|
+
````
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Components
|
|
3
|
+
description: "Choose the right component for the job — when to use Cards, Callouts, Tabs, Steps, and more. Live examples for every component live in the Components tab."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Jamdesk's MDX components are available in every page — no imports needed. This guide helps you pick the right one. For live examples of every component, see the **Components** tab in the sidebar.
|
|
7
|
+
|
|
8
|
+
## Highlighting information
|
|
9
|
+
|
|
10
|
+
<Note>
|
|
11
|
+
**Note** — Helpful context that enhances understanding.
|
|
12
|
+
</Note>
|
|
13
|
+
|
|
14
|
+
<Info>
|
|
15
|
+
**Info** — Neutral facts or supplementary details.
|
|
16
|
+
</Info>
|
|
17
|
+
|
|
18
|
+
<Tip>
|
|
19
|
+
**Tip** — Best practices and "pro tips."
|
|
20
|
+
</Tip>
|
|
21
|
+
|
|
22
|
+
<Warning>
|
|
23
|
+
**Warning** — Important caveats or requirements.
|
|
24
|
+
</Warning>
|
|
25
|
+
|
|
26
|
+
<Danger>
|
|
27
|
+
**Danger** — Critical warnings (data loss, security).
|
|
28
|
+
</Danger>
|
|
29
|
+
|
|
30
|
+
<Check>
|
|
31
|
+
**Check** — Success confirmations.
|
|
32
|
+
</Check>
|
|
33
|
+
|
|
34
|
+
Use the lightest one that conveys the urgency. Reach for `Warning` and `Danger` sparingly so they keep their weight.
|
|
35
|
+
|
|
36
|
+
## Linking to other pages
|
|
37
|
+
|
|
38
|
+
| Component | When to use |
|
|
39
|
+
|-----------|-------------|
|
|
40
|
+
| `<Card>` | A single feature or destination — title, icon, optional href. |
|
|
41
|
+
| `<Columns>` | A grid of cards (2, 3, or 4 columns). Wraps multiple `<Card>` children. |
|
|
42
|
+
|
|
43
|
+
```mdx
|
|
44
|
+
<Columns cols={2}>
|
|
45
|
+
<Card title="Quickstart" icon="rocket" href="/quickstart">
|
|
46
|
+
Get up and running in minutes.
|
|
47
|
+
</Card>
|
|
48
|
+
<Card title="Components" icon="puzzle-piece" href="/components/cards">
|
|
49
|
+
See every built-in component.
|
|
50
|
+
</Card>
|
|
51
|
+
</Columns>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Showing alternatives or steps
|
|
55
|
+
|
|
56
|
+
| Component | When to use |
|
|
57
|
+
|-----------|-------------|
|
|
58
|
+
| `<Tabs>` + `<Tab>` | Equal alternatives the reader chooses between (languages, platforms, OSes). |
|
|
59
|
+
| `<AccordionGroup>` + `<Accordion>` | Collapsed-by-default details (FAQs, advanced options). |
|
|
60
|
+
| `<Steps>` + `<Step>` | A linear sequence the reader walks through in order. |
|
|
61
|
+
| `<CodeGroup>` | Multi-language code blocks side by side with shared tab strip. |
|
|
62
|
+
|
|
63
|
+
If readers do all the work, use `<Steps>`. If they pick one path, use `<Tabs>`. If most readers skip it, use `<AccordionGroup>`.
|
|
64
|
+
|
|
65
|
+
## Documenting APIs
|
|
66
|
+
|
|
67
|
+
| Component | When to use |
|
|
68
|
+
|-----------|-------------|
|
|
69
|
+
| `<ParamField>` | A request parameter (path, query, header, body). |
|
|
70
|
+
| `<ResponseField>` | A response field. |
|
|
71
|
+
| `<RequestExample>` | The request code samples panel. |
|
|
72
|
+
| `<ResponseExample>` | The response payload(s) panel. |
|
|
73
|
+
|
|
74
|
+
See the **API Pages** group for working examples.
|
|
75
|
+
|
|
76
|
+
<Tip>
|
|
77
|
+
For the full component reference (every prop, every variant) see [jamdesk.com/docs/components/overview](https://jamdesk.com/docs/components/overview).
|
|
78
|
+
</Tip>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Pages
|
|
3
|
+
description: "How MDX files become pages, how frontmatter sets titles and descriptions, and how file paths map to URLs in your documentation site."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Every `.mdx` file in your project becomes a page. The file path determines the URL.
|
|
7
|
+
|
|
8
|
+
## Frontmatter
|
|
9
|
+
|
|
10
|
+
Each page starts with frontmatter — metadata between `---` fences:
|
|
11
|
+
|
|
12
|
+
```mdx
|
|
13
|
+
---
|
|
14
|
+
title: My Page Title
|
|
15
|
+
description: A short description for search engines
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Your content starts here.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Field | Required | Description |
|
|
22
|
+
|-------|----------|-------------|
|
|
23
|
+
| `title` | Yes | Page title shown in the browser tab and sidebar |
|
|
24
|
+
| `description` | No | Meta description for SEO |
|
|
25
|
+
|
|
26
|
+
## File organization
|
|
27
|
+
|
|
28
|
+
Your file structure maps directly to URLs:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
introduction.mdx → /introduction
|
|
32
|
+
quickstart.mdx → /quickstart
|
|
33
|
+
guides/deployment.mdx → /guides/deployment
|
|
34
|
+
api/users/list.mdx → /api/users/list
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Markdown features
|
|
38
|
+
|
|
39
|
+
MDX supports all standard Markdown:
|
|
40
|
+
|
|
41
|
+
- **Bold**, *italic*, `inline code`, and [links](https://jamdesk.com).
|
|
42
|
+
- Ordered and unordered lists (you're reading one).
|
|
43
|
+
- Fenced code blocks with syntax highlighting.
|
|
44
|
+
- Tables, blockquotes, and headings (`##`, `###`, `####`).
|
|
45
|
+
|
|
46
|
+
Lists, tables, and blockquotes render natively:
|
|
47
|
+
|
|
48
|
+
| Format | Markdown | Renders as |
|
|
49
|
+
|--------|----------|-----------|
|
|
50
|
+
| Bold | `**bold**` | **bold** |
|
|
51
|
+
| Italic | `*italic*` | *italic* |
|
|
52
|
+
| Code | `` `code` `` | `code` |
|
|
53
|
+
| Link | `[text](url)` | [text](https://jamdesk.com) |
|
|
54
|
+
|
|
55
|
+
> Blockquotes pull a passage out of the flow. Use them sparingly.
|
|
56
|
+
|
|
57
|
+
<Note>
|
|
58
|
+
Headings on the page automatically appear in the right sidebar as a table of contents.
|
|
59
|
+
</Note>
|
|
@@ -28,6 +28,7 @@ import { getDocsPath, getBaseUrl, trackChatAnalytics } from '@/lib/route-helpers
|
|
|
28
28
|
import { getAnthropicClient } from '@/lib/anthropic-client';
|
|
29
29
|
import { rewriteQueryForSearch } from '@/lib/query-rewriter';
|
|
30
30
|
import { fetchDocsConfig } from '@/lib/r2-content';
|
|
31
|
+
import { BCP47_LANGUAGE_RE } from '@/lib/language-utils';
|
|
31
32
|
import { redis } from '@/lib/redis';
|
|
32
33
|
import { CHAT_TOOLS } from '@/lib/chat-tools';
|
|
33
34
|
import { rewriteSlugLinks } from '@/lib/link-rewriter';
|
|
@@ -44,7 +45,6 @@ const MAX_HISTORY = 10;
|
|
|
44
45
|
* Used both for searchQuery enrichment and to skip the rewriter. */
|
|
45
46
|
const SHORT_FOLLOWUP_LEN = 60;
|
|
46
47
|
const VALID_ROLES = new Set<string>(['user', 'assistant']);
|
|
47
|
-
const VALID_LOCALE_RE = /^[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/;
|
|
48
48
|
|
|
49
49
|
export async function POST(
|
|
50
50
|
request: NextRequest,
|
|
@@ -92,7 +92,7 @@ export async function POST(
|
|
|
92
92
|
|
|
93
93
|
let clientLocale: string | undefined;
|
|
94
94
|
if (body.locale !== undefined) {
|
|
95
|
-
if (typeof body.locale !== 'string' || !
|
|
95
|
+
if (typeof body.locale !== 'string' || !BCP47_LANGUAGE_RE.test(body.locale)) {
|
|
96
96
|
return Response.json({ error: 'Invalid locale' }, { status: 400 });
|
|
97
97
|
}
|
|
98
98
|
clientLocale = body.locale;
|
|
@@ -22,6 +22,7 @@ import { getBaseUrl, trackServerAnalytics } from '@/lib/route-helpers';
|
|
|
22
22
|
import { redis } from '@/lib/redis';
|
|
23
23
|
import { fetchDocsConfig } from '@/lib/r2-content';
|
|
24
24
|
import { isMultiLanguageConfig } from '@/lib/navigation-utils';
|
|
25
|
+
import { BCP47_LANGUAGE_RE } from '@/lib/language-utils';
|
|
25
26
|
import { log } from '@/lib/logger';
|
|
26
27
|
|
|
27
28
|
export const runtime = 'nodejs';
|
|
@@ -37,10 +38,6 @@ const MAX_LIMIT = 20;
|
|
|
37
38
|
const DEFAULT_LIMIT = 5;
|
|
38
39
|
const MAX_QUERY_LENGTH = 500;
|
|
39
40
|
const RATE_LIMIT_PER_MIN = 60;
|
|
40
|
-
/** BCP-47-ish: 2-3 letter primary tag, optional 2-4 letter region/script.
|
|
41
|
-
* Matches the chat endpoint's VALID_LOCALE_RE — keep both contracts in sync.
|
|
42
|
-
* Limitation: 3-segment tags like `zh-Hant-HK` are rejected. Documented. */
|
|
43
|
-
const VALID_LANGUAGE_RE = /^[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/;
|
|
44
41
|
const DEFAULT_LANGUAGE = 'en';
|
|
45
42
|
|
|
46
43
|
export async function OPTIONS(_request: NextRequest) {
|
|
@@ -109,21 +106,17 @@ export async function POST(
|
|
|
109
106
|
|
|
110
107
|
const { query, limit: rawLimit } = body;
|
|
111
108
|
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// rejecting them as 400 would be gratuitous). A non-string value or a
|
|
115
|
-
// string that doesn't match the BCP-47 pattern is a 400.
|
|
109
|
+
// `null` is treated as omitted: real-world clients emit `language: null`
|
|
110
|
+
// from `?? null` fallbacks, and rejecting them would be gratuitous.
|
|
116
111
|
let language: string = DEFAULT_LANGUAGE;
|
|
117
|
-
let languageWasExplicit = false;
|
|
118
112
|
if (body.language != null) {
|
|
119
|
-
if (typeof body.language !== 'string' || !
|
|
113
|
+
if (typeof body.language !== 'string' || !BCP47_LANGUAGE_RE.test(body.language)) {
|
|
120
114
|
return NextResponse.json(
|
|
121
115
|
{ error: 'Invalid language code' },
|
|
122
116
|
{ status: 400, headers: CORS_HEADERS },
|
|
123
117
|
);
|
|
124
118
|
}
|
|
125
119
|
language = body.language;
|
|
126
|
-
languageWasExplicit = true;
|
|
127
120
|
}
|
|
128
121
|
|
|
129
122
|
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
|
@@ -154,47 +147,19 @@ export async function POST(
|
|
|
154
147
|
// Apply the locale filter only when the project is configured for
|
|
155
148
|
// multiple languages AND the per-project kill switch is not set.
|
|
156
149
|
// Single-language projects' chunks have no locale metadata, so the
|
|
157
|
-
// strict filter would return zero.
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let killSwitch: boolean;
|
|
169
|
-
try {
|
|
170
|
-
[config, killSwitch] = await Promise.all([
|
|
171
|
-
fetchDocsConfig(project),
|
|
172
|
-
killSwitchPromise,
|
|
173
|
-
]);
|
|
174
|
-
} catch (err) {
|
|
175
|
-
// R2 outage — config unavailable. Asymmetric fail-mode:
|
|
176
|
-
// - Caller sent an explicit language: 503 (don't silently leak
|
|
177
|
-
// cross-language results to a caller who asked for filtering).
|
|
178
|
-
// - Caller defaulted to 'en': fall back to today's no-filter behavior
|
|
179
|
-
// so docs sites keep working through R2 incidents.
|
|
180
|
-
log('error', 'docs-search: fetchDocsConfig failed', {
|
|
181
|
-
project,
|
|
182
|
-
error: String(err),
|
|
183
|
-
languageWasExplicit,
|
|
184
|
-
});
|
|
185
|
-
if (languageWasExplicit) {
|
|
186
|
-
return NextResponse.json(
|
|
187
|
-
{ error: 'Search temporarily unavailable' },
|
|
188
|
-
{ status: 503, headers: CORS_HEADERS },
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
config = null;
|
|
192
|
-
killSwitch = await killSwitchPromise.catch(() => false);
|
|
193
|
-
}
|
|
150
|
+
// strict filter would return zero. Both reads tolerate failure (R2
|
|
151
|
+
// outage or Redis blip) by falling back to "filter disabled" — same
|
|
152
|
+
// pattern the chat endpoint uses.
|
|
153
|
+
const [config, killSwitch] = await Promise.all([
|
|
154
|
+
fetchDocsConfig(project).catch(() => null),
|
|
155
|
+
redis
|
|
156
|
+
? redis.get(`searchLocaleFilter:${project}`)
|
|
157
|
+
.then((v) => v === 'disabled')
|
|
158
|
+
.catch(() => false)
|
|
159
|
+
: Promise.resolve(false),
|
|
160
|
+
]);
|
|
194
161
|
|
|
195
162
|
if (killSwitch) {
|
|
196
|
-
// Operators have flipped the kill switch for this project — log so we
|
|
197
|
-
// can spot escapes. Not an error, but worth surfacing.
|
|
198
163
|
log('warn', 'docs-search: locale filter kill switch is enabled', {
|
|
199
164
|
project,
|
|
200
165
|
});
|
package/vendored/app/layout.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Metadata } from 'next';
|
|
2
2
|
import { Inter, JetBrains_Mono } from 'next/font/google';
|
|
3
3
|
import { headers } from 'next/headers';
|
|
4
|
+
import Script from 'next/script';
|
|
4
5
|
import './globals.css';
|
|
5
6
|
import { ThemeProvider } from '@/components/theme/ThemeProvider';
|
|
6
7
|
import { LayoutWrapper } from '@/components/layout/LayoutWrapper';
|
|
@@ -411,10 +412,9 @@ export default async function RootLayout({
|
|
|
411
412
|
{/* Preload FA font files to avoid waterfall: CSS → font discovery → download */}
|
|
412
413
|
<link rel="preload" href="/_jd/fonts/fontawesome/webfonts/fa-light-300.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
|
|
413
414
|
<link rel="preload" href="/_jd/fonts/fontawesome/webfonts/fa-brands-400.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
|
|
414
|
-
<
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
/>
|
|
415
|
+
<Script id="fa-css-async-loader" strategy="beforeInteractive">
|
|
416
|
+
{`var l=document.createElement('link');l.rel='stylesheet';l.href='${FA_CSS_HREF}';document.head.appendChild(l);window.__FA_CSS_LOADED__=true;`}
|
|
417
|
+
</Script>
|
|
418
418
|
<noscript>
|
|
419
419
|
<link rel="stylesheet" href={FA_CSS_HREF} />
|
|
420
420
|
</noscript>
|
|
@@ -278,20 +278,25 @@ export function Sidebar({ config, layout = 'header-logo', tabsPosition: tabsPosi
|
|
|
278
278
|
// Prevent body scroll when sidebar is open on mobile
|
|
279
279
|
useBodyScrollLock(isOpen);
|
|
280
280
|
|
|
281
|
-
// Toggle group expansion; if expanding, navigate to first page
|
|
281
|
+
// Toggle group expansion; if expanding, navigate to first page.
|
|
282
|
+
// The router.push must run AFTER the setState commits — calling it inside the
|
|
283
|
+
// updater triggers "Cannot update a component (Router) while rendering Sidebar".
|
|
282
284
|
const handleGroupClick = useCallback((group: ResolvedGroup) => {
|
|
285
|
+
const willExpand = !expandedGroups.has(group.name);
|
|
283
286
|
setExpandedGroups(prev => {
|
|
284
287
|
const newSet = new Set(prev);
|
|
285
288
|
if (prev.has(group.name)) {
|
|
286
289
|
newSet.delete(group.name);
|
|
287
290
|
} else {
|
|
288
|
-
const firstPagePath = findFirstPageInGroups([group]);
|
|
289
|
-
if (firstPagePath) router.push(`${linkPrefix}/${firstPagePath}`);
|
|
290
291
|
newSet.add(group.name);
|
|
291
292
|
}
|
|
292
293
|
return newSet;
|
|
293
294
|
});
|
|
294
|
-
|
|
295
|
+
if (willExpand) {
|
|
296
|
+
const firstPagePath = findFirstPageInGroups([group]);
|
|
297
|
+
if (firstPagePath) router.push(`${linkPrefix}/${firstPagePath}`);
|
|
298
|
+
}
|
|
299
|
+
}, [router, linkPrefix, expandedGroups]);
|
|
295
300
|
|
|
296
301
|
// Render a navigation group (supports nesting)
|
|
297
302
|
function renderGroup(group: ResolvedGroup, level: number = 0) {
|
|
@@ -74,7 +74,7 @@ function NoResultsState({ query, onSuggestionClick }: { query: string; onSuggest
|
|
|
74
74
|
<button
|
|
75
75
|
key={suggestion}
|
|
76
76
|
onClick={() => onSuggestionClick(suggestion)}
|
|
77
|
-
className="px-2.5 py-1 text-xs bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-tertiary)] text-[var(--color-text-primary)] rounded-full transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
77
|
+
className="px-2.5 py-1 text-xs cursor-pointer bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-tertiary)] text-[var(--color-text-primary)] rounded-full transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
78
78
|
>
|
|
79
79
|
{suggestion}
|
|
80
80
|
</button>
|
|
@@ -410,7 +410,7 @@ export function SearchModal({ isOpen, onClose, popularPages, onNavigate }: Searc
|
|
|
410
410
|
/>
|
|
411
411
|
<button
|
|
412
412
|
onClick={onClose}
|
|
413
|
-
className="p-1.5 hover:bg-[var(--color-bg-tertiary)] rounded-lg transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
413
|
+
className="p-1.5 cursor-pointer hover:bg-[var(--color-bg-tertiary)] rounded-lg transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
414
414
|
aria-label="Close search"
|
|
415
415
|
>
|
|
416
416
|
<i className="fa-solid fa-xmark h-4 w-4 text-[var(--color-text-muted)]" aria-hidden="true" />
|
|
@@ -448,7 +448,7 @@ export function SearchModal({ isOpen, onClose, popularPages, onNavigate }: Searc
|
|
|
448
448
|
onClick={() => handleResultClick(result, index)}
|
|
449
449
|
role="option"
|
|
450
450
|
aria-selected={index === selectedIndex}
|
|
451
|
-
className={`w-full px-4 py-2.5 flex items-start gap-3 transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)] focus-visible:ring-inset ${
|
|
451
|
+
className={`w-full px-4 py-2.5 flex items-start gap-3 cursor-pointer transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)] focus-visible:ring-inset ${
|
|
452
452
|
index === selectedIndex
|
|
453
453
|
? 'bg-[var(--color-bg-secondary)]'
|
|
454
454
|
: 'hover:bg-[var(--color-bg-secondary)]'
|
|
@@ -490,7 +490,7 @@ export function SearchModal({ isOpen, onClose, popularPages, onNavigate }: Searc
|
|
|
490
490
|
</span>
|
|
491
491
|
<button
|
|
492
492
|
onClick={handleClearRecentSearches}
|
|
493
|
-
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
493
|
+
className="text-xs cursor-pointer text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
494
494
|
>
|
|
495
495
|
Clear
|
|
496
496
|
</button>
|
|
@@ -499,7 +499,7 @@ export function SearchModal({ isOpen, onClose, popularPages, onNavigate }: Searc
|
|
|
499
499
|
<button
|
|
500
500
|
key={term}
|
|
501
501
|
onClick={() => handleRecentSearchClick(term)}
|
|
502
|
-
className="w-full px-4 py-2 flex items-center gap-3 hover:bg-[var(--color-bg-secondary)] transition-colors group focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
502
|
+
className="w-full px-4 py-2 flex items-center gap-3 cursor-pointer hover:bg-[var(--color-bg-secondary)] transition-colors group focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]"
|
|
503
503
|
>
|
|
504
504
|
{/* Keyboard shortcut badge */}
|
|
505
505
|
<kbd className="w-4 h-4 flex items-center justify-center bg-[var(--color-bg-tertiary)] border border-[var(--color-border)] rounded text-[9px] font-medium text-[var(--color-text-muted)] group-hover:border-[var(--color-accent)] transition-colors">
|
|
@@ -532,7 +532,7 @@ export function SearchModal({ isOpen, onClose, popularPages, onNavigate }: Searc
|
|
|
532
532
|
router.push(url);
|
|
533
533
|
onClose();
|
|
534
534
|
}}
|
|
535
|
-
className={`w-full px-4 py-2.5 flex items-center gap-3 transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)] focus-visible:ring-inset ${
|
|
535
|
+
className={`w-full px-4 py-2.5 flex items-center gap-3 cursor-pointer transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)] focus-visible:ring-inset ${
|
|
536
536
|
index === popularIndex
|
|
537
537
|
? 'bg-[var(--color-bg-secondary)]'
|
|
538
538
|
: 'hover:bg-[var(--color-bg-secondary)]'
|
|
@@ -15,6 +15,12 @@ import LANGUAGE_CODES_JSON from './language-codes.json';
|
|
|
15
15
|
*/
|
|
16
16
|
export const LANGUAGE_CODES = LANGUAGE_CODES_JSON as readonly LanguageCode[];
|
|
17
17
|
|
|
18
|
+
/** BCP-47 syntax check (2-3 letter primary tag + optional 2-4 letter
|
|
19
|
+
* region/script). Shared by the chat and docs-search REST endpoints to
|
|
20
|
+
* keep their request-validation contracts identical. Limitation:
|
|
21
|
+
* 3-segment tags like `zh-Hant-HK` are rejected. */
|
|
22
|
+
export const BCP47_LANGUAGE_RE = /^[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/;
|
|
23
|
+
|
|
18
24
|
/**
|
|
19
25
|
* Display names for each supported language code
|
|
20
26
|
* Uses native language names where appropriate
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Client-side search with Orama (BM25 ranking)
|
|
2
2
|
import { create, insertMultiple, search as oramaSearch, type Orama } from '@orama/orama';
|
|
3
|
-
import { resolveLocaleWithLoweredSet } from './language-utils';
|
|
3
|
+
import { LANGUAGE_CODES, resolveLocaleWithLoweredSet } from './language-utils';
|
|
4
|
+
|
|
5
|
+
// Lowercased canonical language codes — used to detect slug-prefix locales when
|
|
6
|
+
// the search-data.json predates the locale field (legacy fallback).
|
|
7
|
+
const KNOWN_LANGUAGE_CODES_LOWERED: ReadonlySet<string> = new Set(
|
|
8
|
+
LANGUAGE_CODES.map((c) => c.toLowerCase()),
|
|
9
|
+
);
|
|
4
10
|
|
|
5
11
|
export interface SearchResult {
|
|
6
12
|
id: string;
|
|
@@ -38,16 +44,17 @@ let lastEtag = '';
|
|
|
38
44
|
let lastParsedData: SearchResult[] | null = null;
|
|
39
45
|
|
|
40
46
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
47
|
+
* Locales the URL-resolver treats as real translations for the current
|
|
48
|
+
* project. Populated from the index's `locale` field when present, falling
|
|
49
|
+
* back to slug-prefix derivation for legacy (pre-feature) search-data.json
|
|
50
|
+
* so French pages don't leak English results while customers rebuild.
|
|
45
51
|
*
|
|
46
|
-
* `
|
|
47
|
-
* `
|
|
52
|
+
* `indexHasLocaleField` is the separate gate that controls whether the Orama
|
|
53
|
+
* `where` clause is used — set only when the new index format is detected.
|
|
54
|
+
* Slug-prefix fallback runs in `search()` whenever the where clause is off.
|
|
48
55
|
*/
|
|
49
|
-
let projectLocales: Set<string> = new Set();
|
|
50
56
|
let projectLocalesLowered: ReadonlySet<string> = new Set();
|
|
57
|
+
let indexHasLocaleField = false;
|
|
51
58
|
|
|
52
59
|
/**
|
|
53
60
|
* Cheap fingerprint: count + first/last IDs + a sample of content lengths.
|
|
@@ -80,13 +87,26 @@ async function buildIndex(data: SearchResult[], etag: string): Promise<void> {
|
|
|
80
87
|
},
|
|
81
88
|
});
|
|
82
89
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
90
|
+
// Distinguish new-format docs (have a `locale` field, possibly '') from
|
|
91
|
+
// legacy ones (field missing entirely). Once the `?? ''` fallback runs in
|
|
92
|
+
// the map below the two are indistinguishable, so check the raw input first.
|
|
93
|
+
// A single doc with the field is enough — the build script always emits the
|
|
94
|
+
// field for every doc when it emits any.
|
|
95
|
+
indexHasLocaleField = data.some(
|
|
96
|
+
(d) => typeof (d as { locale?: unknown }).locale === 'string',
|
|
97
|
+
);
|
|
98
|
+
|
|
86
99
|
const seenLocales = new Set<string>();
|
|
100
|
+
const slugPrefixLocales = new Set<string>();
|
|
87
101
|
const normalizedData = data.map(item => {
|
|
88
102
|
const raw = item.locale;
|
|
89
103
|
if (typeof raw === 'string' && raw.length > 0) seenLocales.add(raw);
|
|
104
|
+
// Track first-segment slug prefixes that match a known language code,
|
|
105
|
+
// used as the URL-to-locale whitelist when the index lacks the field.
|
|
106
|
+
const firstSeg = item.slug.split('/', 1)[0]?.toLowerCase();
|
|
107
|
+
if (firstSeg && KNOWN_LANGUAGE_CODES_LOWERED.has(firstSeg)) {
|
|
108
|
+
slugPrefixLocales.add(firstSeg);
|
|
109
|
+
}
|
|
90
110
|
return {
|
|
91
111
|
id: item.id,
|
|
92
112
|
title: item.title,
|
|
@@ -101,8 +121,13 @@ async function buildIndex(data: SearchResult[], etag: string): Promise<void> {
|
|
|
101
121
|
|
|
102
122
|
await insertMultiple(db, normalizedData);
|
|
103
123
|
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
// Trust the index's own locale set when present (handles the dodo case
|
|
125
|
+
// where `de/` is a directory, not a translation — `seenLocales` won't
|
|
126
|
+
// include `de`). Fall back to slug-prefix derivation for legacy indexes so
|
|
127
|
+
// the URL still maps to a sensible locale during the rebuild window.
|
|
128
|
+
projectLocalesLowered = indexHasLocaleField
|
|
129
|
+
? new Set(Array.from(seenLocales, (l) => l.toLowerCase()))
|
|
130
|
+
: slugPrefixLocales;
|
|
106
131
|
|
|
107
132
|
committedFingerprint = buildingFingerprint;
|
|
108
133
|
lastParsedData = data;
|
|
@@ -148,15 +173,15 @@ export async function search(
|
|
|
148
173
|
return [];
|
|
149
174
|
}
|
|
150
175
|
|
|
151
|
-
// Legacy-index safety: if the committed index has zero translated docs
|
|
152
|
-
// (single-language project OR pre-feature search-data.json), skip the where
|
|
153
|
-
// clause so un-rebuilt R2 files behave identically to before this feature.
|
|
154
|
-
const useFilter = projectLocales.size > 0;
|
|
155
176
|
const targetLocale = language ?? '';
|
|
156
177
|
|
|
178
|
+
// Fetch extra when post-filtering by slug prefix so we can still reach
|
|
179
|
+
// `limit` after dropping cross-locale hits.
|
|
180
|
+
const fetchLimit = indexHasLocaleField ? limit : Math.min(limit * 5, 50);
|
|
181
|
+
|
|
157
182
|
const results = await oramaSearch(db, {
|
|
158
183
|
term: query,
|
|
159
|
-
limit,
|
|
184
|
+
limit: fetchLimit,
|
|
160
185
|
tolerance: 1, // Allow 1 typo for fuzzy matching
|
|
161
186
|
boost: {
|
|
162
187
|
title: 2,
|
|
@@ -164,10 +189,32 @@ export async function search(
|
|
|
164
189
|
description: 1,
|
|
165
190
|
content: 0.5,
|
|
166
191
|
},
|
|
167
|
-
...(
|
|
192
|
+
...(indexHasLocaleField ? { where: { locale: { eq: targetLocale } } } : {}),
|
|
168
193
|
});
|
|
169
194
|
|
|
170
|
-
|
|
195
|
+
const docs = results.hits.map(hit => hit.document as unknown as SearchResult);
|
|
196
|
+
|
|
197
|
+
// Slug-prefix fallback for un-rebuilt indexes: the index has no locale
|
|
198
|
+
// field, so the where clause was skipped above. Filter by slug prefix
|
|
199
|
+
// instead so French pages don't see English results during the rebuild
|
|
200
|
+
// window. Inverted on default-language pages: drop slugs whose prefix
|
|
201
|
+
// matches any known project locale.
|
|
202
|
+
if (!indexHasLocaleField) {
|
|
203
|
+
if (targetLocale) {
|
|
204
|
+
const prefix = `${targetLocale}/`;
|
|
205
|
+
return docs.filter(d => d.slug.startsWith(prefix)).slice(0, limit);
|
|
206
|
+
}
|
|
207
|
+
if (projectLocalesLowered.size > 0) {
|
|
208
|
+
return docs
|
|
209
|
+
.filter(d => {
|
|
210
|
+
const firstSeg = d.slug.split('/', 1)[0]?.toLowerCase();
|
|
211
|
+
return !firstSeg || !projectLocalesLowered.has(firstSeg);
|
|
212
|
+
})
|
|
213
|
+
.slice(0, limit);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return docs.slice(0, limit);
|
|
171
218
|
}
|
|
172
219
|
|
|
173
220
|
/** @internal Used by tests only */
|
|
@@ -177,12 +224,13 @@ export function isInitialized(): boolean {
|
|
|
177
224
|
|
|
178
225
|
/**
|
|
179
226
|
* Resolve the locale that the search filter should target for a given pathname,
|
|
180
|
-
* gated by the locales actually present in the currently-committed index
|
|
227
|
+
* gated by the locales actually present in the currently-committed index
|
|
228
|
+
* (or, in legacy mode, by slug-prefix derivation).
|
|
181
229
|
*
|
|
182
230
|
* Returns:
|
|
183
|
-
* - `''` when there is no committed index yet, OR
|
|
184
|
-
* the
|
|
185
|
-
* - The canonical language code when the prefix matches
|
|
231
|
+
* - `''` when there is no committed index yet, OR the pathname has no
|
|
232
|
+
* language prefix, OR the prefix is not a known project locale.
|
|
233
|
+
* - The canonical language code when the prefix matches.
|
|
186
234
|
*
|
|
187
235
|
* Caller (SearchModal) should pass the result to `search()`. Empty string and
|
|
188
236
|
* undefined are equivalent — both target the default-language doc set.
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* duplication across 12 route files (6 at root + 6 at /docs).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
9
12
|
import { NextRequest, NextResponse } from 'next/server';
|
|
10
13
|
|
|
11
14
|
import { log } from '@/lib/logger';
|
|
@@ -55,6 +58,16 @@ export function createStaticFileHandler(
|
|
|
55
58
|
|
|
56
59
|
return async function GET(request: NextRequest): Promise<NextResponse> {
|
|
57
60
|
if (!isIsrMode()) {
|
|
61
|
+
// Dev fallback: `dev-project.cjs` writes static artifacts (search-data.json,
|
|
62
|
+
// sitemap.xml, ...) into `public/`. Next.js prefers route handlers over
|
|
63
|
+
// public/ files when both exist, so without this branch the dev server
|
|
64
|
+
// 404s on these paths and breaks the SearchModal init.
|
|
65
|
+
const localPath = path.join(process.cwd(), 'public', filename);
|
|
66
|
+
if (fs.existsSync(localPath)) {
|
|
67
|
+
return new NextResponse(fs.readFileSync(localPath), {
|
|
68
|
+
headers: { 'Content-Type': contentType, 'Cache-Control': 'no-cache' },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
58
71
|
return new NextResponse('Not found', { status: 404 });
|
|
59
72
|
}
|
|
60
73
|
|
|
@@ -310,32 +310,40 @@ async function buildSearchIndex() {
|
|
|
310
310
|
async function processFile({ filePath, slug }) {
|
|
311
311
|
await semaphore.acquire();
|
|
312
312
|
try {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
313
|
+
try {
|
|
314
|
+
const fileContents = await fsPromises.readFile(filePath, 'utf8');
|
|
315
|
+
const { data, content } = parseFrontmatterLenient(fileContents);
|
|
316
|
+
// Filter for="agents" content out of the search index.
|
|
317
|
+
const visibleContent = filterVisibility(content, 'humans');
|
|
318
|
+
const sections = extractSections(visibleContent);
|
|
319
|
+
|
|
320
|
+
const docs = [];
|
|
321
|
+
const normalizedSlug = slug.replace(/\\/g, '/');
|
|
322
|
+
const pageType = inferPageType(normalizedSlug);
|
|
323
|
+
const locale = resolveLocaleFromPath(normalizedSlug, projectLanguages);
|
|
324
|
+
sections.forEach((section, idx) => {
|
|
325
|
+
const cleanContent = stripMarkdown(section.content);
|
|
326
|
+
if (cleanContent.trim()) {
|
|
327
|
+
docs.push({
|
|
328
|
+
id: `${slug}-${idx}`,
|
|
329
|
+
title: data.title || slug.split('/').pop() || '',
|
|
330
|
+
description: data.description,
|
|
331
|
+
content: cleanContent.substring(0, 300),
|
|
332
|
+
slug: normalizedSlug,
|
|
333
|
+
section: section.heading || undefined,
|
|
334
|
+
type: pageType,
|
|
335
|
+
locale,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
return docs;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
// One malformed MDX file (bad YAML, etc.) used to abort the entire index
|
|
342
|
+
// build — leaving the project searchless. Skip the file with a warning
|
|
343
|
+
// so the rest of the docs remain searchable.
|
|
344
|
+
console.warn(`⚠ Skipping ${filePath} in search index: ${err.message}`);
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
339
347
|
} finally {
|
|
340
348
|
semaphore.release();
|
|
341
349
|
}
|