fumadocs-core 15.7.0 → 15.7.2

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.
@@ -3,11 +3,12 @@ import "./chunk-JSBRDJBE.js";
3
3
  // src/search/client/fetch.ts
4
4
  var cache = /* @__PURE__ */ new Map();
5
5
  async function fetchDocs(query, { api = "/api/search", locale, tag }) {
6
- const params = new URLSearchParams();
7
- params.set("query", query);
8
- if (locale) params.set("locale", locale);
9
- if (tag) params.set("tag", Array.isArray(tag) ? tag.join(",") : tag);
10
- const key = `${api}?${params}`;
6
+ const url = new URL(api, window.location.origin);
7
+ url.searchParams.set("query", query);
8
+ if (locale) url.searchParams.set("locale", locale);
9
+ if (tag)
10
+ url.searchParams.set("tag", Array.isArray(tag) ? tag.join(",") : tag);
11
+ const key = `${url.pathname}?${url.searchParams}`;
11
12
  const cached = cache.get(key);
12
13
  if (cached) return cached;
13
14
  const res = await fetch(key);
@@ -26,7 +26,13 @@ interface I18nConfig<Languages extends string = string> {
26
26
  *
27
27
  * @defaultValue 'dot'
28
28
  */
29
- parser?: 'dot' | 'dir';
29
+ parser?: 'dot' | 'dir' | 'none';
30
+ /**
31
+ * the fallback language when the page has no translations available for a given locale.
32
+ *
33
+ * Default to ``defaultLanguage`, no fallback when set to `null`.
34
+ */
35
+ fallbackLanguage?: Languages | null;
30
36
  }
31
37
  declare function defineI18n<Languages extends string>(config: I18nConfig<Languages>): I18nConfig<Languages>;
32
38
 
@@ -3,10 +3,10 @@ import { Root } from 'hast';
3
3
  import { RehypeShikiOptions } from '@shikijs/rehype';
4
4
  import { Processor, Transformer } from 'unified';
5
5
  import { ShikiTransformer } from 'shiki';
6
- import { Root as Root$1, Code } from 'mdast';
6
+ import { Root as Root$1, Code, BlockContent, Text } from 'mdast';
7
7
  export { a as StructureOptions, S as StructuredData, r as remarkStructure, s as structure } from '../remark-structure-DVje0Sib.js';
8
8
  export { R as RemarkHeadingOptions, r as remarkHeading } from '../remark-heading-BPCoYwjn.js';
9
- import { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
9
+ import { MdxJsxFlowElement, MdxJsxAttribute } from 'mdast-util-mdx-jsx';
10
10
 
11
11
  interface CodeBlockIcon {
12
12
  viewBox: string;
@@ -37,7 +37,7 @@ type RehypeCodeOptions = RehypeShikiOptions & {
37
37
  *
38
38
  * @defaultValue true
39
39
  */
40
- tab?: false;
40
+ tab?: boolean;
41
41
  /**
42
42
  * Enable Shiki's experimental JS engine
43
43
  *
@@ -163,10 +163,14 @@ declare function remarkSteps({ steps, step, }?: RemarkStepsOptions): Transformer
163
163
 
164
164
  interface PackageManager {
165
165
  name: string;
166
+ /**
167
+ * Default to `name`
168
+ */
169
+ value?: string;
166
170
  /**
167
171
  * Convert from npm to another package manager
168
172
  */
169
- command: (command: string) => string;
173
+ command: (command: string) => string | undefined;
170
174
  }
171
175
  interface RemarkNpmOptions {
172
176
  /**
@@ -184,4 +188,29 @@ interface RemarkNpmOptions {
184
188
  */
185
189
  declare function remarkNpm({ persist, packageManagers, }?: RemarkNpmOptions): Transformer<Root$1, Root$1>;
186
190
 
187
- export { type CodeBlockIcon, type RehypeCodeOptions, type RehypeTocOptions, type RemarkAdmonitionOptions, type RemarkCodeTabOptions, type RemarkImageOptions, type RemarkNpmOptions, type RemarkStepsOptions, rehypeCode, rehypeCodeDefaultOptions, rehypeToc, remarkAdmonition, remarkCodeTab, remarkImage, remarkNpm, remarkSteps, transformerIcon, transformerTab };
191
+ interface CodeBlockTabsOptions {
192
+ attributes?: MdxJsxAttribute[];
193
+ defaultValue?: string;
194
+ persist?: {
195
+ id: string;
196
+ } | false;
197
+ triggers: {
198
+ value: string;
199
+ children: (BlockContent | Text)[];
200
+ }[];
201
+ tabs: {
202
+ value: string;
203
+ children: BlockContent[];
204
+ }[];
205
+ }
206
+ declare function generateCodeBlockTabs({ persist, defaultValue, triggers, tabs, ...options }: CodeBlockTabsOptions): MdxJsxFlowElement;
207
+ interface CodeBlockAttributes<Name extends string = string> {
208
+ attributes: Partial<Record<Name, string>>;
209
+ rest: string;
210
+ }
211
+ /**
212
+ * Parse Fumadocs-style code block attributes from meta string, like `title="hello world"`
213
+ */
214
+ declare function parseCodeBlockAttributes<Name extends string = string>(meta: string, allowedNames?: Name[]): CodeBlockAttributes<Name>;
215
+
216
+ export { type CodeBlockAttributes, type CodeBlockIcon, type CodeBlockTabsOptions, type RehypeCodeOptions, type RehypeTocOptions, type RemarkAdmonitionOptions, type RemarkCodeTabOptions, type RemarkImageOptions, type RemarkNpmOptions, type RemarkStepsOptions, generateCodeBlockTabs, parseCodeBlockAttributes, rehypeCode, rehypeCodeDefaultOptions, rehypeToc, remarkAdmonition, remarkCodeTab, remarkImage, remarkNpm, remarkSteps, transformerIcon, transformerTab };
@@ -172,29 +172,87 @@ function transformerIcon(options = {}) {
172
172
  };
173
173
  }
174
174
 
175
- // src/mdx-plugins/rehype-code.ts
176
- var metaValues = [
177
- {
178
- name: "title",
179
- regex: /title="(?<value>[^"]*)"/
180
- },
181
- {
182
- name: "custom",
183
- regex: /custom="(?<value>[^"]+)"/
184
- },
185
- {
186
- name: "tab",
187
- regex: /tab="(?<value>[^"]+)"/
188
- },
189
- {
190
- regex: /lineNumbers=(\d+)|lineNumbers/,
191
- onSet(map, args) {
192
- map["data-line-numbers"] = true;
193
- if (args[0] !== void 0)
194
- map["data-line-numbers-start"] = Number(args[0]);
175
+ // src/mdx-plugins/codeblock-utils.ts
176
+ function generateCodeBlockTabs({
177
+ persist = false,
178
+ defaultValue,
179
+ triggers,
180
+ tabs,
181
+ ...options
182
+ }) {
183
+ const attributes = [];
184
+ if (options.attributes) attributes.push(...options.attributes);
185
+ if (defaultValue) {
186
+ attributes.push({
187
+ type: "mdxJsxAttribute",
188
+ name: "defaultValue",
189
+ value: defaultValue
190
+ });
191
+ }
192
+ if (typeof persist === "object") {
193
+ attributes.push(
194
+ {
195
+ type: "mdxJsxAttribute",
196
+ name: "groupId",
197
+ value: persist.id
198
+ },
199
+ {
200
+ type: "mdxJsxAttribute",
201
+ name: "persist",
202
+ value: null
203
+ }
204
+ );
205
+ }
206
+ const children = [
207
+ {
208
+ type: "mdxJsxFlowElement",
209
+ name: "CodeBlockTabsList",
210
+ attributes: [],
211
+ children: triggers.map(
212
+ (trigger) => ({
213
+ type: "mdxJsxFlowElement",
214
+ attributes: [
215
+ { type: "mdxJsxAttribute", name: "value", value: trigger.value }
216
+ ],
217
+ name: "CodeBlockTabsTrigger",
218
+ children: trigger.children
219
+ })
220
+ )
195
221
  }
222
+ ];
223
+ for (const tab of tabs) {
224
+ children.push({
225
+ type: "mdxJsxFlowElement",
226
+ name: "CodeBlockTab",
227
+ attributes: [
228
+ { type: "mdxJsxAttribute", name: "value", value: tab.value }
229
+ ],
230
+ children: tab.children
231
+ });
196
232
  }
197
- ];
233
+ return {
234
+ type: "mdxJsxFlowElement",
235
+ name: "CodeBlockTabs",
236
+ attributes,
237
+ children
238
+ };
239
+ }
240
+ function parseCodeBlockAttributes(meta, allowedNames) {
241
+ let str = meta;
242
+ const StringRegex = /(?<=^|\s)(?<name>\w+)=(?:"([^"]*)"|'([^']*)')/g;
243
+ const attributes = {};
244
+ str = str.replaceAll(StringRegex, (match, name, value_1, value_2) => {
245
+ if (allowedNames && !allowedNames.includes(name)) return match;
246
+ attributes[name] = value_1 ?? value_2;
247
+ return "";
248
+ });
249
+ return {
250
+ rest: str,
251
+ attributes
252
+ };
253
+ }
254
+
255
+ // src/mdx-plugins/rehype-code.ts
198
256
  var rehypeCodeDefaultOptions = {
199
257
  lazy: true,
200
258
  themes: defaultThemes,
@@ -216,22 +274,21 @@ var rehypeCodeDefaultOptions = {
216
274
  })
