Merge branch 'PingChuan' into shiy-login
This commit is contained in:
@@ -14,15 +14,7 @@ import { RefObject, useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { COLLABORA_URL } from '~/config/api-config';
|
import { COLLABORA_URL } from '~/config/api-config';
|
||||||
import { toastService } from '../ui/Toast';
|
import { toastService } from '../ui/Toast';
|
||||||
import {
|
import {
|
||||||
unoEscape,
|
|
||||||
// unoHighlightText,
|
|
||||||
unoReplaceText,
|
|
||||||
unoSave,
|
|
||||||
unoScrollToTop,
|
unoScrollToTop,
|
||||||
unoSearchText,
|
|
||||||
unoSetZoom,
|
|
||||||
unoZoomMinus,
|
|
||||||
unoZoomPlus,
|
|
||||||
} from './lib';
|
} from './lib';
|
||||||
import type { CollaboraConfig } from './types';
|
import type { CollaboraConfig } from './types';
|
||||||
|
|
||||||
@@ -125,111 +117,6 @@ export function useDocumentReady(iframeRef: RefObject<HTMLIFrameElement>) {
|
|||||||
* @returns UNO 命令方法集合
|
* @returns UNO 命令方法集合
|
||||||
*/
|
*/
|
||||||
export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>) {
|
export function useCollaboraUnoCommands(iframeRef: RefObject<HTMLIFrameElement>) {
|
||||||
/**
|
|
||||||
* 搜索文本(用于定位)
|
|
||||||
*/
|
|
||||||
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<HTMLIFrameElement>)
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
}, [iframeRef]);
|
}, [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(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
searchText,
|
scrollToTop
|
||||||
// locateText,
|
|
||||||
replaceText,
|
|
||||||
// highlightText,
|
|
||||||
// removeHighlight,
|
|
||||||
escapeSelection,
|
|
||||||
scrollToTop,
|
|
||||||
saveDocument,
|
|
||||||
zoomIn,
|
|
||||||
zoomOut,
|
|
||||||
setZoom,
|
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
searchText,
|
scrollToTop
|
||||||
// locateText,
|
|
||||||
replaceText,
|
|
||||||
// highlightText,
|
|
||||||
// removeHighlight,
|
|
||||||
escapeSelection,
|
|
||||||
scrollToTop,
|
|
||||||
saveDocument,
|
|
||||||
zoomIn,
|
|
||||||
zoomOut,
|
|
||||||
setZoom,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
@@ -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<SaveResponse> {
|
||||||
|
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), '*');
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,12 +4,6 @@
|
|||||||
* @encoding UTF-8
|
* @encoding UTF-8
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// // 搜索功能
|
|
||||||
// export { unoSearchText } from './search';
|
|
||||||
|
|
||||||
// // 替换功能
|
|
||||||
// export { unoReplaceText } from './replace';
|
|
||||||
|
|
||||||
|
|
||||||
// 高亮,已经封装好了
|
// 高亮,已经封装好了
|
||||||
export { switchHighlight } from './Highlightselecttext';
|
export { switchHighlight } from './Highlightselecttext';
|
||||||
@@ -19,11 +13,6 @@ export {
|
|||||||
unoScrollToTop
|
unoScrollToTop
|
||||||
} from './navigation';
|
} from './navigation';
|
||||||
|
|
||||||
// 缩放功能
|
|
||||||
// export { unoSetZoom, unoZoomMinus, unoZoomPlus } from './zoom';
|
|
||||||
|
|
||||||
// 文档操作
|
|
||||||
export { unoSave } from './document';
|
|
||||||
|
|
||||||
// 页数信息
|
// 页数信息
|
||||||
export {
|
export {
|
||||||
|
|||||||
Reference in New Issue
Block a user