fumadocs-core 15.7.12 → 15.8.0

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createContentHighlighter
3
- } from "./chunk-CNWEGOUF.js";
3
+ } from "./chunk-OTD7MV33.js";
4
4
  import "./chunk-JSBRDJBE.js";
5
5
 
6
6
  // src/search/client/algolia.ts
@@ -13,6 +13,7 @@ function groupResults(hits) {
13
13
  grouped.push({
14
14
  id: hit.url,
15
15
  type: "page",
16
+ breadcrumbs: hit.breadcrumbs,
16
17
  url: hit.url,
17
18
  content: hit.title
18
19
  });
@@ -7,18 +7,20 @@ interface BreadcrumbItem {
7
7
  }
8
8
  interface BreadcrumbOptions {
9
9
  /**
10
- * Include the root itself in the breadcrumb items array.
11
- * Specify the url by passing an object instead
10
+ * Include the root folders in the breadcrumb items array.
12
11
  *
13
12
  * @defaultValue false
14
13
  */
15
14
  includeRoot?: boolean | {
15
+ /**
16
+ * Specify the url of root
17
+ */
16
18
  url: string;
17
19
  };
18
20
  /**
19
21
  * Include the page itself in the breadcrumb items array
20
22
  *
21
- * @defaultValue true
23
+ * @defaultValue false
22
24
  */
23
25
  includePage?: boolean;
24
26
  /**
@@ -37,7 +39,7 @@ declare function getBreadcrumbItemsFromPath(tree: Root, path: Node[], options: B
37
39
  * - When the page doesn't exist, return null
38
40
  *
39
41
  * @returns The path to the target node from root
40
- * @internal
42
+ * @internal Don't use this on your own
41
43
  */
42
44
  declare function searchPath(nodes: Node[], url: string): Node[] | null;
43
45
 
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  normalizeUrl
3
3
  } from "./chunk-PFNP6PEB.js";
4
+ import {
5
+ findPath
6
+ } from "./chunk-HV3YIUWE.js";
4
7
  import "./chunk-JSBRDJBE.js";
5
8
 
6
9
  // src/breadcrumb.tsx
@@ -19,33 +22,42 @@ function getBreadcrumbItems(url, tree, options = {}) {
19
22
  );
20
23
  }
