diff --git a/app/components/collabora/hooks.ts b/app/components/collabora/hooks.ts index 869243c..1b14347 100644 --- a/app/components/collabora/hooks.ts +++ b/app/components/collabora/hooks.ts @@ -14,15 +14,7 @@ import { RefObject, useCallback, useEffect, useMemo, useState } from 'react'; import { COLLABORA_URL } from '~/config/api-config'; import { toastService } from '../ui/Toast'; import { - unoEscape, - // unoHighlightText, - unoReplaceText, - unoSave, unoScrollToTop, - unoSearchText, - unoSetZoom, - unoZoomMinus, - unoZoomPlus, } from './lib'; import type { CollaboraConfig } from './types'; @@ -125,111 +117,6 @@ export function useDocumentReady(iframeRef: RefObject) { * @returns UNO 命令方法集合 */ export function useCollaboraUnoCommands(iframeRef: RefObject) { - /** - * 搜索文本(用于定位) - */ - const searchText = useCallback( - async (text: string) => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - console.log(`[UNO] 搜索文本: "${text}"`); - await unoSearchText(iframeRef.current.contentWindow, text); - await new Promise((resolve) => setTimeout(resolve, 100)); - }, - [iframeRef] - ); - - // /** - // * 定位文本(搜索 + 立即取消选中) - // * 用于"只看不改"的场景,避免蓝色选中背景 - // */ - // const locateText = useCallback( - // async (text: string) => { - // if (!iframeRef.current?.contentWindow) { - // console.warn('[UNO] iframe 不可用'); - // return; - // } - - // console.log(`[UNO] 定位文本(无选中): "${text}"`); - - // // 1. 执行搜索(滚动到目标并选中) - // await searchText(text); - - // // 2. 等待渲染完成 - // await new Promise((resolve) => setTimeout(resolve, 50)); - - // // 3. 取消选中(去除蓝色背景,保留视图位置) - // unoEscape(iframeRef.current.contentWindow); - - // console.log(`[UNO] 定位完成,已取消选中`); - // }, - // [searchText, iframeRef] - // ); - - /** - * 替换文本(ReplaceAll) - */ - const replaceText = useCallback( - async (searchText: string, replaceText: string) => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - console.log(`[UNO] 替换文本: "${searchText}" -> "${replaceText}"`); - await unoReplaceText(iframeRef.current.contentWindow, searchText, replaceText); - await new Promise((resolve) => setTimeout(resolve, 200)); - }, - [iframeRef] - ); - - // /** - // * 高亮文本 - // */ - // const highlightText = useCallback( - // async (text: string, color?: number) => { - // if (!iframeRef.current?.contentWindow) { - // console.warn('[UNO] iframe 不可用'); - // return; - // } - // console.log(`[UNO] 高亮文本: "${text}"`); - // await unoHighlightText(iframeRef.current.contentWindow, text, color); - // await new Promise((resolve) => setTimeout(resolve, 200)); - // }, - // [iframeRef] - // ); - - // /** - // * 移除高亮 - // */ - // const removeHighlight = useCallback( - // async (text: string) => { - // if (!iframeRef.current?.contentWindow) { - // console.warn('[UNO] iframe 不可用'); - // return; - // } - // console.log(`[UNO] 移除高亮: "${text}"`); - // await unoRemoveHighlight(iframeRef.current.contentWindow, text); - // await new Promise((resolve) => setTimeout(resolve, 200)); - // }, - // [iframeRef] - // ); - - /** - * 取消选中(Escape) - */ - const escapeSelection = useCallback(async () => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - - console.log('[UNO] 取消选中'); - await unoEscape(iframeRef.current.contentWindow); - await new Promise((resolve) => setTimeout(resolve, 50)); - }, [iframeRef]); - /** * 滚动到文档顶部 */ @@ -244,93 +131,14 @@ export function useCollaboraUnoCommands(iframeRef: RefObject) await new Promise((resolve) => setTimeout(resolve, 100)); }, [iframeRef]); - /** - * 保存文档 - */ - const saveDocument = useCallback(async () => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - - console.log('[UNO] 保存文档'); - await unoSave(iframeRef.current.contentWindow); - await new Promise((resolve) => setTimeout(resolve, 1000)); - }, [iframeRef]); - - /** - * 放大文档 - */ - const zoomIn = useCallback(async () => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - - console.log('[UNO] 放大文档'); - await unoZoomPlus(iframeRef.current.contentWindow); - await new Promise((resolve) => setTimeout(resolve, 100)); - }, [iframeRef]); - - /** - * 缩小文档 - */ - const zoomOut = useCallback(async () => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - - console.log('[UNO] 缩小文档'); - await unoZoomMinus(iframeRef.current.contentWindow); - await new Promise((resolve) => setTimeout(resolve, 100)); - }, [iframeRef]); - - /** - * 设置缩放比例 - * @param percentage - 缩放百分比 (例如:100 表示 100%) - */ - const setZoom = useCallback( - async (percentage: number) => { - if (!iframeRef.current?.contentWindow) { - console.warn('[UNO] iframe 不可用'); - return; - } - - console.log(`[UNO] 设置缩放比例: ${percentage}%`); - await unoSetZoom(iframeRef.current.contentWindow, percentage); - await new Promise((resolve) => setTimeout(resolve, 100)); - }, - [iframeRef] - ); return useMemo( () => ({ - searchText, - // locateText, - replaceText, - // highlightText, - // removeHighlight, - escapeSelection, - scrollToTop, - saveDocument, - zoomIn, - zoomOut, - setZoom, + scrollToTop }), [ - searchText, - // locateText, - replaceText, - // highlightText, - // removeHighlight, - escapeSelection, - scrollToTop, - saveDocument, - zoomIn, - zoomOut, - setZoom, + scrollToTop ] ); } diff --git a/app/components/collabora/lib/document.ts b/app/components/collabora/lib/document.ts deleted file mode 100644 index 540062f..0000000 --- a/app/components/collabora/lib/document.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Collabora 文档操作功能模块 - * - * @encoding UTF-8 - */ - -import { sendUnoCommand } from '../Uno'; - -/** - * 保存文档 - * @param iframeWindow - iframe 的 contentWindow - */ -export function unoSave(iframeWindow: Window): void { - sendUnoCommand(iframeWindow, '.uno:Save'); -} diff --git a/app/components/collabora/lib/documentSave.ts b/app/components/collabora/lib/documentSave.ts new file mode 100644 index 0000000..a4fbb48 --- /dev/null +++ b/app/components/collabora/lib/documentSave.ts @@ -0,0 +1,207 @@ +/** + * Collabora 文档保存功能模块 + * + * 使用 PostMessage 协议实现文档保存,支持保存参数配置和状态监听 + * + * @encoding UTF-8 + */ + +/** + * 保存选项接口 + */ +export interface SaveOptions { + /** + * 保存后不关闭编辑模式 + * @default false + */ + dontTerminateEdit?: boolean; + + /** + * 如果文档未修改则不保存 + * @default false + */ + dontSaveIfUnmodified?: boolean; + + /** + * 是否显示保存通知 + * @default true + */ + notify?: boolean; + + /** + * 扩展数据(可选) + */ + extendedData?: string; +} + +/** + * 保存响应接口 + */ +export interface SaveResponse { + /** + * 是否保存成功 + */ + success: boolean; + + /** + * 结果代码(失败时提供) + */ + result?: string; + + /** + * 错误消息(失败时提供) + */ + errorMsg?: string; + + /** + * 保存时间戳 + */ + timestamp: number; +} + +/** + * Collabora PostMessage 数据类型 + */ +interface CollaboraMessageData { + MessageId?: string; + msgId?: string; + Values?: { + success?: boolean; + result?: string; + errorMsg?: string; + [key: string]: unknown; + }; + args?: { + success?: boolean; + result?: string; + errorMsg?: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +/** + * 解析 PostMessage 数据 + * @param data - PostMessage 原始数据 + * @returns 解析后的消息对象 + */ +function parseMessageData(data: unknown): CollaboraMessageData { + if (typeof data === 'string') { + return JSON.parse(data) as CollaboraMessageData; + } + return data as CollaboraMessageData; +} + +/** + * 保存文档 + * + * 使用 PostMessage 协议发送 Action_Save 命令,并监听 Action_Save_Resp 响应。 + * 支持自定义保存参数,如保存后是否关闭编辑、是否显示通知等。 + * + * @param iframeWindow - Collabora iframe 的 contentWindow + * @param options - 保存选项(可选) + * @returns Promise,解析为保存响应信息 + * + * @example + * ```typescript + * // 基本保存 + * const result = await saveDocument(iframeWindow); + * if (result.success) { + * console.log('保存成功'); + * } + * + * // 带参数保存 + * const result = await saveDocument(iframeWindow, { + * dontTerminateEdit: true, // 保存后不关闭编辑 + * dontSaveIfUnmodified: true, // 未修改则不保存 + * notify: true // 显示通知 + * }); + * ``` + */ +export async function saveDocument( + iframeWindow: Window, + options?: SaveOptions +): Promise { + return new Promise((resolve, reject) => { + // 超时设置(10秒,保存操作可能需要较长时间) + const timeout = setTimeout(() => { + cleanup(); + reject(new Error('保存操作超时')); + }, 10000); + + /** + * 消息监听器 + */ + const handleMessage = (event: MessageEvent) => { + try { + // 验证消息来源 + if (event.source !== iframeWindow) { + return; + } + + // 解析消息数据 + const data = parseMessageData(event.data); + + // 监听 Action_Save_Resp 响应 + // bundle.js 中的响应格式: { MessageId: 'Action_Save_Resp', args: { success, result, errorMsg } } + if (data.MessageId === 'Action_Save_Resp' || data.msgId === 'Action_Save_Resp') { + clearTimeout(timeout); + cleanup(); + + // 获取响应数据(可能在 Values 或 args 中) + const responseData = data.Values || data.args || {}; + const success = responseData.success === true; + + if (success) { + // 保存成功 + const response: SaveResponse = { + success: true, + timestamp: Date.now(), + }; + resolve(response); + } else { + // 保存失败 + const response: SaveResponse = { + success: false, + result: responseData.result as string | undefined, + errorMsg: responseData.errorMsg as string | undefined, + timestamp: Date.now(), + }; + resolve(response); + } + } + } catch (error) { + clearTimeout(timeout); + cleanup(); + reject(error); + } + }; + + /** + * 清理函数 + */ + const cleanup = () => { + window.removeEventListener('message', handleMessage); + }; + + // 注册消息监听器 + window.addEventListener('message', handleMessage); + + // 构建保存请求消息 + const message = { + MessageId: 'Action_Save', + SendTime: Date.now(), + Values: { + DontTerminateEdit: options?.dontTerminateEdit || false, + DontSaveIfUnmodified: options?.dontSaveIfUnmodified || false, + Notify: options?.notify !== false, // 默认显示通知 + ExtendedData: options?.extendedData || '', + }, + }; + + console.log('[DocumentSave] 发送保存命令:', message); + + // 发送保存请求 + iframeWindow.postMessage(JSON.stringify(message), '*'); + }); +} diff --git a/app/components/collabora/lib/index.ts b/app/components/collabora/lib/index.ts index fa648bb..a3ef342 100644 --- a/app/components/collabora/lib/index.ts +++ b/app/components/collabora/lib/index.ts @@ -4,12 +4,6 @@ * @encoding UTF-8 */ -// // 搜索功能 -// export { unoSearchText } from './search'; - -// // 替换功能 -// export { unoReplaceText } from './replace'; - // 高亮,已经封装好了 export { switchHighlight } from './Highlightselecttext'; @@ -19,11 +13,6 @@ export { unoScrollToTop } from './navigation'; -// 缩放功能 -// export { unoSetZoom, unoZoomMinus, unoZoomPlus } from './zoom'; - -// 文档操作 -export { unoSave } from './document'; // 页数信息 export {