fumadocs-core 13.4.9 → 14.0.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.
Files changed (36) hide show
  1. package/dist/{search-algolia/client.js → algolia-NTWLS6J3.js} +10 -27
  2. package/dist/breadcrumb.d.ts +15 -7
  3. package/dist/breadcrumb.js +47 -25
  4. package/dist/chunk-2V6SCS43.js +12 -0
  5. package/dist/chunk-2ZSMGYVH.js +78 -0
  6. package/dist/{chunk-UQV4A7HQ.js → chunk-4MNUWZIW.js} +9 -7
  7. package/dist/{chunk-KGMG4N3Y.js → chunk-I5BWASD6.js} +2 -2
  8. package/dist/dynamic-link.js +2 -1
  9. package/dist/fetch-4K7QOPFM.js +17 -0
  10. package/dist/i18n/index.js +64 -3
  11. package/dist/mdx-plugins/index.d.ts +20 -5
  12. package/dist/mdx-plugins/index.js +140 -14
  13. package/dist/{page-tree-BTCDMLTU.d.ts → page-tree-r8qjoUla.d.ts} +7 -4
  14. package/dist/{search-algolia/server.d.ts → search/algolia.d.ts} +1 -1
  15. package/dist/{search-algolia/server.js → search/algolia.js} +1 -1
  16. package/dist/search/client.d.ts +37 -7
  17. package/dist/search/client.js +74 -22
  18. package/dist/search/server.d.ts +65 -32
  19. package/dist/search/server.js +272 -234
  20. package/dist/server/index.d.ts +56 -4
  21. package/dist/server/index.js +59 -1
  22. package/dist/sidebar.js +7 -5
  23. package/dist/source/index.d.ts +20 -29
  24. package/dist/source/index.js +89 -81
  25. package/dist/static-5GPJ7RUY.js +60 -0
  26. package/dist/toc.js +1 -1
  27. package/dist/{search/shared.d.ts → types-Ch8gnVgO.d.ts} +1 -1
  28. package/dist/utils/use-on-change.d.ts +6 -1
  29. package/dist/utils/use-on-change.js +1 -1
  30. package/package.json +27 -86
  31. package/dist/chunk-MXOJWF66.js +0 -67
  32. package/dist/chunk-NREWOIVI.js +0 -19
  33. package/dist/middleware.d.ts +0 -3
  34. package/dist/middleware.js +0 -7
  35. package/dist/search/shared.js +0 -0
  36. package/dist/search-algolia/client.d.ts +0 -37
@@ -1,11 +1,6 @@
1
- import {
2
- useDebounce
3
- } from "../chunk-NREWOIVI.js";
4
- import "../chunk-MLKGABMK.js";
1
+ import "./chunk-MLKGABMK.js";
5
2
 
6
- // src/search-algolia/client.ts
7
- import { useState } from "react";
8
- import useSWR from "swr";
3
+ // src/search/client/algolia.ts
9
4
  function groupResults(hits) {
10
5
  const grouped = [];
11
6
  const scannedUrls = /* @__PURE__ */ new Set();
@@ -28,39 +23,27 @@ function groupResults(hits) {
28
23
  }
29
24
  return grouped;
30
25
  }
