/** * Collabora Online 搜索和替换功能 * * 职责: 封装 UNO 搜索替换命令 * * @encoding UTF-8 */ import { sendUnoCommand } from '../Uno'; /** * 搜索命令类型 */ export enum SearchCommand { /** 查找下一个 */ FindNext = 0, /** 查找上一个 */ FindPrevious = 1, /** 替换当前选中 */ Replace = 2, /** 替换全部 */ ReplaceAll = 3, } /** * 搜索选项 */ export interface SearchOptions { /** 区分大小写 */ caseSensitive?: boolean; /** 全字匹配 */ wholeWord?: boolean; /** 使用正则表达式 */ regexp?: boolean; /** 向后搜索 */ backward?: boolean; /** 静默模式(不显示搜索结果提示) */ quiet?: boolean; } /** * 替换选项 */ export interface ReplaceOptions extends SearchOptions { /** 替换全部 */ replaceAll?: boolean; } /** * 搜索文本 - 查找下一个匹配项 * @param iframeWindow - iframe 的 contentWindow * @param text - 要搜索的文本 * @param options - 搜索选项 */ export function unoSearchNext( iframeWindow: Window, text: string, options: SearchOptions = {} ): void { const { caseSensitive = false, wholeWord = false, regexp = false, backward = false, quiet = true, } = options; // 计算 SearchFlags let searchFlags = 0; if (wholeWord) searchFlags |= 0x00010000; // SEARCH_WORD sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: text }, 'SearchItem.Command': { type: 'long', value: backward ? SearchCommand.FindPrevious : SearchCommand.FindNext }, 'SearchItem.Backward': { type: 'boolean', value: backward }, 'SearchItem.SearchCaseSensitive': { type: 'boolean', value: caseSensitive }, 'SearchItem.SearchRegularExpression': { type: 'boolean', value: regexp }, 'SearchItem.SearchFlags': { type: 'long', value: searchFlags }, 'SearchItem.AlgorithmType': { type: 'short', value: regexp ? 1 : 0 }, 'Quiet': { type: 'boolean', value: quiet }, }); console.log('[SearchReplace] 搜索下一个:', text, options); } /** * 搜索文本 - 查找上一个匹配项 * @param iframeWindow - iframe 的 contentWindow * @param text - 要搜索的文本 * @param options - 搜索选项 */ export function unoSearchPrevious( iframeWindow: Window, text: string, options: SearchOptions = {} ): void { unoSearchNext(iframeWindow, text, { ...options, backward: true }); console.log('[SearchReplace] 搜索上一个:', text); } /** * 替换当前选中的匹配项 * @param iframeWindow - iframe 的 contentWindow * @param searchText - 要搜索的文本 * @param replaceText - 替换后的文本 * @param options - 替换选项 */ export function unoReplaceCurrent( iframeWindow: Window, searchText: string, replaceText: string, options: SearchOptions = {} ): void { const { caseSensitive = false, wholeWord = false, regexp = false, quiet = true, } = options; let searchFlags = 0; if (wholeWord) searchFlags |= 0x00010000; sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: searchText }, 'SearchItem.ReplaceString': { type: 'string', value: replaceText }, 'SearchItem.Command': { type: 'long', value: SearchCommand.Replace }, 'SearchItem.Backward': { type: 'boolean', value: false }, 'SearchItem.SearchCaseSensitive': { type: 'boolean', value: caseSensitive }, 'SearchItem.SearchRegularExpression': { type: 'boolean', value: regexp }, 'SearchItem.SearchFlags': { type: 'long', value: searchFlags }, 'SearchItem.AlgorithmType': { type: 'short', value: regexp ? 1 : 0 }, 'Quiet': { type: 'boolean', value: quiet }, }); console.log('[SearchReplace] 替换当前:', searchText, '->', replaceText); } /** * 替换所有匹配项 * @param iframeWindow - iframe 的 contentWindow * @param searchText - 要搜索的文本 * @param replaceText - 替换后的文本 * @param options - 替换选项 */ export function unoReplaceAll( iframeWindow: Window, searchText: string, replaceText: string, options: SearchOptions = {} ): void { const { caseSensitive = false, wholeWord = false, regexp = false, quiet = true, } = options; let searchFlags = 0; if (wholeWord) searchFlags |= 0x00010000; sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: searchText }, 'SearchItem.ReplaceString': { type: 'string', value: replaceText }, 'SearchItem.Command': { type: 'long', value: SearchCommand.ReplaceAll }, 'SearchItem.SearchAll': { type: 'boolean', value: true }, 'SearchItem.Backward': { type: 'boolean', value: false }, 'SearchItem.SearchCaseSensitive': { type: 'boolean', value: caseSensitive }, 'SearchItem.SearchRegularExpression': { type: 'boolean', value: regexp }, 'SearchItem.SearchFlags': { type: 'long', value: searchFlags }, 'SearchItem.AlgorithmType': { type: 'short', value: regexp ? 1 : 0 }, 'Quiet': { type: 'boolean', value: quiet }, }); console.log('[SearchReplace] 替换全部:', searchText, '->', replaceText); } /** * 统一的搜索替换接口 * @param iframeWindow - iframe 的 contentWindow * @param searchText - 要搜索的文本 * @param replaceText - 替换后的文本(可选,不提供则只搜索) * @param options - 选项 */ export function unoSearchAndReplace( iframeWindow: Window, searchText: string, replaceText?: string, options: ReplaceOptions = {} ): void { if (replaceText === undefined || replaceText === null) { // 只搜索 unoSearchNext(iframeWindow, searchText, options); } else if (options.replaceAll) { // 替换全部 unoReplaceAll(iframeWindow, searchText, replaceText, options); } else { // 替换当前 unoReplaceCurrent(iframeWindow, searchText, replaceText, options); } } /** * 取消搜索选中状态 * @param iframeWindow - iframe 的 contentWindow */ export function unoCancelSearch(iframeWindow: Window): void { sendUnoCommand(iframeWindow, '.uno:Escape', {}); console.log('[SearchReplace] 取消搜索'); } /** * 在指定页面替换文本(组合命令) * 流程:跳转页面 -> 等待 -> 搜索选中 -> 等待 -> 替换 * * @param iframeWindow - iframe 的 contentWindow * @param pageNumber - 目标页码(从1开始) * @param searchText - 要搜索的文本 * @param replaceText - 替换后的文本 * @param options - 搜索选项 * @returns Promise - 是否成功 */ export async function replaceTextInPage( iframeWindow: Window, pageNumber: number, searchText: string, replaceText: string, options: SearchOptions = {} ): Promise<{ success: boolean; message: string }> { const { caseSensitive = false, wholeWord = false, regexp = false, quiet = true, } = options; console.log('[SearchReplace] 开始在第', pageNumber, '页替换:', searchText, '->', replaceText); try { // 1. 发送自定义消息跳转到指定页面 const gotoMessage = { MessageId: 'custompostMessage', SendTime: Date.now(), Values: { Command: 'GOTO_PAGE', Args: { pageNumber }, }, }; iframeWindow.postMessage(JSON.stringify(gotoMessage), '*'); console.log('[SearchReplace] 步骤1: 跳转到第', pageNumber, '页'); // 等待页面跳转完成 await delay(500); // 2. 搜索文本(查找下一个) let searchFlags = 0; if (wholeWord) searchFlags |= 0x00010000; sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: searchText }, 'SearchItem.Command': { type: 'long', value: SearchCommand.FindNext }, 'SearchItem.Backward': { type: 'boolean', value: false }, 'SearchItem.SearchCaseSensitive': { type: 'boolean', value: caseSensitive }, 'SearchItem.SearchRegularExpression': { type: 'boolean', value: regexp }, 'SearchItem.SearchFlags': { type: 'long', value: searchFlags }, 'SearchItem.AlgorithmType': { type: 'short', value: regexp ? 1 : 0 }, 'Quiet': { type: 'boolean', value: quiet }, }); console.log('[SearchReplace] 步骤2: 搜索文本'); // 等待搜索完成 await delay(300); // 3. 替换选中的文本 sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: searchText }, 'SearchItem.ReplaceString': { type: 'string', value: replaceText }, 'SearchItem.Command': { type: 'long', value: SearchCommand.Replace }, 'SearchItem.Backward': { type: 'boolean', value: false }, 'SearchItem.SearchCaseSensitive': { type: 'boolean', value: caseSensitive }, 'SearchItem.SearchRegularExpression': { type: 'boolean', value: regexp }, 'SearchItem.SearchFlags': { type: 'long', value: searchFlags }, 'SearchItem.AlgorithmType': { type: 'short', value: regexp ? 1 : 0 }, 'Quiet': { type: 'boolean', value: quiet }, }); console.log('[SearchReplace] 步骤3: 替换文本'); return { success: true, message: `已在第${pageNumber}页替换: "${searchText}" -> "${replaceText}"`, }; } catch (e) { console.error('[SearchReplace] 替换失败:', e); return { success: false, message: e instanceof Error ? e.message : '未知错误', }; } } /** * 延迟函数 */ function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * 高亮文本(使用 UNO 命令) * 流程:先搜索选中所有匹配项,再设置背景色 * * @param iframeWindow - iframe 的 contentWindow * @param text - 要高亮的文本 * @param color - 高亮颜色,默认 16776960 = 黄色 */ export function unoHighlightText( iframeWindow: Window, text: string, color: number = 16776960 ): void { // 1. 查找所有匹配项(FindAll) sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: text }, 'SearchItem.Command': { type: 'long', value: 1 }, // 1 = FindAll / Search Next 'SearchItem.SearchFlags': { type: 'long', value: 0 }, 'SearchItem.AlgorithmType': { type: 'short', value: 0 }, 'SearchItem.Backward': { type: 'boolean', value: false }, 'SearchItem.SearchCaseSensitive': { type: 'boolean', value: false }, 'Quiet': { type: 'boolean', value: true }, }); // 2. 设置背景色高亮 sendUnoCommand(iframeWindow, '.uno:BackColor', { BackColor: { type: 'long', value: color }, }); console.log('[SearchReplace] 高亮文本:', text, '颜色:', color); } /** * 清除高亮(使用 UNO 命令) * 通过设置背景色为透明来清除高亮 * * @param iframeWindow - iframe 的 contentWindow * @param text - 要清除高亮的文本(可选,不传则清除当前选中) */ export function unoClearHighlight( iframeWindow: Window, text?: string ): void { if (text) { // 先搜索选中文本 sendUnoCommand(iframeWindow, '.uno:ExecuteSearch', { 'SearchItem.SearchString': { type: 'string', value: text }, 'SearchItem.Command': { type: 'long', value: 1 }, 'SearchItem.SearchFlags': { type: 'long', value: 0 }, 'SearchItem.AlgorithmType': { type: 'short', value: 0 }, 'SearchItem.Backward': { type: 'boolean', value: false }, 'Quiet': { type: 'boolean', value: true }, }); } // 设置背景色为透明(-1 表示无背景色) sendUnoCommand(iframeWindow, '.uno:BackColor', { BackColor: { type: 'long', value: -1 }, }); console.log('[SearchReplace] 清除高亮:', text || '当前选中'); }