217
275
  ],
218
276
  parseMetaString(meta) {
219
- const map = {};
220
- for (const value of metaValues) {
221
- meta = meta.replace(value.regex, (_, ...args) => {
222
- if ("onSet" in value) {
223
- value.onSet(map, args);
224
- } else {
225
- const first = args.at(0);
226
- map[value.name] = typeof first === "string" ? first : "";
227
- }
228
- return "";
229
- });
230
- }
231
- map.__parsed_raw = meta;
232
- return map;
277
+ const parsed = parseCodeBlockAttributes(meta);
278
+ const data = parsed.attributes;
279
+ parsed.rest = parseLineNumber(parsed.rest, data);
280
+ data.__parsed_raw = parsed.rest;
281
+ return data;
233
282
  }
234
283
  };
284
+ function parseLineNumber(str, data) {
285
+ return str.replace(/lineNumbers=(\d+)|lineNumbers/, (_, ...args) => {
286
+ data["data-line-numbers"] = true;
287
+ if (args[0] !== void 0)
288
+ data["data-line-numbers-start"] = Number(args[0]);
289
+ return "";
290
+ });
291
+ }
235
292
  function rehypeCode(_options = {}) {
236
293
  const options = {
237
294
  ...rehypeCodeDefaultOptions,
@@ -1159,78 +1216,45 @@ function remarkNpm({
1159
1216
  } = {}) {
1160
1217
  return (tree) => {
1161
1218
  visit7(tree, "code", (node) => {
1162
- if (!node.lang || !aliases.includes(node.lang)) return "skip";
1163
- const value = node.value.startsWith("npm") || node.value.startsWith("npx") ? node.value : `npm install ${node.value}`;
1164
- const attributes = [
1165
- {
1166
- type: "mdxJsxAttribute",
1167
- name: "defaultValue",
1168
- value: packageManagers[0].name
1169
- }
1170
- ];
1171
- if (typeof persist === "object") {
1172
- attributes.push(
1173
- {
1174
- type: "mdxJsxAttribute",
1175
- name: "groupId",
1176
- value: persist.id
1177
- },
1178
- {
1179
- type: "mdxJsxAttribute",
1180
- name: "persist",
1181
- value: null
1182
- }
1183
- );
1219
+ if (!node.lang || !aliases.includes(node.lang)) return;
1220
+ let code = node.value;
1221
+ if (node.lang === "package-install" && !code.startsWith("npm") && !code.startsWith("npx")) {
1222
+ code = `npm install ${code}`;
1184
1223
  }
1185
- const children = [
1186
- {
1187
- type: "mdxJsxFlowElement",
1188
- name: "CodeBlockTabsList",
1189
- attributes: [],
1190
- children: packageManagers.map(
1191
- ({ name }) => ({
1192
- type: "mdxJsxFlowElement",
1193
- attributes: [
1194
- { type: "mdxJsxAttribute", name: "value", value: name }
1195
- ],
1196
- name: "CodeBlockTabsTrigger",
1197
- children: [
1198
- {
1199
- type: "text",
1200
- value: name
1201
- }
1202
- ]
1203
- })
1204
- )
1205
- }
1206
- ];
1207
- for (const { name, command } of packageManagers) {
1208
- children.push({
1209
- type: "mdxJsxFlowElement",
1210
- name: "CodeBlockTab",
1211
- attributes: [{ type: "mdxJsxAttribute", name: "value", value: name }],
1224
+ const options = {
1225
+ persist,
1226
+ tabs: [],
1227
+ triggers: []
1228
+ };
1229
+ for (const manager of packageManagers) {
1230
+ const value = manager.value ?? manager.name;
1231
+ const command = manager.command(code);
1232
+ if (!command || command.length === 0) continue;
1233
+ options.defaultValue ??= value;
1234
+ options.triggers.push({
1235
+ value,
1236
+ children: [{ type: "text", value: manager.name }]
1237
+ });
1238
+ options.tabs.push({
1239
+ value,
1212
1240
  children: [
1213
1241
  {
1214
1242
  type: "code",
1215
1243
  lang: "bash",
1216
1244
  meta: node.meta,
1217
- value: command(value)
1245
+ value: command
1218
1246
  }
1219
1247
  ]
1220
1248
  });
1221
1249
  }
1222
- const tab = {
1223
- type: "mdxJsxFlowElement",
1224
- name: "CodeBlockTabs",
1225
- attributes,
1226
- children
1227
- };
1228
- Object.assign(node, tab);
1229
- return;
1250
+ Object.assign(node, generateCodeBlockTabs(options));
1251
+ return "skip";
1230
1252
  });
1231
1253
  };
1232
1254
  }
1233
1255
  export {
1256
+ generateCodeBlockTabs,
1257
+ parseCodeBlockAttributes,
1234
1258
  rehypeCode,
1235
1259
  rehypeCodeDefaultOptions,
1236
1260
  rehypeToc,
@@ -65,7 +65,7 @@ function useDocsSearch(clientOptions, _locale, _tag, _delayMs = 100, _allowEmpty
65
65
  async function run() {
66
66
  if (debouncedValue.length === 0 && !allowEmpty) return "empty";
67
67
  if (client.type === "fetch") {
68
- const { fetchDocs } = await import("../fetch-ITPHBPBE.js");
68
+ const { fetchDocs } = await import("../fetch-C3XV44E6.js");
69
69
  return fetchDocs(debouncedValue, client);
70
70
  }
71
71
  if (client.type === "algolia") {
@@ -43,21 +43,12 @@ type Transformer = (context: {
43
43
  storage: ContentStorage;
44
44
  options: LoadOptions;
45
45
  }) => void;
46
- declare const parsers: {
47
- dir(path: string): [string, string?];
48
- dot(path: string): [string, string?];
49
- none(path: string): [string, string?];
50
- };
51
46
  /**
52
47
  * @returns a map of locale and its content storage.
53
48
  *
54
49
  * in the storage, locale codes are removed from file paths, hence the same file will have same file paths in every storage.
55
50
  */
56
- declare function loadFiles(files: VirtualFile[], options: LoadOptions, i18n: {
57
- parser: keyof typeof parsers;
58
- languages: string[];
59
- defaultLanguage: string;
60
- }): Record<string, ContentStorage>;
51
+ declare function loadFiles(files: VirtualFile[], options: LoadOptions, i18n: I18nConfig): Record<string, ContentStorage>;
61
52
 
62
53
  interface FileInfo {
63
54
  /**
@@ -112,7 +103,7 @@ interface LoaderOptions<T extends SourceConfig = SourceConfig, I18n extends I18n
112
103
  icon?: NonNullable<BaseOptions['resolveIcon']>;
113
104
  slugs?: (info: FileInfo) => string[];
114
105
  url?: UrlFn;
115
- source: Source<T>;
106
+ source: Source<T> | Source<T>[];
116
107
  transformers?: Transformer[];
117
108
  /**
118
109
  * Additional options for page tree builder
@@ -265,7 +256,7 @@ interface LegacyTransformerOptions<Page extends PageData, Meta extends MetaData>
265
256
 
266
257
  interface PageTreeBuilderContext<Page extends PageData = PageData, Meta extends MetaData = MetaData> {
267
258
  /**
268
- * @internal
259
+ * @internal resolve paths without extensions
269
260
  */
270
261
  resolveName: (name: string, format: 'meta' | 'page') => string;
271
262
  options: BaseOptions<Page, Meta>;
@@ -275,6 +266,7 @@ interface PageTreeBuilderContext<Page extends PageData = PageData, Meta extends
275
266
  getUrl: UrlFn;
276
267
  storages?: Record<string, ContentStorage<Page, Meta>>;
277
268
  locale?: string;
269
+ visitedPaths: Set<string>;
278
270
  }
279
271
  interface PageTreeTransformer<Page extends PageData = any, Meta extends MetaData = any> {
280
272
  name?: string;
@@ -292,17 +284,17 @@ interface BaseOptions<Page extends PageData = PageData, Meta extends MetaData =
292
284
  noRef?: boolean;
293
285
  transformers?: PageTreeTransformer<Page, Meta>[];
294
286
  resolveIcon?: (icon: string | undefined) => ReactNode | undefined;
287
+ /**
288
+ * generate fallback page tree
289
+ *
290
+ * @defaultValue true
291
+ */
292
+ generateFallback?: boolean;
295
293
  }
296
294
  interface PageTreeBuilder<Page extends PageData = PageData, Meta extends MetaData = MetaData> {
297
295
  build: (options: BaseOptions<Page, Meta> & {
298
296
  id?: string;
299
297
  storage: ContentStorage<Page, Meta>;
300
- /**
301
- * generate fallback page tree
302
- *
303
- * @defaultValue true
304
- */
305
- generateFallback?: boolean;
306
298
  }) => Root;
307
299
  /**
308
300
  * Build page tree and fallback to the default language if the localized page doesn't exist
@@ -83,24 +83,26 @@ var rest = "...";
83
83
  var restReversed = "z...a";
84
84
  var extractPrefix = "...";
85
85
  var excludePrefix = "!";
86
- function buildAll(paths, ctx, filter, reversed = false) {
87
- const output = [];
88
- const sortedPaths = (filter ? paths.filter(filter) : [...paths]).sort(
86
+ function buildAll(paths, ctx, reversed = false) {
87
+ const items = [];
88
+ const folders = [];
89
+ const sortedPaths = paths.sort(
89
90
  (a, b) => a.localeCompare(b) * (reversed ? -1 : 1)
90
91
  );
91
92
  for (const path of sortedPaths) {
93
+ ctx.visitedPaths.add(path);
92
94
  const fileNode = buildFileNode(path, ctx);
93
- if (!fileNode) continue;
94
- if (basename(path, extname(path)) === "index") output.unshift(fileNode);
95
- else output.push(fileNode);
96
- }
97
- for (const dir of sortedPaths) {
98
- const dirNode = buildFolderNode(dir, false, ctx);
99
- if (dirNode) output.push(dirNode);
95
+ if (fileNode) {
96
+ if (basename(path, extname(path)) === "index") items.unshift(fileNode);
97
+ else items.push(fileNode);
98
+ continue;
99
+ }
100
+ const dirNode = buildFolderNode(path, false, ctx);
101
+ if (dirNode) folders.push(dirNode);
100
102
  }
101
- return output;
103
+ return [...items, ...folders];
102
104
  }
103
- function resolveFolderItem(folderPath, item, ctx, idx, restNodePaths) {
105
+ function resolveFolderItem(folderPath, item, ctx, idx) {
104
106
  if (item === rest || item === restReversed) return item;
105
107
  const { options, resolveName } = ctx;
106
108
  let match = separator.exec(item);
@@ -143,7 +145,7 @@ function resolveFolderItem(folderPath, item, ctx, idx, restNodePaths) {
143
145
  filename = item.slice(extractPrefix.length);
144
146
  }
145
147
  const path = resolveName(joinPath(folderPath, filename), "page");
146
- restNodePaths.delete(path);
148
+ ctx.visitedPaths.add(path);
147
149
  if (isExcept) return [];
148
150
  const dirNode = buildFolderNode(path, false, ctx);
149
151
  if (dirNode) {
@@ -162,27 +164,29 @@ function buildFolderNode(folderPath, isGlobalRoot, ctx) {
162
164
  if (meta?.format !== "meta") {
163
165
  meta = void 0;
164
166
  }
165
- let indexDisabled = meta?.data.root ?? isGlobalRoot;
167
+ const isRoot = meta?.data.root ?? isGlobalRoot;
168
+ let index;
166
169
  let children;
170
+ function setIndexIfUnused() {
171
+ if (isRoot || ctx.visitedPaths.has(indexPath)) return;
172
+ ctx.visitedPaths.add(indexPath);
173
+ index = buildFileNode(indexPath, ctx);
174
+ }
167
175
  if (!meta?.data.pages) {
176
+ setIndexIfUnused();
168
177
  children = buildAll(
169
- files,
170
- ctx,
171
- (file) => indexDisabled || file !== indexPath
178
+ files.filter((file) => !ctx.visitedPaths.has(file)),
179
+ ctx
172
180
  );
173
181
  } else {
174
- const restItems = new Set(files);
175
- const resolved = meta.data.pages.flatMap((item, i) => resolveFolderItem(folderPath, item, ctx, i, restItems));
176
- if (!indexDisabled && !restItems.has(indexPath)) {
177
- indexDisabled = true;
178
- }
182
+ const resolved = meta.data.pages.flatMap((item, i) => resolveFolderItem(folderPath, item, ctx, i));
183
+ setIndexIfUnused();
179
184
  for (let i = 0; i < resolved.length; i++) {
180
185
  const item = resolved[i];
181
186
  if (item !== rest && item !== restReversed) continue;
182
187
  const items = buildAll(
183
- files,
188
+ files.filter((file) => !ctx.visitedPaths.has(file)),
184
189
  ctx,
185
- (file) => (indexDisabled || file !== indexPath) && restItems.has(file),
186
190
  item === restReversed
187
191
  );
188
192
  resolved.splice(i, 1, ...items);
@@ -190,7 +194,6 @@ function buildFolderNode(folderPath, isGlobalRoot, ctx) {
190
194
  }
191
195
  children = resolved;
192
196
  }
193
- const index = !indexDisabled ? buildFileNode(indexPath, ctx) : void 0;
194
197
  let name = meta?.data.title ?? index?.name;
195
198
  if (!name) {
196
199
  const folderName = basename(folderPath);
@@ -252,7 +255,7 @@ function build(id, ctx) {
252
255
  return root;
253
256
  }
254
257
  function createPageTreeBuilder(getUrl) {
255
- function getTransformers(options, generateFallback = true) {
258
+ function getTransformers(options, generateFallback) {
256
259
  const transformers = [legacyTransformer(options)];
257
260
  if (options.transformers) {
258
261
  transformers.push(...options.transformers);
@@ -275,25 +278,21 @@ function createPageTreeBuilder(getUrl) {
275
278
  };
276
279
  }
277
280
  return {
278
- build({ storage, id, generateFallback, ...options }) {
279
- const resolve = createFlattenPathResolver(storage);
280
- return build(id ?? "root", {
281
- transformers: getTransformers(options, generateFallback),
282
- options,
283
- builder: this,
284
- storage,
285
- getUrl,
286
- resolveName(name, format) {
287
- return resolve(name, format) ?? name;
288
- }
289
- });
281
+ build({ storage, id, ...options }) {
282
+ const key = "";
283
+ return this.buildI18n({
284
+ id,
285
+ storages: { [key]: storage },
286
+ ...options
287
+ })[key];
290
288
  },
291
- buildI18n({ id, storages, ...options }) {
292
- const transformers = getTransformers(options);
289
+ buildI18n({ id, storages, generateFallback = true, ...options }) {
290
+ const transformers = getTransformers(options, generateFallback);
293
291
  const out = {};
294
292
  for (const [locale, storage] of Object.entries(storages)) {
295
293
  const resolve = createFlattenPathResolver(storage);
296
- out[locale] = build(id ?? (locale.length === 0 ? "root" : locale), {
294
+ const branch = locale.length === 0 ? "root" : locale;
295
+ out[locale] = build(id ? `${id}-${branch}` : branch, {
297
296
  transformers,
298
297
  builder: this,
299
298
  options,
@@ -301,6 +300,7 @@ function createPageTreeBuilder(getUrl) {
301
300
  locale,
302
301
  storage,
303
302
  storages,
303
+ visitedPaths: /* @__PURE__ */ new Set(),
304
304
  resolveName(name, format) {
305
305
  return resolve(name, format) ?? name;
306
306
  }
@@ -401,7 +401,7 @@ var parsers = {
401
401
  };
402
402
  function loadFiles(files, options, i18n) {
403
403
  const { buildFile, transformers = [] } = options;
404
- const parser = parsers[i18n.parser];
404
+ const parser = parsers[i18n.parser ?? "dot"];
405
405
  const storages = {};
406
406
  const normalized = files.map(
407
407
  (file) => buildFile({
@@ -409,8 +409,16 @@ function loadFiles(files, options, i18n) {
409
409
  path: normalizePath(file.path)
410
410
  })
411
411
  );
412
- function scan(lang, fallback) {
413
- const storage = new FileSystem(fallback);
412
+ const fallbackLang = i18n.fallbackLanguage !== null ? i18n.fallbackLanguage ?? i18n.defaultLanguage : null;
413
+ function scan(lang) {
414
+ if (storages[lang]) return;
415
+ let storage;
416
+ if (fallbackLang && fallbackLang !== lang) {
417
+ scan(fallbackLang);
418
+ storage = new FileSystem(storages[fallbackLang]);
419
+ } else {
420
+ storage = new FileSystem();
421
+ }
414
422
  for (const item of normalized) {
415
423
  const [path, locale = i18n.defaultLanguage] = parser(item.path);
416
424
  if (locale === lang) storage.write(path, item);
@@ -421,12 +429,9 @@ function loadFiles(files, options, i18n) {
421
429
  options
422
430
  });
423
431
  }
424
- return storage;
425
- }
426
- storages[i18n.defaultLanguage] = scan(i18n.defaultLanguage);
427
- for (const lang of i18n.languages) {
428
- storages[lang] ??= scan(lang, storages[i18n.defaultLanguage]);
432
+ storages[lang] = storage;
429
433
  }
434
+ for (const lang of i18n.languages) scan(lang);
430
435
  return storages;
431
436
  }
432
437
  function normalizePath(path) {
@@ -479,6 +484,17 @@ function createGetUrl(baseUrl, i18n) {
479
484
  function loader(options) {
480
485
  return createOutput(options);
481
486
  }
487
+ function loadSource(source) {
488
+ const out = [];
489
+ for (const item of Array.isArray(source) ? source : [source]) {
490
+ if (typeof item.files === "function") {
491
+ out.push(...item.files());
492
+ } else {
493
+ out.push(...item.files);
494
+ }
495
+ }
496
+ return out;
497
+ }
482
498
  function createOutput(options) {
483
499
  if (!options.url && !options.baseUrl) {
484
500
  console.warn("`loader()` now requires a `baseUrl` option to be defined.");
@@ -492,14 +508,15 @@ function createOutput(options) {
492
508
  transformers = []
493
509
  } = options;
494
510
  const defaultLanguage = i18n?.defaultLanguage ?? "";
495
- const files = typeof source.files === "function" ? source.files() : source.files;
511
+ const files = loadSource(source);
496
512
  const transformerSlugs = ({ storage }) => {
497
513
  const indexFiles = /* @__PURE__ */ new Set();
498
514
  const taken = /* @__PURE__ */ new Set();
515
+ const autoIndex = slugsFn === void 0;
499
516
  for (const path of storage.getFiles()) {
500
517
  const file = storage.read(path);
501
518
  if (!file || file.format !== "page" || file.slugs) continue;
502
- if (isIndex(path) && !slugsFn) {
519
+ if (isIndex(path) && autoIndex) {
503
520
  indexFiles.add(path);
504
521
  continue;
505
522
  }
@@ -537,10 +554,7 @@ function createOutput(options) {
537
554
  },
538
555
  transformers: [transformerSlugs, ...transformers]
539
556
  },
540
- i18n ? {
541
- ...i18n,
542
- parser: i18n.parser ?? "dot"
543
- } : {
557
+ i18n ?? {
544
558
  defaultLanguage,
545
559
  parser: "none",
546
560
  languages: [defaultLanguage]
@@ -564,7 +578,7 @@ function createOutput(options) {
564
578
  pageTree = v;
565
579
  } else {
566
580
  pageTree = {
567
- defaultLanguage: v
581
+ [defaultLanguage]: v
568
582
  };
569
583
  }
570
584
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "15.7.0",
3
+ "version": "15.7.2",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -110,7 +110,7 @@
110
110
  },
111
111
  "devDependencies": {
112
112
  "@mdx-js/mdx": "^3.1.0",
113
- "@mixedbread/sdk": "^0.25.0",
113
+ "@mixedbread/sdk": "^0.26.0",
114
114
  "@oramacloud/client": "^2.1.4",
115
115
  "@tanstack/react-router": "^1.131.27",
116
116
  "@types/estree-jsx": "^1.0.5",
@@ -118,21 +118,21 @@
118
118
  "@types/mdast": "^4.0.3",
119
119
  "@types/negotiator": "^0.6.4",
120
120
  "@types/node": "24.3.0",
121
- "@types/react": "^19.1.10",
121
+ "@types/react": "^19.1.11",
122
122
  "@types/react-dom": "^19.1.7",
123
123
  "algoliasearch": "5.35.0",
124
124
  "mdast-util-mdx-jsx": "^3.2.0",
125
125
  "mdast-util-mdxjs-esm": "^2.0.1",
126
126
  "next": "^15.5.0",
127
- "react-router": "^7.8.1",
127
+ "react-router": "^7.8.2",
128
128
  "remark-mdx": "^3.1.0",
129
129
  "remove-markdown": "^0.6.2",
130
130
  "typescript": "^5.9.2",
131
131
  "unified": "^11.0.5",
132
132
  "vfile": "^6.0.3",
133
133
  "waku": "^0.25.0",
134
- "eslint-config-custom": "0.0.0",
135
- "tsconfig": "0.0.0"
134
+ "tsconfig": "0.0.0",
135
+ "eslint-config-custom": "0.0.0"
136
136
  },
137
137
  "peerDependencies": {
138
138
  "@mixedbread/sdk": "^0.19.0",