fumadocs-core 13.2.0 → 13.2.1

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.
@@ -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 {
@@ -270,68 +270,101 @@ 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(resolveSrc(src, publicDir))
329
+ });
330
+ Object.assign(node, {
331
+ type: "mdxJsxFlowElement",
332
+ name: "img",
333
+ attributes: [
334
+ {
335
+ type: "mdxJsxAttribute",
336
+ name: "alt",
337
+ value: node.alt ?? "image"
338
+ },
339
+ hasBlur && {
340
+ type: "mdxJsxAttribute",
341
+ name: "placeholder",
342
+ value: "blur"
343
+ },
344
+ {
345
+ type: "mdxJsxAttribute",
346
+ name: "src",
347
+ value: {
348
+ type: "mdxJsxAttributeValueExpression",
349
+ value: variableName,
350
+ data: {
351
+ estree: {
352
+ body: [
353
+ {
354
+ type: "ExpressionStatement",
355
+ expression: { type: "Identifier", name: variableName }
356
+ }
357
+ ]
358
+ }
327
359
  }
328
360
  }
329
361
  }
330
- }
331
- ].filter(Boolean)
332
- });
362
+ ].filter(Boolean)
363
+ });
364
+ }
333
365
  });
334
- if (importsToInject.length) {
366
+ await Promise.all(promises);
367
+ if (importsToInject.length > 0) {
335
368
  const imports = importsToInject.map(
336
369
  ({ variableName, importPath }) => ({
337
370
  type: "mdxjsEsm",
@@ -355,9 +388,20 @@ function remarkImage({
355
388
  );
356
389
  tree.children.unshift(...imports);
357
390
  }
358
- done();
359
391
  };
360
392
  }
393
+ function resolveSrc(src, dir) {
394
+ return src.startsWith("/") || !path.isAbsolute(src) ? path.join(dir, src) : src;
395
+ }
396
+ async function getImageSize(src, dir) {
397
+ if (EXTERNAL_URL_REGEX.test(src)) {
398
+ const res = await fetch(src);
399
+ return sizeOf(
400
+ await res.arrayBuffer().then((buffer) => new Uint8Array(buffer))
401
+ );
402
+ }
403
+ return sizeOf(resolveSrc(src, dir));
404
+ }
361
405
 
362
406
  // src/mdx-plugins/remark-structure.ts
363
407
  import Slugger from "github-slugger";
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.1",
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
  },