fumadocs-core 15.7.1 → 15.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,25 +24,24 @@ function remarkHeading({
24
24
  visit(root, "heading", (heading) => {
25
25
  heading.data ||= {};
26
26
  heading.data.hProperties ||= {};
27
- let id = heading.data.hProperties.id;
27
+ const props = heading.data.hProperties;
28
28
  const lastNode = heading.children.at(-1);
29
- if (!id && lastNode?.type === "text" && customId) {
29
+ if (lastNode?.type === "text" && customId) {
30
30
  const match = regex.exec(lastNode.value);
31
31
  if (match?.[1]) {
32
- id = match[1];
32
+ props.id = match[1];
33
33
  lastNode.value = lastNode.value.slice(0, match.index);
34
34
  }
35
35
  }
36
36
  let flattened = null;
37
- if (!id) {
37
+ if (!props.id) {
38
38
  flattened ??= flattenNode(heading);
39
- id = defaultSlug ? defaultSlug(root, heading, flattened) : slugger.slug(flattened);
39
+ props.id = defaultSlug ? defaultSlug(root, heading, flattened) : slugger.slug(flattened);
40
40
  }
41
- heading.data.hProperties.id = id;
42
41
  if (generateToc) {
43
42
  toc.push({
44
43
  title: flattened ?? flattenNode(heading),
45
- url: `#${id}`,
44
+ url: `#${props.id}`,
46
45
  depth: heading.depth
47
46
  });
48
47
  }
@@ -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);
@@ -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';
7
- export { a as StructureOptions, S as StructuredData, r as remarkStructure, s as structure } from '../remark-structure-DVje0Sib.js';
8
- export { R as RemarkHeadingOptions, r as remarkHeading } from '../remark-heading-BPCoYwjn.js';
9
- import { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
6
+ import { Root as Root$1, BlockContent, Text } from 'mdast';
7
+ export { a as StructureOptions, S as StructuredData, r as remarkStructure, s as structure } from '../remark-structure-DkCXCzpD.js';
8
+ export { a as RemarkCodeTabOptions, R as RemarkHeadingOptions, b as remarkCodeTab, r as remarkHeading } from '../remark-code-tab-DmyIyi6m.js';
9
+ import { MdxJsxAttribute, MdxJsxFlowElement } 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
  *
@@ -122,26 +122,6 @@ interface RehypeTocOptions {
122
122
  }
123
123
  declare function rehypeToc(this: Processor, { exportToc }?: RehypeTocOptions): Transformer<Root, Root>;
124
124
 
125
- type TabType = keyof typeof Types;
126
- interface RemarkCodeTabOptions {
127
- Tabs?: TabType;
128
- /**
129
- * Parse MDX in tab values
130
- *
131
- * @defaultValue false
132
- */
133
- parseMdx?: boolean;
134
- }
135
- declare const Types: {
136
- CodeBlockTabs: {
137
- convert(processor: Processor, nodes: Code[], withMdx?: boolean, withParent?: boolean): MdxJsxFlowElement;
138
- };
139
- Tabs: {
140
- convert(processor: Processor, nodes: Code[], withMdx?: boolean, withParent?: boolean): MdxJsxFlowElement;
141
- };
142
- };
143
- declare function remarkCodeTab(this: Processor, options?: RemarkCodeTabOptions): Transformer<Root$1, Root$1>;
144
-
145
125
  interface RemarkStepsOptions {
146
126
  /**
147
127
  * Class name for steps container
@@ -163,10 +143,14 @@ declare function remarkSteps({ steps, step, }?: RemarkStepsOptions): Transformer
163
143
 
164
144
  interface PackageManager {
165
145
  name: string;
146
+ /**
147
+ * Default to `name`
148
+ */
149
+ value?: string;
166
150
  /**
167
151
  * Convert from npm to another package manager
168
152
  */
169
- command: (command: string) => string;
153
+ command: (command: string) => string | undefined;
170
154
  }
171
155
  interface RemarkNpmOptions {
172
156
  /**
@@ -184,4 +168,29 @@ interface RemarkNpmOptions {
184
168
  */
185
169
  declare function remarkNpm({ persist, packageManagers, }?: RemarkNpmOptions): Transformer<Root$1, Root$1>;
186
170
 
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 };
171
+ interface CodeBlockTabsOptions {
172
+ attributes?: MdxJsxAttribute[];
173
+ defaultValue?: string;
174
+ persist?: {
175
+ id: string;
176
+ } | false;
177
+ triggers: {
178
+ value: string;
179
+ children: (BlockContent | Text)[];
180
+ }[];
181
+ tabs: {
182
+ value: string;
183
+ children: BlockContent[];
184
+ }[];
185
+ }
186
+ declare function generateCodeBlockTabs({ persist, defaultValue, triggers, tabs, ...options }: CodeBlockTabsOptions): MdxJsxFlowElement;
187
+ interface CodeBlockAttributes<Name extends string = string> {
188
+ attributes: Partial<Record<Name, string>>;
189
+ rest: string;
190
+ }
191
+ /**
192
+ * Parse Fumadocs-style code block attributes from meta string, like `title="hello world"`
193
+ */
194
+ declare function parseCodeBlockAttributes<Name extends string = string>(meta: string, allowedNames?: Name[]): CodeBlockAttributes<Name>;
195
+
196
+ export { type CodeBlockAttributes, type CodeBlockIcon, type CodeBlockTabsOptions, type RehypeCodeOptions, type RehypeTocOptions, type RemarkAdmonitionOptions, type RemarkImageOptions, type RemarkNpmOptions, type RemarkStepsOptions, generateCodeBlockTabs, parseCodeBlockAttributes, rehypeCode, rehypeCodeDefaultOptions, rehypeToc, remarkAdmonition, remarkImage, remarkNpm, remarkSteps, transformerIcon, transformerTab };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  flattenNode,
3
3
  remarkHeading
4
- } from "../chunk-Y2774T3B.js";
4
+ } from "../chunk-QMATWJ5F.js";
5
5
  import {
6
6
  joinPath,
7
7
  slash
@@ -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,
@@ -261,7 +318,8 @@ function rehypeCode(_options = {}) {
261
318
  options.experimentalJSEngine ? "js" : "oniguruma",
262
319
  {
263
320
  themes: "themes" in options ? Object.values(options.themes).filter(Boolean) : [options.theme],
264
- langs: options.langs ?? (options.lazy ? ["ts", "tsx"] : Object.keys(bundledLanguages))
321
+ langs: options.langs ?? (options.lazy ? ["ts", "tsx"] : Object.keys(bundledLanguages)),
322
+ langAlias: options.langAlias
265
323
  }
266
324
  );
267
325
  const transformer = highlighter.then(
@@ -512,7 +570,6 @@ import Slugger from "github-slugger";
512
570
  import { remark } from "remark";
513
571
  import remarkGfm from "remark-gfm";
514
572
  import { visit as visit2 } from "unist-util-visit";
515
- var slugger = new Slugger();
516
573
  function remarkStructure({
517
574
  types = [
518
575
  "heading",
@@ -526,6 +583,7 @@ function remarkStructure({
526
583
  return ["TypeTable", "Callout"].includes(node.name);
527
584
  }
528
585
  } = {}) {
586
+ const slugger = new Slugger();
529
587
  if (Array.isArray(allowedMdxAttributes)) {
530
588
  const arr = allowedMdxAttributes;
531
589
  allowedMdxAttributes = (_node, attribute) => attribute.type === "mdxJsxAttribute" && arr.includes(attribute.name);
@@ -537,7 +595,7 @@ function remarkStructure({
537
595
  return (node, file) => {
538
596
  slugger.reset();
539
597
  const data = { contents: [], headings: [] };
540
- let lastHeading = "";
598
+ let lastHeading;
541
599
  if (file.data.frontmatter) {
542
600
  const frontmatter = file.data.frontmatter;
543
601
  if (frontmatter._openapi?.structuredData) {
@@ -690,7 +748,7 @@ function rehypeToc({ exportToc = true } = {}) {
690
748
  const output = [];
691
749
  visit4(tree, ["h1", "h2", "h3", "h4", "h5", "h6"], (element) => {
692
750
  const id = element.properties.id;
693
- if (!id) return "skip";
751
+ if (typeof id !== "string") return "skip";
694
752
  let isTocOnly = false;
695
753
  const last = element.children.at(-1);
696
754
  if (last?.type === "text" && last.value.endsWith(TocOnlyTag)) {
@@ -810,7 +868,6 @@ function rehypeToc({ exportToc = true } = {}) {
810
868
 
811
869
  // src/mdx-plugins/remark-code-tab.ts
812
870
  import { visit as visit5 } from "unist-util-visit";
813
- var TabRegex = /tab="(.+?)"/;
814
871
  var Tabs = {
815
872
  convert(processor, nodes, withMdx = false, withParent = true) {
816
873
  const names = processTabValue(nodes);
@@ -919,62 +976,27 @@ var Tabs = {
919
976
  var CodeBlockTabs = {
920
977
  convert(processor, nodes, withMdx = false, withParent = true) {
921
978
  const names = processTabValue(nodes);
922
- const children = [
923
- {
924
- type: "mdxJsxFlowElement",
925
- name: "CodeBlockTabsList",
926
- attributes: [],
927
- children: names.map((name) => {
928
- return {
929
- type: "mdxJsxFlowElement",
930
- name: "CodeBlockTabsTrigger",
931
- attributes: [
932
- {
933
- type: "mdxJsxAttribute",
934
- name: "value",
935
- value: name
936
- }
937
- ],
938
- children: [
939
- withMdx ? (
940
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- needed
941
- mdxToAst(processor, name)
942
- ) : {
943
- type: "text",
944
- value: name
945
- }
946
- ]
947
- };
948
- })
949
- },
950
- ...nodes.map((node, i) => {
951
- return {
952
- type: "mdxJsxFlowElement",
953
- name: "CodeBlockTab",
954
- attributes: [
955
- {
956
- type: "mdxJsxAttribute",
957
- name: "value",
958
- value: names[i]
959
- }
960
- ],
961
- children: [node]
962
- };
963
- })
964
- ];
965
- if (!withParent) return createFragment(children);
966
- return {
967
- type: "mdxJsxFlowElement",
968
- name: "CodeBlockTabs",
969
- attributes: [
970
- {
971
- type: "mdxJsxAttribute",
972
- name: "defaultValue",
973
- value: names[0]
974
- }
975
- ],
976
- children
977
- };
979
+ const node = generateCodeBlockTabs({
980
+ defaultValue: names[0],
981
+ triggers: names.map((name) => ({
982
+ value: name,
983
+ children: [
984
+ withMdx ? (
985
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- needed
986
+ mdxToAst(processor, name)
987
+ ) : {
988
+ type: "text",
989
+ value: name
990
+ }
991
+ ]
992
+ })),
993
+ tabs: nodes.map((node2, i) => ({
994
+ value: names[i],
995
+ children: [node2]
996
+ }))
997
+ });
998
+ if (!withParent) return createFragment(node.children);
999
+ return node;
978
1000
  }
979
1001
  };
980
1002
  var Types = {
@@ -984,58 +1006,58 @@ var Types = {
984
1006
  function remarkCodeTab(options = {}) {
985
1007
  const { parseMdx = false, Tabs: Tabs2 = "CodeBlockTabs" } = options;
986
1008
  return (tree) => {
1009
+ const ignored = /* @__PURE__ */ new WeakSet();
987
1010
  visit5(tree, (node) => {
988
- if (!("children" in node)) return;
989
- let start = -1;
990
- let i = 0;
1011
+ if (!("children" in node) || ignored.has(node)) return "skip";
991
1012
  let localTabs = Tabs2;
992
1013
  let localParseMdx = parseMdx;
993
1014
  let withParent = true;
994
1015
  if (node.type === "mdxJsxFlowElement" && node.name && node.name in Types) {
995
1016
  withParent = false;
996
1017
  localTabs = node.name;
997
- if (node.name === "Tabs") {
1018
+ if (node.name === "Tabs" && localParseMdx) {
998
1019
  localParseMdx = node.attributes.every(
999
1020
  (attribute) => attribute.type !== "mdxJsxAttribute" || attribute.name !== "items"
1000
1021
  );
1001
1022
  }
1002
1023
  }
1003
- while (i < node.children.length) {
1004
- const child = node.children[i];
1005
- const isSwitcher = child.type === "code" && child.meta && child.meta.match(TabRegex);
1006
- if (isSwitcher && start === -1) {
1007
- start = i;
1024
+ let start = -1;
1025
+ let end = 0;
1026
+ const close = () => {
1027
+ if (start === -1 || start === end) return;
1028
+ const replacement = Types[localTabs].convert(
1029
+ this,
1030
+ node.children.slice(start, end),
1031
+ localParseMdx,
1032
+ withParent
1033
+ );
1034
+ ignored.add(replacement);
1035
+ node.children.splice(start, end - start, replacement);
1036
+ end = start;
1037
+ start = -1;
1038
+ };
1039
+ for (; end < node.children.length; end++) {
1040
+ const child = node.children[end];
1041
+ if (child.type !== "code" || !child.meta) {
1042
+ close();
1043
+ continue;
1008
1044
  }
1009
- const isLast = i === node.children.length - 1;
1010
- if (start !== -1 && (isLast || !isSwitcher)) {
1011
- const end = isSwitcher ? i + 1 : i;
1012
- const targets = node.children.slice(start, end);
1013
- const replacement = Types[localTabs].convert(
1014
- this,
1015
- targets,
1016
- localParseMdx,
1017
- withParent
1018
- );
1019
- node.children.splice(start, end - start, replacement);
1020
- if (isLast) break;
1021
- i = start + 1;
1022
- start = -1;
1023
- } else {
1024
- i++;
1045
+ const meta = parseCodeBlockAttributes(child.meta, ["tab"]);
1046
+ if (!meta.attributes.tab) {
1047
+ close();
1048
+ continue;
1025
1049
  }
1050
+ if (start === -1) start = end;
1051
+ child.meta = meta.rest;
1052
+ child.data ??= {};
1053
+ child.data.tab = meta.attributes.tab;
1026
1054
  }
1055
+ close();
1027
1056
  });
1028
1057
  };
1029
1058
  }
1030
1059
  function processTabValue(nodes) {
1031
- return nodes.map((node, i) => {
1032
- let title = `Tab ${i + 1}`;
1033
- node.meta = node.meta?.replace(TabRegex, (_, value) => {
1034
- title = value;
1035
- return "";
1036
- });
1037
- return title;
1038
- });
1060
+ return nodes.map((node, i) => node.data?.tab ?? `Tab ${i + 1}`);
1039
1061
  }
1040
1062
  function mdxToAst(processor, name) {
1041
1063
  const node = processor.parse(name);
@@ -1159,78 +1181,45 @@ function remarkNpm({
1159
1181
  } = {}) {
1160
1182
  return (tree) => {
1161
1183
  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
- );
1184
+ if (!node.lang || !aliases.includes(node.lang)) return;
1185
+ let code = node.value;
1186
+ if (node.lang === "package-install" && !code.startsWith("npm") && !code.startsWith("npx")) {
1187
+ code = `npm install ${code}`;
1184
1188
  }
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 }],
1189
+ const options = {
1190
+ persist,
1191
+ tabs: [],
1192
+ triggers: []
1193
+ };
1194
+ for (const manager of packageManagers) {
1195
+ const value = manager.value ?? manager.name;
1196
+ const command = manager.command(code);
1197
+ if (!command || command.length === 0) continue;
1198
+ options.defaultValue ??= value;
1199
+ options.triggers.push({
1200
+ value,
1201
+ children: [{ type: "text", value: manager.name }]
1202
+ });
1203
+ options.tabs.push({
1204
+ value,
1212
1205
  children: [
1213
1206
  {
1214
1207
  type: "code",
1215
1208
  lang: "bash",
1216
1209
  meta: node.meta,
1217
- value: command(value)
1210
+ value: command
1218
1211
  }
1219
1212
  ]
1220
1213
  });
1221
1214
  }
1222
- const tab = {
1223
- type: "mdxJsxFlowElement",
1224
- name: "CodeBlockTabs",
1225
- attributes,
1226
- children
1227
- };
1228
- Object.assign(node, tab);
1229
- return;
1215
+ Object.assign(node, generateCodeBlockTabs(options));
1216
+ return "skip";
1230
1217
  });
1231
1218
  };
1232
1219
  }
1233
1220
  export {
1221
+ generateCodeBlockTabs,
1222
+ parseCodeBlockAttributes,
1234
1223
  rehypeCode,
1235
1224
  rehypeCodeDefaultOptions,
1236
1225
  rehypeToc,
@@ -0,0 +1,57 @@
1
+ import { Root, Heading, Code } from 'mdast';
2
+ import { Transformer, Processor } from 'unified';
3
+ import { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
4
+
5
+ declare module 'mdast' {
6
+ interface HeadingData extends Data {
7
+ hProperties?: {
8
+ id?: string;
9
+ };
10
+ }
11
+ }
12
+ interface RemarkHeadingOptions {
13
+ slug?: (root: Root, heading: Heading, text: string) => string;
14
+ /**
15
+ * Allow custom headings ids
16
+ *
17
+ * @defaultValue true
18
+ */
19
+ customId?: boolean;
20
+ /**
21
+ * Attach an array of `TOCItemType` to `file.data.toc`
22
+ *
23
+ * @defaultValue true
24
+ */
25
+ generateToc?: boolean;
26
+ }
27
+ /**
28
+ * Add heading ids and extract TOC
29
+ */
30
+ declare function remarkHeading({ slug: defaultSlug, customId, generateToc, }?: RemarkHeadingOptions): Transformer<Root, Root>;
31
+
32
+ type TabType = keyof typeof Types;
33
+ interface RemarkCodeTabOptions {
34
+ Tabs?: TabType;
35
+ /**
36
+ * Parse MDX in tab values
37
+ *
38
+ * @defaultValue false
39
+ */
40
+ parseMdx?: boolean;
41
+ }
42
+ declare module 'mdast' {
43
+ interface CodeData {
44
+ tab?: string;
45
+ }
46
+ }
47
+ declare const Types: {
48
+ CodeBlockTabs: {
49
+ convert(processor: Processor, nodes: Code[], withMdx?: boolean, withParent?: boolean): MdxJsxFlowElement;
50
+ };
51
+ Tabs: {
52
+ convert(processor: Processor, nodes: Code[], withMdx?: boolean, withParent?: boolean): MdxJsxFlowElement;
53
+ };
54
+ };
55
+ declare function remarkCodeTab(this: Processor, options?: RemarkCodeTabOptions): Transformer<Root, Root>;
56
+
57
+ export { type RemarkHeadingOptions as R, type RemarkCodeTabOptions as a, remarkCodeTab as b, remarkHeading as r };
@@ -42,6 +42,11 @@ declare module 'mdast' {
42
42
  _string?: string[];
43
43
  }
44
44
  }
45
+ declare module 'vfile' {
46
+ interface DataMap {
47
+ structuredData: StructuredData;
48
+ }
49
+ }
45
50
  /**
46
51
  * Attach structured data to VFile, you can access via `vfile.data.structuredData`.
47
52
  */
@@ -1,5 +1,5 @@
1
1
  import { Algoliasearch } from 'algoliasearch';
2
- import { S as StructuredData } from '../remark-structure-DVje0Sib.js';
2
+ import { S as StructuredData } from '../remark-structure-DkCXCzpD.js';
3
3
  import 'mdast';
4
4
  import 'unified';
5
5
  import 'mdast-util-mdx-jsx';
@@ -1,5 +1,5 @@
1
1
  import { AnyOrama } from '@orama/orama';
2
- import '../remark-structure-DVje0Sib.js';
2
+ import '../remark-structure-DkCXCzpD.js';
3
3
  import { BaseIndex } from './algolia.js';
4
4
  import { LiteClient, SearchResponse } from 'algoliasearch/lite';
5
5
  import { OramaClient, ClientSearchParams } from '@oramacloud/client';
@@ -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") {
@@ -1,6 +1,6 @@
1
1
  import { CloudManager } from '@oramacloud/client';
2
- import { S as StructuredData } from '../remark-structure-DVje0Sib.js';
3
- import '../remark-heading-BPCoYwjn.js';
2
+ import { S as StructuredData } from '../remark-structure-DkCXCzpD.js';
3
+ import '../remark-code-tab-DmyIyi6m.js';
4
4
  import 'mdast';
5
5
  import 'unified';
6
6
  import 'mdast-util-mdx-jsx';
@@ -1,5 +1,5 @@
1
1
  import { TypedDocument, Orama, Language, RawData, create, SearchParams } from '@orama/orama';
2
- import { S as StructuredData } from '../remark-structure-DVje0Sib.js';
2
+ import { S as StructuredData } from '../remark-structure-DkCXCzpD.js';
3
3
  import { S as SortedResult } from '../shared-ORgOfXFw.js';
4
4
  export { H as HighlightedText, c as createContentHighlighter } from '../shared-ORgOfXFw.js';
5
5
  import { I18nConfig } from '../i18n/index.js';
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  remarkHeading
3
- } from "../chunk-Y2774T3B.js";
3
+ } from "../chunk-QMATWJ5F.js";
4
4
  import "../chunk-JSBRDJBE.js";
5
5
 
6
6
  // src/server/get-toc.ts
@@ -266,6 +266,7 @@ interface PageTreeBuilderContext<Page extends PageData = PageData, Meta extends
266
266
  getUrl: UrlFn;
267
267
  storages?: Record<string, ContentStorage<Page, Meta>>;
268
268
  locale?: string;
269
+ visitedPaths: Set<string>;
269
270
  }
270
271
  interface PageTreeTransformer<Page extends PageData = any, Meta extends MetaData = any> {
271
272
  name?: string;
@@ -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);
@@ -297,6 +300,7 @@ function createPageTreeBuilder(getUrl) {
297
300
  locale,
298
301
  storage,
299
302
  storages,
303
+ visitedPaths: /* @__PURE__ */ new Set(),
300
304
  resolveName(name, format) {
301
305
  return resolve(name, format) ?? name;
302
306
  }
@@ -574,7 +578,7 @@ function createOutput(options) {
574
578
  pageTree = v;
575
579
  } else {
576
580
  pageTree = {
577
- defaultLanguage: v
581
+ [defaultLanguage]: v
578
582
  };
579
583
  }
580
584
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "15.7.1",
3
+ "version": "15.7.3",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -118,13 +118,13 @@
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",
@@ -1,31 +0,0 @@
1
- import { Root, Heading } from 'mdast';
2
- import { Transformer } from 'unified';
3
-
4
- declare module 'mdast' {
5
- interface HeadingData extends Data {
6
- hProperties?: {
7
- id?: string;
8
- };
9
- }
10
- }
11
- interface RemarkHeadingOptions {
12
- slug?: (root: Root, heading: Heading, text: string) => string;
13
- /**
14
- * Allow custom headings ids
15
- *
16
- * @defaultValue true
17
- */
18
- customId?: boolean;
19
- /**
20
- * Attach an array of `TOCItemType` to `file.data.toc`
21
- *
22
- * @defaultValue true
23
- */
24
- generateToc?: boolean;
25
- }
26
- /**
27
- * Add heading ids and extract TOC
28
- */
29
- declare function remarkHeading({ slug: defaultSlug, customId, generateToc, }?: RemarkHeadingOptions): Transformer<Root, Root>;
30
-
31
- export { type RemarkHeadingOptions as R, remarkHeading as r };