31
- async function searchDocs(index, query, options) {
26
+ async function searchDocs(index, query, tag, options) {
27
+ let filters = options?.filters;
28
+ if (tag) filters = filters ? `tag:${tag} AND (${filters})` : `tag:${tag}`;
32
29
  if (query.length === 0) {
33
30
  const result2 = await index.search(query, {
34
31
  distinct: 1,
35
32
  hitsPerPage: 8,
36
- ...options
33
+ ...options,
34
+ filters
37
35
  });
38
36
  return groupResults(result2.hits).filter((hit) => hit.type === "page");
39
37
  }
40
38
  const result = await index.search(query, {
41
39
  distinct: 5,
42
40
  hitsPerPage: 10,
43
- ...options
41
+ ...options,
42
+ filters
44
43
  });
45
44
  return groupResults(result.hits);
46
45
  }
47
- function useAlgoliaSearch(index, { allowEmpty = true, delay = 150, ...options } = {}) {
48
- const [search, setSearch] = useState("");
49
- const debouncedValue = useDebounce(search, delay);
50
- const query = useSWR(
51
- ["algolia-search", debouncedValue, allowEmpty, options],
52
- async () => {
53
- if (allowEmpty && debouncedValue.length === 0) return "empty";
54
- return searchDocs(index, debouncedValue, options);
55
- },
56
- {
57
- keepPreviousData: true
58
- }
59
- );
60
- return { search, setSearch, query };
61
- }
62
46
  export {
63
47
  groupResults,
64
- searchDocs,
65
- useAlgoliaSearch
48
+ searchDocs
66
49
  };
@@ -1,11 +1,11 @@
1
1
  import { ReactNode } from 'react';
2
- import { R as Root } from './page-tree-BTCDMLTU.js';
2
+ import { R as Root, N as Node, F as Folder, I as Item, S as Separator } from './page-tree-r8qjoUla.js';
3
3
 
4
4
  interface BreadcrumbItem {
5
5
  name: ReactNode;
6
6
  url?: string;
7
7
  }
8
- interface BreadcrumbOptions extends SearchOptions {
8
+ interface BreadcrumbOptions {
9
9
  /**
10
10
  * Include the root itself in the breadcrumb items array.
11
11
  * Specify the url by passing an object instead
@@ -15,10 +15,6 @@ interface BreadcrumbOptions extends SearchOptions {
15
15
  includeRoot?: boolean | {
16
16
  url: string;
17
17
  };
18
- }
19
- declare function useBreadcrumb(url: string, tree: Root, options?: BreadcrumbOptions): BreadcrumbItem[];
20
- declare function getBreadcrumbItems(url: string, tree: Root, options?: BreadcrumbOptions): BreadcrumbItem[];
21
- interface SearchOptions {
22
18
  /**
23
19
  * Include the page itself in the breadcrumb items array
24
20
  *
@@ -32,5 +28,17 @@ interface SearchOptions {
32
28
  */
33
29
  includeSeparator?: boolean;
34
30
  }
31
+ declare function useBreadcrumb(url: string, tree: Root, options?: BreadcrumbOptions): BreadcrumbItem[];
32
+ declare function getBreadcrumbItems(url: string, tree: Root, options?: BreadcrumbOptions): BreadcrumbItem[];
33
+ declare function getBreadcrumbItemsFromPath(tree: Root, path: Node[], options: BreadcrumbOptions): BreadcrumbItem[];
34
+ /**
35
+ * Search the path of a node in the tree by a specified url
36
+ *
37
+ * - When the page doesn't exist, return null
38
+ *
39
+ * @returns The path to the target node from root
40
+ * @internal
41
+ */
42
+ declare function searchPath(nodes: Node[], url: string): (Folder | Item | Separator)[] | null;
35
43
 
36
- export { type BreadcrumbItem, type BreadcrumbOptions, getBreadcrumbItems, useBreadcrumb };
44
+ export { type BreadcrumbItem, type BreadcrumbOptions, getBreadcrumbItems, getBreadcrumbItemsFromPath, searchPath, useBreadcrumb };
@@ -9,50 +9,70 @@ function useBreadcrumb(url, tree, options) {
9
9
  );
10
10
  }
11
11
  function getBreadcrumbItems(url, tree, options = {}) {
12
- const { includeRoot, ...rest } = options;
13
- const path = searchPath(tree.children, url, rest) ?? [];
12
+ return getBreadcrumbItemsFromPath(
13
+ tree,
14
+ searchPath(tree.children, url) ?? [],
15
+ options
16
+ );
17
+ }
18
+ function getBreadcrumbItemsFromPath(tree, path, options) {
19
+ const { includePage = true, includeSeparator = false, includeRoot } = options;
20
+ let items = [];
21
+ path.forEach((item, i) => {
22
+ if (item.type === "separator" && includeSeparator) {
23
+ items.push({
24
+ name: item.name
25
+ });
26
+ }
27
+ if (item.type === "folder") {
28
+ const next = path.at(i + 1);
29
+ if (next && item.index === next) return;
30
+ if (item.root) {
31
+ items = [];
32
+ return;
33
+ }
34
+ items.push({
35
+ name: item.name,
36
+ url: item.index?.url
37
+ });
38
+ }
39
+ if (item.type === "page" && includePage) {
40
+ items.push({
41
+ name: item.name,
42
+ url: item.url
43
+ });
44
+ }
45
+ });
14
46
  if (includeRoot) {
15
- path.unshift({
47
+ items.unshift({
16
48
  name: tree.name,
17
49
  url: typeof includeRoot === "object" ? includeRoot.url : void 0
18
50
  });
19
51
  }
20
- return path;
52
+ return items;
21
53
  }
22
- function searchPath(nodes, url, options) {
23
- const { includePage = true, includeSeparator = false } = options;
54
+ function searchPath(nodes, url) {
24
55
  let separator;
25
56
  for (const node of nodes) {
26
- if (includeSeparator && node.type === "separator") separator = node.name;
57
+ if (node.type === "separator") separator = node;
27
58
  if (node.type === "folder") {
28
59
  if (node.index?.url === url) {
29
60
  const items2 = [];
30
- if (separator) items2.push({ name: separator });
31
- if (options.includePage)
32
- items2.push({
33
- name: node.index.name,
34
- url: node.index.url
35
- });
61
+ if (separator) items2.push(separator);
62
+ items2.push(node, node.index);
36
63
  return items2;
37
64
  }
38
- const items = searchPath(node.children, url, options);
65
+ const items = searchPath(node.children, url);
39
66
  if (items) {
40
- items.unshift({
41
- name: node.name,
42
- url: node.index?.url
43
- });
44
- if (separator) items.unshift({ name: separator });
67
+ items.unshift(node);
68
+ if (separator) items.unshift(separator);
45
69
  return items;
46
70
  }
47
71
  }
48
72
  if (node.type === "page" && node.url === url) {
49
73
  const items = [];
50
- if (separator) items.push({ name: separator });
51
- if (includePage)
52
- items.push({
53
- name: node.name,
54
- url: node.url
55
- });
74
+ if (separator) items.push(separator);
75
+ items.push(node);
56
76
  return items;
57
77
  }
58
78
  }
@@ -60,5 +80,7 @@ function searchPath(nodes, url, options) {
60
80
  }
61
81
  export {
62
82
  getBreadcrumbItems,
83
+ getBreadcrumbItemsFromPath,
84
+ searchPath,
63
85
  useBreadcrumb
64
86
  };
@@ -0,0 +1,12 @@
1
+ // src/utils/remove-undefined.ts
2
+ function removeUndefined(value) {
3
+ const obj = value;
4
+ Object.keys(obj).forEach((key) => {
5
+ if (obj[key] === void 0) delete obj[key];
6
+ });
7
+ return value;
8
+ }
9
+
10
+ export {
11
+ removeUndefined
12
+ };
@@ -0,0 +1,78 @@
1
+ import {
2
+ removeUndefined
3
+ } from "./chunk-2V6SCS43.js";
4
+
5
+ // src/search/search/simple.ts
6
+ import { search } from "@orama/orama";
7
+ async function searchSimple(db, query, params = {}) {
8
+ const result = await search(db, {
9
+ term: query,
10
+ tolerance: 1,
11
+ ...params,
12
+ boost: {
13
+ title: 2,
14
+ ..."boost" in params ? params.boost : void 0
15
+ }
16
+ });
17
+ return result.hits.map((hit) => ({
18
+ type: "page",
19
+ content: hit.document.title,
20
+ id: hit.document.url,
21
+ url: hit.document.url
22
+ }));
23
+ }
24
+
25
+ // src/search/search/advanced.ts
26
+ import { getByID, search as search2 } from "@orama/orama";
27
+ async function searchAdvanced(db, query, tag, extraParams = {}) {
28
+ let params = {
29
+ where: removeUndefined({
30
+ tag,
31
+ ...extraParams.where
32
+ }),
33
+ groupBy: {
34
+ properties: ["page_id"],
35
+ maxResult: 8,
36
+ ...extraParams.groupBy
37
+ }
38
+ };
39
+ if (query.length > 0) {
40
+ params = {
41
+ ...params,
42
+ term: query,
43
+ tolerance: 1,
44
+ properties: ["content", "keywords"],
45
+ ...extraParams,
46
+ where: params.where,
47
+ groupBy: params.groupBy
48
+ };
49
+ }
50
+ const result = await search2(db, params);
51
+ const list = [];
52
+ for (const item of result.groups ?? []) {
53
+ const pageId = item.values[0];
54
+ const page = getByID(db, pageId);
55
+ if (!page) continue;
56
+ list.push({
57
+ id: pageId,
58
+ type: "page",
59
+ content: page.content,
60
+ url: page.url
61
+ });
62
+ for (const hit of item.result) {
63
+ if (hit.document.type === "page") continue;
64
+ list.push({
65
+ id: hit.document.id.toString(),
66
+ content: hit.document.content,
67
+ type: hit.document.type,
68
+ url: hit.document.url
69
+ });
70
+ }
71
+ }
72
+ return list;
73
+ }
74
+
75
+ export {
76
+ searchSimple,
77
+ searchAdvanced
78
+ };
@@ -15,7 +15,8 @@ var slugger = new Slugger();
15
15
  var regex = /\s*\[#(?<slug>[^]+?)]\s*$/;
16
16
  function remarkHeading({
17
17
  slug: defaultSlug,
18
- customId = true
18
+ customId = true,
19
+ generateToc = true
19
20
  } = {}) {
20
21
  return (root, file) => {
21
22
  const toc = [];
@@ -43,14 +44,15 @@ function remarkHeading({
43
44
  id = defaultSlug ? defaultSlug(root, heading, value) : slugger.slug(value);
44
45
  }
45
46
  heading.data.hProperties.id = id;
46
- toc.push({
47
- title: value,
48
- url: `#${id}`,
49
- depth: heading.depth
50
- });
47
+ if (generateToc)
48
+ toc.push({
49
+ title: value,
50
+ url: `#${id}`,
51
+ depth: heading.depth
52
+ });
51
53
  return "skip";
52
54
  });
53
- file.data.toc = toc;
55
+ if (generateToc) file.data.toc = toc;
54
56
  };
55
57
  }
56
58
 
@@ -1,8 +1,8 @@
1
1
  // src/utils/use-on-change.ts
2
2
  import { useState } from "react";
3
- function useOnChange(value, onChange) {
3
+ function useOnChange(value, onChange, isUpdated = (prev, current) => prev !== current) {
4
4
  const [prev, setPrev] = useState(value);
5
- if (prev !== value) {
5
+ if (isUpdated(prev, value)) {
6
6
  onChange(value, prev);
7
7
  setPrev(value);
8
8
  }
@@ -14,7 +14,8 @@ var DynamicLink = forwardRef(
14
14
  const url = useMemo(() => {
15
15
  return href?.replace(/\[.*]/, (key) => {
16
16
  const mappedKey = key.slice(1, -1);
17
- const value = mappedKey in params ? params[mappedKey] : "undefined";
17
+ const value = mappedKey in params ? params[mappedKey] : void 0;
18
+ if (!value) return "";
18
19
  return typeof value === "string" ? value : value.join("/");
19
20
  });
20
21
  }, [params, href]);
@@ -0,0 +1,17 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/search/client/fetch.ts
4
+ async function fetchDocs(query, locale, tag, options) {
5
+ const params = new URLSearchParams();
6
+ params.set("query", query);
7
+ if (locale) params.set("locale", locale);
8
+ if (tag) params.set("tag", tag);
9
+ const res = await fetch(
10
+ `${options.api ?? "/api/search"}?${params.toString()}`
11
+ );
12
+ if (!res.ok) throw new Error(await res.text());
13
+ return await res.json();
14
+ }
15
+ export {
16
+ fetchDocs
17
+ };
@@ -1,7 +1,68 @@
1
- import {
2
- createI18nMiddleware
3
- } from "../chunk-MXOJWF66.js";
4
1
  import "../chunk-MLKGABMK.js";
2
+
3
+ // src/i18n/middleware.ts
4
+ import { match as matchLocale } from "@formatjs/intl-localematcher";
5
+ import Negotiator from "negotiator";
6
+ import { NextResponse } from "next/server";
7
+ var COOKIE = "FD_LOCALE";
8
+ function getLocale(request, locales, defaultLanguage) {
9
+ const negotiatorHeaders = {};
10
+ request.headers.forEach((value, key) => negotiatorHeaders[key] = value);
11
+ const languages = new Negotiator({ headers: negotiatorHeaders }).languages(
12
+ locales
13
+ );
14
+ return matchLocale(languages, locales, defaultLanguage);
15
+ }
16
+ var defaultFormat = (locale, path) => {
17
+ return `/${locale}/${path}`;
18
+ };
19
+ function createI18nMiddleware({
20
+ languages,
21
+ defaultLanguage,
22
+ format = defaultFormat,
23
+ hideLocale = "never"
24
+ }) {
25
+ function getUrl(request, pathname, locale) {
26
+ if (!locale) {
27
+ return new URL(
28
+ pathname.startsWith("/") ? pathname : `/${pathname}`,
29
+ request.url
30
+ );
31
+ }
32
+ return new URL(
33
+ format(locale, pathname.startsWith("/") ? pathname.slice(1) : pathname),
34
+ request.url
35
+ );
36
+ }
37
+ return (request) => {
38
+ const { pathname } = request.nextUrl;
39
+ const pathLocale = languages.find(
40
+ (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
41
+ );
42
+ if (!pathLocale) {
43
+ if (hideLocale === "default-locale") {
44
+ return NextResponse.rewrite(getUrl(request, pathname, defaultLanguage));
45
+ }
46
+ const preferred = getLocale(request, languages, defaultLanguage);
47
+ if (hideLocale === "always") {
48
+ const locale = request.cookies.get(COOKIE)?.value ?? preferred;
49
+ return NextResponse.rewrite(getUrl(request, pathname, locale));
50
+ }
51
+ return NextResponse.redirect(getUrl(request, pathname, preferred));
52
+ }
53
+ if (hideLocale === "always") {
54
+ const path = pathname.slice(`/${pathLocale}`.length);
55
+ const res = NextResponse.redirect(getUrl(request, path));
56
+ res.cookies.set(COOKIE, pathLocale);
57
+ return res;
58
+ }
59
+ if (hideLocale === "default-locale" && pathLocale === defaultLanguage) {
60
+ const path = pathname.slice(`/${pathLocale}`.length);
61
+ return NextResponse.redirect(getUrl(request, path));
62
+ }
63
+ return NextResponse.next();
64
+ };
65
+ }
5
66
  export {
6
67
  createI18nMiddleware
7
68
  };
@@ -48,6 +48,7 @@ type RehypeCodeOptions = RehypeShikiOptions & {
48
48
  * Handle codeblocks
49
49
  */
50
50
  declare function rehypeCode(this: Processor, options?: Partial<RehypeCodeOptions>): Transformer<Root, Root>;
51
+ declare function transformerTab(): ShikiTransformer;
51
52
 
52
53
  interface RemarkImageOptions {
53
54
  /**
@@ -98,16 +99,20 @@ interface RemarkHeadingOptions {
98
99
  /**
99
100
  * Allow custom headings ids
100
101
  *
101
- * @defaultValue `true`
102
+ * @defaultValue true
102
103
  */
103
104
  customId?: boolean;
105
+ /**
106
+ * Attach an array of `TOCItemType` to `file.data.toc`
107
+ *
108
+ * @defaultValue true
109
+ */
110
+ generateToc?: boolean;
104
111
  }
105
112
  /**
106
113
  * Add heading ids and extract TOC
107
- *
108
- * Attach an array of `TOCItemType` to `file.data.toc`
109
114
  */
110
- declare function remarkHeading({ slug: defaultSlug, customId, }?: RemarkHeadingOptions): Transformer<Root$1, Root$1>;
115
+ declare function remarkHeading({ slug: defaultSlug, customId, generateToc, }?: RemarkHeadingOptions): Transformer<Root$1, Root$1>;
111
116
 
112
117
  interface RemarkAdmonitionOptions {
113
118
  tag?: string;
@@ -124,4 +129,14 @@ interface RemarkAdmonitionOptions {
124
129
  */
125
130
  declare function remarkAdmonition(options?: RemarkAdmonitionOptions): Transformer<Root$1, Root$1>;
126
131
 
127
- export { type CodeBlockIcon, type RehypeCodeOptions, type RemarkAdmonitionOptions, type RemarkHeadingOptions, type RemarkImageOptions, rehypeCode, rehypeCodeDefaultOptions, remarkAdmonition, remarkHeading, remarkImage, transformerIcon };
132
+ interface RehypeTocOptions {
133
+ /**
134
+ * Export generated toc as a variable
135
+ *
136
+ * @defaultValue true
137
+ */
138
+ exportToc?: boolean;
139
+ }
140
+ declare function rehypeToc(this: Processor, { exportToc }?: RehypeTocOptions): Transformer<Root, Root>;
141
+
142
+ export { type CodeBlockIcon, type RehypeCodeOptions, type RehypeTocOptions, type RemarkAdmonitionOptions, type RemarkHeadingOptions, type RemarkImageOptions, rehypeCode, rehypeCodeDefaultOptions, rehypeToc, remarkAdmonition, remarkHeading, remarkImage, transformerIcon, transformerTab };