fumadocs-core 15.2.8 → 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.
Files changed (92) hide show
  1. package/README.md +1 -1
  2. package/dist/algolia-IZEDLPHE.js +58 -0
  3. package/dist/breadcrumb.d.ts +7 -5
  4. package/dist/breadcrumb.js +46 -52
  5. package/dist/builder-feW_xVjc.d.ts +296 -0
  6. package/dist/{chunk-FVY6EZ3N.js → chunk-BBP7MIO4.js} +12 -14
  7. package/dist/{chunk-ORHEEQVY.js → chunk-EMWGTXSW.js} +0 -7
  8. package/dist/chunk-FAEPKD7U.js +20 -0
  9. package/dist/{chunk-NNKVN7WA.js → chunk-H2GMUTQG.js} +4 -2
  10. package/dist/chunk-IZPLHEX4.js +113 -0
  11. package/dist/chunk-OTD7MV33.js +53 -0
  12. package/dist/chunk-PFNP6PEB.js +11 -0
  13. package/dist/{chunk-Y2774T3B.js → chunk-QMATWJ5F.js} +6 -7
  14. package/dist/chunk-U67V476Y.js +35 -0
  15. package/dist/{chunk-BUCUQ3WX.js → chunk-XN2LKXFZ.js} +39 -34
  16. package/dist/{chunk-WFUH5VBX.js → chunk-XOFXGHS4.js} +26 -10
  17. package/dist/chunk-XZSI7AHE.js +67 -0
  18. package/dist/chunk-YVVDKJ2H.js +34 -0
  19. package/dist/chunk-ZMWYLUDP.js +21 -0
  20. package/dist/content/github.d.ts +34 -0
  21. package/dist/content/github.js +43 -0
  22. package/dist/content/index.d.ts +16 -0
  23. package/dist/content/index.js +30 -0
  24. package/dist/{get-toc-Cr2URuiP.d.ts → content/toc.d.ts} +6 -10
  25. package/dist/content/toc.js +21 -0
  26. package/dist/{page-tree-BG3wP0gU.d.ts → definitions-BRsJlZ6m.d.ts} +10 -15
  27. package/dist/dynamic-link.js +3 -3
  28. package/dist/fetch-2XFMBLBA.js +22 -0
  29. package/dist/framework/index.d.ts +1 -1
  30. package/dist/framework/index.js +2 -2
  31. package/dist/framework/next.js +2 -2
  32. package/dist/framework/react-router.js +2 -2
  33. package/dist/framework/tanstack.js +2 -2
  34. package/dist/framework/waku.d.ts +8 -0
  35. package/dist/framework/waku.js +51 -0
  36. package/dist/hide-if-empty.d.ts +18 -0
  37. package/dist/hide-if-empty.js +83 -0
  38. package/dist/highlight/client.d.ts +8 -5
  39. package/dist/highlight/client.js +9 -93
  40. package/dist/highlight/index.d.ts +20 -5
  41. package/dist/highlight/index.js +10 -6
  42. package/dist/i18n/index.d.ts +35 -8
  43. package/dist/i18n/index.js +5 -69
  44. package/dist/i18n/middleware.d.ts +12 -0
  45. package/dist/i18n/middleware.js +63 -0
  46. package/dist/link.js +3 -3
  47. package/dist/mdx-plugins/index.d.ts +124 -18
  48. package/dist/mdx-plugins/index.js +605 -203
  49. package/dist/mixedbread-RAHDVXGJ.js +118 -0
  50. package/dist/negotiation/index.d.ts +19 -0
  51. package/dist/negotiation/index.js +11 -0
  52. package/dist/{orama-cloud-USLSOSXS.js → orama-cloud-WEGQE5A6.js} +37 -27
  53. package/dist/page-tree/index.d.ts +32 -0
  54. package/dist/page-tree/index.js +15 -0
  55. package/dist/remark-code-tab-DmyIyi6m.d.ts +57 -0
  56. package/dist/{remark-structure-FIjTA11P.d.ts → remark-structure-DkCXCzpD.d.ts} +13 -2
  57. package/dist/search/algolia.d.ts +9 -7
  58. package/dist/search/algolia.js +31 -17
  59. package/dist/search/client.d.ts +88 -17
  60. package/dist/search/client.js +71 -50
  61. package/dist/search/index.d.ts +26 -0
  62. package/dist/search/index.js +7 -0
  63. package/dist/search/orama-cloud.d.ts +7 -5
  64. package/dist/search/orama-cloud.js +18 -10
  65. package/dist/search/server.d.ts +33 -25
  66. package/dist/search/server.js +109 -47
  67. package/dist/source/index.d.ts +33 -254
  68. package/dist/source/index.js +532 -353
  69. package/dist/source/plugins/lucide-icons.d.ts +14 -0
  70. package/dist/source/plugins/lucide-icons.js +23 -0
  71. package/dist/static-A2YJ5TXV.js +62 -0
  72. package/dist/toc.d.ts +11 -7
  73. package/dist/toc.js +6 -5
  74. package/dist/utils/use-effect-event.d.ts +4 -3
  75. package/dist/utils/use-effect-event.js +9 -6
  76. package/dist/utils/use-media-query.d.ts +3 -0
  77. package/dist/utils/use-media-query.js +23 -0
  78. package/dist/utils/use-on-change.js +2 -2
  79. package/package.json +92 -40
  80. package/dist/algolia-NTWLS6J3.js +0 -49
  81. package/dist/chunk-KAOEMCTI.js +0 -17
  82. package/dist/chunk-MLKGABMK.js +0 -9
  83. package/dist/chunk-XMCPKVJQ.js +0 -34
  84. package/dist/config-inq6kP6y.d.ts +0 -26
  85. package/dist/fetch-W5EHIBOE.js +0 -21
  86. package/dist/remark-heading-BPCoYwjn.d.ts +0 -31
  87. package/dist/server/index.d.ts +0 -117
  88. package/dist/server/index.js +0 -202
  89. package/dist/sidebar.d.ts +0 -33
  90. package/dist/sidebar.js +0 -89
  91. package/dist/static-VESU2S64.js +0 -61
  92. package/dist/types-Ch8gnVgO.d.ts +0 -8
