import * as React from 'react'; import { Subject } from 'rxjs'; import { BaseEditor, Node, NodeMatch, Path, RangeMode, Selection } from 'slate'; import { HistoryEditor } from 'slate-history'; import { ReactEditor } from 'slate-react'; import { T } from 'vitest/dist/chunks/environment.LoooBwUu.js'; import { FootnoteDefinitionNode } from '../el'; import type { MarkdownEditorPlugin } from '../plugin'; import { CommentDataType, MarkdownEditorProps } from '../types'; import { KeyboardTask, Methods } from './utils'; import type { MarkdownToHtmlOptions } from './utils/markdownToHtml'; /** * 编辑器上下文接口 * * 提供编辑器组件间共享的状态和方法 */ export interface EditorStoreContextType { /** 编辑器核心状态存储 */ store: EditorStore; /** 是否启用打字机模式 */ typewriter: boolean; /** 根容器引用 */ rootContainer?: React.MutableRefObject; /** 设置显示评论列表 */ setShowComment: (list: CommentDataType[]) => void; /** 是否为只读模式 */ readonly: boolean; /** 键盘任务流 */ keyTask$: Subject<{ key: Methods; args?: any[]; }>; /** 插入自动完成文本流 */ insertCompletionText$: Subject; /** 打开插入链接流 */ openInsertLink$: Subject; /** 是否刷新浮动工具栏 */ refreshFloatBar?: boolean; /** DOM矩形信息 */ domRect: DOMRect | null; /** 设置DOM矩形 */ setDomRect: (rect: DOMRect | null) => void; /** 设置刷新浮动工具栏状态 */ setRefreshFloatBar?: (refresh: boolean) => void; /** 是否打开插入自动完成 */ openInsertCompletion?: boolean; /** 设置打开插入自动完成状态 */ setOpenInsertCompletion?: (open: boolean) => void; /** 编辑器属性配置 */ editorProps: MarkdownEditorProps; /** Markdown编辑器引用 */ markdownEditorRef: React.MutableRefObject; /** Markdown容器引用 */ markdownContainerRef: React.MutableRefObject; } export declare const EditorStoreContext: React.Context; /** * 获取编辑器存储上下文的Hook * * 提供安全的上下文访问,包含默认值处理 * * @returns 编辑器存储上下文对象 * * @example * ```tsx * const { store, readonly, typewriter } = useEditorStore(); * ``` */ export declare const useEditorStore: () => EditorStoreContextType; /** * 编辑器核心状态管理类 * * 负责管理编辑器的所有状态、操作和事件处理 * 包括文档结构、选择状态、高亮、拖拽、历史记录等 * * @class */ export declare class EditorStore { /** 高亮缓存映射表 */ highlightCache: Map; /** 允许进入的元素类型集合 */ private ableToEnter; /** 当前拖拽的元素 */ draggedElement: null | HTMLElement; footnoteDefinitionMap: Map; inputComposition: boolean; plugins?: MarkdownEditorPlugin[]; domRect: DOMRect | null; _editor: React.MutableRefObject; /** 当前 setMDContent 操作的 AbortController */ private _currentAbortController; private markdownToHtmlOptions?; /** * 获取当前编辑器实例。 * * @returns 当前的 Slate 编辑器实例。 */ get editor(): BaseEditor & ReactEditor & HistoryEditor; constructor(_editor: React.MutableRefObject, plugins?: MarkdownEditorPlugin[], markdownToHtmlOptions?: MarkdownToHtmlOptions); /** * 聚焦到编辑器 */ focus(): void; /** * 查找最新的节点索引。 * * @param node - 当前节点。 * @param index - 当前索引路径。 * @returns 最新节点的索引路径。 */ private findLatest; /** * 检查给定节点是否是最新节点。用于展示 闪动光标 * * @param node - 要检查的节点。 * @returns 如果节点是最新节点,则返回 true;否则返回 false。 */ isLatestNode(node: Node): boolean; /** * 插入一个链接到编辑器中。 * * @param filePath - 要插入的文件路径。如果路径以 'http' 开头,则链接文本为路径本身,否则为文件名。 * * 该方法首先解析文件路径,并创建一个包含文本和 URL 的节点对象。 * 然后,它检查当前编辑器的选区是否存在且未折叠。如果选区不存在或已折叠,则方法返回。 * * 接下来,它查找当前选区所在的最低层级的元素节点。如果节点类型是 'table-cell' 或 'paragraph', * 则在当前选区插入链接节点并选中它。 * * 如果当前选区所在的元素节点类型不是 'table-cell' 或 'paragraph',则查找包含当前选区的父级元素节点, * 并在其后插入一个包含链接节点的新段落。 */ insertLink(filePath: string): void; /** * 插入节点到编辑器中。 * * @param nodes - 要插入的节点,可以是单个节点或节点数组。 * @param options - 可选参数,用于指定插入节点的选项。 */ insertNodes(nodes: Node | Node[], options?: any): void; /** * Converts an HTML element to a Slate path and node. * * @param el - The HTML element to convert. * @returns A tuple containing the path and the corresponding Slate node. * @private */ private toPath; /** * Clears all content from the editor, replacing it with an empty paragraph. */ clearContent(): void; /** * 从 markdown 文本设置编辑器内容 * * @param md - 要设置为编辑器内容的 Markdown 字符串 * 如果为 undefined,方法将直接返回不做任何更改 * 如果 markdown 与当前内容相同,则不做任何更改 * @param plugins - 可选的自定义 markdown 解析插件 * @param options - 可选的配置参数 * - chunkSize: 分块大小阈值,默认 5000 字符。超过此大小会启用分批处理 * - separator: 分隔符,默认为双换行符 '\n\n',用于拆分长文本 * - useRAF: 是否使用 requestAnimationFrame 优化,默认 true,避免长文本处理时卡顿 * - batchSize: 每帧处理的节点数量,默认 50,仅在 useRAF=true 时生效 * - onProgress: 进度回调函数,接收当前进度 (0-1) 作为参数 * @returns 如果使用 RAF,返回 Promise;否则同步执行 */ setMDContent(md?: string, plugins?: MarkdownEditorPlugin[], options?: { chunkSize?: number; separator?: string | RegExp; useRAF?: boolean; batchSize?: number; onProgress?: (progress: number) => void; }): void | Promise; /** * 取消当前正在进行的 setMDContent 操作 */ cancelSetMDContent(): void; /** * 使用 requestAnimationFrame 分批解析和设置内容 * 边解析边插入,实时显示内容 * * @param chunks - markdown 分块数组 * @param plugins - 解析插件 * @param batchSize - 每帧处理的数量 * @param onProgress - 进度回调函数 * @returns Promise,在所有内容处理完成后 resolve * @private */ private _parseAndSetContentWithRAF; /** * 使用 requestAnimationFrame 分批设置内容 * 每帧插入一批节点,避免长时间阻塞主线程 * * @param allNodes - 所有要插入的节点 * @param batchSize - 每帧处理的节点数量 * @param onProgress - 进度回调函数 * @returns Promise,在所有节点插入完成后 resolve * @private */ private _setContentWithRAF; /** * 按指定分隔符拆分 markdown 内容 * 保持内容结构的完整性 * * @param md - 要拆分的 markdown 字符串 * @param separator - 分隔符,可以是字符串或正则表达式 * @returns 拆分后的字符串数组 * @private */ private _splitMarkdown; private _collectSeparatorMatches; private _isLineStart; private _matchFence; private _findLineEnd; /** * 移除节点 * @param options */ removeNodes: (options?: { at?: Location; match?: NodeMatch; mode?: RangeMode; hanging?: boolean; voids?: boolean; }) => void; /** * 获取当前编辑器内容作为节点列表 * * @returns 当前编辑器内容的节点列表 */ getContent(): any[]; /** * 获取当前编辑器内容作为 markdown 字符串 * * @param plugins - 可选的自定义 markdown 转换插件 * @returns 转换为 markdown 的当前编辑器内容 */ getMDContent(plugins?: MarkdownEditorPlugin[]): string; /** * 获取当前编辑器内容作为 HTML 字符串 * * @returns 转换为 HTML 的当前编辑器内容 */ getHtmlContent(options?: MarkdownToHtmlOptions): string; /** * 使用节点列表设置编辑器内容 * * @param nodeList - 要设置为编辑器内容的节点列表 */ setContent(nodeList: Node[]): void; /** * 使用差异检测和操作队列优化更新节点列表。 * * @param nodeList - 新的节点列表 * * 优化步骤: * 1. 过滤无效节点 * 2. 生成差异操作 * 3. 执行优化后的操作 * 4. 处理可能的错误情况 * * 过滤规则: * - 移除空的段落节点 * - 移除空的列表节点 * - 移除空的列表项节点 * - 移除无效的代码块节点 * - 移除无源的图片节点 * * 错误处理: * - 如果优化更新失败,会回退到直接替换整个节点列表 * - 错误信息会被记录到控制台 */ updateNodeList(nodeList: Node[]): void; /** * 生成两个节点列表之间的差异操作队列。 * * @param newNodes - 新的节点列表 * @param oldNodes - 旧的节点列表 * @returns 包含所有需要执行的操作的队列 * * 该方法通过以下步骤生成差异: * 1. 处理节点数量不同的情况 * 2. 对共有节点进行深度比较 * 3. 生成最小化的操作序列 * 4. 根据优先级排序操作 */ private generateDiffOperations; /** * 递归比较两个节点及其子节点的差异。 * * @param newNode - 新节点 * @param oldNode - 旧节点 * @param path - 当前节点的路径 * @param operations - 操作队列,用于存储发现的差异操作 * * 比较过程包括: * 1. 检查节点类型是否相同 * 2. 特殊处理表格节点 * 3. 比较文本节点内容 * 4. 比较节点属性 * 5. 递归比较子节点 * @private */ private compareNodes; /** * 特殊处理表格节点的比较。 * * @param newTable - 新的表格节点 * @param oldTable - 旧的表格节点 * @param path - 表格节点的路径 * @param operations - 操作队列 * * 处理步骤: * 1. 检查表格结构是否相同 * 2. 比较表格属性 * 3. 逐行比较和更新 * 4. 处理行数变化 * 5. 必要时进行整表替换 * @private */ private compareTableNodes; /** * 比较和更新表格单元格。 * * @param newCell - 新的单元格节点 * @param oldCell - 旧的单元格节点 * @param path - 单元格的路径 * @param operations - 操作队列 * * 处理步骤: * 1. 检查单元格属性变化 * 2. 处理简单文本单元格 * 3. 处理复杂单元格内容 * 4. 生成适当的更新操作 * @private */ private compareCells; /** * 比较单元格属性 */ private compareCellProperties; /** * 比较单元格子节点 */ private compareCellChildren; /** * 判断是否是简单文本单元格 */ private isSimpleTextCell; /** * 比较简单文本单元格 */ private compareSimpleTextCell; /** * 比较文本节点属性 */ private compareTextNodeProperties; /** * 比较复杂单元格子节点 */ private compareComplexCellChildren; /** * 判断结构是否不同 */ private isStructurallyDifferent; /** * 替换复杂单元格子节点 */ private replaceComplexCellChildren; /** * 逐个比较子节点 */ private compareChildrenSequentially; /** * 执行操作队列中的所有操作。 * * @param operations - 要执行的操作队列 * * 执行过程: * 1. 使用批处理模式执行所有操作 * 2. 按照操作类型分别处理 * 3. 处理可能的错误情况 * 4. 保证操作的原子性 */ private executeOperations; /** * 处理拖拽开始事件。 * * @param e - React 拖拽事件对象 * @param container - 容器 div 元素 * * 此方法会在拖拽开始时调用,主要功能包括: * 1. 阻止事件传播 * 2. 设置拖拽图像 * 3. 初始化拖拽相关的元素和位置数据 * 4. 添加拖拽过程中和拖拽结束时的事件监听器 * 5. 计算可拖拽目标的位置 * 6. 处理拖拽过程中的视觉反馈 * 7. 在拖拽结束时更新编辑器内容 */ dragStart(e: any, container: HTMLDivElement): void; /** * 替换编辑器中的文本内容 * * @param searchText - 要查找和替换的原始文本 * @param replaceText - 替换后的新文本 * @param options - 可选参数 * @param options.caseSensitive - 是否区分大小写,默认为 false * @param options.wholeWord - 是否只匹配完整单词,默认为 false * @param options.replaceAll - 是否替换所有匹配项,默认为 true * * @returns 替换操作的数量 * * @example * ```ts * // 替换所有 "old" 为 "new" * const count = store.replaceText("old", "new"); * * // 只替换第一个匹配项,区分大小写 * const count = store.replaceText("Old", "New", { * caseSensitive: true, * replaceAll: false * }); * ``` */ replaceText(searchText: string, replaceText: string, options?: { caseSensitive?: boolean; wholeWord?: boolean; replaceAll?: boolean; }): number; /** * 在选中的区域内替换文本 * * @param searchText - 要查找和替换的原始文本 * @param replaceText - 替换后的新文本 * @param options - 可选参数 * @param options.caseSensitive - 是否区分大小写,默认为 false * @param options.wholeWord - 是否只匹配完整单词,默认为 false * @param options.replaceAll - 是否替换所有匹配项,默认为 true * * @returns 替换操作的数量,如果没有选中区域则返回 0 * * @example * ```ts * // 在选中区域内替换文本 * const count = store.replaceTextInSelection("old", "new"); * ``` */ replaceTextInSelection(searchText: string, replaceText: string, options?: { caseSensitive?: boolean; wholeWord?: boolean; replaceAll?: boolean; }): number; /** * 替换所有匹配的文本(replaceText 的简化版本) * * @param searchText - 要查找和替换的原始文本 * @param replaceText - 替换后的新文本 * @param caseSensitive - 是否区分大小写,默认为 false * * @returns 替换操作的数量 * * @example * ```ts * // 替换所有 "old" 为 "new" * const count = store.replaceAll("old", "new"); * * // 区分大小写替换 * const count = store.replaceAll("Old", "New", true); * ``` */ replaceAll(searchText: string, replaceText: string, caseSensitive?: boolean): number; /** * 转义正则表达式特殊字符 * * @param string - 需要转义的字符串 * @returns 转义后的字符串 * @private */ private escapeRegExp; /** * 在编辑器中根据路径描述和文本内容查找并选择匹配位置 * * @param pathDescription - 路径描述,用于限制搜索范围。可以是: * - 节点类型(如 "paragraph", "table", "list") * - 包含特定文本的节点区域 * - 空字符串表示在整个编辑器中搜索 * @param searchText - 要查找的文本内容 * @param options - 查找选项 * @returns 匹配结果数组 * * @example * ```ts * // 查找所有包含 "focus" 的位置 * const results = store.findByPathAndText("", "focus"); */ findByPathAndText(pathDescription: Path, searchText: string, options?: { caseSensitive?: boolean; wholeWord?: boolean; maxResults?: number; }): { path: Path; range: import("slate").BaseRange; node: any; matchedText: string; offset: { start: number; end: number; }; lineContent: string; nodeType?: string | undefined; searchVariant?: string | undefined; isLink?: boolean | undefined; linkUrl?: string | undefined; }[]; /** * 更新编辑器的状态数据 * * @param value - 状态更新器,可以是函数或对象 * * 该方法提供两种更新状态的方式: * 1. 函数式更新:传入一个函数,可以直接修改状态 * 2. 对象式更新:传入一个对象,直接覆盖对应的状态值 * * @example * ```ts * // 函数式更新 - 可以访问当前状态 * setState((state) => { * state.focus = true; * state.manual = false; * }); * * // 对象式更新 - 直接设置新值 * setState({ * focus: true, * manual: false * }); * ``` */ setState(value: (state: EditorStore) => void): void; }