21
24
  function getBreadcrumbItemsFromPath(tree, path, options) {
22
- const { includePage = true, includeSeparator = false, includeRoot } = options;
25
+ const {
26
+ includePage = false,
27
+ includeSeparator = false,
28
+ includeRoot = false
29
+ } = options;
23
30
  let items = [];
24
- path.forEach((item, i) => {
25
- if (item.type === "separator" && item.name && includeSeparator) {
26
- items.push({
27
- name: item.name
28
- });
29
- }
30
- if (item.type === "folder") {
31
- const next = path.at(i + 1);
32
- if (next && item.index === next) return;
33
- if (item.root) {
34
- items = [];
35
- return;
36
- }
37
- items.push({
38
- name: item.name,
39
- url: item.index?.url
40
- });
41
- }
42
- if (item.type === "page" && includePage) {
43
- items.push({
44
- name: item.name,
45
- url: item.url
46
- });
31
+ for (let i = 0; i < path.length; i++) {
32
+ const item = path[i];
33
+ switch (item.type) {
34
+ case "page":
35
+ if (includePage)
36
+ items.push({
37
+ name: item.name,
38
+ url: item.url
39
+ });
40
+ break;
41
+ case "folder":
42
+ if (item.root && !includeRoot) {
43
+ items = [];
44
+ break;
45
+ }
46
+ if (i === path.length - 1 || item.index !== path[i + 1]) {
47
+ items.push({
48
+ name: item.name,
49
+ url: item.index?.url
50
+ });
51
+ }
52
+ break;
53
+ case "separator":
54
+ if (item.name && includeSeparator)
55
+ items.push({
56
+ name: item.name
57
+ });
58
+ break;
47
59
  }
48
- });
60
+ }
49
61
  if (includeRoot) {
50
62
  items.unshift({
51
63
  name: tree.name,
@@ -55,34 +67,11 @@ function getBreadcrumbItemsFromPath(tree, path, options) {
55
67
  return items;
56
68
  }
57
69
  function searchPath(nodes, url) {
58
- const items = [];
59
- url = normalizeUrl(url);
60
- function run(nodes2) {
61
- let separator;
62
- for (const node of nodes2) {
63
- if (node.type === "separator") separator = node;
64
- if (node.type === "folder") {
65
- if (node.index?.url === url) {
66
- if (separator) items.push(separator);
67
- items.push(node, node.index);
68
- return true;
69
- }
70
- if (run(node.children)) {
71
- items.unshift(node);
72
- if (separator) items.unshift(separator);
73
- return true;
74
- }
75
- }
76
- if (node.type === "page" && node.url === url) {
77
- if (separator) items.push(separator);
78
- items.push(node);
79
- return true;
80
- }
81
- }
82
- return false;
83
- }
84
- if (run(nodes)) return items;
85
- return null;
70
+ const normalizedUrl = normalizeUrl(url);
71
+ return findPath(
72
+ nodes,
73
+ (node) => node.type === "page" && node.url === normalizedUrl
74
+ );
86
75
  }
87
76
  export {
88
77
  getBreadcrumbItems,
@@ -0,0 +1,108 @@
1
+ // src/utils/page-tree.tsx
2
+ function flattenTree(nodes) {
3
+ const out = [];
4
+ for (const node of nodes) {
5
+ if (node.type === "folder") {
6
+ if (node.index) out.push(node.index);
7
+ out.push(...flattenTree(node.children));
8
+ } else if (node.type === "page") {
9
+ out.push(node);
10
+ }
11
+ }
12
+ return out;
13
+ }
14
+ function findNeighbour(tree, url, options) {
15
+ const { separateRoot = true } = options ?? {};
16
+ const roots = separateRoot ? getPageTreeRoots(tree) : [tree];
17
+ if (tree.fallback) roots.push(tree.fallback);
18
+ for (const root of roots) {
19
+ const list = flattenTree(root.children);
20
+ const idx = list.findIndex((item) => item.url === url);
21
+ if (idx === -1) continue;
22
+ return {
23
+ previous: list[idx - 1],
24
+ next: list[idx + 1]
25
+ };
26
+ }
27
+ return {};
28
+ }
29
+ function getPageTreeRoots(pageTree) {
30
+ const result = pageTree.children.flatMap((child) => {
31
+ if (child.type !== "folder") return [];
32
+ const roots = getPageTreeRoots(child);
33
+ if (child.root) roots.push(child);
34
+ return roots;
35
+ });
36
+ if (!("type" in pageTree)) result.push(pageTree);
37
+ return result;
38
+ }
39
+ function separatePageTree(pageTree) {
40
+ return pageTree.children.flatMap((child) => {
41
+ if (child.type !== "folder") return [];
42
+ return {
43
+ name: child.name,
44
+ url: child.index?.url,
45
+ children: child.children
46
+ };
47
+ });
48
+ }
49
+ function getPageTreePeers(tree, url) {
50
+ const parent = findParentFromTree(tree, url);
51
+ if (!parent) return [];
52
+ return parent.children.filter(
53
+ (item) => item.type === "page" && item.url !== url
54
+ );
55
+ }
56
+ function findParentFromTree(node, url) {
57
+ if ("index" in node && node.index?.url === url) {
58
+ return node;
59
+ }
60
+ for (const child of node.children) {
61
+ if (child.type === "folder") {
62
+ const parent = findParentFromTree(child, url);
63
+ if (parent) return parent;
64
+ }
65
+ if (child.type === "page" && child.url === url) {
66
+ return node;
67
+ }
68
+ }
69
+ if ("fallback" in node && node.fallback) {
70
+ return findParentFromTree(node.fallback, url);
71
+ }
72
+ }
73
+ function findPath(nodes, matcher, options = {}) {
74
+ const { includeSeparator = true } = options;
75
+ function run(nodes2) {
76
+ let separator;
77
+ for (const node of nodes2) {
78
+ if (matcher(node)) {
79
+ const items = [];
80
+ if (separator) items.push(separator);
81
+ items.push(node);
82
+ return items;
83
+ }
84
+ if (node.type === "separator" && includeSeparator) {
85
+ separator = node;
86
+ continue;
87
+ }
88
+ if (node.type === "folder") {
89
+ const items = node.index && matcher(node.index) ? [node.index] : run(node.children);
90
+ if (items) {
91
+ items.unshift(node);
92
+ if (separator) items.unshift(separator);
93
+ return items;
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return run(nodes) ?? null;
99
+ }
100
+
101
+ export {
102
+ flattenTree,
103
+ findNeighbour,
104
+ getPageTreeRoots,
105
+ separatePageTree,
106
+ getPageTreePeers,
107
+ findPath
108
+ };
@@ -1,4 +1,4 @@
1
- // src/search/shared.ts
1
+ // src/search/index.ts
2
2
  function escapeRegExp(input) {
3
3
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
4
  }
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-ZMWYLUDP.js";
4
4
  import {
5
5
  createContentHighlighter
6
- } from "./chunk-CNWEGOUF.js";
6
+ } from "./chunk-OTD7MV33.js";
7
7
 
8
8
  // src/search/orama/search/simple.ts
9
9
  import { search } from "@orama/orama";
@@ -21,6 +21,7 @@ async function searchSimple(db, query, params = {}) {
21
21
  return result.hits.map((hit) => ({
22
22
  type: "page",
23
23
  content: hit.document.title,
24
+ breadcrumbs: hit.document.breadcrumbs,
24
25
  contentWithHighlights: highlighter.highlight(hit.document.title),
25
26
  id: hit.document.url,
26
27
  url: hit.document.url
@@ -67,6 +68,7 @@ async function searchAdvanced(db, query, tag = [], {
67
68
  id: pageId,
68
69
  type: "page",
69
70
  content: page.content,
71
+ breadcrumbs: page.breadcrumbs,
70
72
  contentWithHighlights: highlighter.highlight(page.content),
71
73
  url: page.url
72
74
  });
@@ -75,6 +77,7 @@ async function searchAdvanced(db, query, tag = [], {
75
77
  list.push({
76
78
  id: hit.document.id.toString(),
77
79
  content: hit.document.content,
80
+ breadcrumbs: hit.document.breadcrumbs,
78
81
  contentWithHighlights: highlighter.highlight(hit.document.content),
79
82
  type: hit.document.type,
80
83
  url: hit.document.url
@@ -185,7 +185,7 @@ interface CodeBlockTabsOptions {
185
185
  }
186
186
  declare function generateCodeBlockTabs({ persist, defaultValue, triggers, tabs, ...options }: CodeBlockTabsOptions): MdxJsxFlowElement;
187
187
  interface CodeBlockAttributes<Name extends string = string> {
188
- attributes: Partial<Record<Name, string>>;
188
+ attributes: Partial<Record<Name, string | null>>;
189
189
  rest: string;
190
190
  }
191
191
  /**
@@ -235,11 +235,11 @@ function generateCodeBlockTabs({
235
235
  }
236
236
  function parseCodeBlockAttributes(meta, allowedNames) {
237
237
  let str = meta;
238
- const StringRegex = /(?<=^|\s)(?<name>\w+)=(?:"([^"]*)"|'([^']*)')/g;
238
+ const StringRegex = /(?<=^|\s)(?<name>\w+)(?:=(?:"([^"]*)"|'([^']*)'))?/g;
239
239
  const attributes = {};
240
240
  str = str.replaceAll(StringRegex, (match, name, value_1, value_2) => {
241
241
  if (allowedNames && !allowedNames.includes(name)) return match;
242
- attributes[name] = value_1 ?? value_2;
242
+ attributes[name] = value_1 ?? value_2 ?? null;
243
243
  return "";
244
244
  });
245
245
  return {
@@ -270,7 +270,7 @@ var rehypeCodeDefaultOptions = {
270
270
  })
271
271
  ],
272
272
  parseMetaString(meta) {
273
- const parsed = parseCodeBlockAttributes(meta);
273
+ const parsed = parseCodeBlockAttributes(meta, ["title", "tab"]);
274
274
  const data = parsed.attributes;
275
275
  parsed.rest = parseLineNumber(parsed.rest, data);
276
276
  data.__parsed_raw = parsed.rest;
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-ZMWYLUDP.js";
4
4
  import {
5
5
  createContentHighlighter
6
- } from "./chunk-CNWEGOUF.js";
6
+ } from "./chunk-OTD7MV33.js";
7
7
  import "./chunk-JSBRDJBE.js";
8
8
 
9
9
  // src/search/client/orama-cloud.ts
@@ -69,6 +69,7 @@ async function searchDocs(query, options) {
69
69
  id: doc.page_id,
70
70
  type: "page",
71
71
  content: doc.title,
72
+ breadcrumbs: doc.breadcrumbs,
72
73
  contentWithHighlights: highlighter.highlight(doc.title),
73
74
  url: doc.url
74
75
  });
@@ -11,6 +11,7 @@ interface DocumentRecord {
11
11
  _id: string;
12
12
  title: string;
13
13
  description?: string;
14
+ breadcrumbs?: string[];
14
15
  /**
15
16
  * URL to the page
16
17
  */
@@ -67,6 +68,7 @@ interface BaseIndex {
67
68
  * Heading (anchor) id
68
69
  */
69
70
  section_id?: string;
71
+ breadcrumbs?: string[];
70
72
  content: string;
71
73
  }
72
74
 
@@ -16,7 +16,8 @@ async function setIndexSettings(client, indexName) {
16
16
  "section",
17
17
  "content",
18
18
  "url",
19
- "section_id"
19
+ "section_id",
20
+ "breadcrumbs"
20
21
  ],
21
22
  searchableAttributes: ["title", "section", "content"],
22
23
  attributesToSnippet: [],
@@ -31,6 +32,7 @@ function toIndex(page) {
31
32
  function createIndex(section, sectionId, content) {
32
33
  return {
33
34
  objectID: `${page._id}-${(id++).toString()}`,
35
+ breadcrumbs: page.breadcrumbs,
34
36
  title: page.title,
35
37
  url: page.url,
36
38
  page_id: page._id,
@@ -4,11 +4,12 @@ import { BaseIndex } from './algolia.js';
4
4
  import { LiteClient, SearchResponse } from 'algoliasearch/lite';
5
5
  import { OramaClient, ClientSearchParams } from '@oramacloud/client';
6
6
  import Mixedbread from '@mixedbread/sdk';
7
- import { S as SortedResult } from '../shared-ORgOfXFw.js';
7
+ import { SortedResult } from './index.js';
8
8
  import 'mdast';
9
9
  import 'unified';
10
10
  import 'mdast-util-mdx-jsx';
11
11
  import 'algoliasearch';
12
+ import 'react';
12
13
 
13
14
  interface FetchOptions {
14
15
  /**
@@ -69,15 +69,15 @@ function useDocsSearch(clientOptions, _locale, _tag, _delayMs = 100, _allowEmpty
69
69
  return fetchDocs(debouncedValue, client);
70
70
  }
71
71
  if (client.type === "algolia") {
72
- const { searchDocs } = await import("../algolia-KPRGMSJO.js");
72
+ const { searchDocs } = await import("../algolia-Z232AL35.js");
73
73
  return searchDocs(debouncedValue, client);
74
74
  }
75
75
  if (client.type === "orama-cloud") {
76
- const { searchDocs } = await import("../orama-cloud-TXCWJTK3.js");
76
+ const { searchDocs } = await import("../orama-cloud-GMFFJOIB.js");
77
77
  return searchDocs(debouncedValue, client);
78
78
  }
79
79
  if (client.type === "static") {
80
- const { search: search2 } = await import("../static-PIZYNE45.js");
80
+ const { search: search2 } = await import("../static-UVSWNGTY.js");
81
81
  return search2(debouncedValue, client);
82
82
  }
83
83
  if (client.type === "mixedbread") {
@@ -0,0 +1,26 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ interface SortedResult<Content = string> {
4
+ id: string;
5
+ url: string;
6
+ type: 'page' | 'heading' | 'text';
7
+ content: Content;
8
+ /**
9
+ * breadcrumbs to be displayed on UI
10
+ */
11
+ breadcrumbs?: Content[];
12
+ contentWithHighlights?: HighlightedText<Content>[];
13
+ }
14
+ type ReactSortedResult = SortedResult<ReactNode>;
15
+ interface HighlightedText<Content = string> {
16
+ type: 'text';
17
+ content: Content;
18
+ styles?: {
19
+ highlight?: boolean;
20
+ };
21
+ }
22
+ declare function createContentHighlighter(query: string | RegExp): {
23
+ highlight(content: string): HighlightedText[];
24
+ };
25
+
26
+ export { type HighlightedText, type ReactSortedResult, type SortedResult, createContentHighlighter };
@@ -0,0 +1,7 @@
1
+ import {
2
+ createContentHighlighter
3
+ } from "../chunk-OTD7MV33.js";
4
+ import "../chunk-JSBRDJBE.js";
5
+ export {
6
+ createContentHighlighter
7
+ };
@@ -50,6 +50,7 @@ interface OramaDocument {
50
50
  * Data to be added to each section index
51
51
  */
52
52
  extra_data?: object;
53
+ breadcrumbs?: string[];
53
54
  }
54
55
  interface OramaIndex {
55
56
  id: string;
@@ -64,6 +65,7 @@ interface OramaIndex {
64
65
  * Heading content
65
66
  */
66
67
  section?: string;
68
+ breadcrumbs?: string[];
67
69
  /**
68
70
  * Heading (anchor) id
69
71
  */
@@ -30,6 +30,7 @@ function toIndex(page) {
30
30
  section,
31
31
  section_id: sectionId,
32
32
  content,
33
+ breadcrumbs: page.breadcrumbs,
33
34
  ...page.extra_data
34
35
  };
35
36
  }
@@ -1,7 +1,7 @@
1
1
  import { TypedDocument, Orama, Language, RawData, create, SearchParams } from '@orama/orama';
2
2
  import { S as StructuredData } from '../remark-structure-DkCXCzpD.js';
3
- import { S as SortedResult } from '../shared-ORgOfXFw.js';
4
- export { H as HighlightedText, c as createContentHighlighter } from '../shared-ORgOfXFw.js';
3
+ import { SortedResult } from './index.js';
4
+ export { HighlightedText, ReactSortedResult, createContentHighlighter } from './index.js';
5
5
  import { I18nConfig } from '../i18n/index.js';
6
6
  import { LoaderOutput, LoaderConfig, InferPageType } from '../source/index.js';
7
7
  import 'mdast';
@@ -10,23 +10,25 @@ import 'mdast-util-mdx-jsx';
10
10
  import 'react';
11
11
  import '../definitions-Q95-psoo.js';
12
12
 
13
+ type SimpleDocument = TypedDocument<Orama<typeof simpleSchema>>;
14
+ declare const simpleSchema: {
15
+ readonly url: "string";
16
+ readonly title: "string";
17
+ readonly breadcrumbs: "string[]";
18
+ readonly description: "string";
19
+ readonly content: "string";
20
+ readonly keywords: "string";
21
+ };
13
22
  type AdvancedDocument = TypedDocument<Orama<typeof advancedSchema>>;
14
23
  declare const advancedSchema: {
15
24
  readonly content: "string";
16
25
  readonly page_id: "string";
17
26
  readonly type: "string";
27
+ readonly breadcrumbs: "string[]";
18
28
  readonly tags: "enum[]";
19
29
  readonly url: "string";
20
30
  readonly embeddings: "vector[512]";
21
31
  };
22
- type SimpleDocument = TypedDocument<Orama<typeof simpleSchema>>;
23
- declare const simpleSchema: {
24
- readonly url: "string";
25
- readonly title: "string";
26
- readonly description: "string";
27
- readonly content: "string";
28
- readonly keywords: "string";
29
- };
30
32
 
31
33
  type Awaitable<T> = T | Promise<T>;
32
34
  interface Options<S extends LoaderOutput<LoaderConfig>> extends Omit<AdvancedOptions, 'indexes'> {
@@ -112,6 +114,7 @@ declare function createSearchAPI<T extends SearchType>(type: T, options: T exten
112
114
  interface Index {
113
115
  title: string;
114
116
  description?: string;
117
+ breadcrumbs?: string[];
115
118
  content: string;
116
119
  url: string;
117
120
  keywords?: string;
@@ -125,6 +128,7 @@ interface AdvancedIndex {
125
128
  * @deprecated No longer used
126
129
  */
127
130
  keywords?: string;
131
+ breadcrumbs?: string[];
128
132
  /**
129
133
  * Required if tag filter is enabled
130
134
  */
@@ -1,15 +1,18 @@
1
+ import {
2
+ basename,
3
+ extname
4
+ } from "../chunk-BDG7Y4PS.js";
1
5
  import {
2
6
  searchAdvanced,
3
7
  searchSimple
4
- } from "../chunk-XMAVUWLD.js";
8
+ } from "../chunk-XOFXGHS4.js";
5
9
  import "../chunk-ZMWYLUDP.js";
6
10
  import {
7
11
  createContentHighlighter
8
- } from "../chunk-CNWEGOUF.js";
12
+ } from "../chunk-OTD7MV33.js";
9
13
  import {
10
- basename,
11
- extname
12
- } from "../chunk-BDG7Y4PS.js";
14
+ findPath
15
+ } from "../chunk-HV3YIUWE.js";
13
16
  import "../chunk-JSBRDJBE.js";
14
17
 
15
18
  // src/search/server.ts
@@ -45,10 +48,19 @@ import {
45
48
  create,
46
49
  insertMultiple
47
50
  } from "@orama/orama";
51
+ var simpleSchema = {
52
+ url: "string",
53
+ title: "string",
54
+ breadcrumbs: "string[]",
55
+ description: "string",
56
+ content: "string",
57
+ keywords: "string"
58
+ };
48
59
  var advancedSchema = {
49
60
  content: "string",
50
61
  page_id: "string",
51
62
  type: "string",
63
+ breadcrumbs: "string[]",
52
64
  tags: "enum[]",
53
65
  url: "string",
54
66
  embeddings: "vector[512]"
@@ -70,7 +82,8 @@ async function createDB({
70
82
  });
71
83
  const mapTo = [];
72
84
  items.forEach((page) => {
73
- const tags = Array.isArray(page.tag) ? page.tag : page.tag ? [page.tag] : [];
85
+ const pageTag = page.tag ?? [];
86
+ const tags = Array.isArray(pageTag) ? pageTag : [pageTag];
74
87
  const data = page.structuredData;
75
88
  let id = 0;
76
89
  mapTo.push({
@@ -78,12 +91,14 @@ async function createDB({
78
91
  page_id: page.id,
79
92
  type: "page",
80
93
  content: page.title,
94
+ breadcrumbs: page.breadcrumbs,
81
95
  tags,
82
96
  url: page.url
83
97
  });
98
+ const nextId = () => `${page.id}-${id++}`;
84
99
  if (page.description) {
85
100
  mapTo.push({
86
- id: `${page.id}-${(id++).toString()}`,
101
+ id: nextId(),
87
102
  page_id: page.id,
88
103
  tags,
89
104
  type: "text",
@@ -93,7 +108,7 @@ async function createDB({
93
108
  }
94
109
  for (const heading of data.headings) {
95
110
  mapTo.push({
96
- id: `${page.id}-${(id++).toString()}`,
111
+ id: nextId(),
97
112
  page_id: page.id,
98
113
  type: "heading",
99
114
  tags,
@@ -103,7 +118,7 @@ async function createDB({
103
118
  }
104
119
  for (const content of data.contents) {
105
120
  mapTo.push({
106
- id: `${page.id}-${(id++).toString()}`,
121
+ id: nextId(),
107
122
  page_id: page.id,
108
123
  tags,
109
124
  type: "text",
@@ -115,13 +130,6 @@ async function createDB({
115
130
  await insertMultiple(db, mapTo);
116
131
  return db;
117
132
  }
118
- var simpleSchema = {
119
- url: "string",
120
- title: "string",
121
- description: "string",
122
- content: "string",
123
- keywords: "string"
124
- };
125
133
  async function createDBSimple({
126
134
  indexes,
127
135
  tokenizer,
@@ -141,6 +149,7 @@ async function createDBSimple({
141
149
  items.map((page) => ({
142
150
  title: page.title,
143
151
  description: page.description,
152
+ breadcrumbs: page.breadcrumbs,
144
153
  url: page.url,
145
154
  content: page.content,
146
155
  keywords: page.keywords
@@ -150,73 +159,77 @@ async function createDBSimple({
150
159
  }
151
160
 
152
161
  // src/search/orama/create-from-source.ts
153
- async function pageToIndex(page) {
154
- let structuredData;
155
- if ("structuredData" in page.data) {
156
- structuredData = page.data.structuredData;
157
- } else if ("load" in page.data && typeof page.data.load === "function") {
158
- structuredData = (await page.data.load()).structuredData;
162
+ function defaultBuildIndex(source) {
163
+ function isBreadcrumbItem(item) {
164
+ return typeof item === "string" && item.length > 0;
159
165
  }
160
- if (!structuredData)
161
- throw new Error(
162
- "Cannot find structured data from page, please define the page to index function."
166
+ return async (page) => {
167
+ let breadcrumbs;
168
+ let structuredData;
169
+ if ("structuredData" in page.data) {
170
+ structuredData = page.data.structuredData;
171
+ } else if ("load" in page.data && typeof page.data.load === "function") {
172
+ structuredData = (await page.data.load()).structuredData;
173
+ }
174
+ if (!structuredData)
175
+ throw new Error(
176
+ "Cannot find structured data from page, please define the page to index function."
177
+ );
178
+ const pageTree = source.getPageTree(page.locale);
179
+ const path = findPath(
180
+ pageTree.children,
181
+ (node) => node.type === "page" && node.url === page.url
163
182
  );
164
- return {
165
- title: page.data.title ?? basename(page.path, extname(page.path)),
166
- description: "description" in page.data ? page.data.description : void 0,
167
- url: page.url,
168
- id: page.url,
169
- structuredData
183
+ if (path) {
184
+ breadcrumbs = [];
185
+ path.pop();
186
+ if (isBreadcrumbItem(pageTree.name)) {
187
+ breadcrumbs.push(pageTree.name);
188
+ }
189
+ for (const segment of path) {
190
+ if (!isBreadcrumbItem(segment.name)) continue;
191
+ breadcrumbs.push(segment.name);
192
+ }
193
+ }
194
+ return {
195
+ title: page.data.title ?? basename(page.path, extname(page.path)),
196
+ breadcrumbs,
197
+ description: page.data.description,
198
+ url: page.url,
199
+ id: page.url,
200
+ structuredData
201
+ };
170
202
  };
171
203
  }
172
- function createFromSource(source, _buildIndexOrOptions = pageToIndex, _options) {
173
- const { buildIndex = pageToIndex, ...options } = {
204
+ function createFromSource(source, _buildIndexOrOptions, _options) {
205
+ const { buildIndex = defaultBuildIndex(source), ...options } = {
174
206
  ...typeof _buildIndexOrOptions === "function" ? {
175
207
  buildIndex: _buildIndexOrOptions
176
208
  } : _buildIndexOrOptions,
177
209
  ..._options
178
210
  };
179
- const i18n = source._i18n;
180
- let server;
181
- if (i18n) {
182
- const indexes = source.getLanguages().flatMap((entry) => {
183
- return entry.pages.map(async (page) => ({
184
- ...await buildIndex(page),
185
- locale: entry.language
186
- }));
187
- });
188
- server = Promise.all(indexes).then(
189
- (loaded) => createI18nSearchAPI("advanced", {
190
- ...options,
191
- i18n,
192
- indexes: loaded
193
- })
194
- );
195
- } else {
196
- const indexes = source.getPages().map(async (page) => {
197
- return buildIndex(page);
211
+ if (source._i18n) {
212
+ return createI18nSearchAPI("advanced", {
213
+ ...options,
214
+ i18n: source._i18n,
215
+ indexes: async () => {
216
+ const indexes = source.getLanguages().flatMap((entry) => {
217
+ return entry.pages.map(async (page) => ({
218
+ ...await buildIndex(page),
219
+ locale: entry.language
220
+ }));
221
+ });
222
+ return Promise.all(indexes);
223
+ }
198
224
  });
199
- server = Promise.all(indexes).then(
200
- (loaded) => createSearchAPI("advanced", {
201
- ...options,
202
- indexes: loaded
203
- })
204
- );
205
225
  }
206
- return {
207
- async export(...args) {
208
- return (await server).export(...args);
209
- },
210
- async GET(...args) {
211
- return (await server).GET(...args);
212
- },
213
- async search(...args) {
214
- return (await server).search(...args);
215
- },
216
- async staticGET(...args) {
217
- return (await server).staticGET(...args);
226
+ return createSearchAPI("advanced", {
227
+ ...options,
228
+ indexes: async () => {
229
+ const indexes = source.getPages().map((page) => buildIndex(page));
230
+ return Promise.all(indexes);
218
231
  }
219
- };
232
+ });
220
233
  }
221
234
 
222
235
  // src/search/orama/_stemmers.ts
@@ -4,7 +4,7 @@ export { d as PageTree } from '../definitions-Q95-psoo.js';
4
4
  import { Metadata } from 'next';
5
5
  import { NextRequest } from 'next/server';
6
6
  import { LoaderOutput, LoaderConfig, InferPageType } from '../source/index.js';
7
- export { S as SortedResult } from '../shared-ORgOfXFw.js';
7
+ export { SortedResult } from '../search/index.js';
8
8
  import 'react';
9
9
  import 'unified';
10
10
  import 'vfile';
@@ -34,6 +34,14 @@ declare function separatePageTree(pageTree: Root): Root[];
34
34
  * Get other page tree nodes that lives under the same parent
35
35
  */
36
36
  declare function getPageTreePeers(tree: Root, url: string): Item[];
37
+ /**
38
+ * Search the path of a node in the tree matched by the matcher.
39
+ *
40
+ * @returns The path to the target node (from starting root), or null if the page doesn't exist
41
+ */
42
+ declare function findPath(nodes: Node[], matcher: (node: Node) => boolean, options?: {
43
+ includeSeparator?: boolean;
44
+ }): Node[] | null;
37
45
 
38
46
  interface GetGithubLastCommitOptions {
39
47
  /**
@@ -116,4 +124,4 @@ declare function createMetadataImage<S extends LoaderOutput<LoaderConfig>>(optio
116
124
  }) => Response | Promise<Response>) => (request: NextRequest, options: any) => Response | Promise<Response>;
117
125
  };
118
126
 
119
- export { type GetGithubLastCommitOptions, createMetadataImage, findNeighbour, flattenTree, getGithubLastEdit, getPageTreePeers, getPageTreeRoots, separatePageTree };
127
+ export { type GetGithubLastCommitOptions, createMetadataImage, findNeighbour, findPath, flattenTree, getGithubLastEdit, getPageTreePeers, getPageTreeRoots, separatePageTree };
@@ -1,6 +1,14 @@
1
1
  import {
2
2
  remarkHeading
3
3
  } from "../chunk-QMATWJ5F.js";
4
+ import {
5
+ findNeighbour,
6
+ findPath,
7
+ flattenTree,
8
+ getPageTreePeers,
9
+ getPageTreeRoots,
10
+ separatePageTree
11
+ } from "../chunk-HV3YIUWE.js";
4
12
  import "../chunk-JSBRDJBE.js";
5
13
 
6
14
  // src/server/get-toc.ts
@@ -17,79 +25,6 @@ function getTableOfContents(content, remarkPlugins) {
17
25
  return [];
18
26
  }
19
27
 
20
- // src/utils/page-tree.tsx
21
- function flattenTree(nodes) {
22
- const out = [];
23
- for (const node of nodes) {
24
- if (node.type === "folder") {
25
- if (node.index) out.push(node.index);
26
- out.push(...flattenTree(node.children));
27
- } else if (node.type === "page") {
28
- out.push(node);
29
- }
30
- }
31
- return out;
32
- }
33
- function findNeighbour(tree, url, options) {
34
- const { separateRoot = true } = options ?? {};
35
- const roots = separateRoot ? getPageTreeRoots(tree) : [tree];
36
- if (tree.fallback) roots.push(tree.fallback);
37
- for (const root of roots) {
38
- const list = flattenTree(root.children);
39
- const idx = list.findIndex((item) => item.url === url);
40
- if (idx === -1) continue;
41
- return {
42
- previous: list[idx - 1],
43
- next: list[idx + 1]
44
- };
45
- }
46
- return {};
47
- }
48
- function getPageTreeRoots(pageTree) {
49
- const result = pageTree.children.flatMap((child) => {
50
- if (child.type !== "folder") return [];
51
- const roots = getPageTreeRoots(child);
52
- if (child.root) roots.push(child);
53
- return roots;
54
- });
55
- if (!("type" in pageTree)) result.push(pageTree);
56
- return result;
57
- }
58
- function separatePageTree(pageTree) {
59
- return pageTree.children.flatMap((child) => {
60
- if (child.type !== "folder") return [];
61
- return {
62
- name: child.name,
63
- url: child.index?.url,
64
- children: child.children
65
- };
66
- });
67
- }
68
- function getPageTreePeers(tree, url) {
69
- const parent = findParentFromTree(tree, url);
70
- if (!parent) return [];
71
- return parent.children.filter(
72
- (item) => item.type === "page" && item.url !== url
73
- );
74
- }
75
- function findParentFromTree(node, url) {
76
- if ("index" in node && node.index?.url === url) {
77
- return node;
78
- }
79
- for (const child of node.children) {
80
- if (child.type === "folder") {
81
- const parent = findParentFromTree(child, url);
82
- if (parent) return parent;
83
- }
84
- if (child.type === "page" && child.url === url) {
85
- return node;
86
- }
87
- }
88
- if ("fallback" in node && node.fallback) {
89
- return findParentFromTree(node.fallback, url);
90
- }
91
- }
92
-
93
28
  // src/source/page-tree/definitions.ts
94
29
  var definitions_exports = {};
95
30
 
@@ -194,6 +129,7 @@ export {
194
129
  definitions_exports as PageTree,
195
130
  createMetadataImage,
196
131
  findNeighbour,
132
+ findPath,
197
133
  flattenTree,
198
134
  getGithubLastEdit,
199
135
  getPageTreePeers,
@@ -246,7 +246,7 @@ function build(id, ctx) {
246
246
  const folder = buildFolderNode("", true, ctx);
247
247
  let root = {
248
248
  $id: id,
249
- name: folder.name,
249
+ name: folder.name || "Docs",
250
250
  children: folder.children
251
251
  };
252
252
  for (const transformer of ctx.transformers) {
@@ -584,7 +584,7 @@ function createOutput(options) {
584
584
  };
585
585
  }
586
586
  },
587
- getPageByHref(href, { dir = "", language } = {}) {
587
+ getPageByHref(href, { dir = "", language = defaultLanguage } = {}) {
588
588
  const [value, hash] = href.split("#", 2);
589
589
  let target;
590
590
  if (value.startsWith(".") && (value.endsWith(".md") || value.endsWith(".mdx"))) {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  searchAdvanced,
3
3
  searchSimple
4
- } from "./chunk-XMAVUWLD.js";
4
+ } from "./chunk-XOFXGHS4.js";
5
5
  import "./chunk-ZMWYLUDP.js";
6
- import "./chunk-CNWEGOUF.js";
6
+ import "./chunk-OTD7MV33.js";
7
7
  import "./chunk-JSBRDJBE.js";
8
8
 
9
9
  // src/search/client/static.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "15.7.12",
3
+ "version": "15.8.0",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -32,6 +32,10 @@
32
32
  "import": "./dist/hide-if-empty.js",
33
33
  "types": "./dist/hide-if-empty.d.ts"
34
34
  },
35
+ "./search": {
36
+ "import": "./dist/search/index.js",
37
+ "types": "./dist/search/index.d.ts"
38
+ },
35
39
  "./search/*": {
36
40
  "import": "./dist/search/*.js",
37
41
  "types": "./dist/search/*.d.ts"
@@ -91,9 +95,9 @@
91
95
  ],
92
96
  "dependencies": {
93
97
  "@formatjs/intl-localematcher": "^0.6.1",
94
- "@orama/orama": "^3.1.13",
95
- "@shikijs/rehype": "^3.12.2",
96
- "@shikijs/transformers": "^3.12.2",
98
+ "@orama/orama": "^3.1.14",
99
+ "@shikijs/rehype": "^3.13.0",
100
+ "@shikijs/transformers": "^3.13.0",
97
101
  "github-slugger": "^2.0.0",
98
102
  "hast-util-to-estree": "^3.1.3",
99
103
  "hast-util-to-jsx-runtime": "^2.3.6",
@@ -105,25 +109,25 @@
105
109
  "remark-gfm": "^4.0.1",
106
110
  "remark-rehype": "^11.1.2",
107
111
  "scroll-into-view-if-needed": "^3.1.0",
108
- "shiki": "^3.12.2",
112
+ "shiki": "^3.13.0",
109
113
  "unist-util-visit": "^5.0.0"
110
114
  },
111
115
  "devDependencies": {
112
116
  "@mdx-js/mdx": "^3.1.1",
113
117
  "@mixedbread/sdk": "^0.28.1",
114
118
  "@oramacloud/client": "^2.1.4",
115
- "@tanstack/react-router": "^1.131.41",
119
+ "@tanstack/react-router": "^1.132.2",
116
120
  "@types/estree-jsx": "^1.0.5",
117
121
  "@types/hast": "^3.0.4",
118
122
  "@types/mdast": "^4.0.3",
119
123
  "@types/negotiator": "^0.6.4",
120
- "@types/node": "24.3.3",
124
+ "@types/node": "24.5.2",
121
125
  "@types/react": "^19.1.13",
122
126
  "@types/react-dom": "^19.1.9",
123
127
  "algoliasearch": "5.37.0",
124
128
  "mdast-util-mdx-jsx": "^3.2.0",
125
129
  "mdast-util-mdxjs-esm": "^2.0.1",
126
- "next": "^15.5.3",
130
+ "next": "^15.5.4",
127
131
  "react-router": "^7.9.1",
128
132
  "remark-mdx": "^3.1.1",
129
133
  "remove-markdown": "^0.6.2",
@@ -1,19 +0,0 @@
1
- interface SortedResult {
2
- id: string;
3
- url: string;
4
- type: 'page' | 'heading' | 'text';
5
- content: string;
6
- contentWithHighlights?: HighlightedText[];
7
- }
8
- type HighlightedText = {
9
- type: 'text';
10
- content: string;
11
- styles?: {
12
- highlight?: boolean;
13
- };
14
- };
15
- declare function createContentHighlighter(query: string | RegExp): {
16
- highlight(content: string): HighlightedText[];
17
- };
18
-
19
- export { type HighlightedText as H, type SortedResult as S, createContentHighlighter as c };