create-fumadocs-app 16.0.1 → 16.0.3
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/chunk-3VWJJEHU.js +204 -0
- package/dist/chunk-HLHY7KAF.js +181 -0
- package/dist/create-app.d.ts +23 -8
- package/dist/create-app.js +4 -5
- package/dist/index.js +33 -31
- package/dist/orama-cloud-VQUOOEZ2.js +313 -0
- package/package.json +5 -3
- package/template/+next+fuma-docs-mdx/app/(home)/page.tsx +16 -0
- package/template/{+next+tailwindcss → +next+fuma-docs-mdx}/app/layout.tsx +1 -1
- package/template/{+next → +next+fuma-docs-mdx}/example.gitignore +0 -2
- package/template/+next+fuma-docs-mdx/lib/layout.shared.tsx +9 -0
- package/template/+next+fuma-docs-mdx/lib/source.ts +1 -1
- package/template/{+next → +next+fuma-docs-mdx}/mdx-components.tsx +0 -1
- package/template/+next+fuma-docs-mdx/package.json +29 -0
- package/template/+next+fuma-docs-mdx/source.config.ts +1 -0
- package/template/+next+fuma-docs-mdx/tsconfig.json +2 -2
- package/template/+orama-cloud/@app/components/search.tsx +59 -0
- package/template/+orama-cloud/@app/lib/export-static-indexes.ts +14 -0
- package/template/+orama-cloud/@root/.env.example +6 -0
- package/template/react-router/app/docs/page.tsx +4 -4
- package/template/react-router/example.gitignore +0 -1
- package/template/react-router/react-router.config.ts +3 -3
- package/template/react-router-spa/README.md +12 -0
- package/template/react-router-spa/app/app.css +3 -0
- package/template/react-router-spa/app/components/search.tsx +50 -0
- package/template/react-router-spa/app/docs/page.tsx +53 -0
- package/template/react-router-spa/app/docs/search.ts +10 -0
- package/template/react-router-spa/app/lib/layout.shared.tsx +9 -0
- package/template/react-router-spa/app/lib/source.ts +7 -0
- package/template/react-router-spa/app/root.tsx +76 -0
- package/template/react-router-spa/app/routes/home.tsx +30 -0
- package/template/react-router-spa/app/routes.ts +7 -0
- package/template/react-router-spa/content/docs/index.mdx +32 -0
- package/template/react-router-spa/content/docs/test.mdx +24 -0
- package/template/react-router-spa/example.gitignore +7 -0
- package/template/react-router-spa/package.json +36 -0
- package/template/react-router-spa/public/favicon.ico +0 -0
- package/template/react-router-spa/react-router.config.ts +23 -0
- package/template/react-router-spa/serve.json +3 -0
- package/template/react-router-spa/source.config.ts +7 -0
- package/template/react-router-spa/tsconfig.json +28 -0
- package/template/react-router-spa/vite.config.ts +10 -0
- package/template/tanstack-start/example.gitignore +9 -1
- package/template/tanstack-start/package.json +6 -6
- package/template/waku/example.gitignore +3 -1
- package/template/waku/package.json +3 -3
- package/dist/chunk-YVEWA65C.js +0 -370
- package/template/+next/README.md +0 -37
- package/template/+next/app/(home)/page.tsx +0 -42
- package/template/+next/app/layout.tsx +0 -23
- package/template/+next/lib/layout.shared.tsx +0 -30
- package/template/+next+content-collections/app/docs/[[...slug]]/page.tsx +0 -51
- package/template/+next+content-collections/content-collections.ts +0 -26
- package/template/+next+content-collections/lib/source.ts +0 -8
- package/template/+next+content-collections/next.config.mjs +0 -8
- package/template/+next+content-collections/tsconfig.json +0 -35
- package/template/+next+tailwindcss/app/(home)/page.tsx +0 -19
- package/template/{+next → +next+fuma-docs-mdx}/app/(home)/layout.tsx +0 -0
- package/template/{+next → +next+fuma-docs-mdx}/app/api/search/route.ts +0 -0
- package/template/{+next → +next+fuma-docs-mdx}/app/docs/layout.tsx +1 -1
- /package/template/{+next+tailwindcss → +next+fuma-docs-mdx}/app/global.css +0 -0
- /package/template/{+next → +next+fuma-docs-mdx}/content/docs/index.mdx +0 -0
- /package/template/{+next → +next+fuma-docs-mdx}/content/docs/test.mdx +0 -0
- /package/template/{+next+tailwindcss → +next+fuma-docs-mdx}/postcss.config.mjs +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copy,
|
|
3
|
+
depVersions,
|
|
4
|
+
pick,
|
|
5
|
+
sourceDir,
|
|
6
|
+
writeFile
|
|
7
|
+
} from "./chunk-HLHY7KAF.js";
|
|
8
|
+
|
|
9
|
+
// src/plugins/orama-cloud.ts
|
|
10
|
+
import path from "path";
|
|
11
|
+
import fs2 from "fs/promises";
|
|
12
|
+
|
|
13
|
+
// src/transform/shared.ts
|
|
14
|
+
import { IndentationText, Project, QuoteKind } from "ts-morph";
|
|
15
|
+
import fs from "fs/promises";
|
|
16
|
+
var project = new Project({
|
|
17
|
+
manipulationSettings: {
|
|
18
|
+
indentationText: IndentationText.TwoSpaces,
|
|
19
|
+
quoteKind: QuoteKind.Single
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
async function createSourceFile(path2) {
|
|
23
|
+
return project.createSourceFile(path2, (await fs.readFile(path2)).toString(), {
|
|
24
|
+
overwrite: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function getCodeValue(v) {
|
|
28
|
+
return new Function(`return ${v}`)();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/transform/tanstack-start.ts
|
|
32
|
+
import { SyntaxKind } from "ts-morph";
|
|
33
|
+
function addTanstackPrerender(sourceFile, paths) {
|
|
34
|
+
const optionsArg = getTanstackStartCall(sourceFile)?.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
35
|
+
if (!optionsArg) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const pagesProperty = optionsArg.getProperty("pages")?.asKind(SyntaxKind.PropertyAssignment);
|
|
39
|
+
if (pagesProperty) {
|
|
40
|
+
const initializer = pagesProperty.getInitializerIfKindOrThrow(
|
|
41
|
+
SyntaxKind.ArrayLiteralExpression
|
|
42
|
+
);
|
|
43
|
+
const existingPaths = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const element of initializer.getElements()) {
|
|
45
|
+
const value = element.asKind(SyntaxKind.ObjectLiteralExpression)?.getProperty("path")?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()?.getText();
|
|
46
|
+
if (value) {
|
|
47
|
+
existingPaths.add(getCodeValue(value));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const path2 of paths) {
|
|
51
|
+
if (existingPaths.has(path2)) continue;
|
|
52
|
+
initializer.addElement(toItem(path2));
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
optionsArg.addProperty(
|
|
56
|
+
`pages: [
|
|
57
|
+
${paths.map((path2) => ` ${toItem(path2)}`).join(",\n")}
|
|
58
|
+
]`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function toItem(path2) {
|
|
63
|
+
return `{ path: '${path2}' }`;
|
|
64
|
+
}
|
|
65
|
+
function getTanstackStartCall(sourceFile) {
|
|
66
|
+
const pluginsProperty = sourceFile.getDefaultExportSymbol()?.getValueDeclaration()?.getFirstDescendantByKind(SyntaxKind.ObjectLiteralExpression)?.getProperty("plugins")?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
|
|
67
|
+
if (!pluginsProperty) return;
|
|
68
|
+
for (const element of pluginsProperty.getElements()) {
|
|
69
|
+
const expression = element.asKind(SyntaxKind.CallExpression);
|
|
70
|
+
if (expression && expression.getFirstChildByKind(SyntaxKind.Identifier)?.getText() === "tanstackStart") {
|
|
71
|
+
return expression;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/transform/react-router.ts
|
|
77
|
+
import {
|
|
78
|
+
ts
|
|
79
|
+
} from "ts-morph";
|
|
80
|
+
var SyntaxKind2 = ts.SyntaxKind;
|
|
81
|
+
function removeReactRouterPrerenderExclude(sourceFile, paths) {
|
|
82
|
+
const methodBody = getPrerenderMethod(sourceFile)?.getBody();
|
|
83
|
+
if (!methodBody) return;
|
|
84
|
+
const initializer = methodBody.getDescendantsOfKind(SyntaxKind2.VariableDeclaration).find((item) => item.getName() === "excluded")?.getInitializerIfKind(SyntaxKind2.ArrayLiteralExpression);
|
|
85
|
+
if (!initializer) return;
|
|
86
|
+
for (const element of initializer.getElements()) {
|
|
87
|
+
const value = getCodeValue(element.getText());
|
|
88
|
+
if (paths.includes(value)) {
|
|
89
|
+
initializer.removeElement(element);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function addReactRouterRoute(sourceFile, routes) {
|
|
94
|
+
modifyReactRouterRoutes(sourceFile, (arr) => {
|
|
95
|
+
for (const { path: path2, entry } of routes) {
|
|
96
|
+
arr.addElement(`route('${path2}', '${entry}')`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function filterReactRouterRoute(sourceFile, filter) {
|
|
101
|
+
modifyReactRouterRoutes(sourceFile, (arr) => {
|
|
102
|
+
for (const element of arr.getElements()) {
|
|
103
|
+
if (!element.isKind(SyntaxKind2.CallExpression) || element.getFirstChildByKind(SyntaxKind2.Identifier)?.getText() !== "route" || filter({
|
|
104
|
+
path: getCodeValue(element.getArguments()[0].getText())
|
|
105
|
+
}))
|
|
106
|
+
continue;
|
|
107
|
+
arr.removeElement(element);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function modifyReactRouterRoutes(sourceFile, mod) {
|
|
112
|
+
const initializer = sourceFile.getDefaultExportSymbol()?.getValueDeclaration()?.getFirstDescendantByKind(SyntaxKind2.ArrayLiteralExpression);
|
|
113
|
+
if (initializer) mod(initializer);
|
|
114
|
+
}
|
|
115
|
+
function getPrerenderMethod(sourceFile) {
|
|
116
|
+
return sourceFile.getDefaultExportSymbol()?.getValueDeclaration()?.getFirstDescendantByKind(SyntaxKind2.ObjectLiteralExpression)?.getProperty("prerender")?.asKind(SyntaxKind2.MethodDeclaration) ?? null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/transform/provider.ts
|
|
120
|
+
import { StructureKind, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
121
|
+
function addSearchDialog(sourceFile) {
|
|
122
|
+
const elements = sourceFile.getDescendantsOfKind(SyntaxKind3.JsxElement);
|
|
123
|
+
for (const element of elements) {
|
|
124
|
+
const provider = element.getFirstChildByKind(SyntaxKind3.JsxOpeningElement);
|
|
125
|
+
if (provider?.getTagNameNode().getText() !== "RootProvider") continue;
|
|
126
|
+
if (provider.getAttributes().some(
|
|
127
|
+
(attr) => attr.isKind(SyntaxKind3.JsxAttribute) && attr.getNameNode().getText() === "search"
|
|
128
|
+
))
|
|
129
|
+
continue;
|
|
130
|
+
provider.addAttribute({
|
|
131
|
+
kind: StructureKind.JsxAttribute,
|
|
132
|
+
name: "search",
|
|
133
|
+
initializer: "{{ SearchDialog }}"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
sourceFile.addImportDeclaration({
|
|
137
|
+
moduleSpecifier: "@/components/search",
|
|
138
|
+
defaultImport: "SearchDialog"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/plugins/orama-cloud.ts
|
|
143
|
+
function oramaCloud() {
|
|
144
|
+
return {
|
|
145
|
+
packageJson(packageJson) {
|
|
146
|
+
return {
|
|
147
|
+
...packageJson,
|
|
148
|
+
scripts: {
|
|
149
|
+
...packageJson.scripts,
|
|
150
|
+
build: `${packageJson.scripts.build} && bun scripts/sync-content.ts`
|
|
151
|
+
},
|
|
152
|
+
dependencies: {
|
|
153
|
+
...packageJson.dependencies,
|
|
154
|
+
...pick(depVersions, ["@orama/core"])
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
readme(content) {
|
|
159
|
+
return `${content}
|
|
160
|
+
|
|
161
|
+
## Orama Cloud
|
|
162
|
+
|
|
163
|
+
This project uses Orama Cloud for 3rd party search solution.
|
|
164
|
+
|
|
165
|
+
See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama Cloud to Fumadocs.`;
|
|
166
|
+
},
|
|
167
|
+
async afterWrite() {
|
|
168
|
+
const { dest, template, options } = this;
|
|
169
|
+
const appDir = path.join(dest, options.useSrcDir ? "src" : ".");
|
|
170
|
+
await copy(path.join(sourceDir, "template/+orama-cloud/@root"), dest);
|
|
171
|
+
await copy(path.join(sourceDir, "template/+orama-cloud/@app"), appDir);
|
|
172
|
+
if (template.value === "tanstack-start") {
|
|
173
|
+
await Promise.all([
|
|
174
|
+
fluent(
|
|
175
|
+
createSourceFile(path.join(dest, "vite.config.ts")),
|
|
176
|
+
(file) => addTanstackPrerender(file, ["/static.json"]),
|
|
177
|
+
(file) => file.save()
|
|
178
|
+
),
|
|
179
|
+
fs2.unlink(path.join(dest, "src/routes/api/search.ts")).catch(() => null),
|
|
180
|
+
writeFile(
|
|
181
|
+
path.join(dest, "src/routes/static[.]json.ts"),
|
|
182
|
+
route.tanstack
|
|
183
|
+
),
|
|
184
|
+
updateRootProvider(path.join(dest, "src/routes/__root.tsx"))
|
|
185
|
+
]);
|
|
186
|
+
} else if (template.value.startsWith("react-router")) {
|
|
187
|
+
await Promise.all([
|
|
188
|
+
fluent(
|
|
189
|
+
createSourceFile(path.join(dest, "app/routes.ts")),
|
|
190
|
+
(file) => filterReactRouterRoute(file, ({ path: path2 }) => path2 !== "api/search"),
|
|
191
|
+
(file) => addReactRouterRoute(file, [
|
|
192
|
+
{
|
|
193
|
+
path: "static.json",
|
|
194
|
+
entry: "routes/static.ts"
|
|
195
|
+
}
|
|
196
|
+
]),
|
|
197
|
+
(file) => file.save()
|
|
198
|
+
),
|
|
199
|
+
fluent(
|
|
200
|
+
createSourceFile(path.join(dest, "react-router.config.ts")),
|
|
201
|
+
(file) => removeReactRouterPrerenderExclude(file, ["/api/search"]),
|
|
202
|
+
(file) => file.save()
|
|
203
|
+
),
|
|
204
|
+
fs2.unlink(path.join(dest, "app/docs/search.ts")).catch(() => null),
|
|
205
|
+
writeFile(
|
|
206
|
+
path.join(dest, "app/routes/static.ts"),
|
|
207
|
+
route["react-router"]
|
|
208
|
+
),
|
|
209
|
+
updateRootProvider(path.join(dest, "app/root.tsx"))
|
|
210
|
+
]);
|
|
211
|
+
} else if (template.value.startsWith("+next")) {
|
|
212
|
+
await Promise.all([
|
|
213
|
+
fs2.unlink(path.join(appDir, "app/api/search/route.ts")).catch(() => null),
|
|
214
|
+
writeFile(path.join(appDir, "app/static.json/route.ts"), route.next),
|
|
215
|
+
updateRootProvider(path.join(appDir, "app/layout.tsx"))
|
|
216
|
+
]);
|
|
217
|
+
} else {
|
|
218
|
+
await Promise.all([
|
|
219
|
+
fs2.unlink(path.join(dest, "src/pages/api/search.ts")).catch(() => null),
|
|
220
|
+
writeFile(
|
|
221
|
+
path.join(dest, "src/pages/api/static.json.ts"),
|
|
222
|
+
route.waku
|
|
223
|
+
),
|
|
224
|
+
updateRootProvider(path.join(dest, "src/components/provider.tsx"))
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
const filePath = {
|
|
228
|
+
"+next+fuma-docs-mdx": ".next/server/app/static.json.body",
|
|
229
|
+
"tanstack-start": ".output/public/static.json",
|
|
230
|
+
"react-router": "build/client/static.json",
|
|
231
|
+
"react-router-spa": "build/client/static.json",
|
|
232
|
+
waku: "dist/public/static.json"
|
|
233
|
+
}[template.value];
|
|
234
|
+
await writeFile(
|
|
235
|
+
path.join(dest, "scripts/sync-content.ts"),
|
|
236
|
+
`import { type OramaDocument, sync } from 'fumadocs-core/search/orama-cloud';
|
|
237
|
+
import * as fs from 'node:fs/promises';
|
|
238
|
+
import { OramaCloud } from '@orama/core';
|
|
239
|
+
|
|
240
|
+
// the path of pre-rendered \`static.json\`
|
|
241
|
+
const filePath = '${filePath}';
|
|
242
|
+
|
|
243
|
+
async function main() {
|
|
244
|
+
const orama = new OramaCloud({
|
|
245
|
+
projectId: process.env.NEXT_PUBLIC_ORAMA_PROJECT_ID,
|
|
246
|
+
apiKey: process.env.ORAMA_PRIVATE_API_KEY,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const content = await fs.readFile(filePath);
|
|
250
|
+
const records = JSON.parse(content.toString()) as OramaDocument[];
|
|
251
|
+
|
|
252
|
+
await sync(orama, {
|
|
253
|
+
index: process.env.NEXT_PUBLIC_ORAMA_DATASOURCE_ID,
|
|
254
|
+
documents: records,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
console.log(\`search updated: \${records.length} records\`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
void main();`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function updateRootProvider(rootPath) {
|
|
266
|
+
await fluent(
|
|
267
|
+
createSourceFile(rootPath),
|
|
268
|
+
(file) => addSearchDialog(file),
|
|
269
|
+
(file) => file.save()
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
async function fluent(value, ...actions) {
|
|
273
|
+
for (const action of actions) {
|
|
274
|
+
await action(await value);
|
|
275
|
+
}
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
var route = {
|
|
279
|
+
next: `import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
280
|
+
|
|
281
|
+
export const revalidate = false;
|
|
282
|
+
|
|
283
|
+
export async function GET() {
|
|
284
|
+
return Response.json(await exportSearchIndexes());
|
|
285
|
+
}`,
|
|
286
|
+
"react-router": `import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
287
|
+
|
|
288
|
+
export async function loader() {
|
|
289
|
+
return Response.json(await exportSearchIndexes());
|
|
290
|
+
}`,
|
|
291
|
+
tanstack: `import { createFileRoute } from '@tanstack/react-router';
|
|
292
|
+
import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
293
|
+
|
|
294
|
+
export const Route = createFileRoute('/static.json')({
|
|
295
|
+
server: {
|
|
296
|
+
handlers: {
|
|
297
|
+
GET: async () => Response.json(await exportSearchIndexes()),
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
});`,
|
|
301
|
+
waku: `import { exportSearchIndexes } from '@/lib/export-search-indexes';
|
|
302
|
+
|
|
303
|
+
export async function GET() {
|
|
304
|
+
return Response.json(await exportSearchIndexes());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const getConfig = () => ({
|
|
308
|
+
render: 'static',
|
|
309
|
+
});`
|
|
310
|
+
};
|
|
311
|
+
export {
|
|
312
|
+
oramaCloud
|
|
313
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fumadocs-app",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.3",
|
|
4
4
|
"description": "Create a new documentation site with Fumadocs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"NextJs",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"@clack/prompts": "^0.11.0",
|
|
25
25
|
"commander": "^14.0.1",
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
|
-
"tinyexec": "^1.0.1"
|
|
27
|
+
"tinyexec": "^1.0.1",
|
|
28
|
+
"ts-morph": "^27.0.2"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/cross-spawn": "^6.0.6",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"clean": "rimraf dist",
|
|
46
47
|
"dev": "tsup --watch",
|
|
47
48
|
"lint": "eslint .",
|
|
48
|
-
"types:check": "tsc --noEmit"
|
|
49
|
+
"types:check": "tsc --noEmit",
|
|
50
|
+
"sync": "bun ./scripts/sync.ts"
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
|
|
3
|
+
export default function HomePage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex flex-col justify-center text-center flex-1">
|
|
6
|
+
<h1 className="text-2xl font-bold mb-4">Hello World</h1>
|
|
7
|
+
<p>
|
|
8
|
+
You can open{' '}
|
|
9
|
+
<Link href="/docs" className="font-medium underline">
|
|
10
|
+
/docs
|
|
11
|
+
</Link>{' '}
|
|
12
|
+
and see the documentation.
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -21,7 +21,7 @@ export function getPageImage(page: InferPageType<typeof source>) {
|
|
|
21
21
|
export async function getLLMText(page: InferPageType<typeof source>) {
|
|
22
22
|
const processed = await page.data.getText('processed');
|
|
23
23
|
|
|
24
|
-
return `# ${page.data.title}
|
|
24
|
+
return `# ${page.data.title}
|
|
25
25
|
|
|
26
26
|
${processed}`;
|
|
27
27
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
|
2
2
|
import type { MDXComponents } from 'mdx/types';
|
|
3
3
|
|
|
4
|
-
// use this function to get MDX components, you will need it for rendering MDX
|
|
5
4
|
export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
|
6
5
|
return {
|
|
7
6
|
...defaultMdxComponents,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "example-next-mdx",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "next build",
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"start": "next start"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"fumadocs-core": "workspace:*",
|
|
12
|
+
"fumadocs-mdx": "workspace:*",
|
|
13
|
+
"fumadocs-ui": "workspace:*",
|
|
14
|
+
"lucide-react": "^0.546.0",
|
|
15
|
+
"next": "16.0.0",
|
|
16
|
+
"react": "^19.2.0",
|
|
17
|
+
"react-dom": "^19.2.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@tailwindcss/postcss": "^4.1.15",
|
|
21
|
+
"@types/mdx": "^2.0.13",
|
|
22
|
+
"@types/node": "^24.9.1",
|
|
23
|
+
"@types/react": "^19.2.2",
|
|
24
|
+
"@types/react-dom": "^19.2.2",
|
|
25
|
+
"postcss": "^8.5.6",
|
|
26
|
+
"tailwindcss": "^4.1.15",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
SearchDialog,
|
|
5
|
+
SearchDialogClose,
|
|
6
|
+
SearchDialogContent,
|
|
7
|
+
SearchDialogFooter,
|
|
8
|
+
SearchDialogHeader,
|
|
9
|
+
SearchDialogIcon,
|
|
10
|
+
SearchDialogInput,
|
|
11
|
+
SearchDialogList,
|
|
12
|
+
SearchDialogOverlay,
|
|
13
|
+
type SharedProps,
|
|
14
|
+
} from 'fumadocs-ui/components/dialog/search';
|
|
15
|
+
import { useDocsSearch } from 'fumadocs-core/search/client';
|
|
16
|
+
import { OramaCloud } from '@orama/core';
|
|
17
|
+
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
|
18
|
+
|
|
19
|
+
const client = new OramaCloud({
|
|
20
|
+
projectId: process.env.NEXT_PUBLIC_ORAMA_DATASOURCE_ID,
|
|
21
|
+
apiKey: process.env.NEXT_PUBLIC_ORAMA_API_KEY,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default function CustomSearchDialog(props: SharedProps) {
|
|
25
|
+
const { locale } = useI18n(); // (optional) for i18n
|
|
26
|
+
const { search, setSearch, query } = useDocsSearch({
|
|
27
|
+
type: 'orama-cloud',
|
|
28
|
+
client,
|
|
29
|
+
locale,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<SearchDialog
|
|
34
|
+
search={search}
|
|
35
|
+
onSearchChange={setSearch}
|
|
36
|
+
isLoading={query.isLoading}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<SearchDialogOverlay />
|
|
40
|
+
<SearchDialogContent>
|
|
41
|
+
<SearchDialogHeader>
|
|
42
|
+
<SearchDialogIcon />
|
|
43
|
+
<SearchDialogInput />
|
|
44
|
+
<SearchDialogClose />
|
|
45
|
+
</SearchDialogHeader>
|
|
46
|
+
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
|
|
47
|
+
<SearchDialogFooter>
|
|
48
|
+
<a
|
|
49
|
+
href="https://orama.com"
|
|
50
|
+
rel="noreferrer noopener"
|
|
51
|
+
className="ms-auto text-xs text-fd-muted-foreground"
|
|
52
|
+
>
|
|
53
|
+
Search powered by Orama
|
|
54
|
+
</a>
|
|
55
|
+
</SearchDialogFooter>
|
|
56
|
+
</SearchDialogContent>
|
|
57
|
+
</SearchDialog>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { source } from '@/lib/source';
|
|
2
|
+
import type { OramaDocument } from 'fumadocs-core/search/orama-cloud';
|
|
3
|
+
|
|
4
|
+
export async function exportSearchIndexes() {
|
|
5
|
+
return source.getPages().map((page) => {
|
|
6
|
+
return {
|
|
7
|
+
id: page.url,
|
|
8
|
+
structured: page.data.structuredData,
|
|
9
|
+
url: page.url,
|
|
10
|
+
title: page.data.title,
|
|
11
|
+
description: page.data.description,
|
|
12
|
+
} satisfies OramaDocument;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
DocsTitle,
|
|
8
8
|
} from 'fumadocs-ui/page';
|
|
9
9
|
import { source } from '@/lib/source';
|
|
10
|
-
import { baseOptions } from '@/lib/layout.shared';
|
|
11
10
|
import type * as PageTree from 'fumadocs-core/page-tree';
|
|
12
11
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
|
13
12
|
import { docs } from '@/.source';
|
|
14
13
|
import { toClientRenderer } from 'fumadocs-mdx/runtime/vite';
|
|
14
|
+
import { baseOptions } from '@/lib/layout.shared';
|
|
15
15
|
|
|
16
16
|
export async function loader({ params }: Route.LoaderArgs) {
|
|
17
17
|
const slugs = params['*'].split('/').filter((v) => v.length > 0);
|
|
@@ -20,7 +20,7 @@ export async function loader({ params }: Route.LoaderArgs) {
|
|
|
20
20
|
|
|
21
21
|
return {
|
|
22
22
|
path: page.path,
|
|
23
|
-
tree: source.
|
|
23
|
+
tree: source.getPageTree(),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -41,8 +41,8 @@ const renderer = toClientRenderer(
|
|
|
41
41
|
},
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
export default function Page(
|
|
45
|
-
const { tree, path } =
|
|
44
|
+
export default function Page({ loaderData }: Route.ComponentProps) {
|
|
45
|
+
const { tree, path } = loaderData;
|
|
46
46
|
const Content = renderer[path];
|
|
47
47
|
|
|
48
48
|
return (
|
|
@@ -8,10 +8,10 @@ export default {
|
|
|
8
8
|
ssr: true,
|
|
9
9
|
async prerender({ getStaticPaths }) {
|
|
10
10
|
const paths: string[] = [];
|
|
11
|
+
const excluded: string[] = ['/api/search'];
|
|
12
|
+
|
|
11
13
|
for (const path of getStaticPaths()) {
|
|
12
|
-
|
|
13
|
-
if (path === '/api/search') continue;
|
|
14
|
-
paths.push(path);
|
|
14
|
+
if (!excluded.includes(path)) paths.push(path);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
SearchDialog,
|
|
4
|
+
SearchDialogClose,
|
|
5
|
+
SearchDialogContent,
|
|
6
|
+
SearchDialogHeader,
|
|
7
|
+
SearchDialogIcon,
|
|
8
|
+
SearchDialogInput,
|
|
9
|
+
SearchDialogList,
|
|
10
|
+
SearchDialogOverlay,
|
|
11
|
+
type SharedProps,
|
|
12
|
+
} from 'fumadocs-ui/components/dialog/search';
|
|
13
|
+
import { useDocsSearch } from 'fumadocs-core/search/client';
|
|
14
|
+
import { create } from '@orama/orama';
|
|
15
|
+
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
|
16
|
+
|
|
17
|
+
function initOrama() {
|
|
18
|
+
return create({
|
|
19
|
+
schema: { _: 'string' },
|
|
20
|
+
language: 'english',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function DefaultSearchDialog(props: SharedProps) {
|
|
25
|
+
const { locale } = useI18n();
|
|
26
|
+
const { search, setSearch, query } = useDocsSearch({
|
|
27
|
+
type: 'static',
|
|
28
|
+
initOrama,
|
|
29
|
+
locale,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<SearchDialog
|
|
34
|
+
search={search}
|
|
35
|
+
onSearchChange={setSearch}
|
|
36
|
+
isLoading={query.isLoading}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<SearchDialogOverlay />
|
|
40
|
+
<SearchDialogContent>
|
|
41
|
+
<SearchDialogHeader>
|
|
42
|
+
<SearchDialogIcon />
|
|
43
|
+
<SearchDialogInput />
|
|
44
|
+
<SearchDialogClose />
|
|
45
|
+
</SearchDialogHeader>
|
|
46
|
+
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
|
|
47
|
+
</SearchDialogContent>
|
|
48
|
+
</SearchDialog>
|
|
49
|
+
);
|
|
50
|
+
}
|