mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core (Remark Image): Support fetching image size of external urls
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
Remark Image: Support fetching image size of external urls
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: Remark Image
|
||||
description: Convert images into static imports
|
||||
description: Make images compatible with Next.js Image Optimization
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Convert images into imports (MDX only).
|
||||
Add it to your Remark plugins.
|
||||
|
||||
```ts
|
||||
import { remarkImage } from 'fumadocs-core/mdx-plugins';
|
||||
@@ -15,19 +15,82 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### Example
|
||||
Supported:
|
||||
|
||||
- Local Images
|
||||
- External URLs
|
||||
- Next.js static imports
|
||||
|
||||
### How It Works
|
||||
|
||||
It transforms your `` into Next.js Image usage, and add required props like `width` and `height`.
|
||||
|
||||
By default, it uses Next.js static imports to import local images, which supports the `placeholder` option of Next.js Image.
|
||||
|
||||
Next.js can handle image imports with its built-in image loader.
|
||||
|
||||
```mdx
|
||||

|
||||
import MyImage from './public/image.png';
|
||||
|
||||
<img alt="image" src={MyImage} />
|
||||
```
|
||||
|
||||
Otherwise, it obtains the image size from file-system or HTTP request.
|
||||
|
||||
```mdx
|
||||
<img alt="image" src="/image.png" widht="1980" height="1080" />
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```json doc-gen:typescript
|
||||
{
|
||||
"file": "./content/docs/headless/props.ts",
|
||||
"name": "RemarkImageOptions"
|
||||
}
|
||||
```
|
||||
|
||||
### Example: With Imports
|
||||
|
||||
```mdx
|
||||

|
||||

|
||||
```
|
||||
|
||||
Yields:
|
||||
|
||||
```mdx
|
||||
import HelloImage from './hello.png';
|
||||
import HelloImage from './public/hello.png';
|
||||
|
||||
<img alt="Hello" src={HelloImage} />
|
||||
<img
|
||||
alt="Test"
|
||||
src="https://example.com/image.png"
|
||||
width="1980"
|
||||
height="1080"
|
||||
/>
|
||||
```
|
||||
|
||||
You need an image loader to handle image imports.
|
||||
In Next.js, this is already done by a Webpack/Turbopack plugin
|
||||
### Example: Without Imports
|
||||
|
||||
You can disable Next.js static imports on local images.
|
||||
|
||||
```ts
|
||||
import { remarkImage } from 'fumadocs-core/mdx-plugins';
|
||||
|
||||
export default {
|
||||
remarkPlugins: [[remarkImage, { useImport: false }]],
|
||||
};
|
||||
```
|
||||
|
||||
```mdx
|
||||

|
||||

|
||||
```
|
||||
|
||||
Yields:
|
||||
|
||||
```mdx
|
||||
<img alt="Hello" src"/hello.png" width="1980" height="1080" />
|
||||
<img alt="Test" src"https://example.com/image.png" width="1980" height="1080" />
|
||||
```
|
||||
|
||||
@@ -3,7 +3,7 @@ import type * as TOC from 'fumadocs-core/toc';
|
||||
import type * as Server from 'fumadocs-core/server';
|
||||
import type * as Sidebar from 'fumadocs-core/sidebar';
|
||||
import type { SortedResult as OriginalSortedResult } from 'fumadocs-core/search/shared';
|
||||
import type { ComponentPropsWithoutRef, ElementType } from 'react';
|
||||
import type { ElementType } from 'react';
|
||||
import type * as MDX from 'fumadocs-core/mdx-plugins';
|
||||
|
||||
export type SortedResult = OriginalSortedResult;
|
||||
@@ -24,3 +24,5 @@ export type PageTreeItem = Server.PageTree.Item;
|
||||
export type PageTreeFolder = Server.PageTree.Folder;
|
||||
export type PageTreeRoot = Server.PageTree.Root;
|
||||
export type PageTreeSeparator = Server.PageTree.Separator;
|
||||
|
||||
export type RemarkImageOptions = MDX.RemarkImageOptions;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"geist": "^1.3.1",
|
||||
"katex": "^0.16.11",
|
||||
"lucide-react": "^0.414.0",
|
||||
"lucide-react": "^0.427.0",
|
||||
"next": "^14.2.5",
|
||||
"phenomenon": "^1.6.0",
|
||||
"react": "^18.3.1",
|
||||
@@ -37,8 +37,8 @@
|
||||
"rehype-katex": "^7.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"satori": "^0.10.14",
|
||||
"shiki": "^1.11.1",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"shiki": "^1.12.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -48,15 +48,15 @@
|
||||
"@types/node": "20.14.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"glslify": "^7.1.1",
|
||||
"glslx": "^0.3.0",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"postcss": "^8.4.41",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tsconfig": "workspace:*",
|
||||
"tsx": "^4.16.2",
|
||||
"tsx": "^4.17.0",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.90.3"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@content-collections/core": "^0.6.3",
|
||||
"@content-collections/core": "^0.6.4",
|
||||
"@content-collections/mdx": "^0.1.3",
|
||||
"@content-collections/next": "^0.2.0",
|
||||
"@types/mdx": "^2.0.13",
|
||||
|
||||
@@ -34,3 +34,5 @@ curl -XPOST "https://api.unkey.dev/v1/keys.createKey" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"apiId": "api_123", "name": "My Key"}'
|
||||
```
|
||||
|
||||

|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
"fumadocs-openapi": "workspace:*",
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"next": "^14.2.5",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss": "^8.4.41",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+3
-3
@@ -19,8 +19,8 @@
|
||||
"prettier": "@vercel/style-guide/prettier",
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.7",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||
"@typescript-eslint/parser": "^8.1.0",
|
||||
"@vercel/style-guide": "^6.0.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.57.0",
|
||||
@@ -30,7 +30,7 @@
|
||||
"tsup": "8.2.4",
|
||||
"turbo": "2.0.12",
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^2.0.4"
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"engines": {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"rehype-img-size": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@content-collections/core": "^0.6.3",
|
||||
"@content-collections/core": "^0.6.4",
|
||||
"@content-collections/mdx": "^0.1.3",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"fumadocs-core": "workspace:*",
|
||||
|
||||
@@ -127,10 +127,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.4",
|
||||
"@shikijs/rehype": "^1.11.1",
|
||||
"@shikijs/transformers": "^1.11.1",
|
||||
"@shikijs/rehype": "^1.12.1",
|
||||
"@shikijs/transformers": "^1.12.1",
|
||||
"flexsearch": "0.7.21",
|
||||
"github-slugger": "^2.0.0",
|
||||
"image-size": "^1.1.1",
|
||||
"negotiator": "^0.6.3",
|
||||
"npm-to-yarn": "^2.2.1",
|
||||
"react-remove-scroll": "^2.5.10",
|
||||
@@ -138,7 +139,7 @@
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-mdx": "^3.0.1",
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"shiki": "^1.11.1",
|
||||
"shiki": "^1.12.1",
|
||||
"swr": "^2.2.5",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
|
||||
@@ -1,95 +1,155 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { Root, RootContent } from 'mdast';
|
||||
import type { Transformer } from 'unified';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import sizeOf from 'image-size';
|
||||
import { type ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||
import slash from '@/utils/slash';
|
||||
|
||||
const VALID_BLUR_EXT = ['.jpeg', '.png', '.webp', '.avif', '.jpg'];
|
||||
const EXTERNAL_URL_REGEX = /^https?:\/\//;
|
||||
const PUBLIC_DIR = path.join(process.cwd(), 'public');
|
||||
|
||||
export interface RemarkImageOptions {
|
||||
/**
|
||||
* Directory to resolve absolute image paths
|
||||
*/
|
||||
publicDir?: string;
|
||||
|
||||
/**
|
||||
* Preferred placeholder type
|
||||
*
|
||||
* @defaultValue 'blur'
|
||||
*/
|
||||
placeholder?: 'blur' | 'none';
|
||||
|
||||
/**
|
||||
* Import images in the file, and let bundlers handle it.
|
||||
*
|
||||
* ```tsx
|
||||
* import MyImage from "./public/img.png";
|
||||
*
|
||||
* <img src={MyImage} />
|
||||
* ```
|
||||
*
|
||||
* When disabled, `placeholder` will be ignored.
|
||||
*
|
||||
* @defaultValue true
|
||||
*/
|
||||
useImport?: boolean;
|
||||
|
||||
/**
|
||||
* Fetch image size of external URLs
|
||||
*
|
||||
* @defaultValue true
|
||||
*/
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
// Based on the Nextra: https://github.com/shuding/nextra
|
||||
|
||||
/**
|
||||
* Turn images into static imports
|
||||
* Turn images into Next.js Image compatible usage.
|
||||
*/
|
||||
export function remarkImage({
|
||||
placeholder = 'blur',
|
||||
external = true,
|
||||
useImport = true,
|
||||
publicDir = path.join(process.cwd(), 'public'),
|
||||
}: RemarkImageOptions = {}): Transformer<Root, Root> {
|
||||
return (tree, _file, done) => {
|
||||
return async (tree) => {
|
||||
const importsToInject: { variableName: string; importPath: string }[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
visit(tree, 'image', (node) => {
|
||||
let url = decodeURI(node.url);
|
||||
const src = decodeURI(node.url);
|
||||
if (!src) return;
|
||||
const isExternal = EXTERNAL_URL_REGEX.test(src);
|
||||
|
||||
if (!url || EXTERNAL_URL_REGEX.test(url)) {
|
||||
return;
|
||||
}
|
||||
if ((isExternal && external) || !useImport) {
|
||||
promises.push(
|
||||
getImageSize(src, publicDir).then((size) => {
|
||||
if (!size.width || !size.height) return;
|
||||
|
||||
if (url.startsWith('/')) {
|
||||
const urlPath = path.join(PUBLIC_DIR, url);
|
||||
Object.assign(node, {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'img',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'alt',
|
||||
value: node.alt ?? 'image',
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'src',
|
||||
value: src,
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'width',
|
||||
value: size.width.toString(),
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'height',
|
||||
value: size.height.toString(),
|
||||
},
|
||||
],
|
||||
});
|
||||
}),
|
||||
);
|
||||
} else if (!isExternal) {
|
||||
// Unique variable name for the given static image URL
|
||||
const variableName = `__img${importsToInject.length.toString()}`;
|
||||
const hasBlur =
|
||||
placeholder === 'blur' &&
|
||||
VALID_BLUR_EXT.some((ext) => src.endsWith(ext));
|
||||
|
||||
if (!existsSync(urlPath)) {
|
||||
return;
|
||||
}
|
||||
importsToInject.push({
|
||||
variableName,
|
||||
importPath: slash(resolveSrc(src, publicDir)),
|
||||
});
|
||||
|
||||
url = slash(urlPath);
|
||||
}
|
||||
|
||||
// Unique variable name for the given static image URL
|
||||
const variableName = `__img${importsToInject.length.toString()}`;
|
||||
const hasBlur =
|
||||
placeholder === 'blur' &&
|
||||
VALID_BLUR_EXT.some((ext) => url.endsWith(ext));
|
||||
importsToInject.push({ variableName, importPath: url });
|
||||
// Replace the image node with an MDX component node (Next.js Image)
|
||||
Object.assign(node, {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'img',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'alt',
|
||||
value: node.alt ?? 'image',
|
||||
},
|
||||
hasBlur && {
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'placeholder',
|
||||
value: 'blur',
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'src',
|
||||
value: {
|
||||
type: 'mdxJsxAttributeValueExpression',
|
||||
value: variableName,
|
||||
data: {
|
||||
estree: {
|
||||
body: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
expression: { type: 'Identifier', name: variableName },
|
||||
},
|
||||
],
|
||||
Object.assign(node, {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'img',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'alt',
|
||||
value: node.alt ?? 'image',
|
||||
},
|
||||
hasBlur && {
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'placeholder',
|
||||
value: 'blur',
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'src',
|
||||
value: {
|
||||
type: 'mdxJsxAttributeValueExpression',
|
||||
value: variableName,
|
||||
data: {
|
||||
estree: {
|
||||
body: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
expression: { type: 'Identifier', name: variableName },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
].filter(Boolean),
|
||||
});
|
||||
].filter(Boolean),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (importsToInject.length) {
|
||||
await Promise.all(promises);
|
||||
|
||||
if (importsToInject.length > 0) {
|
||||
const imports = importsToInject.map(
|
||||
({ variableName, importPath }) =>
|
||||
({
|
||||
@@ -115,7 +175,29 @@ export function remarkImage({
|
||||
|
||||
tree.children.unshift(...imports);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve `src` to an absolute path
|
||||
*/
|
||||
function resolveSrc(src: string, dir: string): string {
|
||||
return src.startsWith('/') || !path.isAbsolute(src)
|
||||
? path.join(dir, src)
|
||||
: src;
|
||||
}
|
||||
|
||||
async function getImageSize(
|
||||
src: string,
|
||||
dir: string,
|
||||
): Promise<ISizeCalculationResult> {
|
||||
if (EXTERNAL_URL_REGEX.test(src)) {
|
||||
const res = await fetch(src);
|
||||
|
||||
return sizeOf(
|
||||
await res.arrayBuffer().then((buffer) => new Uint8Array(buffer)),
|
||||
);
|
||||
}
|
||||
|
||||
return sizeOf(resolveSrc(src, dir));
|
||||
}
|
||||
|
||||
@@ -25,19 +25,21 @@ export function useAnchorObserver(watch: string[]): string | undefined {
|
||||
return f ?? watch[0];
|
||||
});
|
||||
},
|
||||
{ rootMargin: `-100px 0% -75% 0%`, threshold: 1 },
|
||||
{ rootMargin: `-80px 0% -78% 0%`, threshold: 1 },
|
||||
);
|
||||
|
||||
const scroll = (): void => {
|
||||
const element = document.scrollingElement;
|
||||
if (!element) return;
|
||||
|
||||
// select the last item when reached the bottom
|
||||
if (
|
||||
if (element.scrollTop === 0) {
|
||||
setActiveAnchor(watch.at(0));
|
||||
} else if (
|
||||
element.scrollTop >=
|
||||
// assume you have a 10px margin
|
||||
element.scrollHeight - element.clientHeight - 10
|
||||
) {
|
||||
// select the last item when reached the bottom
|
||||
setActiveAnchor(watch.at(-1));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||
function _createMdxContent(props) {
|
||||
const _components = {
|
||||
img: "img",
|
||||
p: "p",
|
||||
...props.components
|
||||
};
|
||||
return _jsxs(_Fragment, {
|
||||
children: [_jsx(_components.p, {
|
||||
children: _jsx(_components.img, {
|
||||
alt: "Test",
|
||||
src: "./test.png",
|
||||
width: "1299",
|
||||
height: "731"
|
||||
})
|
||||
}), "\n", _jsx(_components.p, {
|
||||
children: _jsx(_components.img, {
|
||||
alt: "External",
|
||||
src: "https://picsum.photos/id/237/200/300",
|
||||
width: "200",
|
||||
height: "300"
|
||||
})
|
||||
})]
|
||||
});
|
||||
}
|
||||
export default function MDXContent(props = {}) {
|
||||
const {wrapper: MDXLayout} = props.components || ({});
|
||||
return MDXLayout ? _jsx(MDXLayout, {
|
||||
...props,
|
||||
children: _jsx(_createMdxContent, {
|
||||
...props
|
||||
})
|
||||
}) : _createMdxContent(props);
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||

|
||||
|
||||

|
||||
@@ -0,0 +1,34 @@
|
||||
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||
import __img0 from "/Users/xred/dev/fumadocs/packages/core/test/fixtures/test.png";
|
||||
function _createMdxContent(props) {
|
||||
const _components = {
|
||||
img: "img",
|
||||
p: "p",
|
||||
...props.components
|
||||
};
|
||||
return _jsxs(_Fragment, {
|
||||
children: [_jsx(_components.p, {
|
||||
children: _jsx(_components.img, {
|
||||
alt: "Test",
|
||||
placeholder: "blur",
|
||||
src: __img0
|
||||
})
|
||||
}), "\n", _jsx(_components.p, {
|
||||
children: _jsx(_components.img, {
|
||||
alt: "External",
|
||||
src: "https://picsum.photos/id/237/200/300",
|
||||
width: "200",
|
||||
height: "300"
|
||||
})
|
||||
})]
|
||||
});
|
||||
}
|
||||
export default function MDXContent(props = {}) {
|
||||
const {wrapper: MDXLayout} = props.components || ({});
|
||||
return MDXLayout ? _jsx(MDXLayout, {
|
||||
...props,
|
||||
children: _jsx(_createMdxContent, {
|
||||
...props
|
||||
})
|
||||
}) : _createMdxContent(props);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 391 KiB |
@@ -5,10 +5,12 @@ import { remark } from 'remark';
|
||||
import {
|
||||
remarkAdmonition,
|
||||
remarkHeading,
|
||||
remarkImage,
|
||||
remarkStructure,
|
||||
} from '@/mdx-plugins';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import remarkMdx from 'remark-mdx';
|
||||
import { compile } from '@mdx-js/mdx';
|
||||
|
||||
const cwd = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -47,3 +49,32 @@ test('Remark Admonition', async () => {
|
||||
path.resolve(cwd, './fixtures/remark-admonition.output.mdx'),
|
||||
);
|
||||
});
|
||||
|
||||
test('Remark Image', async () => {
|
||||
const content = readFileSync(path.resolve(cwd, './fixtures/remark-image.md'));
|
||||
const result = await compile(content, {
|
||||
remarkPlugins: [
|
||||
[remarkImage, { publicDir: path.resolve(cwd, './fixtures') }],
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.value).toMatchFileSnapshot(
|
||||
path.resolve(cwd, './fixtures/remark-image.output.js'),
|
||||
);
|
||||
});
|
||||
|
||||
test('Remark Image: Without Import', async () => {
|
||||
const content = readFileSync(path.resolve(cwd, './fixtures/remark-image.md'));
|
||||
const result = await compile(content, {
|
||||
remarkPlugins: [
|
||||
[
|
||||
remarkImage,
|
||||
{ publicDir: path.resolve(cwd, './fixtures'), useImport: false },
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.value).toMatchFileSnapshot(
|
||||
path.resolve(cwd, './fixtures/remark-image-without-import.output.js'),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
"description": "Used to track dependency versions in create-fumadocs-app",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@content-collections/core": "^0.6.3",
|
||||
"@content-collections/core": "^0.6.4",
|
||||
"@content-collections/mdx": "^0.1.3",
|
||||
"@content-collections/next": "^0.2.0",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"next": "^14.2.5",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss": "^8.4.41",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"@types/micromatch": "^4.0.9",
|
||||
"@types/node": "20.14.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/ws": "^8.5.11",
|
||||
"@types/ws": "^8.5.12",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"fumadocs-core": "workspace:*",
|
||||
"next": "^14.2.5",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^11.6.4",
|
||||
"@apidevtools/json-schema-ref-parser": "^11.7.0",
|
||||
"@mdx-js/mdx": "^3.0.1",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
@@ -63,11 +63,11 @@
|
||||
"github-slugger": "^2.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-schema-to-typescript": "^15.0.0",
|
||||
"lucide-react": "^0.414.0",
|
||||
"lucide-react": "^0.427.0",
|
||||
"openapi-sampler": "^1.5.1",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"remark": "^15.0.0",
|
||||
"shiki": "^1.11.1",
|
||||
"shiki": "^1.12.1",
|
||||
"swr": "^2.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -75,7 +75,7 @@
|
||||
"@types/node": "20.14.12",
|
||||
"@types/openapi-sampler": "^1.0.3",
|
||||
"@types/react": "^18.3.3",
|
||||
"bunchee": "^5.3.1",
|
||||
"bunchee": "^5.3.2",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"next": "^14.2.5",
|
||||
"openapi-types": "^12.1.3",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@shikijs/twoslash": "^1.11.1",
|
||||
"@shikijs/twoslash": "^1.12.1",
|
||||
"mdast-util-from-markdown": "^2.0.1",
|
||||
"mdast-util-gfm": "^3.0.0",
|
||||
"mdast-util-to-hast": "^13.2.0"
|
||||
@@ -35,7 +35,7 @@
|
||||
"@types/mdast": "^4.0.3",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"shiki": "^1.11.1",
|
||||
"shiki": "^1.12.1",
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -106,15 +106,15 @@
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@tailwindcss/typography": "^0.5.14",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"cmdk": "^1.0.0",
|
||||
"fumadocs-core": "workspace:*",
|
||||
"lucide-react": "^0.414.0",
|
||||
"lucide-react": "^0.427.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"react-medium-image-zoom": "^5.2.8",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^2.4.0"
|
||||
"tailwind-merge": "^2.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@algolia/client-search": "^4.24.0",
|
||||
@@ -124,10 +124,10 @@
|
||||
"algoliasearch": "^4.24.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"next": "^14.2.5",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss": "^8.4.41",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-lightningcss": "^1.0.0",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"postcss-lightningcss": "^1.0.1",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -12,11 +12,18 @@ import { Card, Cards } from '@/components/card';
|
||||
import { Callout } from '@/components/callout';
|
||||
import { Heading } from '@/components/heading';
|
||||
import { defaultImageSizes } from '@/utils/shared';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
declare const { Pre }: typeof import('./mdx.client');
|
||||
|
||||
function Image(props: ImgHTMLAttributes<HTMLImageElement>): React.ReactElement {
|
||||
return <NextImage sizes={defaultImageSizes} {...(props as ImageProps)} />;
|
||||
return (
|
||||
<NextImage
|
||||
sizes={defaultImageSizes}
|
||||
{...(props as ImageProps)}
|
||||
className={cn('rounded-lg', props.className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Table(
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageTree } from 'fumadocs-core/server';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const defaultImageSizes =
|
||||
'(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 800px';
|
||||
'(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px';
|
||||
|
||||
export function isActive(
|
||||
url: string,
|
||||
|
||||
Generated
+600
-484
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user