@@ -1,182 +1,285 @@
1
1
  import {
2
+ basename,
3
+ dirname,
4
+ extname,
2
5
  joinPath,
6
+ path_exports,
3
7
  slash,
4
8
  splitPath
5
- } from "../chunk-XMCPKVJQ.js";
9
+ } from "../chunk-XZSI7AHE.js";
6
10
  import {
7
- __export
8
- } from "../chunk-MLKGABMK.js";
11
+ iconPlugin
12
+ } from "../chunk-FAEPKD7U.js";
13
+ import {
14
+ normalizeUrl
15
+ } from "../chunk-PFNP6PEB.js";
16
+ import "../chunk-U67V476Y.js";
17
+
18
+ // src/source/page-tree/transformer-fallback.ts
19
+ function transformerFallback() {
20
+ const addedFiles = /* @__PURE__ */ new Set();
21
+ return {
22
+ root(root) {
23
+ const isolatedStorage = new FileSystem();
24
+ for (const file of this.storage.getFiles()) {
25
+ if (addedFiles.has(file)) continue;
26
+ const content = this.storage.read(file);
27
+ if (content) isolatedStorage.write(file, content);
28
+ }
29
+ if (isolatedStorage.getFiles().length === 0) return root;
30
+ root.fallback = this.builder.build(isolatedStorage, {
31
+ ...this.options,
32
+ id: `fallback-${root.$id ?? ""}`,
33
+ generateFallback: false
34
+ });
35
+ addedFiles.clear();
36
+ return root;
37
+ },
38
+ file(node, file) {
39
+ if (file) addedFiles.add(file);
40
+ return node;
41
+ },
42
+ folder(node, _dir, metaPath) {
43
+ if (metaPath) addedFiles.add(metaPath);
44
+ return node;
45
+ }
46
+ };
47
+ }
9
48
 
10
- // src/source/page-tree-builder.ts
49
+ // src/source/page-tree/builder.ts
11
50
  var group = /^\((?<name>.+)\)$/;
12
51
  var link = /^(?:\[(?<icon>[^\]]+)])?\[(?<name>[^\]]+)]\((?<url>[^)]+)\)$/;
13
- var separator = /^---(?:\[(?<icon>[^\]]+)])?(?<name>.+)---$/;
52
+ var separator = /^---(?:\[(?<icon>[^\]]+)])?(?<name>.+)---|^---$/;
14
53
  var rest = "...";
15
54
  var restReversed = "z...a";
16
55
  var extractPrefix = "...";
17
56
  var excludePrefix = "!";
