fumadocs-core 16.15.17 → 16.16.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,7 +1,7 @@
1
1
  import { remarkHeading } from "./remark-heading.js";
2
2
  import { generateCodeBlockTabs, parseCodeBlockAttributes } from "./codeblock-utils.js";
3
3
  import { remarkGfm } from "./remark-gfm.js";
4
- import { r as transformerTab } from "../rehype-code.core-BwKlAUQz.js";
4
+ import { r as transformerTab } from "../rehype-code.core-BFENmmvo.js";
5
5
  import { transformerIcon } from "./transformer-icon.js";
6
6
  import { rehypeCode, rehypeCodeDefaultOptions } from "./rehype-code.js";
7
7
  import { remarkImage } from "./remark-image.js";
@@ -1,3 +1,3 @@
1
- import { n as rehypeCodeDefaultOptions, r as transformerTab, t as createRehypeCode } from "../rehype-code.core-BwKlAUQz.js";
1
+ import { n as rehypeCodeDefaultOptions, r as transformerTab, t as createRehypeCode } from "../rehype-code.core-BFENmmvo.js";
2
2
  import { transformerIcon } from "./transformer-icon.js";
3
3
  export { createRehypeCode, rehypeCodeDefaultOptions, transformerIcon, transformerTab };
@@ -1,5 +1,5 @@
1
1
  import { defaultShikiFactory, wasmShikiFactory } from "../highlight/shiki/full.js";
2
- import { n as rehypeCodeDefaultOptions$1, r as transformerTab, t as createRehypeCode } from "../rehype-code.core-BwKlAUQz.js";
2
+ import { n as rehypeCodeDefaultOptions$1, r as transformerTab, t as createRehypeCode } from "../rehype-code.core-BFENmmvo.js";
3
3
  import { transformerIcon } from "./transformer-icon.js";
4
4
  //#region src/mdx-plugins/rehype-code.ts
5
5
  const rehypeCodeDefaultOptions = {
@@ -121,10 +121,11 @@ function transformerAddLanguage(lang) {
121
121
  };
122
122
  }
123
123
  //#endregion
124
- //#region ../../node_modules/.pnpm/@shikijs+transformers@4.4.3/node_modules/@shikijs/transformers/dist/index.mjs
124
+ //#region ../../node_modules/.pnpm/@shikijs+transformers@4.5.0/node_modules/@shikijs/transformers/dist/index.mjs
125
125
  const RE_SPLIT_COMMENT = /(\s+\/\/)/;
