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

370 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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));
}
/**
* 高亮文本(使用 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 || '当前选中');
}