mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 10:55:57 +00:00
Core: Improve AST plugins
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'fumadocs-core': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve AST plugins
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Processor, Transformer } from 'unified';
|
import type { Processor, Transformer } from 'unified';
|
||||||
import type { BlockContent, Code, DefinitionContent, Root } from 'mdast';
|
import type { BlockContent, Code, Root, RootContent } from 'mdast';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
|
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
|
||||||
import { generateCodeBlockTabs, parseCodeBlockAttributes } from '@/mdx-plugins/codeblock-utils';
|
import { generateCodeBlockTabs, parseCodeBlockAttributes } from '@/mdx-plugins/codeblock-utils';
|
||||||
|
|
||||||
type TabType = keyof typeof Types;
|
type TabType = 'CodeBlockTabs' | 'Tabs';
|
||||||
export interface RemarkCodeTabOptions {
|
export interface RemarkCodeTabOptions {
|
||||||
Tabs?: TabType;
|
Tabs?: TabType;
|
||||||
|
|
||||||
@@ -18,17 +18,54 @@ export interface RemarkCodeTabOptions {
|
|||||||
|
|
||||||
declare module 'mdast' {
|
declare module 'mdast' {
|
||||||
export interface CodeData {
|
export interface CodeData {
|
||||||
|
/**
|
||||||
|
* [Fumadocs: remark-code-tab] the associated tab value
|
||||||
|
*/
|
||||||
tab?: string;
|
tab?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Data {
|
||||||
|
/**
|
||||||
|
* [Fumadocs: remark-code-tab] internal
|
||||||
|
*/
|
||||||
|
_code_tab_visited?: true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tabs = {
|
type TabHandler = (
|
||||||
convert(
|
processor: Processor,
|
||||||
processor: Processor,
|
nodes: Code[],
|
||||||
nodes: Code[],
|
withMdx: boolean,
|
||||||
withMdx = false,
|
withParent: boolean,
|
||||||
withParent = true,
|
) => RootContent[];
|
||||||
): MdxJsxFlowElement {
|
|
||||||
|
const Types: Record<TabType, TabHandler> = {
|
||||||
|
CodeBlockTabs(processor, nodes, withMdx, withParent) {
|
||||||
|
const tabs = Array.from(processTabValue(nodes).entries());
|
||||||
|
|
||||||
|
const node = generateCodeBlockTabs({
|
||||||
|
defaultValue: tabs[0][0],
|
||||||
|
triggers: tabs.map(([name]) => ({
|
||||||
|
value: name,
|
||||||
|
children: [
|
||||||
|
withMdx
|
||||||
|
? (mdxToAst(processor, name) as unknown as BlockContent)
|
||||||
|
: {
|
||||||
|
type: 'text',
|
||||||
|
value: name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
tabs: tabs.map(([name, codes]) => ({
|
||||||
|
value: name,
|
||||||
|
children: codes,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!withParent) return node.children;
|
||||||
|
return [node];
|
||||||
|
},
|
||||||
|
Tabs(processor, nodes, withMdx, withParent) {
|
||||||
const tabs = Array.from(processTabValue(nodes).entries());
|
const tabs = Array.from(processTabValue(nodes).entries());
|
||||||
|
|
||||||
if (!withMdx) {
|
if (!withMdx) {
|
||||||
@@ -47,42 +84,44 @@ const Tabs = {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!withParent) return createFragment(children);
|
if (!withParent) return children;
|
||||||
|
|
||||||
return {
|
return [
|
||||||
type: 'mdxJsxFlowElement',
|
{
|
||||||
name: 'Tabs',
|
type: 'mdxJsxFlowElement',
|
||||||
attributes: [
|
name: 'Tabs',
|
||||||
{
|
attributes: [
|
||||||
type: 'mdxJsxAttribute',
|
{
|
||||||
name: 'items',
|
type: 'mdxJsxAttribute',
|
||||||
value: {
|
name: 'items',
|
||||||
type: 'mdxJsxAttributeValueExpression',
|
value: {
|
||||||
value: tabs.map(([name]) => name).join(', '),
|
type: 'mdxJsxAttributeValueExpression',
|
||||||
data: {
|
value: tabs.map(([name]) => name).join(', '),
|
||||||
estree: {
|
data: {
|
||||||
type: 'Program',
|
estree: {
|
||||||
sourceType: 'module',
|
type: 'Program',
|
||||||
comments: [],
|
sourceType: 'module',
|
||||||
body: [
|
comments: [],
|
||||||
{
|
body: [
|
||||||
type: 'ExpressionStatement',
|
{
|
||||||
expression: {
|
type: 'ExpressionStatement',
|
||||||
type: 'ArrayExpression',
|
expression: {
|
||||||
elements: tabs.map(([name]) => ({
|
type: 'ArrayExpression',
|
||||||
type: 'Literal',
|
elements: tabs.map(([name]) => ({
|
||||||
value: name,
|
type: 'Literal',
|
||||||
})),
|
value: name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
children,
|
||||||
children,
|
},
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const children: MdxJsxFlowElement[] = [
|
const children: MdxJsxFlowElement[] = [
|
||||||
@@ -120,61 +159,25 @@ const Tabs = {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!withParent) return createFragment(children);
|
if (!withParent) return children;
|
||||||
|
|
||||||
return {
|
return [
|
||||||
type: 'mdxJsxFlowElement',
|
{
|
||||||
name: 'Tabs',
|
type: 'mdxJsxFlowElement',
|
||||||
attributes: [
|
name: 'Tabs',
|
||||||
{
|
attributes: [
|
||||||
type: 'mdxJsxAttribute',
|
{
|
||||||
name: 'defaultValue',
|
type: 'mdxJsxAttribute',
|
||||||
value: tabs[0][0],
|
name: 'defaultValue',
|
||||||
},
|
value: tabs[0][0],
|
||||||
],
|
},
|
||||||
children,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const CodeBlockTabs = {
|
|
||||||
convert(
|
|
||||||
processor: Processor,
|
|
||||||
nodes: Code[],
|
|
||||||
withMdx = false,
|
|
||||||
withParent = true,
|
|
||||||
): MdxJsxFlowElement {
|
|
||||||
const tabs = Array.from(processTabValue(nodes).entries());
|
|
||||||
|
|
||||||
const node = generateCodeBlockTabs({
|
|
||||||
defaultValue: tabs[0][0],
|
|
||||||
triggers: tabs.map(([name]) => ({
|
|
||||||
value: name,
|
|
||||||
children: [
|
|
||||||
withMdx
|
|
||||||
? (mdxToAst(processor, name) as unknown as BlockContent)
|
|
||||||
: {
|
|
||||||
type: 'text',
|
|
||||||
value: name,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})),
|
children,
|
||||||
tabs: tabs.map(([name, codes]) => ({
|
},
|
||||||
value: name,
|
];
|
||||||
children: codes,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!withParent) return createFragment(node.children);
|
|
||||||
return node;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Types = {
|
|
||||||
CodeBlockTabs,
|
|
||||||
Tabs,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function remarkCodeTab(
|
export function remarkCodeTab(
|
||||||
this: Processor,
|
this: Processor,
|
||||||
options: RemarkCodeTabOptions = {},
|
options: RemarkCodeTabOptions = {},
|
||||||
@@ -182,10 +185,8 @@ export function remarkCodeTab(
|
|||||||
const { parseMdx = false, Tabs = 'CodeBlockTabs' } = options;
|
const { parseMdx = false, Tabs = 'CodeBlockTabs' } = options;
|
||||||
|
|
||||||
return (tree) => {
|
return (tree) => {
|
||||||
const ignored = new WeakSet();
|
|
||||||
|
|
||||||
visit(tree, (node) => {
|
visit(tree, (node) => {
|
||||||
if (!('children' in node) || ignored.has(node)) return 'skip';
|
if (!('children' in node) || node.data?._code_tab_visited) return 'skip';
|
||||||
let localTabs: TabType = Tabs;
|
let localTabs: TabType = Tabs;
|
||||||
let localParseMdx = parseMdx;
|
let localParseMdx = parseMdx;
|
||||||
let withParent = true;
|
let withParent = true;
|
||||||
@@ -206,15 +207,19 @@ export function remarkCodeTab(
|
|||||||
let end = 0;
|
let end = 0;
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (start === -1 || start === end) return;
|
if (start === -1 || start === end) return;
|
||||||
const replacement = Types[localTabs].convert(
|
const replacement = Types[localTabs](
|
||||||
this,
|
this,
|
||||||
node.children.slice(start, end) as Code[],
|
node.children.slice(start, end) as Code[],
|
||||||
localParseMdx,
|
localParseMdx,
|
||||||
withParent,
|
withParent,
|
||||||
);
|
);
|
||||||
|
|
||||||
ignored.add(replacement);
|
for (const element of replacement) {
|
||||||
node.children.splice(start, end - start, replacement);
|
element.data ??= {};
|
||||||
|
element.data._code_tab_visited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children.splice(start, end - start, ...replacement);
|
||||||
end = start;
|
end = start;
|
||||||
start = -1;
|
start = -1;
|
||||||
};
|
};
|
||||||
@@ -272,12 +277,3 @@ function mdxToAst(processor: Processor, name: string) {
|
|||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFragment(children: (BlockContent | DefinitionContent)[]): MdxJsxFlowElement {
|
|
||||||
return {
|
|
||||||
type: 'mdxJsxFlowElement',
|
|
||||||
name: null,
|
|
||||||
attributes: [],
|
|
||||||
children,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { BlockContent, DefinitionContent, PhrasingContent, Root } from 'mdast';
|
import type { BlockContent, DefinitionContent, PhrasingContent, Root } from 'mdast';
|
||||||
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx';
|
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx';
|
||||||
import { Transformer } from 'unified';
|
import type { Transformer } from 'unified';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
export interface RemarkDirectiveAdmonitionOptions {
|
export interface RemarkDirectiveAdmonitionOptions {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import type { Image, Root } from 'mdast';
|
import type { Image, Root, RootContent } from 'mdast';
|
||||||
import type { Transformer } from 'unified';
|
import type { Transformer } from 'unified';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
import type { MdxjsEsm } from 'mdast-util-mdx';
|
import type { MdxjsEsm } from 'mdast-util-mdx';
|
||||||
import type { ISizeCalculationResult } from 'image-size/types/interface';
|
import type { ISizeCalculationResult } from 'image-size/types/interface';
|
||||||
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx';
|
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
const VALID_BLUR_EXT = ['.jpeg', '.png', '.webp', '.avif', '.jpg'];
|
const VALID_BLUR_EXT = ['.jpeg', '.png', '.webp', '.avif', '.jpg'];
|
||||||
@@ -90,23 +90,7 @@ export function remarkImage({
|
|||||||
const importsToInject: { variableName: string; importPath: string }[] = [];
|
const importsToInject: { variableName: string; importPath: string }[] = [];
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
async function onImage(src: Source, node: Image): Promise<MdxJsxFlowElement | undefined> {
|
async function updateImage(src: Source, node: Image): Promise<RootContent | undefined> {
|
||||||
const attributes: MdxJsxAttribute[] = [
|
|
||||||
{
|
|
||||||
type: 'mdxJsxAttribute',
|
|
||||||
name: 'alt',
|
|
||||||
value: node.alt ?? 'image',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (node.title) {
|
|
||||||
attributes.push({
|
|
||||||
type: 'mdxJsxAttribute',
|
|
||||||
name: 'title',
|
|
||||||
value: node.title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (src.type === 'file' && useImport) {
|
if (src.type === 'file' && useImport) {
|
||||||
// Unique variable name for the given static image URL
|
// Unique variable name for the given static image URL
|
||||||
const variableName = `__img${importsToInject.length}`;
|
const variableName = `__img${importsToInject.length}`;
|
||||||
@@ -124,34 +108,47 @@ export function remarkImage({
|
|||||||
importPath: getImportPath(src.file, file.dirname),
|
importPath: getImportPath(src.file, file.dirname),
|
||||||
});
|
});
|
||||||
|
|
||||||
attributes.push({
|
|
||||||
type: 'mdxJsxAttribute',
|
|
||||||
name: 'src',
|
|
||||||
value: {
|
|
||||||
type: 'mdxJsxAttributeValueExpression',
|
|
||||||
value: variableName,
|
|
||||||
data: {
|
|
||||||
estree: {
|
|
||||||
body: [
|
|
||||||
{
|
|
||||||
type: 'ExpressionStatement',
|
|
||||||
expression: { type: 'Identifier', name: variableName },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: 'Program',
|
|
||||||
sourceType: 'script',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const out: MdxJsxFlowElement = {
|
const out: MdxJsxFlowElement = {
|
||||||
children: [],
|
children: [],
|
||||||
type: 'mdxJsxFlowElement',
|
type: 'mdxJsxFlowElement',
|
||||||
name: 'img',
|
name: 'img',
|
||||||
attributes,
|
attributes: [
|
||||||
|
{
|
||||||
|
type: 'mdxJsxAttribute',
|
||||||
|
name: 'alt',
|
||||||
|
value: node.alt ?? 'image',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'mdxJsxAttribute',
|
||||||
|
name: 'src',
|
||||||
|
value: {
|
||||||
|
type: 'mdxJsxAttributeValueExpression',
|
||||||
|
value: variableName,
|
||||||
|
data: {
|
||||||
|
estree: {
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'ExpressionStatement',
|
||||||
|
expression: { type: 'Identifier', name: variableName },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'Program',
|
||||||
|
sourceType: 'script',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (node.title) {
|
||||||
|
out.attributes.push({
|
||||||
|
type: 'mdxJsxAttribute',
|
||||||
|
name: 'title',
|
||||||
|
value: node.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (hasBlur) {
|
if (hasBlur) {
|
||||||
out.attributes.push({
|
out.attributes.push({
|
||||||
type: 'mdxJsxAttribute',
|
type: 'mdxJsxAttribute',
|
||||||
@@ -173,39 +170,20 @@ export function remarkImage({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!size) return;
|
if (!size) return;
|
||||||
|
node.data ??= {};
|
||||||
attributes.push(
|
node.data.hProperties ??= {};
|
||||||
{
|
// `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
|
||||||
type: 'mdxJsxAttribute',
|
node.data.hProperties.src = src.type === 'url' ? src.url.href : node.url;
|
||||||
name: 'src',
|
node.data.hProperties.width = size.width.toString();
|
||||||
// `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
|
node.data.hProperties.height = size.height.toString();
|
||||||
value: src.type === 'url' ? src.url.toString() : node.url,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'mdxJsxAttribute',
|
|
||||||
name: 'width',
|
|
||||||
value: size.width.toString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'mdxJsxAttribute',
|
|
||||||
name: 'height',
|
|
||||||
value: size.height.toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'mdxJsxFlowElement',
|
|
||||||
name: 'img',
|
|
||||||
attributes,
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, 'image', (node) => {
|
visit(tree, 'image', (node, idx, parent) => {
|
||||||
|
if (typeof idx !== 'number' || !parent) return;
|
||||||
const src = parseSrc(decodeURI(node.url), publicDir, file.dirname);
|
const src = parseSrc(decodeURI(node.url), publicDir, file.dirname);
|
||||||
if (!src) return;
|
if (!src) return;
|
||||||
|
|
||||||
const task = onImage(src, node)
|
const task = updateImage(src, node)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
// ignore SVG as it is not always needed
|
// ignore SVG as it is not always needed
|
||||||
if (onError === 'ignore' || node.url.endsWith('.svg')) {
|
if (onError === 'ignore' || node.url.endsWith('.svg')) {
|
||||||
@@ -213,19 +191,17 @@ export function remarkImage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onError === 'hide') {
|
if (onError === 'hide') {
|
||||||
return {
|
parent.children.splice(idx, 1);
|
||||||
type: 'mdxJsxFlowElement',
|
return;
|
||||||
name: null,
|
|
||||||
attributes: [],
|
|
||||||
children: [],
|
|
||||||
} satisfies MdxJsxFlowElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onError === 'error') throw e;
|
if (onError === 'error') throw e;
|
||||||
onError(e);
|
onError(e);
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res) Object.assign(node, res);
|
if (res) {
|
||||||
|
parent.children[idx] = res;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
promises.push(task);
|
promises.push(task);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { toMdxExport } from './utils';
|
|||||||
import type { Heading, Parents, Root } from 'mdast';
|
import type { Heading, Parents, Root } from 'mdast';
|
||||||
import { defaultStringifier, type StringifyOptions } from './stringifier';
|
import { defaultStringifier, type StringifyOptions } from './stringifier';
|
||||||
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx';
|
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx';
|
||||||
import type { Info, State } from 'mdast-util-to-markdown';
|
import { defaultHandlers, type Info, type State } from 'mdast-util-to-markdown';
|
||||||
import type { PlaceholderData } from './remark-llms.runtime';
|
import type { PlaceholderData } from './remark-llms.runtime';
|
||||||
|
|
||||||
export interface LLMsOptions extends StringifyOptions {
|
export interface LLMsOptions extends StringifyOptions {
|
||||||
@@ -92,8 +92,8 @@ export function remarkLLMs(
|
|||||||
handlers: {
|
handlers: {
|
||||||
heading(node: Heading, _p, state, info) {
|
heading(node: Heading, _p, state, info) {
|
||||||
const id = node.data?.hProperties?.id;
|
const id = node.data?.hProperties?.id;
|
||||||
const content = state.containerPhrasing(node, info);
|
const defaultValue = defaultHandlers.heading(node, _p, state, info);
|
||||||
return headingIds && id ? `${content} [#${id}]` : content;
|
return headingIds && id ? `${defaultValue} [#${id}]` : defaultValue;
|
||||||
},
|
},
|
||||||
...rest.handlers,
|
...rest.handlers,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ export function remarkMdxMermaid(options: RemarkMdxMermaidOptions = {}): Transfo
|
|||||||
const { lang = 'mermaid' } = options;
|
const { lang = 'mermaid' } = options;
|
||||||
|
|
||||||
return (tree) => {
|
return (tree) => {
|
||||||
visit(tree, 'code', (node) => {
|
visit(tree, 'code', (node, idx, parent) => {
|
||||||
if (node.lang !== lang || !node.value) return;
|
if (node.lang !== lang || !node.value || typeof idx !== 'number' || !parent) return;
|
||||||
|
|
||||||
Object.assign(node, toMDX(node.value));
|
parent.children[idx] = toMDX(node.value);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ export interface RemarkNpmOptions {
|
|||||||
packageManagers?: PackageManager[];
|
packageManagers?: PackageManager[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const aliases = ['npm', 'package-install'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It generates multiple tabs of codeblocks for different package managers from a npm command codeblock.
|
* It generates multiple tabs of codeblocks for different package managers from a npm command codeblock.
|
||||||
*/
|
*/
|
||||||
@@ -48,13 +46,25 @@ export function remarkNpm({
|
|||||||
],
|
],
|
||||||
}: RemarkNpmOptions = {}): Transformer<Root, Root> {
|
}: RemarkNpmOptions = {}): Transformer<Root, Root> {
|
||||||
return (tree) => {
|
return (tree) => {
|
||||||
visit(tree, 'code', (node) => {
|
visit(tree, 'code', (node, idx, parent) => {
|
||||||
if (!node.lang || !aliases.includes(node.lang)) return;
|
if (typeof idx !== 'number' || !parent) return;
|
||||||
let code = node.value;
|
let code: string;
|
||||||
|
|
||||||
if (node.lang === 'package-install' && !code.startsWith('npm') && !code.startsWith('npx')) {
|
switch (node.lang) {
|
||||||
code = `npm install ${code}`;
|
case 'package-install':
|
||||||
|
code = node.value;
|
||||||
|
|
||||||
|
if (!code.startsWith('npm') && !code.startsWith('npx')) {
|
||||||
|
code = `npm install ${code}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'npm':
|
||||||
|
code = node.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: CodeBlockTabsOptions = {
|
const options: CodeBlockTabsOptions = {
|
||||||
persist,
|
persist,
|
||||||
tabs: [],
|
tabs: [],
|
||||||
@@ -84,7 +94,7 @@ export function remarkNpm({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(node, generateCodeBlockTabs(options));
|
parent.children[idx] = generateCodeBlockTabs(options);
|
||||||
return 'skip';
|
return 'skip';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import remarkGfm from 'remark-gfm';
|
|||||||
import type { PluggableList, Processor, Transformer } from 'unified';
|
import type { PluggableList, Processor, Transformer } from 'unified';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
import { toMdxExport } from './utils';
|
import { toMdxExport } from './utils';
|
||||||
import {
|
import type {
|
||||||
type MdxJsxAttribute,
|
MdxJsxAttribute,
|
||||||
type MdxJsxExpressionAttribute,
|
MdxJsxExpressionAttribute,
|
||||||
type MdxJsxFlowElement,
|
MdxJsxFlowElement,
|
||||||
type MdxJsxTextElement,
|
MdxJsxTextElement,
|
||||||
} from 'mdast-util-mdx';
|
} from 'mdast-util-mdx';
|
||||||
import { remarkHeading } from './remark-heading';
|
import { remarkHeading } from './remark-heading';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"alt": "External",
|
||||||
|
"data": {
|
||||||
|
"hProperties": {
|
||||||
|
"height": "630",
|
||||||
|
"src": "https://fumadocs.dev/banner.png",
|
||||||
|
"width": "1200",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 25,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 24,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "/banner.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 25,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 24,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "paragraph",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 2,
|
||||||
|
"offset": 25,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "root",
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<img alt="External" src="https://fumadocs.dev/banner.png" width="1200" height="630" />
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"alt": "Test",
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 20,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 19,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "./test.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 20,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 19,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "paragraph",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"alt": "External",
|
||||||
|
"data": {
|
||||||
|
"hProperties": {
|
||||||
|
"height": "630",
|
||||||
|
"src": "https://fumadocs.dev/banner.png",
|
||||||
|
"width": "1200",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 45,
|
||||||
|
"line": 3,
|
||||||
|
"offset": 65,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 3,
|
||||||
|
"offset": 21,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://fumadocs.dev/banner.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 45,
|
||||||
|
"line": 3,
|
||||||
|
"offset": 65,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 3,
|
||||||
|
"offset": 21,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "paragraph",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"alt": "Inline",
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 30,
|
||||||
|
"line": 5,
|
||||||
|
"offset": 96,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 5,
|
||||||
|
"offset": 67,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "./remark-image.svg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 30,
|
||||||
|
"line": 5,
|
||||||
|
"offset": 96,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 5,
|
||||||
|
"offset": 67,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "paragraph",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 6,
|
||||||
|
"offset": 97,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "root",
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||

|
|
||||||
|
|
||||||
<img alt="External" src="https://fumadocs.dev/banner.png" width="1200" height="630" />
|
|
||||||
|
|
||||||

|
|
||||||
+7
-54
@@ -53,7 +53,6 @@
|
|||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"alt": "Test",
|
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"name": "alt",
|
"name": "alt",
|
||||||
@@ -86,21 +85,7 @@
|
|||||||
],
|
],
|
||||||
"children": [],
|
"children": [],
|
||||||
"name": "img",
|
"name": "img",
|
||||||
"position": {
|
|
||||||
"end": {
|
|
||||||
"column": 20,
|
|
||||||
"line": 1,
|
|
||||||
"offset": 19,
|
|
||||||
},
|
|
||||||
"start": {
|
|
||||||
"column": 1,
|
|
||||||
"line": 1,
|
|
||||||
"offset": 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"title": null,
|
|
||||||
"type": "mdxJsxFlowElement",
|
"type": "mdxJsxFlowElement",
|
||||||
"url": "./test.png",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"position": {
|
"position": {
|
||||||
@@ -121,30 +106,13 @@
|
|||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"alt": "External",
|
"alt": "External",
|
||||||
"attributes": [
|
"data": {
|
||||||
{
|
"hProperties": {
|
||||||
"name": "alt",
|
"height": "630",
|
||||||
"type": "mdxJsxAttribute",
|
"src": "https://fumadocs.dev/banner.png",
|
||||||
"value": "External",
|
"width": "1200",
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
"name": "src",
|
|
||||||
"type": "mdxJsxAttribute",
|
|
||||||
"value": "https://fumadocs.dev/banner.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "width",
|
|
||||||
"type": "mdxJsxAttribute",
|
|
||||||
"value": "1200",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "height",
|
|
||||||
"type": "mdxJsxAttribute",
|
|
||||||
"value": "630",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"children": [],
|
|
||||||
"name": "img",
|
|
||||||
"position": {
|
"position": {
|
||||||
"end": {
|
"end": {
|
||||||
"column": 45,
|
"column": 45,
|
||||||
@@ -158,7 +126,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"title": null,
|
"title": null,
|
||||||
"type": "mdxJsxFlowElement",
|
"type": "image",
|
||||||
"url": "https://fumadocs.dev/banner.png",
|
"url": "https://fumadocs.dev/banner.png",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -179,7 +147,6 @@
|
|||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"alt": "Inline",
|
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"name": "alt",
|
"name": "alt",
|
||||||
@@ -212,21 +179,7 @@
|
|||||||
],
|
],
|
||||||
"children": [],
|
"children": [],
|
||||||
"name": "img",
|
"name": "img",
|
||||||
"position": {
|
|
||||||
"end": {
|
|
||||||
"column": 30,
|
|
||||||
"line": 5,
|
|
||||||
"offset": 96,
|
|
||||||
},
|
|
||||||
"start": {
|
|
||||||
"column": 1,
|
|
||||||
"line": 5,
|
|
||||||
"offset": 67,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"title": null,
|
|
||||||
"type": "mdxJsxFlowElement",
|
"type": "mdxJsxFlowElement",
|
||||||
"url": "./remark-image.svg",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"position": {
|
"position": {
|
||||||
|
|||||||
+2
@@ -1,3 +1,5 @@
|
|||||||
|
## Hello World
|
||||||
|
|
||||||
Below is a **MDX** component:
|
Below is a **MDX** component:
|
||||||
|
|
||||||
<MyPage title="test">
|
<MyPage title="test">
|
||||||
|
|||||||
Binary file not shown.
@@ -85,9 +85,7 @@ test('Remark Steps', async () => {
|
|||||||
test('Remark Image: With Path', async () => {
|
test('Remark Image: With Path', async () => {
|
||||||
const file = path.resolve(cwd, './fixtures/remark-image.md');
|
const file = path.resolve(cwd, './fixtures/remark-image.md');
|
||||||
const content = await fs.readFile(file);
|
const content = await fs.readFile(file);
|
||||||
const processor = remark()
|
const processor = remark().use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') });
|
||||||
.use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') })
|
|
||||||
.use(remarkMdx);
|
|
||||||
|
|
||||||
const result = await processor.run(processor.parse(content), {
|
const result = await processor.run(processor.parse(content), {
|
||||||
path: file,
|
path: file,
|
||||||
@@ -100,31 +98,27 @@ test('Remark Image: With Path', async () => {
|
|||||||
|
|
||||||
test('Remark Image: Without Import', async () => {
|
test('Remark Image: Without Import', async () => {
|
||||||
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image.md'));
|
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image.md'));
|
||||||
const result = await remark()
|
const processor = remark().use(remarkImage, {
|
||||||
.use(remarkImage, {
|
publicDir: path.resolve(cwd, './fixtures'),
|
||||||
publicDir: path.resolve(cwd, './fixtures'),
|
useImport: false,
|
||||||
useImport: false,
|
});
|
||||||
})
|
|
||||||
.use(remarkMdx)
|
|
||||||
.process(content);
|
|
||||||
|
|
||||||
await expect(result.value).toMatchFileSnapshot(
|
const result = await processor.run(processor.parse(content));
|
||||||
path.resolve(cwd, './fixtures/remark-image-without-import.output.mdx'),
|
await expect(result).toMatchFileSnapshot(
|
||||||
|
path.resolve(cwd, './fixtures/remark-image-without-import.output.json'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remark Image: `publicDir` with URL', async () => {
|
test('Remark Image: `publicDir` with URL', async () => {
|
||||||
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image-public-dir.md'));
|
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image-public-dir.md'));
|
||||||
const result = await remark()
|
const processor = remark().use(remarkImage, {
|
||||||
.use(remarkImage, {
|
publicDir: 'https://fumadocs.dev',
|
||||||
publicDir: 'https://fumadocs.dev',
|
useImport: false,
|
||||||
useImport: false,
|
});
|
||||||
})
|
|
||||||
.use(remarkMdx)
|
|
||||||
.process(content);
|
|
||||||
|
|
||||||
await expect(result.value).toMatchFileSnapshot(
|
const result = await processor.run(processor.parse(content));
|
||||||
path.resolve(cwd, './fixtures/remark-image-public-dir.output.mdx'),
|
await expect(result).toMatchFileSnapshot(
|
||||||
|
path.resolve(cwd, './fixtures/remark-image-public-dir.output.json'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user