18
- function isPageFile(node) {
19
- return "data" in node && node.format === "page";
20
- }
21
- function buildAll(nodes, ctx, skipIndex) {
22
- const output = [];
57
+ function buildAll(paths, ctx, reversed = false) {
58
+ const items = [];
23
59
  const folders = [];
24
- for (const node of [...nodes].sort(
25
- (a, b) => a.file.name.localeCompare(b.file.name)
26
- )) {
27
- if (isPageFile(node)) {
28
- const localized = ctx.localeStorage?.read(
29
- joinPath(node.file.dirname, node.file.name),
30
- "page"
31
- );
32
- const treeNode = buildFileNode(localized ?? node, ctx);
33
- if (node.file.name === "index") {
34
- if (!skipIndex) output.unshift(treeNode);
35
- } else {
36
- output.push(treeNode);
37
- }
38
- }
39
- if ("children" in node) {
40
- folders.push(buildFolderNode(node, false, ctx));
60
+ const sortedPaths = paths.sort(
61
+ (a, b) => a.localeCompare(b) * (reversed ? -1 : 1)
62
+ );
63
+ for (const path of sortedPaths) {
64
+ ctx.visitedPaths.add(path);
65
+ const fileNode = buildFileNode(path, ctx);
66
+ if (fileNode) {
67
+ if (basename(path, extname(path)) === "index") items.unshift(fileNode);
68
+ else items.push(fileNode);
69
+ continue;
41
70
  }
71
+ const dirNode = buildFolderNode(path, false, ctx);
72
+ if (dirNode) folders.push(dirNode);
42
73
  }
43
- output.push(...folders);
44
- return output;
74
+ return [...items, ...folders];
45
75
  }
46
- function resolveFolderItem(folder, item, ctx, idx, addedNodePaths) {
76
+ function resolveFolderItem(folderPath, item, ctx, idx) {
47
77
  if (item === rest || item === restReversed) return item;
78
+ const { resolveName } = ctx;
48
79
  let match = separator.exec(item);
49
80
  if (match?.groups) {
50
- const node = {
51
- $id: `${folder.file.path}#${idx}`,
81
+ let node = {
82
+ $id: `${folderPath}#${idx}`,
52
83
  type: "separator",
53
- icon: ctx.options.resolveIcon?.(match.groups.icon),
84
+ icon: match.groups.icon,
54
85
  name: match.groups.name
55
86
  };
56
- return [ctx.options.attachSeparator?.(node) ?? node];
87
+ for (const transformer of ctx.transformers) {
88
+ if (!transformer.separator) continue;
89
+ node = transformer.separator.call(ctx, node);
90
+ }
91
+ return [node];
57
92
  }
58
93
  match = link.exec(item);
59
94
  if (match?.groups) {
60
95
  const { icon, url, name } = match.groups;
61
96
  const isRelative = url.startsWith("/") || url.startsWith("#") || url.startsWith(".");
62
- const node = {
97
+ let node = {
63
98
  type: "page",
64
- icon: ctx.options.resolveIcon?.(icon),
99
+ icon,
65
100
  name,
66
101
  url,
67
102
  external: !isRelative
68
103
  };
69
- return [ctx.options.attachFile?.(node) ?? node];
104
+ for (const transformer of ctx.transformers) {
105
+ if (!transformer.file) continue;
106
+ node = transformer.file.call(ctx, node);
107
+ }
108
+ return [node];
70
109
  }
71
- const isExcept = item.startsWith(excludePrefix), isExtract = item.startsWith(extractPrefix);
110
+ const isExcept = item.startsWith(excludePrefix);
111
+ const isExtract = !isExcept && item.startsWith(extractPrefix);
72
112
  let filename = item;
73
113
  if (isExcept) {
74
114
  filename = item.slice(excludePrefix.length);
75
115
  } else if (isExtract) {
76
116
  filename = item.slice(extractPrefix.length);
77
117
  }
78
- const path = joinPath(folder.file.path, filename);
79
- const itemNode = ctx.storage.readDir(path) ?? ctx.localeStorage?.read(path, "page") ?? ctx.storage.read(path, "page");
80
- if (!itemNode) return [];
81
- addedNodePaths.add(itemNode.file.path);
118
+ const path = resolveName(joinPath(folderPath, filename), "page");
119
+ ctx.visitedPaths.add(path);
82
120
  if (isExcept) return [];
83
- if ("children" in itemNode) {
84
- const node = buildFolderNode(itemNode, false, ctx);
85
- return isExtract ? node.children : [node];
121
+ const dirNode = buildFolderNode(path, false, ctx);
122
+ if (dirNode) {
123
+ return isExtract ? dirNode.children : [dirNode];
86
124
  }
87
- return [buildFileNode(itemNode, ctx)];
125
+ const fileNode = buildFileNode(path, ctx);
126
+ return fileNode ? [fileNode] : [];
88
127
  }
89
- function buildFolderNode(folder, isGlobalRoot, ctx) {
90
- const metaPath = joinPath(folder.file.path, "meta");
91
- const meta = ctx.localeStorage?.read(metaPath, "meta") ?? ctx.storage.read(metaPath, "meta");
92
- const indexPath = joinPath(folder.file.path, "index");
93
- const indexFile = ctx.localeStorage?.read(indexPath, "page") ?? ctx.storage.read(indexPath, "page");
128
+ function buildFolderNode(folderPath, isGlobalRoot, ctx) {
129
+ const { storage, options, resolveName, transformers } = ctx;
130
+ const files = storage.readDir(folderPath);
131
+ if (!files) return;
132
+ const metaPath = resolveName(joinPath(folderPath, "meta"), "meta");
133
+ const indexPath = resolveName(joinPath(folderPath, "index"), "page");
134
+ let meta = storage.read(metaPath);
135
+ if (meta?.format !== "meta") {
136
+ meta = void 0;
137
+ }
94
138
  const isRoot = meta?.data.root ?? isGlobalRoot;
95
- const index = indexFile ? buildFileNode(indexFile, ctx) : void 0;
96
- const addedNodePaths = /* @__PURE__ */ new Set();
139
+ let index;
97
140
  let children;
98
- if (!meta?.data.pages) {
99
- children = buildAll(folder.children, ctx, !isRoot);
141
+ function setIndexIfUnused() {
142
+ if (isRoot || ctx.visitedPaths.has(indexPath)) return;
143
+ ctx.visitedPaths.add(indexPath);
144
+ index = buildFileNode(indexPath, ctx);
145
+ }
146
+ if (meta && meta.data.pages) {
147
+ const resolved = meta.data.pages.flatMap((item, i) => resolveFolderItem(folderPath, item, ctx, i));
148
+ setIndexIfUnused();
149
+ for (let i = 0; i < resolved.length; i++) {
150
+ const item = resolved[i];
151
+ if (item !== rest && item !== restReversed) continue;
152
+ const items = buildAll(
153
+ files.filter((file) => !ctx.visitedPaths.has(file)),
154
+ ctx,
155
+ item === restReversed
156
+ );
157
+ resolved.splice(i, 1, ...items);
158
+ break;
159
+ }
160
+ children = resolved;
100
161
  } else {
101
- const resolved = meta.data.pages.flatMap((item, i) => {
102
- return resolveFolderItem(folder, item, ctx, i, addedNodePaths);
103
- });
104
- const restNodes = buildAll(
105
- folder.children.filter((node2) => !addedNodePaths.has(node2.file.path)),
106
- ctx,
107
- !isRoot
162
+ setIndexIfUnused();
163
+ children = buildAll(
164
+ files.filter((file) => !ctx.visitedPaths.has(file)),
165
+ ctx
108
166
  );
109
- const nodes = resolved?.flatMap((item) => {
110
- if (item === rest) {
111
- return restNodes;
112
- } else if (item === restReversed) {
113
- return restNodes.reverse();
114
- }
115
- return item;
116
- });
117
- children = nodes ?? restNodes;
118
167
  }
119
- const node = {
168
+ let name = meta?.data.title ?? index?.name;
169
+ if (!name) {
170
+ const folderName = basename(folderPath);
171
+ name = pathToName(group.exec(folderName)?.[1] ?? folderName);
172
+ }
173
+ let node = {
120
174
  type: "folder",
121
- name: meta?.data.title ?? index?.name ?? // resolve folder groups like (group_name)
122
- pathToName(group.exec(folder.file.name)?.[1] ?? folder.file.name),
123
- icon: ctx.options.resolveIcon?.(meta?.data.icon) ?? index?.icon,
175
+ name,
176
+ icon: meta?.data.icon ?? index?.icon,
124
177
  root: meta?.data.root,
125
178
  defaultOpen: meta?.data.defaultOpen,
126
179
  description: meta?.data.description,
127
- index: isRoot || indexFile && !addedNodePaths.has(indexFile.file.path) ? index : void 0,
180
+ index,
128
181
  children,
129
- $id: folder.file.path,
130
- $ref: !ctx.options.noRef ? {
131
- metaFile: meta?.file.path
182
+ $id: folderPath,
183
+ $ref: !options.noRef && meta ? {
184
+ metaFile: metaPath
132
185
  } : void 0
133
186
  };
134
- return ctx.options.attachFolder?.(node, folder, meta) ?? node;
187
+ for (const transformer of transformers) {
188
+ if (!transformer.folder) continue;
189
+ node = transformer.folder.call(ctx, node, folderPath, metaPath);
190
+ }
191
+ return node;
135
192
  }
136
- function buildFileNode(file, ctx) {
137
- const item = {
138
- $id: file.file.path,
193
+ function buildFileNode(path, ctx) {
194
+ const { options, getUrl, storage, locale, transformers } = ctx;
195
+ const page = storage.read(path);
196
+ if (page?.format !== "page") return;
197
+ const { title, description, icon } = page.data;
198
+ let item = {
199
+ $id: path,
139
200
  type: "page",
140
- name: file.data.data.title ?? pathToName(file.file.name),
141
- description: file.data.data.description,
142
- icon: ctx.options.resolveIcon?.(file.data.data.icon),
143
- url: ctx.options.getUrl(file.data.slugs, ctx.locale),
144
- $ref: !ctx.options.noRef ? {
145
- file: file.file.path
201
+ name: title ?? pathToName(basename(path, extname(path))),
202
+ description,
203
+ icon,
204
+ url: getUrl(page.slugs, locale),
205
+ $ref: !options.noRef ? {
206
+ file: path
146
207
  } : void 0
147
208
  };
148
- return ctx.options.attachFile?.(item, file) ?? item;
209
+ for (const transformer of transformers) {
210
+ if (!transformer.file) continue;
211
+ item = transformer.file.call(ctx, item, path);
212
+ }
213
+ return item;
149
214
  }
150
- function build(ctx) {
151
- const root = ctx.storage.root();
152
- const folder = buildFolderNode(root, true, ctx);
153
- return {
154
- $id: ctx.locale ? ctx.locale : "root",
155
- name: folder.name,
215
+ function build(id, ctx) {
216
+ const folder = buildFolderNode("", true, ctx);
217
+ let root = {
218
+ $id: id,
219
+ name: folder.name || "Docs",
156
220
  children: folder.children
157
221
  };
222
+ for (const transformer of ctx.transformers) {
223
+ if (!transformer.root) continue;
224
+ root = transformer.root.call(ctx, root);
225
+ }
226
+ return root;
158
227
  }
159
- function createPageTreeBuilder() {
228
+ function createPageTreeBuilder(getUrl, plugins) {
229
+ function getTransformers({
230
+ generateFallback = true,
231
+ ...options
232
+ }) {
233
+ const transformers = [];
234
+ if (options.transformers) {
235
+ transformers.push(...options.transformers);
236
+ }
237
+ for (const plugin of plugins ?? []) {
238
+ if (plugin.transformPageTree) transformers.push(plugin.transformPageTree);
239
+ }
240
+ if (generateFallback) {
241
+ transformers.push(transformerFallback());
242
+ }
243
+ return transformers;
244
+ }
245
+ function createFlattenPathResolver(storage) {
246
+ const map2 = /* @__PURE__ */ new Map();
247
+ const files = storage.getFiles();
248
+ for (const file of files) {
249
+ const content = storage.read(file);
250
+ const flattenPath = file.substring(0, file.length - extname(file).length);
251
+ map2.set(flattenPath + "." + content.format, file);
252
+ }
253
+ return (name, format) => {
254
+ return map2.get(name + "." + format);
255
+ };
256
+ }
160
257
  return {
161
- build(options) {
162
- return build({
163
- options,
164
- builder: this,
165
- storage: options.storage
166
- });
258
+ build(storage, options) {
259
+ const key = "";
260
+ return this.buildI18n({ [key]: storage }, options)[key];
167
261
  },
168
- buildI18n({ i18n, ...options }) {
169
- const entries = i18n.languages.map((lang) => {
170
- const tree = build({
171
- options,
262
+ buildI18n(storages, options = {}) {
263
+ const transformers = getTransformers(options);
264
+ const out = {};
265
+ for (const [locale, storage] of Object.entries(storages)) {
266
+ const resolve = createFlattenPathResolver(storage);
267
+ const branch = locale.length === 0 ? "root" : locale;
268
+ out[locale] = build(options.id ? `${options.id}-${branch}` : branch, {
269
+ transformers,
172
270
  builder: this,
173
- locale: lang,
174
- storage: options.storages[i18n.defaultLanguage],
175
- localeStorage: options.storages[lang]
271
+ options,
272
+ getUrl,
273
+ locale,
274
+ storage,
275
+ storages,
276
+ visitedPaths: /* @__PURE__ */ new Set(),
277
+ resolveName(name, format) {
278
+ return resolve(name, format) ?? name;
279
+ }
176
280
  });
177
- return [lang, tree];
178
- });
179
- return Object.fromEntries(entries);
281
+ }
282
+ return out;
180
283
  }
181
284
  };
182
285
  }
@@ -190,196 +293,226 @@ function pathToName(name) {
190
293
  return result.join("");
191
294
  }
192
295
 
193
- // src/source/path.ts
194
- function parseFilePath(path) {
195
- const segments = splitPath(slash(path));
196
- const dirname = segments.slice(0, -1).join("/");
197
- let name = segments.at(-1) ?? "";
198
- let ext = "";
199
- const dotIdx = name.lastIndexOf(".");
200
- if (dotIdx !== -1) {
201
- ext = name.substring(dotIdx);
202
- name = name.substring(0, dotIdx);
203
- }
204
- return {
205
- dirname,
206
- name,
207
- path: segments.join("/"),
208
- ext,
209
- flattenedPath: [dirname, name].filter((p) => p.length > 0).join("/")
210
- };
211
- }
212
- function parseFolderPath(path) {
213
- const segments = splitPath(slash(path));
214
- const base = segments.at(-1) ?? "";
215
- return {
216
- dirname: segments.slice(0, -1).join("/"),
217
- name: base,
218
- path: segments.join("/")
219
- };
220
- }
221
- function normalizePath(path) {
222
- const segments = splitPath(slash(path));
223
- if (segments[0] === "." || segments[0] === "..")
224
- throw new Error("It must not start with './' or '../'");
225
- return segments.join("/");
226
- }
227
-
228
- // src/source/file-system.ts
229
- var file_system_exports = {};
230
- __export(file_system_exports, {
231
- Storage: () => Storage
232
- });
233
- var Storage = class {
234
- constructor() {
296
+ // src/source/storage/file-system.ts
297
+ var FileSystem = class {
298
+ constructor(inherit) {
235
299
  this.files = /* @__PURE__ */ new Map();
236
300
  this.folders = /* @__PURE__ */ new Map();
237
- this.rootFolder = {
238
- file: parseFolderPath(""),
239
- children: []
240
- };
241
- this.folders.set("", this.rootFolder);
301
+ if (inherit) {
302
+ for (const [k, v] of inherit.folders) {
303
+ this.folders.set(k, v);
304
+ }
305
+ for (const [k, v] of inherit.files) {
306
+ this.files.set(k, v);
307
+ }
308
+ } else {
309
+ this.folders.set("", []);
310
+ }
311
+ }
312
+ read(path) {
313
+ return this.files.get(path);
242
314
  }
243
315
  /**
244
- * @param path - flattened path
245
- * @param format - file format
316
+ * get the direct children of folder (in virtual file path)
246
317
  */
247
- read(path, format) {
248
- return this.files.get(`${path}.${format}`);
249
- }
250
318
  readDir(path) {
251
319
  return this.folders.get(path);
252
320
  }
253
- root() {
254
- return this.rootFolder;
321
+ write(path, file) {
322
+ if (!this.files.has(path)) {
323
+ const dir = dirname(path);
324
+ this.makeDir(dir);
325
+ this.readDir(dir)?.push(path);
326
+ }
327
+ this.files.set(path, file);
255
328
  }
256
- write(path, format, data) {
257
- const node = {
258
- format,
259
- file: parseFilePath(path),
260
- data
261
- };
262
- this.makeDir(node.file.dirname);
263
- this.readDir(node.file.dirname)?.children.push(node);
264
- this.files.set(
265
- joinPath(node.file.dirname, `${node.file.name}.${node.format}`),
266
- node
267
- );
329
+ /**
330
+ * Delete files at specified path.
331
+ *
332
+ * @param path - the target path.
333
+ * @param [recursive=false] - if set to `true`, it will also delete directories.
334
+ */
335
+ delete(path, recursive = false) {
336
+ if (this.files.delete(path)) return true;
337
+ if (recursive) {
338
+ const folder = this.folders.get(path);
339
+ if (!folder) return false;
340
+ this.folders.delete(path);
341
+ for (const child of folder) {
342
+ this.delete(child);
343
+ }
344
+ return true;
345
+ }
346
+ return false;
268
347
  }
269
- list() {
270
- return [...this.files.values()];
348
+ getFiles() {
349
+ return Array.from(this.files.keys());
271
350
  }
272
351
  makeDir(path) {
273
352
  const segments = splitPath(path);
274
353
  for (let i = 0; i < segments.length; i++) {
275
354
  const segment = segments.slice(0, i + 1).join("/");
276
355
  if (this.folders.has(segment)) continue;
277
- const folder = {
278
- file: parseFolderPath(segment),
279
- children: []
280
- };
281
- this.folders.set(folder.file.path, folder);
282
- this.readDir(folder.file.dirname)?.children.push(folder);
356
+ this.folders.set(segment, []);
357
+ this.folders.get(dirname(segment)).push(segment);
283
358
  }
284
359
  }
285
360
  };
286
361
 
287
- // src/source/load-files.ts
288
- function loadFiles(files, options) {
289
- const { transformers = [] } = options;
290
- const storage = new Storage();
291
- for (const file of files) {
292
- const parsedPath = normalizePath(file.path);
293
- if (file.type === "page") {
294
- const slugs = file.slugs ?? options.getSlugs(parseFilePath(parsedPath));
295
- storage.write(parsedPath, file.type, {
296
- slugs,
297
- data: file.data
298
- });
299
- }
300
- if (file.type === "meta") {
301
- storage.write(parsedPath, file.type, file.data);
302
- }
303
- }
304
- for (const transformer of transformers) {
305
- transformer({
306
- storage,
307
- options
308
- });
309
- }
310
- return storage;
362
+ // src/source/storage/content.ts
363
+ function isLocaleValid(locale) {
364
+ return locale.length > 0 && !/\d+/.test(locale);
311
365
  }
312
- function loadFilesI18n(files, options) {
313
- const parser = options.i18n.parser === "dir" ? dirParser : dotParser;
366
+ var parsers = {
367
+ dir(path) {
368
+ const [locale, ...segs] = path.split("/");
369
+ if (locale && segs.length > 0 && isLocaleValid(locale))
370
+ return [segs.join("/"), locale];
371
+ return [path];
372
+ },
373
+ dot(path) {
374
+ const dir = dirname(path);
375
+ const base = basename(path);
376
+ const parts = base.split(".");
377
+ if (parts.length < 3) return [path];
378
+ const [locale] = parts.splice(parts.length - 2, 1);
379
+ if (!isLocaleValid(locale)) return [path];
380
+ return [joinPath(dir, parts.join(".")), locale];
381
+ },
382
+ none(path) {
383
+ return [path];
384
+ }
385
+ };
386
+ function buildContentStorage(files, buildFile, plugins, i18n) {
387
+ const parser = parsers[i18n.parser ?? "dot"];
314
388
  const storages = {};
315
- for (const lang of options.i18n.languages) {
316
- storages[lang] = loadFiles(
317
- files.flatMap((file) => {
318
- const [path, locale] = parser(normalizePath(file.path));
319
- if ((locale ?? options.i18n.defaultLanguage) === lang) {
320
- return {
321
- ...file,
322
- path
323
- };
324
- }
325
- return [];
326
- }),
327
- options
328
- );
389
+ const normalized = files.map(
390
+ (file) => buildFile({
391
+ ...file,
392
+ path: normalizePath(file.path)
393
+ })
394
+ );
395
+ const fallbackLang = i18n.fallbackLanguage !== null ? i18n.fallbackLanguage ?? i18n.defaultLanguage : null;
396
+ function scan(lang) {
397
+ if (storages[lang]) return;
398
+ let storage;
399
+ if (fallbackLang && fallbackLang !== lang) {
400
+ scan(fallbackLang);
401
+ storage = new FileSystem(storages[fallbackLang]);
402
+ } else {
403
+ storage = new FileSystem();
404
+ }
405
+ for (const item of normalized) {
406
+ const [path, locale = i18n.defaultLanguage] = parser(item.path);
407
+ if (locale === lang) storage.write(path, item);
408
+ }
409
+ const context = {
410
+ storage
411
+ };
412
+ for (const plugin of plugins) {
413
+ plugin.transformStorage?.(context);
414
+ }
415
+ storages[lang] = storage;
329
416
  }
417
+ for (const lang of i18n.languages) scan(lang);
330
418
  return storages;
331
419
  }
332
- function dirParser(path) {
333
- const parsed = path.split("/");
334
- if (parsed.length >= 2) return [parsed.slice(1).join("/"), parsed[0]];
335
- return [path];
420
+ function normalizePath(path) {
421
+ const segments = splitPath(slash(path));
422
+ if (segments[0] === "." || segments[0] === "..")
423
+ throw new Error("It must not start with './' or '../'");
424
+ return segments.join("/");
336
425
  }
337
- function dotParser(path) {
338
- const segs = path.split("/");
339
- if (segs.length === 0) return [path];
340
- const name = segs[segs.length - 1].split(".");
341
- if (name.length >= 3) {
342
- const locale = name.splice(name.length - 2, 1)[0];
343
- if (locale.length > 0 && !/\d+/.test(locale)) {
344
- segs[segs.length - 1] = name.join(".");
345
- return [segs.join("/"), locale];
346
- }
426
+
427
+ // src/source/plugins/index.ts
428
+ var priorityMap = {
429
+ pre: 1,
430
+ default: 0,
431
+ post: -1
432
+ };
433
+ function buildPlugins(plugins) {
434
+ const flatten = [];
435
+ for (const plugin of plugins) {
436
+ if (Array.isArray(plugin)) flatten.push(...plugin);
437
+ else if (plugin) flatten.push(plugin);
347
438
  }
348
- return [path];
439
+ return flatten.sort(
440
+ (a, b) => priorityMap[b.enforce ?? "default"] - priorityMap[a.enforce ?? "default"]
441
+ );
349
442
  }
350
443
 
351
- // src/source/loader.ts
352
- function indexPages(storages, getUrl, i18n) {
353
- const defaultLanguage = i18n?.defaultLanguage ?? "";
354
- const map = /* @__PURE__ */ new Map();
355
- const fileMapped = /* @__PURE__ */ new WeakMap();
356
- for (const item of storages[defaultLanguage].list()) {
357
- if (item.format === "meta") {
358
- fileMapped.set(item, fileToMeta(item));
359
- }
360
- if (item.format === "page") {
361
- const page = fileToPage(item, getUrl, defaultLanguage);
362
- fileMapped.set(item, page);
363
- map.set(`${defaultLanguage}.${page.slugs.join("/")}`, page);
364
- if (!i18n) continue;
365
- const path = joinPath(item.file.dirname, item.file.name);
366
- for (const lang of i18n.languages) {
367
- if (lang === defaultLanguage) continue;
368
- const localizedItem = storages[lang].read(path, "page");
369
- const localizedPage = fileToPage(localizedItem ?? item, getUrl, lang);
370
- if (localizedItem) {
371
- fileMapped.set(localizedItem, localizedPage);
444
+ // src/source/plugins/slugs.ts
445
+ function slugsPlugin(slugsFn) {
446
+ function isIndex(file) {
447
+ return basename(file, extname(file)) === "index";
448
+ }
449
+ return {
450
+ name: "fumadocs:slugs",
451
+ transformStorage({ storage }) {
452
+ const indexFiles = /* @__PURE__ */ new Set();
453
+ const taken = /* @__PURE__ */ new Set();
454
+ const autoIndex = slugsFn === void 0;
455
+ for (const path of storage.getFiles()) {
456
+ const file = storage.read(path);
457
+ if (!file || file.format !== "page" || file.slugs) continue;
458
+ if (isIndex(path) && autoIndex) {
459
+ indexFiles.add(path);
460
+ continue;
372
461
  }
373
- map.set(`${lang}.${localizedPage.slugs.join("/")}`, localizedPage);
462
+ file.slugs = slugsFn ? slugsFn({ path }) : getSlugs(path);
463
+ const key = file.slugs.join("/");
464
+ if (taken.has(key)) throw new Error("Duplicated slugs");
465
+ taken.add(key);
466
+ }
467
+ for (const path of indexFiles) {
468
+ const file = storage.read(path);
469
+ if (file?.format !== "page") continue;
470
+ file.slugs = getSlugs(path);
471
+ if (taken.has(file.slugs.join("/"))) file.slugs.push("index");
374
472
  }
375
473
  }
474
+ };
475
+ }
476
+ var GroupRegex = /^\(.+\)$/;
477
+ function getSlugs(file) {
478
+ const dir = dirname(file);
479
+ const name = basename(file, extname(file));
480
+ const slugs = [];
481
+ for (const seg of dir.split("/")) {
482
+ if (seg.length > 0 && !GroupRegex.test(seg)) slugs.push(encodeURI(seg));
376
483
  }
377
- return {
378
- pages: map,
379
- getResultFromFile(file) {
380
- return fileMapped.get(file);
381
- }
484
+ if (GroupRegex.test(name))
485
+ throw new Error(`Cannot use folder group in file names: ${file}`);
486
+ if (name !== "index") {
487
+ slugs.push(encodeURI(name));
488
+ }
489
+ return slugs;
490
+ }
491
+
492
+ // src/source/loader.ts
493
+ function indexPages(storages, getUrl) {
494
+ const result = {
495
+ // (locale.slugs -> page)
496
+ pages: /* @__PURE__ */ new Map(),
497
+ // (locale.path -> page)
498
+ pathToMeta: /* @__PURE__ */ new Map(),
499
+ // (locale.path -> meta)
500
+ pathToPage: /* @__PURE__ */ new Map()
382
501
  };
502
+ for (const [lang, storage] of Object.entries(storages)) {
503
+ for (const filePath of storage.getFiles()) {
504
+ const item = storage.read(filePath);
505
+ const path = `${lang}.${filePath}`;
506
+ if (item.format === "meta") {
507
+ result.pathToMeta.set(path, fileToMeta(item));
508
+ continue;
509
+ }
510
+ const page = fileToPage(item, getUrl, lang);
511
+ result.pathToPage.set(path, page);
512
+ result.pages.set(`${lang}.${page.slugs.join("/")}`, page);
513
+ }
514
+ }
515
+ return result;
383
516
  }
384
517
  function createGetUrl(baseUrl, i18n) {
385
518
  const baseSlugs = baseUrl.split("/");
@@ -396,95 +529,108 @@ function createGetUrl(baseUrl, i18n) {
396
529
  return `/${paths.filter((v) => v.length > 0).join("/")}`;
397
530
  };
398
531
  }
399
- function getSlugs(info) {
400
- return [...info.dirname.split("/"), info.name].filter(
401
- // filter empty folder names and file groups like (group_name)
402
- (v, i, arr) => {
403
- if (v.length === 0) return false;
404
- return i === arr.length - 1 ? v !== "index" : !/^\(.+\)$/.test(v);
405
- }
406
- );
532
+ function loader(...args) {
533
+ const resolved = args.length === 2 ? resolveConfig(args[0], args[1]) : resolveConfig(args[0].source, args[0]);
534
+ return createOutput(resolved);
407
535
  }
408
- function loader(options) {
409
- return createOutput(options);
410
- }
411
- function createOutput(options) {
412
- if (!options.url && !options.baseUrl) {
413
- console.warn("`loader()` now requires a `baseUrl` option to be defined.");
536
+ function resolveConfig(source, { slugs, icon, plugins = [], baseUrl, url, ...base }) {
537
+ const getUrl = url ? (...args) => normalizeUrl(url(...args)) : createGetUrl(baseUrl, base.i18n);
538
+ let config = {
539
+ ...base,
540
+ url: getUrl,
541
+ source,
542
+ plugins: buildPlugins([
543
+ slugsPlugin(slugs),
544
+ icon && iconPlugin(icon),
545
+ ...plugins
546
+ ])
547
+ };
548
+ for (const plugin of config.plugins ?? []) {
549
+ const result = plugin.config?.(config);
550
+ if (result) config = result;
414
551
  }
415
- const { source, slugs: slugsFn = getSlugs } = options;
416
- const getUrl = options.url ?? createGetUrl(options.baseUrl ?? "/", options.i18n);
417
- const files = typeof source.files === "function" ? source.files() : source.files;
418
- const storages = options.i18n ? loadFilesI18n(files, {
419
- i18n: {
420
- ...options.i18n,
421
- parser: options.i18n.parser ?? "dot"
552
+ return config;
553
+ }
554
+ function createOutput({
555
+ source: { files },
556
+ url: getUrl,
557
+ i18n,
558
+ plugins = [],
559
+ pageTree: pageTreeConfig
560
+ }) {
561
+ const defaultLanguage = i18n?.defaultLanguage ?? "";
562
+ const storages = buildContentStorage(
563
+ files,
564
+ (file) => {
565
+ if (file.type === "page") {
566
+ return {
567
+ format: "page",
568
+ path: file.path,
569
+ slugs: file.slugs,
570
+ data: file.data,
571
+ absolutePath: file.absolutePath ?? ""
572
+ };
573
+ }
574
+ return {
575
+ format: "meta",
576
+ path: file.path,
577
+ absolutePath: file.absolutePath ?? "",
578
+ data: file.data
579
+ };
422
580
  },
423
- transformers: options.transformers,
424
- getSlugs: slugsFn
425
- }) : {
426
- "": loadFiles(files, {
427
- transformers: options.transformers,
428
- getSlugs: slugsFn
429
- })
430
- };
431
- const walker = indexPages(storages, getUrl, options.i18n);
432
- const builder = createPageTreeBuilder();
581
+ plugins,
582
+ i18n ?? {
583
+ defaultLanguage,
584
+ parser: "none",
585
+ languages: [defaultLanguage]
586
+ }
587
+ );
588
+ const walker = indexPages(storages, getUrl);
589
+ const builder = createPageTreeBuilder(getUrl, plugins);
433
590
  let pageTree;
434
591
  return {
435
- _i18n: options.i18n,
592
+ _i18n: i18n,
436
593
  get pageTree() {
437
- if (options.i18n) {
438
- pageTree ??= builder.buildI18n({
439
- storages,
440
- resolveIcon: options.icon,
441
- getUrl,
442
- i18n: options.i18n,
443
- ...options.pageTree
444
- });
445
- } else {
446
- pageTree ??= builder.build({
447
- storage: storages[""],
448
- resolveIcon: options.icon,
449
- getUrl,
450
- ...options.pageTree
451
- });
452
- }
453
- return pageTree;
594
+ pageTree ??= builder.buildI18n(storages, pageTreeConfig);
595
+ return i18n ? pageTree : pageTree[defaultLanguage];
454
596
  },
455
597
  set pageTree(v) {
456
- pageTree = v;
598
+ if (i18n) {
599
+ pageTree = v;
600
+ } else {
601
+ pageTree = {
602
+ [defaultLanguage]: v
603
+ };
604
+ }
457
605
  },
458
- getPageByHref(href, { dir = "" } = {}) {
459
- const pages = Array.from(walker.pages.values());
606
+ getPageByHref(href, { dir = "", language = defaultLanguage } = {}) {
460
607
  const [value, hash] = href.split("#", 2);
608
+ let target;
461
609
  if (value.startsWith(".") && (value.endsWith(".md") || value.endsWith(".mdx"))) {
462
- const hrefPath = joinPath(dir, value);
463
- const target2 = pages.find((item) => item.file.path === hrefPath);
464
- if (target2)
465
- return {
466
- page: target2,
467
- hash
468
- };
610
+ const path = joinPath(dir, value);
611
+ target = walker.pathToPage.get(`${language}.${path}`);
612
+ } else {
613
+ target = this.getPages(language).find((item) => item.url === value);
469
614
  }
470
- const target = pages.find((item) => item.url === value);
471
615
  if (target)
472
616
  return {
473
617
  page: target,
474
618
  hash
475
619
  };
476
620
  },
477
- getPages(language = options.i18n?.defaultLanguage ?? "") {
621
+ getPages(language) {
478
622
  const pages = [];
479
- for (const key of walker.pages.keys()) {
480
- if (key.startsWith(`${language}.`)) pages.push(walker.pages.get(key));
623
+ for (const [key, value] of walker.pages.entries()) {
624
+ if (language === void 0 || key.startsWith(`${language}.`)) {
625
+ pages.push(value);
626
+ }
481
627
  }
482
628
  return pages;
483
629
  },
484
630
  getLanguages() {
485
631
  const list = [];
486
- if (!options.i18n) return list;
487
- for (const language of options.i18n.languages) {
632
+ if (!i18n) return list;
633
+ for (const language of i18n.languages) {
488
634
  list.push({
489
635
  language,
490
636
  pages: this.getPages(language)
@@ -492,30 +638,28 @@ function createOutput(options) {
492
638
  }
493
639
  return list;
494
640
  },
495
- getPage(slugs = [], language = options.i18n?.defaultLanguage ?? "") {
641
+ getPage(slugs = [], language = defaultLanguage) {
496
642
  return walker.pages.get(`${language}.${slugs.join("/")}`);
497
643
  },
498
- getNodeMeta(node, language = options.i18n?.defaultLanguage ?? "") {
644
+ getNodeMeta(node, language = defaultLanguage) {
499
645
  const ref = node.$ref?.metaFile;
500
646
  if (!ref) return;
501
- const file = storages[language].list().find((v) => v.format === "meta" && v.file.path === ref);
502
- if (file) return walker.getResultFromFile(file);
647
+ return walker.pathToMeta.get(`${language}.${ref}`);
503
648
  },
504
- getNodePage(node, language = options.i18n?.defaultLanguage ?? "") {
649
+ getNodePage(node, language = defaultLanguage) {
505
650
  const ref = node.$ref?.file;
506
651
  if (!ref) return;
507
- const file = storages[language].list().find((v) => v.format === "page" && v.file.path === ref);
508
- if (file) return walker.getResultFromFile(file);
652
+ return walker.pathToPage.get(`${language}.${ref}`);
509
653
  },
510
654
  getPageTree(locale) {
511
- if (options.i18n) {
512
- return this.pageTree[locale ?? options.i18n.defaultLanguage];
655
+ if (i18n) {
656
+ return this.pageTree[locale ?? defaultLanguage];
513
657
  }
514
658
  return this.pageTree;
515
659
  },
516
660
  // @ts-expect-error -- ignore this
517
661
  generateParams(slug, lang) {
518
- if (options.i18n) {
662
+ if (i18n) {
519
663
  return this.getLanguages().flatMap(
520
664
  (entry) => entry.pages.map((page) => ({
521
665
  [slug ?? "slug"]: page.slugs,
@@ -531,26 +675,61 @@ function createOutput(options) {
531
675
  }
532
676
  function fileToMeta(file) {
533
677
  return {
534
- file: file.file,
678
+ path: file.path,
679
+ absolutePath: file.absolutePath,
535
680
  data: file.data
536
681
  };
537
682
  }
538
683
  function fileToPage(file, getUrl, locale) {
539
684
  return {
540
- file: file.file,
541
- url: getUrl(file.data.slugs, locale),
542
- slugs: file.data.slugs,
543
- data: file.data.data,
685
+ absolutePath: file.absolutePath,
686
+ path: file.path,
687
+ url: getUrl(file.slugs, locale),
688
+ slugs: file.slugs,
689
+ data: file.data,
544
690
  locale
545
691
  };
546
692
  }
693
+ function multiple(sources) {
694
+ const out = { files: [] };
695
+ for (const [type, source] of Object.entries(sources)) {
696
+ for (const file of source.files) {
697
+ out.files.push({
698
+ ...file,
699
+ data: {
700
+ ...file.data,
701
+ type
702
+ }
703
+ });
704
+ }
705
+ }
706
+ return out;
707
+ }
708
+ function map(source) {
709
+ return {
710
+ page(fn) {
711
+ return {
712
+ files: source.files.map(
713
+ (file) => file.type === "page" ? fn(file) : file
714
+ )
715
+ };
716
+ },
717
+ meta(fn) {
718
+ return {
719
+ files: source.files.map(
720
+ (file) => file.type === "meta" ? fn(file) : file
721
+ )
722
+ };
723
+ }
724
+ };
725
+ }
547
726
  export {
548
- file_system_exports as FileSystem,
727
+ FileSystem,
728
+ path_exports as PathUtils,
549
729
  createGetUrl,
550
730
  createPageTreeBuilder,
551
731
  getSlugs,
552
- loadFiles,
553
732
  loader,
554
- parseFilePath,
555
- parseFolderPath
733
+ map,
734
+ multiple
556
735
  };