Files
leaudit-platform-frontend/app/components/collabora/lib/SearchandReplace.ts
T

307 lines
9.3 KiB
TypeScript

/**
* 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<boolean> - 是否成功
*/
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<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}