forked from 77media/video-flow
183 lines
4.8 KiB
TypeScript
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);
|
|
}
|