fumadocs-core 15.1.1 → 15.1.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.
@@ -2,21 +2,22 @@
2
2
  function splitPath(path) {
3
3
  return path.split("/").filter((p) => p.length > 0);
4
4
  }
5
- function joinPath(from, join) {
6
- const v1 = splitPath(from), v2 = splitPath(join);
7
- while (v2.length > 0) {
8
- switch (v2[0]) {
5
+ function joinPath(...paths) {
6
+ const out = [];
7
+ const parsed = paths.flatMap(splitPath);
8
+ while (parsed.length > 0) {
9
+ switch (parsed[0]) {
9
10
  case "..":
10
- v1.pop();
11
+ out.pop();
11
12
  break;
12
13
  case ".":
13
14
  break;
14
15
  default:
15
- v1.push(v2[0]);
16
+ out.push(parsed[0]);
16
17
  }
17
- v2.shift();
18
+ parsed.shift();
18
19
  }
19
- return v1.join("/");
20
+ return out.join("/");
20
21
  }
21
22
  function slash(path) {
22
23
  const isExtendedLengthPath = path.startsWith("\\\\?\\");
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  joinPath,
7
7
  slash
8
- } from "../chunk-QTVCCXFT.js";
8
+ } from "../chunk-XMCPKVJQ.js";
9
9
  import {
10
10
  createStyleTransformer,
11
11
  defaultThemes,
@@ -483,6 +483,23 @@ function remarkStructure({
483
483
  }
484
484
  return "skip";
485
485
  }
486
+ if (element.type === "mdxJsxFlowElement" && element.name) {
487
+ data.contents.push(
488
+ {
489
+ heading: lastHeading,
490
+ content: element.name
491
+ },
492
+ ...element.attributes.flatMap((attribute) => {
493
+ const valueStr = typeof attribute.value === "string" ? attribute.value : attribute.value?.value;
494
+ if (!valueStr) return [];
495
+ return {
496
+ heading: lastHeading,
497
+ content: attribute.type === "mdxJsxAttribute" ? `${attribute.name}: ${valueStr}` : valueStr
498
+ };
499
+ })
500
+ );
501
+ return;
502
+ }
486
503
  if (types.includes(element.type)) {
487
504
  const content = flattenNode(element).trim();
488
505
  if (content.length === 0) return;
@@ -3,12 +3,6 @@ import { I as I18nConfig } from '../config-inq6kP6y.js';
3
3
  import { R as Root, I as Item, F as Folder$1, S as Separator } from '../page-tree-9q98UqWL.js';
4
4
 
5
5
  interface FileInfo {
6
- /**
7
- * locale extension from the last second `.`, like `.en`
8
- *
9
- * empty string if no locale
10
- */
11
- locale: string;
12
6
  /**
13
7
  * File path without extension
14
8
  *
@@ -47,9 +41,15 @@ declare function parseFolderPath(path: string): FolderInfo;
47
41
 
48
42
  interface LoadOptions {
49
43
  transformers?: Transformer[];
50
- rootDir?: string;
51
44
  getSlugs: (info: FileInfo) => string[];
52
45
  }
46
+ interface I18nLoadOptions extends LoadOptions {
47
+ i18n: {
48
+ parser: 'dot' | 'dir';
49
+ languages: string[];
50
+ defaultLanguage: string;
51
+ };
52
+ }
53
53
  interface VirtualFile {
54
54
  /**
55
55
  * Relative path
@@ -68,7 +68,7 @@ type Transformer = (context: {
68
68
  storage: Storage;
69
69
  options: LoadOptions;
70
70
  }) => void;
71
- declare function loadFiles(files: VirtualFile[], options: LoadOptions): Storage;
71
+ declare function loadFiles<O extends LoadOptions>(files: VirtualFile[], options: O): Storage;
72
72
 
73
73
  interface LoaderConfig {
74
74
  source: SourceConfig;
@@ -79,11 +79,6 @@ interface SourceConfig {
79
79
  metaData: MetaData;
80
80
  }
81
81
  interface LoaderOptions {
82
- /**
83
- * @deprecated It is now recommended to filter files on `source` level
84
- * @defaultValue `''`
85
- */
86
- rootDir?: string;
87
82
  baseUrl: string;
88
83
  icon?: NonNullable<BuildPageTreeOptions['resolveIcon']>;
89
84
  slugs?: LoadOptions['getSlugs'];
@@ -97,14 +92,16 @@ interface LoaderOptions {
97
92
  /**
98
93
  * Configure i18n
99
94
  */
100
- i18n?: I18nConfig;
95
+ i18n?: I18nConfig & {
96
+ parser?: I18nLoadOptions['i18n']['parser'];
97
+ };
101
98
  }
102
99
  interface Source<Config extends SourceConfig> {
103
100
  /**
104
101
  * @internal
105
102
  */
106
103
  _config?: Config;
107
- files: VirtualFile[] | ((rootDir: string) => VirtualFile[]);
104
+ files: VirtualFile[] | (() => VirtualFile[]);
108
105
  }
109
106
  interface Page<Data = PageData> {
110
107
  file: FileInfo;
@@ -234,7 +231,7 @@ declare namespace fileSystem {
234
231
  export { type fileSystem_File as File, type fileSystem_Folder as Folder, type fileSystem_MetaFile as MetaFile, type fileSystem_PageFile as PageFile, fileSystem_Storage as Storage };
235
232
  }
236
233
 
237
- interface BuildPageTreeOptions {
234
+ interface Options {
238
235
  /**
239
236
  * Remove references to the file path of original nodes (`$ref`)
240
237
  *
@@ -244,11 +241,14 @@ interface BuildPageTreeOptions {
244
241
  attachFile?: (node: Item, file?: PageFile) => Item;
245
242
  attachFolder?: (node: Folder$1, folder: Folder, meta?: MetaFile) => Folder$1;
246
243
  attachSeparator?: (node: Separator) => Separator;
247
- storage: Storage;
248
244
  getUrl: UrlFn;
249
245
  resolveIcon?: (icon: string | undefined) => ReactElement | undefined;
250
246
  }
251
- interface BuildPageTreeOptionsWithI18n extends BuildPageTreeOptions {
247
+ interface BuildPageTreeOptions extends Options {
248
+ storage: Storage;
249
+ }
250
+ interface BuildPageTreeOptionsWithI18n extends Options {
251
+ storages: Record<string, Storage>;
252
252
  i18n: I18nConfig;
253
253
  }
254
254
  interface PageTreeBuilder {
@@ -2,7 +2,7 @@ import {
2
2
  joinPath,
3
3
  slash,
4
4
  splitPath
5
- } from "../chunk-QTVCCXFT.js";
5
+ } from "../chunk-XMCPKVJQ.js";
6
6
  import {
7
7
  __export
8
8
  } from "../chunk-MLKGABMK.js";
@@ -25,10 +25,8 @@ function buildAll(nodes, ctx, skipIndex) {
25
25
  (a, b) => a.file.name.localeCompare(b.file.name)
26
26
  )) {
27
27
  if (isPageFile(node)) {
28
- if (node.file.locale.length > 0 && node.file.locale.slice(1) !== ctx.defaultLanguage)
29
- continue;
30
- const localized = ctx.storage.read(
31
- joinPath(node.file.dirname, `${node.file.name}.${ctx.lang}`),
28
+ const localized = ctx.localeStorage?.read(
29
+ joinPath(node.file.dirname, node.file.name),
32
30
  "page"
33
31
  );
34
32
  const treeNode = buildFileNode(localized ?? node, ctx);
@@ -83,10 +81,7 @@ function resolveFolderItem(folder, item, ctx, idx, addedNodePaths) {
83
81
  filename = item.slice(extractPrefix.length);
84
82
  }
85
83
  const path = joinPath(folder.file.path, filename);
86
- let itemNode = ctx.storage.readDir(path);
87
- if (!itemNode) {
88
- itemNode = (ctx.lang ? ctx.storage.read(`${path}.${ctx.lang}`, "page") : null) ?? ctx.storage.read(`${path}.${ctx.defaultLanguage}`, "page") ?? ctx.storage.read(path, "page");
89
- }
84
+ const itemNode = ctx.storage.readDir(path) ?? ctx.localeStorage?.read(path, "page") ?? ctx.storage.read(path, "page");
90
85
  if (!itemNode) return [];
91
86
  addedNodePaths.add(itemNode.file.path);
92
87
  if (isExcept) return [];
@@ -98,7 +93,7 @@ function resolveFolderItem(folder, item, ctx, idx, addedNodePaths) {
98
93
  }
99
94
  function buildFolderNode(folder, isGlobalRoot, ctx) {
100
95
  const metaPath = joinPath(folder.file.path, "meta");
101
- const meta = ctx.storage.read(`${metaPath}.${ctx.lang}`, "meta") ?? ctx.storage.read(`${metaPath}.${ctx.defaultLanguage}`, "meta") ?? ctx.storage.read(metaPath, "meta");
96
+ const meta = ctx.localeStorage?.read(metaPath, "meta") ?? ctx.storage.read(metaPath, "meta");
102
97
  const indexFile = ctx.storage.read(
103
98
  joinPath(folder.file.path, "index"),
104
99
  "page"
@@ -153,7 +148,7 @@ function buildFileNode(file, ctx) {
153
148
  name: file.data.data.title ?? pathToName(file.file.name),
154
149
  description: file.data.data.description,
155
150
  icon: ctx.options.resolveIcon?.(file.data.data.icon),
156
- url: ctx.options.getUrl(file.data.slugs, ctx.lang),
151
+ url: ctx.options.getUrl(file.data.slugs, ctx.locale),
157
152
  $ref: !ctx.options.noRef ? {
158
153
  file: file.file.path
159
154
  } : void 0
@@ -180,11 +175,11 @@ function createPageTreeBuilder() {
180
175
  buildI18n({ i18n, ...options }) {
181
176
  const entries = i18n.languages.map((lang) => {
182
177
  const tree = build({
183
- lang,
184
178
  options,
185
179
  builder: this,
186
- storage: options.storage,
187
- defaultLanguage: i18n.defaultLanguage
180
+ locale: lang,
181
+ storage: options.storages[i18n.defaultLanguage],
182
+ localeStorage: options.storages[lang]
188
183
  });
189
184
  return [lang, tree];
190
185
  });
@@ -208,29 +203,19 @@ function parseFilePath(path) {
208
203
  const dirname = segments.slice(0, -1).join("/");
209
204
  let name = segments.at(-1) ?? "";
210
205
  let ext = "";
211
- let locale = "";
212
- let dotIdx = name.lastIndexOf(".");
206
+ const dotIdx = name.lastIndexOf(".");
213
207
  if (dotIdx !== -1) {
214
208
  ext = name.substring(dotIdx);
215
209
  name = name.substring(0, dotIdx);
216
210
  }
217
- dotIdx = name.lastIndexOf(".");
218
- if (dotIdx !== -1 && isLocale(name.substring(dotIdx))) {
219
- locale = name.substring(dotIdx);
220
- name = name.substring(0, dotIdx);
221
- }
222
211
  return {
223
212
  dirname,
224
213
  name,
225
- locale,
226
214
  path: segments.join("/"),
227
215
  ext,
228
- flattenedPath: [dirname, `${name}${locale}`].filter((p) => p.length > 0).join("/")
216
+ flattenedPath: [dirname, name].filter((p) => p.length > 0).join("/")
229
217
  };
230
218
  }
231
- function isLocale(code) {
232
- return code.length > 0 && !/\d+/.test(code);
233
- }
234
219
  function parseFolderPath(path) {
235
220
  const segments = splitPath(slash(path));
236
221
  const base = segments.at(-1) ?? "";
@@ -284,10 +269,7 @@ var Storage = class {
284
269
  this.makeDir(node.file.dirname);
285
270
  this.readDir(node.file.dirname)?.children.push(node);
286
271
  this.files.set(
287
- joinPath(
288
- node.file.dirname,
289
- `${node.file.name}${node.file.locale}.${node.format}`
290
- ),
272
+ joinPath(node.file.dirname, `${node.file.name}.${node.format}`),
291
273
  node
292
274
  );
293
275
  }
@@ -313,20 +295,17 @@ var Storage = class {
313
295
  function loadFiles(files, options) {
314
296
  const { transformers = [] } = options;
315
297
  const storage = new Storage();
316
- const rootDir = normalizePath(options.rootDir ?? "");
317
298
  for (const file of files) {
318
- const normalizedPath = normalizePath(file.path);
319
- if (!normalizedPath.startsWith(rootDir)) continue;
320
- const relativePath = normalizedPath.slice(rootDir.length);
299
+ const parsedPath = normalizePath(file.path);
321
300
  if (file.type === "page") {
322
- const slugs = file.slugs ?? options.getSlugs(parseFilePath(relativePath));
323
- storage.write(relativePath, file.type, {
301
+ const slugs = file.slugs ?? options.getSlugs(parseFilePath(parsedPath));
302
+ storage.write(parsedPath, file.type, {
324
303
  slugs,
325
304
  data: file.data
326
305
  });
327
306
  }
328
307
  if (file.type === "meta") {
329
- storage.write(relativePath, file.type, file.data);
308
+ storage.write(parsedPath, file.type, file.data);
330
309
  }
331
310
  }
332
311
  for (const transformer of transformers) {
@@ -337,27 +316,63 @@ function loadFiles(files, options) {
337
316
  }
338
317
  return storage;
339
318
  }
319
+ function loadFilesI18n(files, options) {
320
+ const parser = options.i18n.parser === "dir" ? dirParser : dotParser;
321
+ const storages = {};
322
+ for (const lang of options.i18n.languages) {
323
+ storages[lang] = loadFiles(
324
+ files.flatMap((file) => {
325
+ const [path, locale] = parser(normalizePath(file.path));
326
+ if ((locale ?? options.i18n.defaultLanguage) === lang) {
327
+ return {
328
+ ...file,
329
+ path
330
+ };
331
+ }
332
+ return [];
333
+ }),
334
+ options
335
+ );
336
+ }
337
+ return storages;
338
+ }
339
+ function dirParser(path) {
340
+ const parsed = path.split("/");
341
+ if (parsed.length >= 2) return [parsed.slice(1).join("/"), parsed[0]];
342
+ return [path];
343
+ }
344
+ function dotParser(path) {
345
+ const segs = path.split("/");
346
+ if (segs.length === 0) return [path];
347
+ const name = segs[segs.length - 1].split(".");
348
+ if (name.length >= 3) {
349
+ const locale = name.splice(name.length - 2, 1)[0];
350
+ if (locale.length > 0 && !/\d+/.test(locale)) {
351
+ segs[segs.length - 1] = name.join(".");
352
+ return [segs.join("/"), locale];
353
+ }
354
+ }
355
+ return [path];
356
+ }
340
357
 
341
358
  // src/source/loader.ts
342
- function indexPages(storage, getUrl, i18n) {
359
+ function indexPages(storages, getUrl, i18n) {
343
360
  const defaultLanguage = i18n?.defaultLanguage ?? "";
344
361
  const map = /* @__PURE__ */ new Map();
345
362
  const pathToFile = /* @__PURE__ */ new Map();
346
- for (const item of storage.list()) {
363
+ for (const item of storages[defaultLanguage].list()) {
347
364
  if (item.format === "meta")
348
365
  pathToFile.set(item.file.path, fileToMeta(item));
349
366
  if (item.format === "page") {
350
- if (item.file.locale.length > 0 && item.file.locale.slice(1) !== defaultLanguage)
351
- continue;
352
367
  const page = fileToPage(item, getUrl, defaultLanguage);
353
368
  pathToFile.set(item.file.path, page);
354
369
  map.set(`${defaultLanguage}.${page.slugs.join("/")}`, page);
355
370
  if (!i18n) continue;
356
- const basePath = joinPath(item.file.dirname, item.file.name);
371
+ const path = joinPath(item.file.dirname, item.file.name);
357
372
  for (const lang of i18n.languages) {
358
373
  if (lang === defaultLanguage) continue;
359
374
  const localizedPage = fileToPage(
360
- storage.read(`${basePath}.${lang}`, "page") ?? item,
375
+ storages[lang].read(path, "page") ?? item,
361
376
  getUrl,
362
377
  lang
363
378
  );
@@ -401,17 +416,23 @@ function createOutput(options) {
401
416
  if (!options.url && !options.baseUrl) {
402
417
  console.warn("`loader()` now requires a `baseUrl` option to be defined.");
403
418
  }
404
- const { source, rootDir = "", slugs: slugsFn = getSlugs } = options;
419
+ const { source, slugs: slugsFn = getSlugs } = options;
405
420
  const getUrl = options.url ?? createGetUrl(options.baseUrl ?? "/", options.i18n);
406
- const storage = loadFiles(
407
- typeof source.files === "function" ? source.files(rootDir) : source.files,
408
- {
421
+ const files = typeof source.files === "function" ? source.files() : source.files;
422
+ const storages = options.i18n ? loadFilesI18n(files, {
423
+ i18n: {
424
+ ...options.i18n,
425
+ parser: options.i18n.parser ?? "dot"
426
+ },
427
+ transformers: options.transformers,
428
+ getSlugs: slugsFn
429
+ }) : {
430
+ "": loadFiles(files, {
409
431
  transformers: options.transformers,
410
- rootDir,
411
432
  getSlugs: slugsFn
412
- }
413
- );
414
- const walker = indexPages(storage, getUrl, options.i18n);
433
+ })
434
+ };
435
+ const walker = indexPages(storages, getUrl, options.i18n);
415
436
  const builder = createPageTreeBuilder();
416
437
  let pageTree;
417
438
  return {
@@ -419,7 +440,7 @@ function createOutput(options) {
419
440
  get pageTree() {
420
441
  if (options.i18n) {
421
442
  pageTree ??= builder.buildI18n({
422
- storage,
443
+ storages,
423
444
  resolveIcon: options.icon,
424
445
  getUrl,
425
446
  i18n: options.i18n,
@@ -427,7 +448,7 @@ function createOutput(options) {
427
448
  });
428
449
  } else {
429
450
  pageTree ??= builder.build({
430
- storage,
451
+ storage: storages[""],
431
452
  resolveIcon: options.icon,
432
453
  getUrl,
433
454
  ...options.pageTree
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "15.1.1",
3
+ "version": "15.1.3",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -113,8 +113,8 @@
113
113
  "@types/hast": "^3.0.4",
114
114
  "@types/mdast": "^4.0.3",
115
115
  "@types/negotiator": "^0.6.3",
116
- "@types/node": "22.13.10",
117
- "@types/react": "^19.0.11",
116
+ "@types/node": "22.13.11",
117
+ "@types/react": "^19.0.12",
118
118
  "@types/react-dom": "^19.0.4",
119
119
  "algoliasearch": "4.24.0",
120
120
  "mdast-util-mdx-jsx": "^3.2.0",