video-flow-b/lib/mdx.ts

183 lines
4.8 KiB
TypeScript

import { compile } from '@mdx-js/mdx';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { mdxFromMarkdown } from 'mdast-util-mdx';
import { mdxjs } from 'micromark-extension-mdxjs';
interface StreamChunk {
type: 'text' | 'annotation' | 'html' | 'block-start' | 'block-end';
content: string;
annotation?: {
type: string;
color?: string;
multiline?: boolean;
};
tag?: string;
className?: string;
}
// 自定义插件:将文本内容转换为流式块
function remarkStreamChunks() {
return (tree: any) => {
const chunks: StreamChunk[] = [];
function processNode(node: any) {
try {
if (node.type === 'heading') {
// 处理标题
chunks.push({
type: 'block-start',
content: '',
tag: 'div',
className: `text-${4-node.depth}xl font-bold mb-4`
});
node.children?.forEach(processNode);
chunks.push({
type: 'block-end',
content: '',
tag: 'div'
});
} else if (node.type === 'paragraph') {
// 处理段落
chunks.push({
type: 'block-start',
content: '',
tag: 'div',
className: 'text-base mb-4'
});
node.children?.forEach(processNode);
chunks.push({
type: 'block-end',
content: '',
tag: 'div'
});
} else if (node.type === 'mdxJsxTextElement' && node.name === 'Annot') {
// 处理 Annot 注释
const annotType = node.attributes?.find((attr: any) => attr.name === 'type')?.value;
const color = node.attributes?.find((attr: any) => attr.name === 'color')?.value;
if (!annotType) {
console.warn('Annot 标签缺少 type 属性');
return;
}
chunks.push({
type: 'annotation',
content: node.children?.[0]?.value || '',
annotation: {
type: annotType,
color: color,
multiline: false
},
tag: 'span'
});
} else if (node.type === 'text') {
// 处理普通文本
if (node.value?.trim()) {
chunks.push({
type: 'text',
content: node.value,
tag: 'span'
});
}
} else if (node.type === 'list') {
// 处理列表
chunks.push({
type: 'block-start',
content: '',
tag: 'ul',
className: 'list-disc list-inside mb-4'
});
node.children?.forEach(processNode);
chunks.push({
type: 'block-end',
content: '',
tag: 'ul'
});
} else if (node.type === 'listItem') {
// 处理列表项
chunks.push({
type: 'block-start',
content: '',
tag: 'li',
className: 'mb-2'
});
node.children?.forEach(processNode);
chunks.push({
type: 'block-end',
content: '',
tag: 'li'
});
} else if (node.children) {
// 递归处理其他节点
node.children.forEach(processNode);
}
} catch (error) {
console.error('处理节点时出错:', error, '节点类型:', node.type);
}
}
try {
tree.children?.forEach(processNode);
if (chunks.length === 0) {
throw new Error('解析结果为空');
}
return { chunks };
} catch (error) {
console.error('处理 MDX 树时出错:', error);
throw error;
}
};
}
export async function parseMDXContent(content: string): Promise<StreamChunk[]> {
try {
if (!content || typeof content !== 'string') {
throw new Error('无效的内容输入');
}
console.log('开始处理 MDX 内容...');
// 使用 fromMarkdown 直接解析 MDX
const tree = fromMarkdown(content, {
extensions: [mdxjs()],
mdastExtensions: [mdxFromMarkdown()]
});
// 应用我们的自定义处理器
const chunks: StreamChunk[] = [];
const processor = remarkStreamChunks();
const result = processor(tree);
if (!result || !result.chunks || result.chunks.length === 0) {
throw new Error('解析 MDX 内容失败:没有生成有效的块');
}
console.log('MDX 处理完成,生成块数:', result.chunks.length);
return result.chunks;
} catch (error) {
console.error('解析 MDX 内容时出错:', error);
throw error;
}
}
// 用于客户端渲染的编译函数
export async function compileMDX(source: string) {
const compiled = await compile(source, {
outputFormat: 'function-body',
development: false,
jsx: true,
jsxImportSource: 'react'
});
return String(compiled);
}