fumadocs-core 16.17.1 → 16.17.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.
@@ -11,12 +11,10 @@ import { cache } from "react";
11
11
  */
12
12
  var FileSystem = class {
13
13
  constructor(inherit) {
14
- this.files = /* @__PURE__ */ new Map();
15
14
  this.folders = /* @__PURE__ */ new Map();
16
- if (inherit) {
17
- for (const [k, v] of inherit.folders) this.folders.set(k, [...v]);
18
- for (const [k, v] of inherit.files) this.files.set(k, v);
19
- } else this.folders.set("", []);
15
+ this.files = new Map(inherit?.files);
16
+ if (inherit) for (const [k, v] of inherit.folders) this.folders.set(k, [...v]);
17
+ else this.folders.set("", []);
20
18
  }
21
19
  read(path) {
22
20
  return this.files.get(path);
@@ -56,6 +54,7 @@ var FileSystem = class {
56
54
  return Array.from(this.files.keys());
57
55
  }
58
56
  makeDir(path) {
57
+ if (this.folders.has(path)) return;
59
58
  const cur = [];
60
59
  let parentPath = "";
61
60
  for (const seg of path.split("/")) {
@@ -129,29 +128,26 @@ const EmptyLang = Symbol();
129
128
  */
130
129
  function createContentStorageBuilder(loaderConfig) {
131
130
  const { input, plugins, i18n } = loaderConfig;
132
- let parser;
133
- if (!i18n) parser = (path) => [path];
134
- else if (i18n.parser === "dir") {
131
+ let parser = (path) => [path];
132
+ if (i18n) {
135
133
  const langSet = new Set(i18n.languages);
136
134
  parser = (path) => {
137
- const [locale, ...segs] = path.split("/");
138
- if (!locale || segs.length === 0) return [path];
139
- if (langSet.has(locale)) return [segs.join("/"), locale];
140
- if (locale === "$") return [segs.join("/"), i18n.languages];
141
- return [path];
142
- };
143
- } else {
144
- const langSet = new Set(i18n.languages);
145
- parser = (path) => {
146
- const segs = path.split("/");
147
- const base = segs.pop();
148
- if (!base) return [path];
149
- const parts = base.split(".");
150
- if (parts.length < 3) return [path];
151
- const [locale] = parts.splice(parts.length - 2, 1);
152
- segs.push(parts.join("."));
153
- if (langSet.has(locale)) return [segs.join("/"), locale];
154
- if (locale === "$") return [segs.join("/"), i18n.languages];
135
+ let locale;
136
+ let rest;
137
+ if (i18n.parser === "dir") {
138
+ const slash = path.indexOf("/");
139
+ if (slash <= 0) return [path];
140
+ locale = path.slice(0, slash);
141
+ rest = path.slice(slash + 1);
142
+ } else {
143
+ const ext = path.lastIndexOf(".");
144
+ const dot = path.lastIndexOf(".", ext - 1);
145
+ if (dot === -1 || dot < path.lastIndexOf("/")) return [path];
146
+ locale = path.slice(dot + 1, ext);
147
+ rest = path.slice(0, dot) + path.slice(ext);
148
+ }
149
+ if (langSet.has(locale)) return [rest, locale];
150
+ if (locale === "$") return [rest, i18n.languages];
155
151
  return [path];
156
152
  };
157
153
  }
@@ -216,371 +212,266 @@ function pushMapList(map, k, v) {
216
212
  list.push(v);
217
213
  }
218
214
  //#endregion
219
- //#region src/source/page-tree/transformer-fallback.ts
220
- function transformerFallback() {
221
- const addedFiles = /* @__PURE__ */ new Set();
222
- function shouldIgnore(context) {
223
- return context.custom?._fallback === true;
224
- }
225
- return {
226
- root(root) {
227
- if (shouldIgnore(this)) return root;
228
- const isolatedStorage = new FileSystem();
229
- if (addedFiles.size === this.storage.files.size) return root;
230
- for (const file of this.storage.getFiles()) {
231
- if (addedFiles.has(file)) continue;
232
- isolatedStorage.write(file, this.storage.read(file));
233
- }
234
- root.fallback = createPageTreeBuilder(isolatedStorage, {
235
- ...this.options,
236
- idPrefix: this.options.idPrefix ? `fallback:${this.options.idPrefix}` : "fallback",
237
- generateFallback: false,
238
- context: {
239
- ...this.custom,
240
- _fallback: true
241
- }
242
- }).root();
243
- addedFiles.clear();
244
- return root;
245
- },
246
- file(node, file) {
247
- if (shouldIgnore(this)) return node;
248
- if (file) addedFiles.add(file);
249
- return node;
250
- },
251
- folder(node, _dir, metaPath) {
252
- if (shouldIgnore(this)) return node;
253
- if (metaPath) addedFiles.add(metaPath);
254
- return node;
255
- }
256
- };
257
- }
258
- //#endregion
259
215
  //#region src/source/page-tree/builder.ts
260
216
  const group = /^\((?<name>.+)\)$/;
261
217
  const link = /^(?<external>external:)?(?:\[(?<icon>[^\]]+)])?\[(?<name>[^\]]+)]\((?<url>[^)]+)\)$/;
262
218
  const separator = /^---(?:\[(?<icon>[^\]]+)])?(?<name>.+)---|^---$/;
263
- const rest = "...";
264
- const restReversed = "z...a";
265
- const extractPrefix = "...";
266
- const excludePrefix = "!";
267
- const SymbolUnfinished = Symbol("unfinished");
268
- const SymbolName = Symbol("name");
269
- const SymbolOwner = Symbol("owner");
270
- function createPageTreeBuilder(input, options) {
271
- const flattenPathToFullPath = /* @__PURE__ */ new Map();
272
- const transformers = [];
273
- /** virtual file path -> output page tree node (if cached) */
274
- const pathToNode = /* @__PURE__ */ new Map();
275
- let _nextId = 0;
276
- const { noRef = false, idPrefix, url: getUrl, generateFallback = true, sort: { by: sortBy = "path", locales: sortLocales, options: sortOptions } = {} } = options;
277
- /** passed as additional information to transformers */
278
- let ctx;
279
- if (options.transformers) transformers.push(...options.transformers);
280
- if (generateFallback) transformers.push(transformerFallback());
281
- if (Array.isArray(input)) {
282
- const [locale, storages] = input;
283
- ctx = {
284
- get builder() {
285
- return builder;
286
- },
287
- storage: storages[locale],
288
- storages,
289
- locale,
290
- transformers,
291
- custom: options.context,
292
- options
293
- };
294
- } else ctx = {
295
- get builder() {
296
- return builder;
297
- },
298
- storage: input,
219
+ function createPageTreeBuilder(storage, options, locale, storages) {
220
+ const { idPrefix, transformers = [], sort = {} } = options;
221
+ const byName = sort.by === "name";
222
+ const compare = sort.locales || sort.options ? new Intl.Collator(sort.locales, sort.options).compare : (a, b) => a.localeCompare(b);
223
+ /** file or folder path -> entry */
224
+ const entries = /* @__PURE__ */ new Map();
225
+ const entryOf = /* @__PURE__ */ new Map();
226
+ const metaPaths = /* @__PURE__ */ new Set();
227
+ /** format -> file path without extension -> file path */
228
+ const flattenPaths = /* @__PURE__ */ new Map();
229
+ let nextId = 0;
230
+ let usedPages = 0;
231
+ for (const [path, file] of storage.files) {
232
+ let paths = flattenPaths.get(file.format);
233
+ if (!paths) flattenPaths.set(file.format, paths = /* @__PURE__ */ new Map());
234
+ paths.set(path.slice(0, path.length - extname(path).length), path);
235
+ }
236
+ const builder = {
237
+ resolveFlattenPath,
238
+ root
239
+ };
240
+ const ctx = {
299
241
  transformers,
242
+ builder,
243
+ storage,
244
+ storages,
245
+ locale,
300
246
  custom: options.context,
301
247
  options
302
248
  };
303
- const { storage, locale } = ctx;
304
- for (const file of storage.getFiles()) {
305
- const content = storage.read(file);
306
- const flattenPath = file.substring(0, file.length - extname(file).length);
307
- flattenPathToFullPath.set(flattenPath + "." + content.format, file);
308
- }
309
249
  function resolveFlattenPath(name, format) {
310
- return flattenPathToFullPath.get(name + "." + format) ?? name;
250
+ return flattenPaths.get(format)?.get(name) ?? name;
251
+ }
252
+ function createId(localId = `_${nextId++}`) {
253
+ if (locale) localId = `${locale}:${localId}`;
254
+ return idPrefix ? `${idPrefix}:${localId}` : localId;
311
255
  }
312
256
  /**
313
- * try to register as the owner of `node`.
314
- *
315
- * when a node is referenced by multiple folders, this determines which folder they should belong to.
316
- *
317
- * @returns whether the owner owns the node.
257
+ * claim a node for `folder`, a node referenced by multiple folders belongs to the highest priority:
258
+ * `pagesIndex` (3) > path & extract (2) > rest & default index (0), the first one wins a tie.
318
259
  */
319
- function own(ownerPath, node, priority) {
320
- if (node[SymbolUnfinished]) return false;
321
- const existing = node[SymbolOwner];
322
- if (!existing) {
323
- node[SymbolOwner] = {
324
- owner: ownerPath,
325
- priority
326
- };
327
- return true;
328
- }
329
- if (existing.owner === ownerPath) {
330
- existing.priority = Math.max(existing.priority, priority);
331
- return true;
332
- }
333
- if (existing.priority >= priority) return false;
334
- const folder = pathToNode.get(existing.owner);
335
- if (folder && folder.type === "folder") {
336
- if (folder.index === node) delete folder.index;
260
+ function own(folder, entry, priority) {
261
+ if (!entry.done) return false;
262
+ const prev = entry.owner;
263
+ if (prev && prev !== folder) {
264
+ if (entry.priority >= priority) return false;
265
+ if (prev.node.index === entry.node) delete prev.node.index;
337
266
  else {
338
- const idx = folder.children.indexOf(node);
339
- if (idx !== -1) folder.children.splice(idx, 1);
267
+ const idx = prev.node.children.indexOf(entry.node);
268
+ if (idx !== -1) prev.node.children.splice(idx, 1);
340
269
  }
341
270
  }
342
- existing.owner = ownerPath;
343
- existing.priority = priority;
271
+ entry.owner = folder;
272
+ entry.priority = Math.max(entry.priority, priority);
344
273
  return true;
345
274
  }
346
- function transferOwner(ownerPath, node) {
347
- const existing = node[SymbolOwner];
348
- if (existing) existing.owner = ownerPath;
349
- }
350
- function generateId(localId = `_${_nextId++}`) {
351
- let id = localId;
352
- if (locale) id = `${locale}:${id}`;
353
- if (idPrefix) id = `${idPrefix}:${id}`;
354
- return id;
355
- }
356
- function buildPaths(paths, filter, reversed = false) {
357
- const nodes = [];
358
- let indexNode;
359
- for (const path of paths) {
360
- if (filter && !filter(path)) continue;
361
- const fileNode = buildFile(path);
362
- if (fileNode) {
363
- nodes.push(fileNode);
364
- if (!indexNode && basename(path, extname(path)) === "index") indexNode = fileNode;
365
- continue;
366
- }
367
- const dirNode = buildFolder(path);
368
- if (dirNode) nodes.push(dirNode);
369
- }
370
- const factor = reversed ? -1 : 1;
371
- const useName = sortBy === "name";
372
- return nodes.sort((a, b) => {
373
- if (a === indexNode) return -100;
374
- if (b === indexNode) return 100;
375
- const aT = useName && a[SymbolName] || (a.type === "folder" ? a.$ref.folder : a.$ref);
376
- const bT = useName && b[SymbolName] || (b.type === "folder" ? b.$ref.folder : b.$ref);
377
- const aK = a.type === "folder" ? 10 : 0;
378
- const bK = b.type === "folder" ? 10 : 0;
379
- return factor * (aT.localeCompare(bT, sortLocales, sortOptions) + (aK - bK));
380
- });
381
- }
382
- function resolveLink(item) {
383
- const match = link.exec(item);
384
- if (!match?.groups) return;
385
- const { icon, url, name, external } = match.groups;
275
+ function buildFile(path) {
276
+ const cached = entries.get(path);
277
+ if (cached) return cached;
278
+ const file = storage.read(path);
279
+ if (file?.format !== "page") return;
280
+ const { title, description, icon } = file.data;
386
281
  let node = {
387
- $id: generateId(),
282
+ $id: createId(path),
388
283
  type: "page",
284
+ name: title ?? pathToName(basename(path, extname(path))),
285
+ description,
389
286
  icon,
390
- name,
391
- url,
392
- external: external ? true : void 0
287
+ url: options.url(file.slugs, locale),
288
+ $ref: path
393
289
  };
394
- for (const transformer of transformers) {
395
- if (!transformer.file) continue;
396
- node = transformer.file.call(ctx, node);
397
- }
398
- return node;
399
- }
400
- function resolveSeparator(item) {
401
- const match = separator.exec(item);
402
- if (!match?.groups) return;
403
- let node = {
404
- $id: generateId(),
405
- type: "separator",
406
- icon: match.groups.icon,
407
- name: match.groups.name
290
+ for (const t of transformers) if (t.file) node = t.file.call(ctx, node, path);
291
+ const entry = {
292
+ node,
293
+ path,
294
+ title,
295
+ priority: 0,
296
+ done: true
408
297
  };
409
- for (const transformer of transformers) {
410
- if (!transformer.separator) continue;
411
- node = transformer.separator.call(ctx, node);
412
- }
413
- return node;
298
+ entries.set(path, entry);
299
+ entryOf.set(node, entry);
300
+ usedPages++;
301
+ return entry;
414
302
  }
415
- function resolveFolderItem(folderPath, item, outputArray, excludedPaths) {
416
- if (item === rest || item === restReversed) {
417
- outputArray.push(item);
418
- return;
419
- }
420
- const separator = resolveSeparator(item);
421
- if (separator) {
422
- outputArray.push(separator);
423
- return;
424
- }
425
- const link = resolveLink(item);
426
- if (link) {
427
- outputArray.push(link);
428
- return;
429
- }
430
- if (item.startsWith(excludePrefix)) {
431
- const path = joinPath(folderPath, item.slice(1));
432
- excludedPaths.add(path);
433
- excludedPaths.add(resolveFlattenPath(path, "page"));
434
- return;
435
- }
436
- if (item.startsWith(extractPrefix)) {
437
- const path = joinPath(folderPath, item.slice(3));
438
- const node = buildFolder(path);
439
- if (!node) return;
440
- const children = node.index ? [node.index, ...node.children] : node.children;
441
- if (own(folderPath, node, 2)) {
442
- for (const child of children) {
443
- transferOwner(folderPath, child);
444
- outputArray.push(child);
445
- }
446
- excludedPaths.add(path);
447
- } else for (const child of children) if (own(folderPath, child, 2)) outputArray.push(child);
448
- return;
449
- }
450
- let path = joinPath(folderPath, item);
451
- let node = buildFolder(path);
452
- if (!node) {
453
- path = resolveFlattenPath(path, "page");
454
- node = buildFile(path);
455
- }
456
- if (!node || !own(folderPath, node, 2)) return;
457
- outputArray.push(node);
458
- excludedPaths.add(path);
459
- }
460
- function buildFolder(folderPath, isGlobalRoot = false) {
461
- const cached = pathToNode.get(folderPath);
303
+ function buildFolder(path, isGlobalRoot = false) {
304
+ const cached = entries.get(path);
462
305
  if (cached) return cached;
463
- const files = storage.readDir(folderPath);
306
+ const files = storage.readDir(path);
464
307
  if (!files) return;
465
- let metaPath = resolveFlattenPath(joinPath(folderPath, "meta"), "meta");
466
- let meta = storage.read(metaPath);
467
- if (!meta || meta.format !== "meta") {
468
- meta = void 0;
469
- metaPath = void 0;
470
- }
471
- const metadata = meta?.data ?? {};
472
- const isRoot = metadata.root ?? isGlobalRoot;
473
- let node = {
308
+ let metaPath = resolveFlattenPath(joinPath(path, "meta"), "meta");
309
+ const metaFile = storage.read(metaPath);
310
+ let meta = {};
311
+ if (metaFile?.format === "meta") {
312
+ meta = metaFile.data;
313
+ metaPaths.add(metaPath);
314
+ } else metaPath = void 0;
315
+ const node = {
474
316
  type: "folder",
475
317
  name: null,
476
- root: metadata.root,
477
- defaultOpen: metadata.defaultOpen,
478
- description: metadata.description,
479
- collapsible: metadata.collapsible,
318
+ root: meta.root,
319
+ defaultOpen: meta.defaultOpen,
320
+ description: meta.description,
321
+ collapsible: meta.collapsible,
480
322
  children: [],
481
- $id: generateId(folderPath),
323
+ $id: createId(path),
482
324
  $ref: {
483
- folder: folderPath,
325
+ folder: path,
484
326
  meta: metaPath
485
- },
486
- [SymbolUnfinished]: true
327
+ }
328
+ };
329
+ const self = {
330
+ node,
331
+ path,
332
+ priority: 0,
333
+ done: false
487
334
  };
488
- pathToNode.set(folderPath, node);
335
+ entries.set(path, self);
336
+ const indexName = meta.pagesIndex || (meta.root ?? isGlobalRoot ? void 0 : "index");
337
+ const indexPage = indexName && buildFile(resolveFlattenPath(joinPath(path, indexName), "page"));
489
338
  let indexPath;
490
- if (metadata.pagesIndex) {
491
- const resolvedPath = resolveFlattenPath(joinPath(folderPath, metadata.pagesIndex), "page");
492
- const page = buildFile(resolvedPath);
493
- if (page && own(folderPath, page, 3)) {
494
- indexPath = resolvedPath;
495
- node.index = page;
496
- } else node.index = resolveLink(metadata.pagesIndex);
497
- } else if (!isRoot) {
498
- const defaultPath = resolveFlattenPath(joinPath(folderPath, "index"), "page");
499
- const page = buildFile(defaultPath);
500
- if (page && own(folderPath, page, 0)) {
501
- indexPath = defaultPath;
502
- node.index = page;
339
+ if (indexPage && own(self, indexPage, meta.pagesIndex ? 3 : 0)) {
340
+ node.index = indexPage.node;
341
+ indexPath = indexPage.path;
342
+ } else if (meta.pagesIndex) node.index = resolveLink(meta.pagesIndex);
343
+ const items = [];
344
+ const excluded = /* @__PURE__ */ new Set();
345
+ for (const item of meta.pages ?? ["..."]) {
346
+ if (item === "..." || item === "z...a") {
347
+ items.push(item);
348
+ continue;
503
349
  }
504
- }
505
- if (metadata.pages) {
506
- const outputArray = [];
507
- const excludedPaths = /* @__PURE__ */ new Set();
508
- for (const item of metadata.pages) resolveFolderItem(folderPath, item, outputArray, excludedPaths);
509
- if (indexPath) {
510
- if (excludedPaths.has(indexPath)) delete node.index;
511
- else excludedPaths.add(indexPath);
350
+ const inline = resolveSeparator(item) ?? resolveLink(item);
351
+ if (inline) {
352
+ items.push(inline);
353
+ continue;
512
354
  }
513
- for (const item of outputArray) {
514
- if (item !== rest && item !== restReversed) {
515
- node.children.push(item);
516
- continue;
355
+ if (item.startsWith("!")) {
356
+ const target = joinPath(path, item.slice(1));
357
+ excluded.add(target).add(resolveFlattenPath(target, "page"));
358
+ continue;
359
+ }
360
+ if (item.startsWith("...")) {
361
+ const folder = buildFolder(joinPath(path, item.slice(3)));
362
+ if (!folder) continue;
363
+ const { index, children } = folder.node;
364
+ const owned = own(self, folder, 2);
365
+ if (owned) excluded.add(folder.path);
366
+ for (const child of index ? [index, ...children] : children) {
367
+ const entry = entryOf.get(child);
368
+ if (entry && owned) entry.owner = self;
369
+ else if (entry && !own(self, entry, 2)) continue;
370
+ items.push(child);
517
371
  }
518
- const resolvedItem = buildPaths(files, (file) => !excludedPaths.has(file), item === restReversed);
519
- for (const child of resolvedItem) if (own(folderPath, child, 0)) node.children.push(child);
372
+ continue;
373
+ }
374
+ const target = joinPath(path, item);
375
+ const entry = buildFolder(target) ?? buildFile(resolveFlattenPath(target, "page"));
376
+ if (entry && own(self, entry, 2)) {
377
+ items.push(entry.node);
378
+ excluded.add(entry.path);
379
+ }
380
+ }
381
+ if (indexPath) {
382
+ if (excluded.has(indexPath)) delete node.index;
383
+ else excluded.add(indexPath);
384
+ }
385
+ for (const item of items) {
386
+ if (item === "..." || item === "z...a") {
387
+ for (const entry of buildRest(files, excluded, item === "z...a")) if (own(self, entry, 0)) node.children.push(entry.node);
388
+ continue;
520
389
  }
521
- } else for (const item of buildPaths(files, indexPath ? (file) => file !== indexPath : void 0)) if (own(folderPath, item, 0)) node.children.push(item);
522
- node.icon = metadata.icon ?? node.index?.icon;
523
- node.name = metadata.title ?? node.index?.name;
524
- node[SymbolName] = metadata.title ?? node.index?.[SymbolName];
390
+ const entry = entryOf.get(item);
391
+ if (!entry || entry.owner === self) node.children.push(item);
392
+ }
393
+ node.icon = meta.icon ?? node.index?.icon;
394
+ node.name = meta.title ?? node.index?.name;
395
+ self.title = meta.title ?? (node.index && entryOf.get(node.index)?.title);
525
396
  if (!node.name) {
526
- const folderName = basename(folderPath);
527
- node.name = pathToName(group.exec(folderName)?.[1] ?? folderName);
397
+ const name = basename(path);
398
+ node.name = pathToName(group.exec(name)?.[1] ?? name);
528
399
  }
529
- for (const transformer of transformers) {
530
- if (!transformer.folder) continue;
531
- node = transformer.folder.call(ctx, node, folderPath, metaPath);
400
+ let result = node;
401
+ for (const t of transformers) if (t.folder) result = t.folder.call(ctx, result, path, metaPath);
402
+ self.node = result;
403
+ self.done = true;
404
+ entryOf.set(result, self);
405
+ return self;
406
+ }
407
+ /** build the remaining items of a folder for `...` */
408
+ function buildRest(paths, excluded, reversed) {
409
+ const out = [];
410
+ let index;
411
+ for (const path of paths) {
412
+ if (excluded.has(path)) continue;
413
+ const file = buildFile(path);
414
+ if (file && !index && basename(path, extname(path)) === "index") index = file;
415
+ const entry = file ?? buildFolder(path);
416
+ if (entry) out.push(entry);
532
417
  }
533
- pathToNode.set(folderPath, node);
534
- delete node[SymbolUnfinished];
535
- return node;
418
+ const factor = reversed ? -1 : 1;
419
+ return out.sort((a, b) => {
420
+ if (a === index) return -1;
421
+ if (b === index) return 1;
422
+ const type = Number(a.node.type === "folder") - Number(b.node.type === "folder");
423
+ return factor * (type || compare(byName && a.title || a.path, byName && b.title || b.path));
424
+ });
536
425
  }
537
- function buildFile(path) {
538
- const cached = pathToNode.get(path);
539
- if (cached) return cached;
540
- const page = storage.read(path);
541
- if (!page || page.format !== "page") return;
542
- const { title, description, icon } = page.data;
543
- let item = {
544
- $id: generateId(path),
426
+ function resolveLink(item) {
427
+ const match = link.exec(item)?.groups;
428
+ if (!match) return;
429
+ let node = {
430
+ $id: createId(),
545
431
  type: "page",
546
- name: title ?? pathToName(basename(path, extname(path))),
547
- description,
548
- icon,
549
- url: getUrl(page.slugs, ctx.locale),
550
- $ref: path,
551
- [SymbolName]: title
432
+ icon: match.icon,
433
+ name: match.name,
434
+ url: match.url,
435
+ external: match.external ? true : void 0
552
436
  };
553
- for (const transformer of transformers) {
554
- if (!transformer.file) continue;
555
- item = transformer.file.call(ctx, item, path);
556
- }
557
- pathToNode.set(path, item);
558
- return item;
437
+ for (const t of transformers) if (t.file) node = t.file.call(ctx, node);
438
+ return node;
559
439
  }
560
- const builder = {
561
- resolveFlattenPath,
562
- root(id = "root", path = "") {
563
- const folder = buildFolder(path, true);
564
- for (const node of pathToNode.values()) {
565
- delete node[SymbolName];
566
- delete node[SymbolOwner];
567
- if (noRef && "$ref" in node) delete node.$ref;
568
- }
569
- let root = {
570
- type: "root",
571
- $ref: folder?.$ref,
572
- $id: generateId(id),
573
- name: folder?.name || "Docs",
574
- description: folder?.description,
575
- children: folder ? folder.children : []
576
- };
577
- for (const transformer of transformers) {
578
- if (!transformer.root) continue;
579
- root = transformer.root.call(ctx, root);
580
- }
581
- return root;
440
+ function resolveSeparator(item) {
441
+ const match = separator.exec(item)?.groups;
442
+ if (!match) return;
443
+ let node = {
444
+ $id: createId(),
445
+ type: "separator",
446
+ icon: match.icon,
447
+ name: match.name
448
+ };
449
+ for (const t of transformers) if (t.separator) node = t.separator.call(ctx, node);
450
+ return node;
451
+ }
452
+ function root(id = "root", path = "") {
453
+ const folder = buildFolder(path, true)?.node;
454
+ if (options.noRef) for (const { node } of entries.values()) delete node.$ref;
455
+ let out = {
456
+ type: "root",
457
+ $ref: folder?.$ref,
458
+ $id: createId(id),
459
+ name: folder?.name || "Docs",
460
+ description: folder?.description,
461
+ children: folder ? folder.children : []
462
+ };
463
+ for (const t of transformers) if (t.root) out = t.root.call(ctx, out);
464
+ if ((options.generateFallback ?? true) && usedPages + metaPaths.size < storage.files.size) {
465
+ const unused = new FileSystem();
466
+ for (const [path, file] of storage.files) if (!entries.has(path) && !metaPaths.has(path)) unused.write(path, file);
467
+ out.fallback = createPageTreeBuilder(unused, {
468
+ ...options,
469
+ idPrefix: idPrefix ? `fallback:${idPrefix}` : "fallback",
470
+ generateFallback: false
471
+ }, locale, storages).root();
582
472
  }
583
- };
473
+ return out;
474
+ }
584
475
  return builder;
585
476
  }
586
477
  /**
@@ -589,17 +480,15 @@ function createPageTreeBuilder(input, options) {
589
480
  * @param name - file name
590
481
  */
591
482
  function pathToName(name) {
592
- const result = [];
593
- for (const c of name) if (result.length === 0) result.push(c.toLocaleUpperCase());
594
- else if (c === "-") result.push(" ");
595
- else result.push(c);
596
- return result.join("");
483
+ const [first = "", ...rest] = name;
484
+ return first.toLocaleUpperCase() + rest.join("").replaceAll("-", " ");
597
485
  }
598
486
  //#endregion
599
487
  //#region src/source/loader.ts
600
488
  /** decoded, generated slugs are URI encoded but some routers pass them decoded */
601
489
  function slugsKey(slugs) {
602
490
  const key = slugs.join("/");
491
+ if (!key.includes("%")) return key;
603
492
  try {
604
493
  return decodeURI(key);
605
494
  } catch {
@@ -607,18 +496,20 @@ function slugsKey(slugs) {
607
496
  }
608
497
  }
609
498
  function createPageIndexer({ url }) {
610
- const pages = /* @__PURE__ */ new Map();
611
- const pathToMeta = /* @__PURE__ */ new Map();
612
- const pathToPage = /* @__PURE__ */ new Map();
613
- const urlToPage = /* @__PURE__ */ new Map();
499
+ /** locale (empty without i18n) -> index */
500
+ const indexes = /* @__PURE__ */ new Map();
614
501
  return {
615
502
  scan(storage, lang) {
616
- for (const filePath of storage.getFiles()) {
617
- const item = storage.read(filePath);
618
- const prefix = lang ? `${lang}.` : ".";
619
- const path = prefix + filePath;
503
+ const index = {
504
+ pages: /* @__PURE__ */ new Map(),
505
+ pathToPage: /* @__PURE__ */ new Map(),
506
+ pathToMeta: /* @__PURE__ */ new Map(),
507
+ urlToPage: /* @__PURE__ */ new Map()
508
+ };
509
+ indexes.set(lang ?? "", index);
510
+ for (const [path, item] of storage.files) {
620
511
  if (item.format === "meta") {
621
- pathToMeta.set(path, {
512
+ index.pathToMeta.set(path, {
622
513
  type: item.type,
623
514
  path: item.path,
624
515
  absolutePath: item.absolutePath,
@@ -635,41 +526,43 @@ function createPageIndexer({ url }) {
635
526
  data: item.data,
636
527
  locale: lang
637
528
  };
638
- pathToPage.set(path, page);
639
- pages.set(prefix + slugsKey(page.slugs), page);
640
- urlToPage.set(prefix + page.url, page);
529
+ index.pathToPage.set(path, page);
530
+ index.pages.set(slugsKey(page.slugs), page);
531
+ index.urlToPage.set(page.url, page);
641
532
  }
642
533
  },
643
534
  getPage(path, lang = "") {
644
- return pathToPage.get(`${lang}.${path}`);
535
+ return indexes.get(lang)?.pathToPage.get(path);
645
536
  },
646
537
  getPageByUrl(url, lang = "") {
647
- return urlToPage.get(`${lang}.${url}`);
538
+ return indexes.get(lang)?.urlToPage.get(url);
648
539
  },
649
540
  getMeta(path, lang = "") {
650
- return pathToMeta.get(`${lang}.${path}`);
541
+ return indexes.get(lang)?.pathToMeta.get(path);
651
542
  },
652
543
  getPageBySlugs(slugs, lang = "") {
653
- return pages.get(`${lang}.${slugsKey(slugs)}`);
544
+ return indexes.get(lang)?.pages.get(slugsKey(slugs));
654
545
  },
655
546
  /** do not filter by language if `lang` is not specified */
656
547
  getPages(lang) {
657
548
  const out = [];
658
- for (const [key, value] of pages.entries()) if (lang === void 0 || key.startsWith(`${lang}.`)) out.push(value);
549
+ for (const [key, index] of indexes) {
550
+ if (lang !== void 0 && key !== lang) continue;
551
+ for (const page of index.pages.values()) out.push(page);
552
+ }
659
553
  return out;
660
554
  }
661
555
  };
662
556
  }
663
557
  function createGetUrl(baseUrl, i18n) {
664
- const baseSlugs = baseUrl.split("/");
558
+ const hideLocale = i18n?.hideLocale ?? "never";
559
+ let base = "";
560
+ for (const seg of baseUrl.split("/")) if (seg.length > 0) base += `/${seg}`;
665
561
  return (slugs, locale) => {
666
- const hideLocale = i18n?.hideLocale ?? "never";
667
- let urlLocale;
668
- if (hideLocale === "never") urlLocale = locale;
669
- else if (hideLocale === "default-locale" && locale !== i18n?.defaultLanguage) urlLocale = locale;
670
- const paths = [...baseSlugs, ...slugs];
671
- if (urlLocale) paths.unshift(urlLocale);
672
- return `/${paths.filter((v) => v.length > 0).join("/")}`;
562
+ const showLocale = hideLocale === "never" || hideLocale === "default-locale" && locale !== i18n?.defaultLanguage;
563
+ let url = locale && showLocale ? `/${locale}${base}` : base;
564
+ for (const slug of slugs) if (slug.length > 0) url += `/${slug}`;
565
+ return url || "/";
673
566
  };
674
567
  }
675
568
  function loader(...args) {
@@ -692,11 +585,9 @@ function loader(...args) {
692
585
  transformers
693
586
  };
694
587
  if (storage instanceof FileSystem) return pageTrees = createPageTreeBuilder(storage, options).root();
695
- else {
696
- const out = {};
697
- for (const locale in storage) out[locale] = createPageTreeBuilder([locale, storage], options).root();
698
- return pageTrees = out;
699
- }
588
+ const out = {};
589
+ for (const locale in storage) out[locale] = createPageTreeBuilder(storage[locale], options, locale, storage).root();
590
+ return pageTrees = out;
700
591
  }
701
592
  const out = {
702
593
  _i18n: i18n,
@@ -772,12 +663,17 @@ function loader(...args) {
772
663
  }
773
664
  return getPageTrees();
774
665
  },
775
- generateParams(slug, lang) {
776
- if (i18n) return this.getLanguages().flatMap((entry) => entry.pages.map((page) => ({
777
- [slug ?? "slug"]: page.slugs,
778
- [lang ?? "lang"]: entry.language
779
- })));
780
- return this.getPages().map((page) => ({ [slug ?? "slug"]: page.slugs }));
666
+ generateParams(slug = "slug", lang = "lang") {
667
+ const params = [];
668
+ if (!i18n) {
669
+ for (const page of this.getPages()) params.push({ [slug]: page.slugs });
670
+ return params;
671
+ }
672
+ for (const entry of this.getLanguages()) for (const page of entry.pages) params.push({
673
+ [slug]: page.slugs,
674
+ [lang]: entry.language
675
+ });
676
+ return params;
781
677
  },
782
678
  async serializePageTree(tree) {
783
679
  const { renderToString } = await import("react-dom/server.edge");
@@ -1,2 +1,2 @@
1
- import { t as dynamicLoader } from "../dynamic-Ce3F5rPi.js";
1
+ import { t as dynamicLoader } from "../dynamic-CsxXgQ9d.js";
2
2
  export { dynamicLoader };
@@ -1,5 +1,5 @@
1
1
  import { o as path_exports } from "../path-m9Srg2Jq.js";
2
- import { a as update, i as multiple, n as createGetUrl, o as FileSystem, r as loader, t as dynamicLoader } from "../dynamic-Ce3F5rPi.js";
2
+ import { a as update, i as multiple, n as createGetUrl, o as FileSystem, r as loader, t as dynamicLoader } from "../dynamic-CsxXgQ9d.js";
3
3
  import { getSlugs } from "./plugins/slugs.js";
4
4
  import { llms } from "./llms.js";
5
5
  export { FileSystem, path_exports as PathUtils, createGetUrl, dynamicLoader, getSlugs, llms, loader, multiple, update };
@@ -16,9 +16,8 @@ function slugsPlugin(optsOrFn = {}) {
16
16
  transformStorage({ storage }) {
17
17
  const indexFiles = [];
18
18
  const taken = /* @__PURE__ */ new Set();
19
- for (const path of storage.getFiles()) {
20
- const file = storage.read(path);
21
- if (!file || file.format !== "page") continue;
19
+ for (const [path, file] of storage.files) {
20
+ if (file.format !== "page") continue;
22
21
  if (file.slugs) {
23
22
  if (baseSlugs.length > 0 && !prepended.has(file)) {
24
23
  file.slugs = [...baseSlugs, ...file.slugs];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "16.17.1",
3
+ "version": "16.17.2",
4
4
  "description": "The React.js library for building a documentation website",
5
5
  "keywords": [
6
6
  "Docs",