126
126
  const RE_V1_END_COMMENT_PREFIX = /(?:\/\/|["'#]|;{1,2}|%{1,2}|--)(\s*)$/;
127
127
  const RE_V3_END_COMMENT_PREFIX = /(?:\/\/|#|;{1,2}|%{1,2}|--)(\s*)$/;
128
+ const RE_LINE_COMMENT_PREFIX = /^(?:\/\/|["'#]|;{1,2}|%{1,2}|--)$/;
128
129
  /**
129
130
  * some comment formats have to be located at the end of line
130
131
  * hence we can skip matching them for other tokens
@@ -184,7 +185,7 @@ function parseComments(lines, jsx, matchAlgorithm) {
184
185
  const prevToken = elements[i - 1];
185
186
  if (prevToken?.type === "element") {
186
187
  const prevHead = prevToken.children.at(0);
187
- if (prevHead?.type === "text" && prevHead.value.includes("//")) {
188
+ if (prevHead?.type === "text" && (prevHead.value.includes("//") || RE_LINE_COMMENT_PREFIX.test(prevHead.value.trim()))) {
188
189
  const combinedMatch = matchToken(prevHead.value + head.value, isLast);
189
190
  if (combinedMatch) {
190
191
  match = combinedMatch;
@@ -270,6 +271,7 @@ function v3ClearEndCommentPrefix(text) {
270
271
  if (match && match[1].trim().length === 0) return text.slice(0, match.index).trimEnd();
271
272
  return text;
272
273
  }
274
+ const RE_NOTATION = /#?\s*\[!code\b(?:\\.|[^\]])*\]/gi;
273
275
  function createCommentNotationTransformer(name, regex, onMatch, matchAlgorithm) {
274
276
  matchAlgorithm ??= "v3";
275
277
  return {
@@ -284,7 +286,8 @@ function createCommentNotationTransformer(name, regex, onMatch, matchAlgorithm)
284
286
  for (const comment of parsed) {
285
287
  if (comment.info[1].length === 0) continue;
286
288
  let lineIdx = lines.indexOf(comment.line);
287
- if (comment.isLineCommentOnly && matchAlgorithm !== "v1") lineIdx++;
289
+ const isStandaloneNotation = comment.info[1].replace(RE_NOTATION, "").replace(regex, "").trim().length === 0;
290
+ if (comment.isLineCommentOnly && isStandaloneNotation && matchAlgorithm !== "v1") lineIdx++;
288
291
  let replaced = false;
289
292
  comment.info[1] = comment.info[1].replace(regex, (...match) => {
290
293
  if (onMatch.call(this, match, comment.line, comment.token, lines, lineIdx)) {
@@ -449,12 +452,13 @@ function inheritElement(original, overrides) {
449
452
  const RE_CODE_WORD = /\s*\[!code word:((?:\\.|[^:\]])+)(:\d+)?\]/;
450
453
  const RE_UNESCAPE = /\\(.)/g;
451
454
  function transformerNotationWordHighlight(options = {}) {
452
- const { classActiveWord = "highlighted-word", classActivePre = void 0 } = options;
455
+ const { classActiveWord = "highlighted-word", classActivePre = void 0, classActiveCode = void 0 } = options;
453
456
  return createCommentNotationTransformer("@shikijs/transformers:notation-highlight-word", RE_CODE_WORD, function([_, word, range], _line, comment, lines, index) {
454
457
  const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length;
455
458
  word = word.replace(RE_UNESCAPE, "$1");
456
459
  for (let i = index; i < Math.min(index + lineNum, lines.length); i++) highlightWordInLine.call(this, lines[i], comment, word, classActiveWord);
457
460
  if (classActivePre) this.addClassToHast(this.pre, classActivePre);
461
+ if (classActiveCode) this.addClassToHast(this.code, classActiveCode);
458
462
  return true;
459
463
  }, options.matchAlgorithm);
460
464
  }
@@ -0,0 +1,20 @@
1
+ import { n as SearchClient } from "../../client-BcR4jyFl.js";
2
+ import { Meilisearch } from "meilisearch";
3
+ //#region src/search/client/meilisearch.d.ts
4
+ export interface MeilisearchOptions {
5
+ /**
6
+ * Meilisearch client with a search API key
7
+ */
8
+ client: Meilisearch;
9
+ indexName: string;
10
+ /**
11
+ * Filter results with specific tag(s).
12
+ */
13
+ tag?: string | string[];
14
+ /**
15
+ * Filter results by locale.
16
+ */
17
+ locale?: string;
18
+ }
19
+ export declare function meilisearchClient(options: MeilisearchOptions): SearchClient;
20
+ //#endregion
@@ -0,0 +1,50 @@
1
+ import { createContentHighlighter } from "../index.js";
2
+ //#region src/search/client/meilisearch.ts
3
+ function meilisearchClient(options) {
4
+ const { client, indexName, tag, locale } = options;
5
+ return {
6
+ deps: [
7
+ client,
8
+ indexName,
9
+ tag,
10
+ locale
11
+ ],
12
+ async search(query) {
13
+ if (query.trim().length === 0) return [];
14
+ const filter = [];
15
+ if (locale) filter.push(`locale = ${JSON.stringify(locale)}`);
16
+ for (const item of typeof tag === "string" ? [tag] : tag ?? []) filter.push(`tag = ${JSON.stringify(item)}`);
17
+ const { hits } = await client.index(indexName).search(query, {
18
+ filter,
19
+ limit: 20
20
+ });
21
+ const highlighter = createContentHighlighter(query);
22
+ const groups = /* @__PURE__ */ new Map();
23
+ for (const hit of hits) {
24
+ let group = groups.get(hit.url);
25
+ if (!group) {
26
+ group = [{
27
+ id: hit.url,
28
+ type: "page",
29
+ content: highlighter.highlightMarkdown(hit.title),
30
+ breadcrumbs: hit.breadcrumbs,
31
+ url: hit.url
32
+ }];
33
+ groups.set(hit.url, group);
34
+ }
35
+ if (hit.type === "page") continue;
36
+ group.push({
37
+ id: hit.id.toString(),
38
+ type: hit.type,
39
+ content: highlighter.highlightMarkdown(hit.content),
40
+ url: hit.heading ? `${hit.url}#${hit.heading}` : hit.url
41
+ });
42
+ }
43
+ const results = [];
44
+ for (const group of groups.values()) results.push(...group);
45
+ return results;
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ export { meilisearchClient };
@@ -0,0 +1,50 @@
1
+ import { t as Awaitable } from "../types-D89QoQR-.js";
2
+ import { i as StructuredData } from "../remark-structure-CnHwvNZr.js";
3
+ import { S as LoaderOutput, b as LoaderConfig } from "../index-CBtz3H2e.js";
4
+ import { Meilisearch } from "meilisearch";
5
+ //#region src/search/meilisearch.d.ts
6
+ export interface DocumentRecord {
7
+ title: string;
8
+ description?: string;
9
+ breadcrumbs?: string[];
10
+ /**
11
+ * URL to the page
12
+ */
13
+ url: string;
14
+ structured: StructuredData;
15
+ /**
16
+ * Tag(s) to filter results
17
+ */
18
+ tag?: string | string[];
19
+ locale?: string;
20
+ }
21
+ /**
22
+ * Document in Meilisearch, for the title, headings and paragraphs of a page
23
+ */
24
+ export interface MeilisearchDocument extends Omit<DocumentRecord, 'description' | 'structured'> {
25
+ id: number;
26
+ type: 'page' | 'heading' | 'text';
27
+ /**
28
+ * ID of the heading it belongs to
29
+ */
30
+ heading?: string;
31
+ content: string;
32
+ }
33
+ /**
34
+ * Build the search documents of every page in a source.
35
+ */
36
+ export declare function toDocuments<C extends LoaderConfig>(source: LoaderOutput<C> | (() => Awaitable<LoaderOutput<C>>), options?: {
37
+ /** Tag to filter results by. */
38
+ tag?: (page: C['page']) => string | string[];
39
+ }): Promise<DocumentRecord[]>;
40
+ export interface SyncOptions {
41
+ indexName: string;
42
+ documents: DocumentRecord[];
43
+ }
44
+ /**
45
+ * Configure the index and replace its documents, existing documents stay searchable until the new ones are indexed.
46
+ *
47
+ * @param client - Meilisearch client with an admin API key
48
+ */
49
+ export declare function sync(client: Meilisearch, options: SyncOptions): Promise<void>;
50
+ //#endregion
@@ -0,0 +1,55 @@
1
+ import { n as buildDocuments, t as buildBreadcrumbs } from "../build-index-BD-VRKOW.js";
2
+ //#region src/search/meilisearch.ts
3
+ /**
4
+ * Build the search documents of every page in a source.
5
+ */
6
+ async function toDocuments(source, options = {}) {
7
+ const loader = typeof source === "function" ? await source() : source;
8
+ return buildDocuments(loader, (index, page) => ({
9
+ title: index.title,
10
+ description: index.description,
11
+ breadcrumbs: buildBreadcrumbs(loader, page),
12
+ url: index.url,
13
+ structured: index.structuredData,
14
+ tag: options.tag?.(page),
15
+ locale: page.locale
16
+ }));
17
+ }
18
+ /**
19
+ * Configure the index and replace its documents, existing documents stay searchable until the new ones are indexed.
20
+ *
21
+ * @param client - Meilisearch client with an admin API key
22
+ */
23
+ async function sync(client, options) {
24
+ const index = client.index(options.indexName);
25
+ const documents = [];
26
+ for (const { description, structured, ...page } of options.documents) {
27
+ const add = (type, content, heading) => documents.push({
28
+ ...page,
29
+ id: documents.length,
30
+ type,
31
+ content,
32
+ heading
33
+ });
34
+ add("page", page.title);
35
+ if (description && !structured.contents.some((item) => item.content === description)) add("text", description);
36
+ for (const heading of structured.headings) add("heading", heading.content, heading.id);
37
+ for (const item of structured.contents) add("text", item.content, item.heading);
38
+ }
39
+ await run(index.updateSettings({
40
+ searchableAttributes: ["content"],
41
+ filterableAttributes: [
42
+ "id",
43
+ "tag",
44
+ "locale"
45
+ ]
46
+ }));
47
+ await run(index.addDocuments(documents, { primaryKey: "id" }));
48
+ await run(index.deleteDocuments({ filter: `id >= ${documents.length}` }));
49
+ }
50
+ async function run(task) {
51
+ const { status, error } = await task.waitTask({ timeout: 0 });
52
+ if (status !== "succeeded") throw new Error(error ? error.message : `Meilisearch task ${status}`, { cause: error });
53
+ }
54
+ //#endregion
55
+ export { sync, toDocuments };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "16.15.17",
3
+ "version": "16.16.0",
4
4
  "description": "The React.js library for building a documentation website",
5
5
  "keywords": [
6
6
  "Docs",
@@ -75,11 +75,13 @@
75
75
  "./search/client/algolia": "./dist/search/client/algolia.js",
76
76
  "./search/client/fetch": "./dist/search/client/fetch.js",
77
77
  "./search/client/flexsearch-static": "./dist/search/client/flexsearch-static.js",
78
+ "./search/client/meilisearch": "./dist/search/client/meilisearch.js",
78
79
  "./search/client/mixedbread": "./dist/search/client/mixedbread.js",
79
80
  "./search/client/orama-cloud": "./dist/search/client/orama-cloud.js",
80
81
  "./search/client/orama-cloud-legacy": "./dist/search/client/orama-cloud-legacy.js",
81
82
  "./search/client/orama-static": "./dist/search/client/orama-static.js",
82
83
  "./search/flexsearch": "./dist/search/flexsearch.js",
84
+ "./search/meilisearch": "./dist/search/meilisearch.js",
83
85
  "./search/mixedbread": "./dist/search/mixedbread.js",
84
86
  "./search/orama-cloud": "./dist/search/orama-cloud.js",
85
87
  "./search/orama-cloud-legacy": "./dist/search/orama-cloud-legacy.js",
@@ -116,39 +118,40 @@
116
118
  "hast-util-to-estree": "^3.1.3",
117
119
  "hast-util-to-jsx-runtime": "^2.3.6",
118
120
  "mdast-util-mdx": "^3.0.0",
119
- "mdast-util-to-markdown": "^2.1.2",
121
+ "mdast-util-to-markdown": "^2.1.3",
120
122
  "npm-to-yarn": "3.2.0",
121
123
  "remark": "^15.0.1",
122
124
  "remark-gfm": "^4.0.1",
123
125
  "remark-rehype": "^11.1.2",
124
126
  "scroll-into-view-if-needed": "^3.1.0",
125
- "shiki": "^4.4.3",
127
+ "shiki": "^4.5.0",
126
128
  "tinyglobby": "^0.2.17",
127
129
  "unified": "^11.0.5",
128
130
  "unist-util-visit": "^5.1.0",
129
131
  "vfile": "^6.0.3",
130
132
  "yaml": "^2.9.1",
131
- "zbsearch": "^4.0.0"
133
+ "zbsearch": "^4.0.1"
132
134
  },
133
135
  "devDependencies": {
134
136
  "@formatjs/intl-localematcher": "^0.9.0",
135
137
  "@mdx-js/mdx": "^3.1.1",
136
138
  "@mixedbread/sdk": "0.78.0",
137
- "@modelcontextprotocol/server": "2.0.0",
139
+ "@modelcontextprotocol/server": "2.2.0",
138
140
  "@orama/core": "^1.2.19",
139
141
  "@oramacloud/client": "^2.1.4",
140
- "@shikijs/transformers": "^4.4.3",
141
- "@tanstack/react-router": "1.170.38",
142
+ "@shikijs/transformers": "^4.5.0",
143
+ "@tanstack/react-router": "1.170.41",
142
144
  "@types/estree-jsx": "^1.0.5",
143
145
  "@types/hast": "^3.0.5",
144
146
  "@types/mdast": "^4.0.4",
145
- "@types/node": "26.6.1",
147
+ "@types/node": "26.6.4",
146
148
  "@types/react": "^19.3.0",
147
149
  "@types/react-dom": "^19.3.0",
148
150
  "algoliasearch": "5.59.0",
149
151
  "flexsearch": "^0.8.212",
150
- "lucide-react": "^1.47.0",
151
- "next": "16.3.5",
152
+ "lucide-react": "^1.50.0",
153
+ "meilisearch": "0.62.0",
154
+ "next": "16.3.8",
152
155
  "path-to-regexp": "^8.4.2",
153
156
  "react-router": "^8.4.0",
154
157
  "rehype-raw": "^7.0.0",
@@ -157,7 +160,7 @@
157
160
  "tsconfig": "0.0.0",
158
161
  "tsdown": "0.23.0",
159
162
  "typescript": "^7.0.2",
160
- "waku": "1.0.0-rc.1",
163
+ "waku": "1.0.0-rc.2",
161
164
  "zod": "4.6.5"
162
165
  },
163
166
  "peerDependencies": {
@@ -174,6 +177,7 @@
174
177
  "algoliasearch": "5.x.x",
175
178
  "flexsearch": "*",
176
179
  "lucide-react": "*",
180
+ "meilisearch": ">=0.50.0 <1.0.0",
177
181
  "next": "16.x.x",
178
182
  "react": "^19.2.0",
179
183
  "react-dom": "^19.2.0",
@@ -215,6 +219,9 @@
215
219
  "algoliasearch": {
216
220
  "optional": true
217
221
  },
222
+ "meilisearch": {
223
+ "optional": true
224
+ },
218
225
  "next": {
219
226
  "optional": true
220
227
  },
@@ -243,7 +250,7 @@
243
250
  "inlinedDependencies": {
244
251
  "@formatjs/fast-memoize": "3.1.7",
245
252
  "@formatjs/intl-localematcher": "0.9.0",
246
- "@shikijs/transformers": "4.4.3",
253
+ "@shikijs/transformers": "4.5.0",
247
254
  "path-to-regexp": "8.4.2"
248
255
  },
249
256
  "scripts": {