fumadocs-core 13.2.0 → 13.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,12 @@ function remarkHeading({
20
20
  return (root, file) => {
21
21
  const toc = [];
22
22
  slugger.reset();
23
+ if (file.data.frontmatter) {
24
+ const frontmatter = file.data.frontmatter;
25
+ if (frontmatter._openapi?.toc) {
26
+ toc.push(...frontmatter._openapi.toc);
27
+ }
28
+ }
23
29
  visit(root, "heading", (heading) => {
24
30
  heading.data ||= {};
25
31
  heading.data.hProperties ||= {};
@@ -43,17 +43,41 @@ type RehypeCodeOptions = RehypeShikiOptions & {
43
43
  declare function rehypeCode(this: Processor, options?: Partial<RehypeCodeOptions>): Transformer<Root, Root>;
44
44
 
45
45
  interface RemarkImageOptions {
46
+ /**
47
+ * Directory to resolve absolute image paths
48
+ */
49
+ publicDir?: string;
46
50
  /**
47
51
  * Preferred placeholder type
48
52
  *
49
53
  * @defaultValue 'blur'
50
54
  */
51
55
  placeholder?: 'blur' | 'none';
56
+ /**
57
+ * Import images in the file, and let bundlers handle it.
58
+ *
59
+ * ```tsx
60
+ * import MyImage from "./public/img.png";
61
+ *
62
+ * <img src={MyImage} />
63
+ * ```
64
+ *
65
+ * When disabled, `placeholder` will be ignored.
66
+ *
67
+ * @defaultValue true
68
+ */
69
+ useImport?: boolean;
70
+ /**
71
+ * Fetch image size of external URLs
72
+ *
73
+ * @defaultValue true
74
+ */
75
+ external?: boolean;
52
76
  }
53
77
  /**
54
- * Turn images into static imports
78
+ * Turn images into Next.js Image compatible usage.
55
79
  */
56
- declare function remarkImage({ placeholder, }?: RemarkImageOptions): Transformer<Root$1, Root$1>;
80
+ declare function remarkImage({ placeholder, external, useImport, publicDir, }?: RemarkImageOptions): Transformer<Root$1, Root$1>;
57
81
 
58
82
  declare module 'mdast' {
59
83
  interface HeadingData extends Data {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  flattenNode,
3
3
  remarkHeading
4
- } from "../chunk-YKIM647L.js";
4
+ } from "../chunk-UQV4A7HQ.js";
5
5
  import {
6
6
  slash
7
7
  } from "../chunk-UWEEHUJV.js";
@@ -270,68 +270,104 @@ function transformerTab() {
270
270
  }
271
271
 
272
272
  // src/mdx-plugins/remark-image.ts
273
- import { existsSync } from "fs";
274
273
  import path from "path";
275
274
  import { visit } from "unist-util-visit";
275
+ import sizeOf from "image-size";
276
276
  var VALID_BLUR_EXT = [".jpeg", ".png", ".webp", ".avif", ".jpg"];
277
277
  var EXTERNAL_URL_REGEX = /^https?:\/\//;
278
- var PUBLIC_DIR = path.join(process.cwd(), "public");
279
278
  function remarkImage({
280
- placeholder = "blur"
279
+ placeholder = "blur",
280
+ external = true,
281
+ useImport = true,
282
+ publicDir = path.join(process.cwd(), "public")
281
283
  } = {}) {
282
- return (tree, _file, done) => {
284
+ return async (tree) => {
283
285
  const importsToInject = [];
286
+ const promises = [];
284
287
  visit(tree, "image", (node) => {
285
- let url = decodeURI(node.url);
286
- if (!url || EXTERNAL_URL_REGEX.test(url)) {
287
- return;
288
- }
289
- if (url.startsWith("/")) {
290
- const urlPath = path.join(PUBLIC_DIR, url);
291
- if (!existsSync(urlPath)) {
292
- return;
293
- }
294
- url = slash(urlPath);
295
- }
296
- const variableName = `__img${importsToInject.length.toString()}`;
297
- const hasBlur = placeholder === "blur" && VALID_BLUR_EXT.some((ext) => url.endsWith(ext));
298
- importsToInject.push({ variableName, importPath: url });
299
- Object.assign(node, {
300
- type: "mdxJsxFlowElement",
301
- name: "img",
302
- attributes: [
303
- {
304
- type: "mdxJsxAttribute",
305
- name: "alt",
306
- value: node.alt ?? "image"
307
- },
308
- hasBlur && {
309
- type: "mdxJsxAttribute",
310
- name: "placeholder",
311
- value: "blur"
312
- },
313
- {
314
- type: "mdxJsxAttribute",
315
- name: "src",
316
- value: {
317
- type: "mdxJsxAttributeValueExpression",
318
- value: variableName,
319
- data: {
320
- estree: {
321
- body: [
322
- {
323
- type: "ExpressionStatement",
324
- expression: { type: "Identifier", name: variableName }
325
- }
326
- ]
288
+ const src = decodeURI(node.url);
289
+ if (!src) return;
290
+ const isExternal = EXTERNAL_URL_REGEX.test(src);
291
+ if (isExternal && external || !useImport) {
292
+ promises.push(
293
+ getImageSize(src, publicDir).then((size) => {
294
+ if (!size.width || !size.height) return;
295
+ Object.assign(node, {
296
+ type: "mdxJsxFlowElement",
297
+ name: "img",
298
+ attributes: [
299
+ {
300
+ type: "mdxJsxAttribute",
301
+ name: "alt",
302
+ value: node.alt ?? "image"
303
+ },
304
+ {
305
+ type: "mdxJsxAttribute",
306
+ name: "src",
307
+ value: src
308
+ },
309
+ {
310
+ type: "mdxJsxAttribute",
311
+ name: "width",
312
+ value: size.width.toString()
313
+ },
314
+ {
315
+ type: "mdxJsxAttribute",
316
+ name: "height",
317
+ value: size.height.toString()
318
+ }
319
+ ]
320
+ });
321
+ })
322
+ );
323
+ } else if (!isExternal) {
324
+ const variableName = `__img${importsToInject.length.toString()}`;
325
+ const hasBlur = placeholder === "blur" && VALID_BLUR_EXT.some((ext) => src.endsWith(ext));
326
+ importsToInject.push({
327
+ variableName,
328
+ importPath: slash(
329
+ // with imports, relative paths don't have to be absolute
330
+ src.startsWith("/") ? path.join(publicDir, src) : src
331
+ )
332
+ });
333
+ Object.assign(node, {
334
+ type: "mdxJsxFlowElement",
335
+ name: "img",
336
+ attributes: [
337
+ {
338
+ type: "mdxJsxAttribute",
339
+ name: "alt",
340
+ value: node.alt ?? "image"
341
+ },
342
+ hasBlur && {
343
+ type: "mdxJsxAttribute",
344
+ name: "placeholder",
345
+ value: "blur"
346
+ },
347
+ {
348
+ type: "mdxJsxAttribute",
349
+ name: "src",
350
+ value: {
351
+ type: "mdxJsxAttributeValueExpression",
352
+ value: variableName,
353
+ data: {
354
+ estree: {
355
+ body: [
356
+ {
357
+ type: "ExpressionStatement",
358
+ expression: { type: "Identifier", name: variableName }
359
+ }
360
+ ]
361
+ }
327
362
  }
328
363
  }
329
364
  }
330
- }
331
- ].filter(Boolean)
332
- });
365
+ ].filter(Boolean)
366
+ });
367
+ }
333
368
  });
334
- if (importsToInject.length) {
369
+ await Promise.all(promises);
370
+ if (importsToInject.length > 0) {
335
371
  const imports = importsToInject.map(
336
372
  ({ variableName, importPath }) => ({
337
373
  type: "mdxjsEsm",
@@ -355,9 +391,20 @@ function remarkImage({
355
391
  );
356
392
  tree.children.unshift(...imports);
357
393
  }
358
- done();
359
394
  };
360
395
  }
396
+ function resolveSrc(src, dir) {
397
+ return src.startsWith("/") || !path.isAbsolute(src) ? path.join(dir, src) : src;
398
+ }
399
+ async function getImageSize(src, dir) {
400
+ if (EXTERNAL_URL_REGEX.test(src)) {
401
+ const res = await fetch(src);
402
+ return sizeOf(
403
+ await res.arrayBuffer().then((buffer) => new Uint8Array(buffer))
404
+ );
405
+ }
406
+ return sizeOf(resolveSrc(src, dir));
407
+ }
361
408
 
362
409
  // src/mdx-plugins/remark-structure.ts
363
410
  import Slugger from "github-slugger";
@@ -373,6 +420,13 @@ function remarkStructure({
373
420
  slugger.reset();
374
421
  const data = { contents: [], headings: [] };
375
422
  let lastHeading = "";
423
+ if (file.data.frontmatter) {
424
+ const frontmatter = file.data.frontmatter;
425
+ if (frontmatter._openapi?.structuredData) {
426
+ data.headings.push(...frontmatter._openapi.structuredData.headings);
427
+ data.contents.push(...frontmatter._openapi.structuredData.contents);
428
+ }
429
+ }
376
430
  visit2(node, types, (element) => {
377
431
  if (element.type === "root") return;
378
432
  const content = flattenNode(element).trim();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  remarkHeading
3
- } from "../chunk-YKIM647L.js";
3
+ } from "../chunk-UQV4A7HQ.js";
4
4
  import "../chunk-MLKGABMK.js";
5
5
 
6
6
  // src/server/get-toc.ts
package/dist/toc.js CHANGED
@@ -36,12 +36,14 @@ function useAnchorObserver(watch) {
36
36
  return f ?? watch[0];
37
37
  });
38
38
  },
39
- { rootMargin: `-100px 0% -75% 0%`, threshold: 1 }
39
+ { rootMargin: `-80px 0% -78% 0%`, threshold: 1 }
40
40
  );
41
41
  const scroll = () => {
42
42
  const element = document.scrollingElement;
43
43
  if (!element) return;
44
- if (element.scrollTop >= // assume you have a 10px margin
44
+ if (element.scrollTop === 0) {
45
+ setActiveAnchor(watch.at(0));
46
+ } else if (element.scrollTop >= // assume you have a 10px margin
45
47
  element.scrollHeight - element.clientHeight - 10) {
46
48
  setActiveAnchor(watch.at(-1));
47
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "13.2.0",
3
+ "version": "13.2.2",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -120,10 +120,11 @@
120
120
  ],
121
121
  "dependencies": {
122
122
  "@formatjs/intl-localematcher": "^0.5.4",
123
- "@shikijs/rehype": "^1.11.1",
124
- "@shikijs/transformers": "^1.11.1",
123
+ "@shikijs/rehype": "^1.12.1",
124
+ "@shikijs/transformers": "^1.12.1",
125
125
  "flexsearch": "0.7.21",
126
126
  "github-slugger": "^2.0.0",
127
+ "image-size": "^1.1.1",
127
128
  "negotiator": "^0.6.3",
128
129
  "npm-to-yarn": "^2.2.1",
129
130
  "react-remove-scroll": "^2.5.10",
@@ -131,7 +132,7 @@
131
132
  "remark-gfm": "^4.0.0",
132
133
  "remark-mdx": "^3.0.1",
133
134
  "scroll-into-view-if-needed": "^3.1.0",
134
- "shiki": "^1.11.1",
135
+ "shiki": "^1.12.1",
135
136
  "swr": "^2.2.5",
136
137
  "unist-util-visit": "^5.0.0"
137
138
  },