332 lines
7.5 KiB
TypeScript
332 lines
7.5 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core';
|
||
import {
|
||
ParseMarkdownRequest,
|
||
ParseMarkdownResponse,
|
||
QueryNodesRequest,
|
||
QueryNodesResponse,
|
||
FindNodeAtPositionRequest,
|
||
FindNodeAtPositionResponse,
|
||
ExtractOutlineRequest,
|
||
ExtractOutlineResponse,
|
||
ExtractLinksRequest,
|
||
ExtractLinksResponse,
|
||
ValidateMarkdownRequest,
|
||
ValidateMarkdownResponse,
|
||
MarkdownParseResult,
|
||
MarkdownNode,
|
||
OutlineItem,
|
||
LinkInfo,
|
||
ValidationResult,
|
||
MarkdownParserConfig,
|
||
QueryType,
|
||
} from '../types/markdown';
|
||
|
||
/**
|
||
* Markdown解析服务类
|
||
* 封装与Tauri后端的通信,提供易用的API接口
|
||
*/
|
||
export class MarkdownService {
|
||
private static instance: MarkdownService;
|
||
|
||
private constructor() {}
|
||
|
||
/**
|
||
* 获取单例实例
|
||
*/
|
||
public static getInstance(): MarkdownService {
|
||
if (!MarkdownService.instance) {
|
||
MarkdownService.instance = new MarkdownService();
|
||
}
|
||
return MarkdownService.instance;
|
||
}
|
||
|
||
/**
|
||
* 解析Markdown文档
|
||
* @param text Markdown文本
|
||
* @param config 解析器配置
|
||
* @returns 解析结果
|
||
*/
|
||
public async parseMarkdown(
|
||
text: string,
|
||
config?: MarkdownParserConfig
|
||
): Promise<MarkdownParseResult> {
|
||
const request: ParseMarkdownRequest = {
|
||
text,
|
||
config,
|
||
};
|
||
|
||
const response = await invoke<ParseMarkdownResponse>('parse_markdown', {
|
||
request,
|
||
});
|
||
|
||
if (!response.success || !response.result) {
|
||
throw new Error(response.error || 'Failed to parse markdown');
|
||
}
|
||
|
||
return response.result;
|
||
}
|
||
|
||
/**
|
||
* 查询特定类型的节点
|
||
* @param text Markdown文本
|
||
* @param queryType 查询类型
|
||
* @returns 匹配的节点列表
|
||
*/
|
||
public async queryNodes(
|
||
text: string,
|
||
queryType: QueryType | string
|
||
): Promise<MarkdownNode[]> {
|
||
const request: QueryNodesRequest = {
|
||
text,
|
||
query_name: queryType,
|
||
};
|
||
|
||
const response = await invoke<QueryNodesResponse>('query_markdown_nodes', {
|
||
request,
|
||
});
|
||
|
||
if (!response.success || !response.nodes) {
|
||
throw new Error(response.error || 'Failed to query nodes');
|
||
}
|
||
|
||
return response.nodes;
|
||
}
|
||
|
||
/**
|
||
* 根据位置查找节点
|
||
* @param text Markdown文本
|
||
* @param line 行号(从0开始)
|
||
* @param column 列号(从0开始)
|
||
* @returns 找到的节点,如果没有找到则返回null
|
||
*/
|
||
public async findNodeAtPosition(
|
||
text: string,
|
||
line: number,
|
||
column: number
|
||
): Promise<MarkdownNode | null> {
|
||
const request: FindNodeAtPositionRequest = {
|
||
text,
|
||
line,
|
||
column,
|
||
};
|
||
|
||
const response = await invoke<FindNodeAtPositionResponse>(
|
||
'find_markdown_node_at_position',
|
||
{ request }
|
||
);
|
||
|
||
if (!response.success) {
|
||
throw new Error(response.error || 'Failed to find node at position');
|
||
}
|
||
|
||
return response.node || null;
|
||
}
|
||
|
||
/**
|
||
* 提取文档大纲
|
||
* @param text Markdown文本
|
||
* @returns 大纲项目列表
|
||
*/
|
||
public async extractOutline(text: string): Promise<OutlineItem[]> {
|
||
const request: ExtractOutlineRequest = {
|
||
text,
|
||
};
|
||
|
||
const response = await invoke<ExtractOutlineResponse>(
|
||
'extract_markdown_outline',
|
||
{ request }
|
||
);
|
||
|
||
if (!response.success || !response.outline) {
|
||
throw new Error(response.error || 'Failed to extract outline');
|
||
}
|
||
|
||
return response.outline;
|
||
}
|
||
|
||
/**
|
||
* 提取所有链接
|
||
* @param text Markdown文本
|
||
* @returns 链接信息列表
|
||
*/
|
||
public async extractLinks(text: string): Promise<LinkInfo[]> {
|
||
const request: ExtractLinksRequest = {
|
||
text,
|
||
};
|
||
|
||
const response = await invoke<ExtractLinksResponse>(
|
||
'extract_markdown_links',
|
||
{ request }
|
||
);
|
||
|
||
if (!response.success || !response.links) {
|
||
throw new Error(response.error || 'Failed to extract links');
|
||
}
|
||
|
||
return response.links;
|
||
}
|
||
|
||
/**
|
||
* 验证Markdown文档结构
|
||
* @param text Markdown文本
|
||
* @returns 验证结果
|
||
*/
|
||
public async validateMarkdown(text: string): Promise<ValidationResult> {
|
||
const request: ValidateMarkdownRequest = {
|
||
text,
|
||
};
|
||
|
||
const response = await invoke<ValidateMarkdownResponse>(
|
||
'validate_markdown',
|
||
{ request }
|
||
);
|
||
|
||
if (!response.success || !response.validation) {
|
||
throw new Error(response.error || 'Failed to validate markdown');
|
||
}
|
||
|
||
return response.validation;
|
||
}
|
||
|
||
/**
|
||
* 查询标题节点
|
||
* @param text Markdown文本
|
||
* @returns 标题节点列表
|
||
*/
|
||
public async queryHeadings(text: string): Promise<MarkdownNode[]> {
|
||
return this.queryNodes(text, QueryType.Headings);
|
||
}
|
||
|
||
/**
|
||
* 查询链接节点
|
||
* @param text Markdown文本
|
||
* @returns 链接节点列表
|
||
*/
|
||
public async queryLinkNodes(text: string): Promise<MarkdownNode[]> {
|
||
return this.queryNodes(text, QueryType.Links);
|
||
}
|
||
|
||
/**
|
||
* 查询代码块节点
|
||
* @param text Markdown文本
|
||
* @returns 代码块节点列表
|
||
*/
|
||
public async queryCodeBlocks(text: string): Promise<MarkdownNode[]> {
|
||
return this.queryNodes(text, QueryType.Code);
|
||
}
|
||
|
||
/**
|
||
* 获取节点的纯文本内容
|
||
* @param node Markdown节点
|
||
* @returns 纯文本内容
|
||
*/
|
||
public extractTextContent(node: MarkdownNode): string {
|
||
if (node.node_type === 'Text') {
|
||
return node.content;
|
||
}
|
||
|
||
return node.children
|
||
.map((child) => this.extractTextContent(child))
|
||
.join('');
|
||
}
|
||
|
||
/**
|
||
* 检查节点是否在指定范围内
|
||
* @param node Markdown节点
|
||
* @param startLine 开始行号
|
||
* @param endLine 结束行号
|
||
* @returns 是否在范围内
|
||
*/
|
||
public isNodeInRange(
|
||
node: MarkdownNode,
|
||
startLine: number,
|
||
endLine: number
|
||
): boolean {
|
||
return (
|
||
node.range.start.line >= startLine && node.range.end.line <= endLine
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取节点的行数
|
||
* @param node Markdown节点
|
||
* @returns 节点占用的行数
|
||
*/
|
||
public getNodeLineCount(node: MarkdownNode): number {
|
||
return node.range.end.line - node.range.start.line + 1;
|
||
}
|
||
|
||
/**
|
||
* 查找包含指定文本的节点
|
||
* @param nodes 节点列表
|
||
* @param searchText 搜索文本
|
||
* @returns 匹配的节点列表
|
||
*/
|
||
public findNodesContainingText(
|
||
nodes: MarkdownNode[],
|
||
searchText: string
|
||
): MarkdownNode[] {
|
||
const results: MarkdownNode[] = [];
|
||
|
||
const searchInNode = (node: MarkdownNode) => {
|
||
if (node.content.toLowerCase().includes(searchText.toLowerCase())) {
|
||
results.push(node);
|
||
}
|
||
node.children.forEach(searchInNode);
|
||
};
|
||
|
||
nodes.forEach(searchInNode);
|
||
return results;
|
||
}
|
||
|
||
/**
|
||
* 获取节点的层级深度
|
||
* @param node Markdown节点
|
||
* @returns 层级深度
|
||
*/
|
||
public getNodeDepth(node: MarkdownNode): number {
|
||
let depth = 0;
|
||
let current = node;
|
||
|
||
while (current.children.length > 0) {
|
||
depth++;
|
||
current = current.children[0];
|
||
}
|
||
|
||
return depth;
|
||
}
|
||
|
||
/**
|
||
* 将位置信息转换为字符串
|
||
* @param line 行号
|
||
* @param column 列号
|
||
* @returns 位置字符串
|
||
*/
|
||
public formatPosition(line: number, column: number): string {
|
||
return `${line + 1}:${column + 1}`;
|
||
}
|
||
|
||
/**
|
||
* 将范围信息转换为字符串
|
||
* @param start 开始位置
|
||
* @param end 结束位置
|
||
* @returns 范围字符串
|
||
*/
|
||
public formatRange(
|
||
start: { line: number; column: number },
|
||
end: { line: number; column: number }
|
||
): string {
|
||
return `${this.formatPosition(start.line, start.column)}-${this.formatPosition(end.line, end.column)}`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导出单例实例
|
||
*/
|
||
export const markdownService = MarkdownService.getInstance();
|
||
|
||
/**
|
||
* 默认导出
|
||
*/
|
||
export default markdownService;
|