UNPKG

dpu-onlyoffice-react

Version:

React component for OnlyOffice Document Server integration with version history support

1,046 lines (1,038 loc) 42.7 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); var material = require('@mui/material'); let scriptLoaded = false; let scriptLoading = false; let scriptLoadPromise = null; /** * Default script URLs */ const DEFAULT_SCRIPT_URL = 'https://documentserver.dpunity.com/web-apps/apps/api/documents/api.js'; const DEFAULT_TIMEOUT = 30000; // 30 seconds /** * Load OnlyOffice API script dynamically * @param options Configuration options for script loading * @returns Promise that resolves when script is loaded */ const loadOnlyOfficeScript = (options = {}) => { const { scriptUrl = DEFAULT_SCRIPT_URL, timeout = DEFAULT_TIMEOUT } = options; // If script is already loaded if (scriptLoaded) { return Promise.resolve(); } // If currently loading, return existing promise if (scriptLoading && scriptLoadPromise) { return scriptLoadPromise; } // Start loading script scriptLoading = true; scriptLoadPromise = new Promise((resolve, reject) => { // Check if script is already available if (window.DocsAPI && window.DocsAPI.DocEditor) { scriptLoaded = true; scriptLoading = false; resolve(); return; } // Create script element const script = document.createElement('script'); script.type = 'text/javascript'; script.src = scriptUrl; script.async = true; // Set up timeout const timeoutId = setTimeout(() => { scriptLoading = false; reject(new Error(`OnlyOffice script loading timeout after ${timeout}ms`)); }, timeout); script.onload = () => { clearTimeout(timeoutId); console.log('OnlyOffice API script loaded successfully'); scriptLoaded = true; scriptLoading = false; resolve(); }; script.onerror = () => { clearTimeout(timeoutId); console.error(`Failed to load OnlyOffice API script from ${scriptUrl}`); scriptLoading = false; reject(new Error(`Failed to load OnlyOffice API script from ${scriptUrl}`)); }; // Add script to document document.head.appendChild(script); }); return scriptLoadPromise; }; /** * Check if OnlyOffice script is loaded and available * @returns boolean indicating if script is loaded */ const isOnlyOfficeScriptLoaded = () => { return scriptLoaded && !!(window.DocsAPI && window.DocsAPI.DocEditor); }; /** * Reset script loading state (useful for testing) */ const resetScriptState = () => { scriptLoaded = false; scriptLoading = false; scriptLoadPromise = null; }; /** * Get current script loading state */ const getScriptState = () => ({ loaded: scriptLoaded, loading: scriptLoading, hasPromise: !!scriptLoadPromise }); /** * Utility functions for interacting with OnlyOffice Document Editor * These functions help users easily communicate with the editor instance */ /** * Get editor instance by key * Nếu không có key, sẽ trả về instance đầu tiên có sẵn */ const getEditorInstance = (editorKey) => { if (editorKey !== undefined) { return window.DocEditor?.instances?.[`editor-${editorKey}`] || null; } // Nếu không có key, trả về instance đầu tiên có sẵn const instances = window.DocEditor?.instances; if (instances) { const firstKey = Object.keys(instances)[0]; return firstKey ? instances[firstKey] : null; } return null; }; /** * Get editor instance by key * Nếu không có key, sẽ trả về instance đầu tiên có sẵn */ const getApiInstance = (editorKey) => { if (editorKey !== undefined) { return window.Api; } return null; }; /** * Refresh version history in editor * Supports both history data and error handling */ const refreshHistory = (data, message = 'History updated', editorKey) => { const editor = getEditorInstance(editorKey); console.log('editor', editor); if (editor && typeof editor.refreshHistory === 'function') { if (data && typeof data === 'object' && 'error' in data) { // Handle error case editor.refreshHistory({ error: data.error }, message); } else { // Handle success case editor.refreshHistory(data, message); } } else { console.warn('Editor instance not found or refreshHistory method not available'); } }; /** * Set history data in editor * Supports both history data and error handling */ const setHistoryData = (data, editorKey) => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.setHistoryData === 'function') { if (data && typeof data === 'object' && 'error' in data) { // Handle error case editor.setHistoryData({ error: data.error }); } else { // Handle success case editor.setHistoryData(data); } } else { console.warn('Editor instance not found or setHistoryData method not available'); } }; /** * Map user document data to editor format */ const mapDocumentDataToEditor = (userData) => { return { c: "compare", // Default value for compare mode fileType: userData.fileType, url: userData.url, token: userData.token }; }; /** * Set requested document in editor * Supports both document data and error handling */ const setRequestedDocument = (data, editorKey) => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.setRequestedDocument === 'function') { if (data && typeof data === 'object' && 'error' in data) { // Handle error case editor.setRequestedDocument({ error: data.error }); } else if (data.changesUrl && data.fileType && data.key && data.token && data.url && data.version) { // Handle success case with user data const mappedData = mapDocumentDataToEditor(data); editor.setRequestedDocument(mappedData); } else { console.warn('Invalid data format for setRequestedDocument'); } } else { console.warn('Editor instance not found or setRequestedDocument method not available'); } }; /** * Set data for version selection dialog * This will trigger the dialog to show version selection * @param historyData - Data from OnlyOffice history response * @param onVersionSelect - Callback when version is selected * @param editorKey - Editor key for identification */ const setDataDialog = (historyData, onVersionSelect, editorKey) => { // Store versions and callback in global state for dialog access if (!window.DocEditor) { window.DocEditor = {}; } if (!window.DocEditor.dialogs) { window.DocEditor.dialogs = {}; } const dialogKey = `dialog-${editorKey || 'default'}`; window.DocEditor.dialogs[dialogKey] = { versions: historyData.history, onVersionSelect, open: true }; // Trigger dialog open event const event = new CustomEvent('openVersionDialog', { detail: { dialogKey, versions: historyData.history } }); window.dispatchEvent(event); }; /** * Request editor to close */ const requestClose = (editorKey) => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.requestClose === 'function') { editor.requestClose(); } else { console.warn('Editor instance not found or requestClose method not available'); } }; /** * Destroy editor instance */ const destroyEditor = (editorKey) => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.destroyEditor === 'function') { editor.destroyEditor(); } // Remove from global instances if (editorKey !== undefined && window.DocEditor?.instances?.[`editor-${editorKey}`]) { delete window.DocEditor.instances[`editor-${editorKey}`]; } else if (editorKey === undefined) { // Nếu không có key cụ thể, xóa tất cả instances if (window.DocEditor?.instances) { Object.keys(window.DocEditor.instances).forEach(key => { if (window.DocEditor?.instances) { delete window.DocEditor.instances[key]; } }); } } }; /** * Check if editor is ready */ const isEditorReady = (editorKey) => { const editor = getEditorInstance(editorKey); return !!(editor && typeof editor.refreshHistory === 'function'); }; /** * Get editor status */ const getEditorStatus = (editorKey) => { const editor = getEditorInstance(editorKey); return { exists: !!editor, ready: isEditorReady(editorKey), methods: editor ? { refreshHistory: typeof editor.refreshHistory === 'function', setHistoryData: typeof editor.setHistoryData === 'function', setRequestedDocument: typeof editor.setRequestedDocument === 'function', requestClose: typeof editor.requestClose === 'function', destroyEditor: typeof editor.destroyEditor === 'function' } : null }; }; /** * Dialog component for selecting document version * Styled exactly like OnlyOffice Comparison settings dialog */ const VersionSelectDialog = ({ open, onClose, onSelectVersion, versions, title = "Select document version" }) => { const [selectedVersion, setSelectedVersion] = react.useState(null); const handleVersionSelect = (version) => { setSelectedVersion(version); }; const handleConfirm = () => { if (selectedVersion) { onSelectVersion(selectedVersion); setSelectedVersion(null); onClose(); } }; const handleCancel = () => { setSelectedVersion(null); onClose(); }; const formatDateTime = (timeString) => { try { const date = new Date(timeString); return date.toLocaleString('vi-VN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } catch { return timeString; } }; const getUserDisplayName = (user) => { return user.name || user.id; }; return (jsxRuntime.jsxs(material.Dialog, { open: open, onClose: handleCancel, maxWidth: "sm", fullWidth: false, PaperProps: { sx: { minWidth: 400, maxWidth: 500, borderRadius: '4px', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', backgroundColor: '#ffffff', border: '1px solid #d0d0d0', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif' } }, children: [jsxRuntime.jsxs(material.DialogTitle, { sx: { padding: '12px 16px', borderBottom: '1px solid #e0e0e0', backgroundColor: '#f8f8f8', display: 'flex', justifyContent: 'space-between', alignItems: 'center', minHeight: '20px' }, children: [jsxRuntime.jsx(material.Typography, { variant: "h6", sx: { fontSize: '14px', fontWeight: 500, color: '#333333', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', margin: 0, lineHeight: 1.2 }, children: title }), jsxRuntime.jsx(material.Button, { onClick: handleCancel, sx: { minWidth: 'auto', width: '24px', height: '24px', padding: 0, borderRadius: '50%', backgroundColor: '#f0f0f0', color: '#666666', fontSize: '12px', fontWeight: 'bold', '&:hover': { backgroundColor: '#e0e0e0', color: '#333333' } }, children: "\u00D7" })] }), jsxRuntime.jsx(material.DialogContent, { sx: { padding: '20px 16px', backgroundColor: '#ffffff' }, children: jsxRuntime.jsxs(material.FormControl, { component: "fieldset", sx: { width: '100%' }, children: [jsxRuntime.jsx(material.Typography, { variant: "body1", sx: { fontSize: '13px', fontWeight: 400, color: '#333333', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', marginBottom: '12px', lineHeight: 1.4 }, children: "Select document version" }), jsxRuntime.jsx(material.RadioGroup, { value: selectedVersion?.version || '', onChange: (e) => { const version = versions.find(v => v.version === e.target.value); if (version) { handleVersionSelect(version); } }, sx: { gap: '8px' }, children: versions.length === 0 ? (jsxRuntime.jsx(material.Typography, { variant: "body2", sx: { fontSize: '12px', color: '#666666', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', textAlign: 'center', py: 2 }, children: "No versions available" })) : (versions.map((version, index) => (jsxRuntime.jsx(material.FormControlLabel, { value: version.version, control: jsxRuntime.jsx(material.Radio, { sx: { color: '#666666', '&.Mui-checked': { color: '#1976d2' }, padding: '4px', '& .MuiSvgIcon-root': { fontSize: '16px' } } }), label: jsxRuntime.jsxs(material.Box, { sx: { ml: 1 }, children: [jsxRuntime.jsxs(material.Typography, { variant: "body2", sx: { fontSize: '13px', fontWeight: 400, color: '#333333', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', lineHeight: 1.3 }, children: ["Version ", version.version] }), jsxRuntime.jsxs(material.Typography, { variant: "caption", sx: { fontSize: '11px', color: '#666666', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', display: 'block', lineHeight: 1.2 }, children: [formatDateTime(version.created), " \u2022 ", getUserDisplayName(version.user)] })] }), sx: { margin: 0, padding: '4px 0', alignItems: 'flex-start', '& .MuiFormControlLabel-label': { width: '100%' } } }, version.version)))) })] }) }), jsxRuntime.jsxs(material.DialogActions, { sx: { padding: '12px 16px', backgroundColor: '#f8f8f8', borderTop: '1px solid #e0e0e0', justifyContent: 'flex-end', gap: '8px' }, children: [jsxRuntime.jsx(material.Button, { onClick: handleCancel, sx: { fontSize: '13px', fontWeight: 400, color: '#333333', backgroundColor: '#ffffff', border: '1px solid #d0d0d0', borderRadius: '3px', padding: '6px 12px', minWidth: '60px', height: '28px', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', textTransform: 'none', '&:hover': { backgroundColor: '#f5f5f5', borderColor: '#b0b0b0' } }, children: "Cancel" }), jsxRuntime.jsx(material.Button, { onClick: handleConfirm, disabled: !selectedVersion, sx: { fontSize: '13px', fontWeight: 400, color: '#ffffff', backgroundColor: selectedVersion ? '#4a4a4a' : '#cccccc', border: 'none', borderRadius: '3px', padding: '6px 16px', minWidth: '60px', height: '28px', fontFamily: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif', textTransform: 'none', '&:hover': { backgroundColor: selectedVersion ? '#333333' : '#cccccc' }, '&:disabled': { backgroundColor: '#cccccc', color: '#999999' } }, children: "OK" })] })] })); }; /** * OnlyOffice Document Editor React Component * * A React wrapper for OnlyOffice Document Server that provides: * - Document viewing and editing capabilities * - Version history support * - Event handling * - Proper cleanup and memory management */ const DocumentEditor = react.forwardRef(({ config, editorKey, className, style, onError, eventHandlers, defaultKeys }, ref) => { let messageId = 0; const containerRef = react.useRef(null); const editorInstanceRef = react.useRef(null); const [documentLoaded, setDocumentLoaded] = react.useState(false); // State để quản lý editorKey có thể thay đổi const [currentEditorKey, setCurrentEditorKey] = react.useState(() => { if (editorKey !== undefined) { return editorKey; } // Tạo key duy nhất dựa trên timestamp và random return `editor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }); // State cho version selection dialog const [dialogOpen, setDialogOpen] = react.useState(false); const [dialogVersions, setDialogVersions] = react.useState([]); const [dialogCallback, setDialogCallback] = react.useState(null); const [pluginAddKeySource, setPluginAddKeySource] = react.useState(null); const prevEditorKeyRef = react.useRef(currentEditorKey); const regenerateEditorRef = react.useRef(null); /** * Tạo editorKey mới */ const generateNewEditorKey = react.useCallback(() => { const newKey = `editor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; return newKey; }, []); /** * Create event handlers - user provides handlers, library handles OnlyOffice integration */ const createEventHandlers = react.useCallback(() => { const handlers = {}; function sendMessageToOnlyOffice(event, message) { try { // Add message ID for tracking message.id = ++messageId; // Send message to iframe event.source.postMessage(message, '*'); } catch (error) { console.error('Error sending message:', error); } } function loadSampleData(event, keys) { // Send to OnlyOffice sendMessageToOnlyOffice(event, { type: 'SET_TEXT_DATA', payload: keys }); } // Default handlers with OnlyOffice integration handlers.onDocumentReady = (event) => { setDocumentLoaded(true); // Listen for messages from OnlyOffice iframe if (defaultKeys && defaultKeys.length > 0) { window.addEventListener('message', function (event) { try { const data = event.data; if (data && data.type === 'PLUGIN_READY') { setPluginAddKeySource(event.source); loadSampleData(event, defaultKeys); } else if (data && data.type === 'TEXT_DATA_SET_SUCCESS') { } else if (data && data.type === 'TEXT_DATA_RESPONSE') { setDocumentLoaded(true); } } catch (error) { console.error('Error processing message from OnlyOffice:', error); } }); } if (eventHandlers?.onDocumentReady) { eventHandlers.onDocumentReady(event); } }; handlers.onDocumentStateChange = (event) => { if (eventHandlers?.onDocumentStateChange) { eventHandlers.onDocumentStateChange(event); } }; handlers.onLoadComponentError = (errorCode, errorDescription) => { console.error(`OnlyOffice Error ${errorCode}: ${errorDescription}`); if (eventHandlers?.onLoadComponentError) { eventHandlers.onLoadComponentError(errorCode, errorDescription); } if (onError) { onError(new Error(`OnlyOffice Error ${errorCode}: ${errorDescription}`)); } }; handlers.onRequestClose = () => { const docEditor = getEditorInstance(currentEditorKey); if (docEditor) { docEditor.requestClose(); docEditor.destroyEditor(); } if (eventHandlers?.onRequestClose) { eventHandlers.onRequestClose(); } }; handlers.onSubmit = (event) => { if (eventHandlers?.onSubmit) { eventHandlers.onSubmit(event); } }; // Version history handlers - user provides data, library handles OnlyOffice handlers.onRequestHistory = (event) => { if (eventHandlers?.onRequestHistory) { eventHandlers.onRequestHistory(event); } }; handlers.onRequestHistoryData = (event) => { if (eventHandlers?.onRequestHistoryData) { eventHandlers.onRequestHistoryData(event); } }; handlers.onRequestHistoryClose = (event) => { // Tự động tạo lại editorKey mới khi đóng history if (regenerateEditorRef.current) { regenerateEditorRef.current(); } if (eventHandlers?.onRequestHistoryClose) { eventHandlers.onRequestHistoryClose(event); } }; handlers.onRequestRestore = (event) => { if (eventHandlers?.onRequestRestore) { eventHandlers.onRequestRestore(event); } }; handlers.onRequestSelectDocument = (event) => { // Gọi custom handler để user có thể set data cho dialog if (eventHandlers?.onRequestSelectDocument) { eventHandlers.onRequestSelectDocument(event); } else { // Default behavior: hiển thị dialog với dữ liệu mẫu const sampleHistoryData = { history: [ { version: "1", created: new Date().toISOString(), user: { id: "sample-user-id", name: "Người dùng mẫu" }, key: "sample-key", serverVersion: "", changes: null } ] }; setDataDialog(sampleHistoryData, (selectedVersion) => { // Có thể thêm logic xử lý version được chọn ở đây }, currentEditorKey); } }; handlers.onRequestSaveAs = (event) => { if (eventHandlers?.onRequestSaveAs) { eventHandlers.onRequestSaveAs(event); } }; return handlers; }, [eventHandlers, onError, currentEditorKey]); /** * Initialize OnlyOffice editor */ const initializeEditor = react.useCallback(async () => { if (!containerRef.current || !config) { console.warn("Cannot initialize editor: missing required props"); return; } try { // Load OnlyOffice script first await loadOnlyOfficeScript(); // Clear container containerRef.current.innerHTML = ''; // Create placeholder div const placeholder = document.createElement('div'); placeholder.id = `editor-placeholder-${currentEditorKey}`; placeholder.style.width = '100%'; placeholder.style.height = '100%'; containerRef.current.appendChild(placeholder); // Create editor configuration with event handlers const handlers = createEventHandlers(); const editorConfig = { ...config, events: { ...handlers, // Allow config events to override default handlers ...config.events } }; // Initialize editor instance if (window.DocsAPI && window.DocsAPI.DocEditor) { editorInstanceRef.current = new window.DocsAPI.DocEditor(placeholder.id, editorConfig); // Store instance in global window for external access if (!window.DocEditor) { window.DocEditor = {}; } if (!window.DocEditor.instances) { window.DocEditor.instances = {}; } window.DocEditor.instances[`editor-${currentEditorKey}`] = editorInstanceRef.current; } else { const error = new Error("DocsAPI is not defined"); console.error(error.message); if (onError) { onError(error); } if (config.events?.onLoadComponentError) { config.events.onLoadComponentError(-3, error.message); } } } catch (error) { const errorObj = error instanceof Error ? error : new Error(String(error)); console.error("Error initializing editor:", errorObj); if (onError) { onError(errorObj); } if (config.events?.onLoadComponentError) { config.events.onLoadComponentError(-1, errorObj.message); } } }, [config, currentEditorKey, onError, createEventHandlers]); /** * Cleanup editor instance */ const cleanupEditor = react.useCallback(() => { try { if (editorInstanceRef.current) { // Call destroyEditor if available if (typeof editorInstanceRef.current.destroyEditor === 'function') { editorInstanceRef.current.destroyEditor(); } editorInstanceRef.current = null; } // Remove instance from global window if (window.DocEditor?.instances?.[`editor-${currentEditorKey}`]) { delete window.DocEditor.instances[`editor-${currentEditorKey}`]; } // Clear container if (containerRef.current) { containerRef.current.innerHTML = ''; } } catch (error) { console.warn("Error during editor cleanup:", error); } }, [currentEditorKey]); /** * Tạo lại editor với key mới */ const regenerateEditor = react.useCallback(async () => { // Cleanup editor hiện tại cleanupEditor(); // Tạo key mới const newKey = generateNewEditorKey(); setCurrentEditorKey(newKey); // Đợi một chút để cleanup hoàn tất setTimeout(() => { initializeEditor().catch((error) => { console.error("Failed to regenerate editor:", error); if (onError) { onError(error instanceof Error ? error : new Error(String(error))); } }); }, 100); }, [generateNewEditorKey, cleanupEditor, initializeEditor, onError]); // Lưu regenerateEditor vào ref để sử dụng trong event handlers regenerateEditorRef.current = regenerateEditor; react.useImperativeHandle(ref, () => ({ isDocumentLoaded: () => documentLoaded, getAllKeys: () => { return new Promise((resolve, reject) => { if (!documentLoaded) { reject(new Error(`document_not_found`)); return; } if (!pluginAddKeySource) { reject(new Error(`plugin_add_key_source_not_found`)); return; } // Tạo unique message ID để track response const messageId = `get_all_keys_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Tạo event listener để chờ response const handleResponse = (event) => { if (event.data && event.data.type === 'ALL_KEYS_RESPONSE' && event.data.messageId === messageId) { window.removeEventListener('message', handleResponse); resolve(event.data.payload.keys); } }; // Thêm timeout để tránh chờ vô hạn const timeout = setTimeout(() => { window.removeEventListener('message', handleResponse); reject(new Error('timeout')); }, 10000000); // Listen for response window.addEventListener('message', handleResponse); // Send request pluginAddKeySource.postMessage({ type: 'GET_ALL_KEYS', messageId: messageId }, { targetOrigin: '*' }); // Clear timeout when resolved const originalResolve = resolve; resolve = (value) => { clearTimeout(timeout); originalResolve(value); }; }); } })); // Listen for dialog open events react.useEffect(() => { const handleOpenDialog = (event) => { const { dialogKey, versions } = event.detail; const dialogData = window.DocEditor?.dialogs?.[dialogKey]; if (dialogData && dialogData.open) { setDialogVersions(dialogData.versions); setDialogCallback(() => dialogData.onVersionSelect); setDialogOpen(true); } }; window.addEventListener('openVersionDialog', handleOpenDialog); return () => { window.removeEventListener('openVersionDialog', handleOpenDialog); }; }, []); // Initialize editor when component mounts react.useEffect(() => { initializeEditor().catch((error) => { console.error("Failed to initialize editor:", error); if (onError) { onError(error instanceof Error ? error : new Error(String(error))); } }); return () => { cleanupEditor(); }; }, []); // Only run once on mount // Handle editorKey changes react.useEffect(() => { const prevKey = prevEditorKeyRef.current; // Skip first mount if (prevKey === currentEditorKey) { return; } // Cleanup old editor cleanupEditor(); // Initialize new editor setTimeout(() => { initializeEditor().catch((error) => { console.error("Failed to reinitialize editor:", error); if (onError) { onError(error instanceof Error ? error : new Error(String(error))); } }); }, 100); // Wait a bit to ensure cleanup is complete prevEditorKeyRef.current = currentEditorKey; }, [currentEditorKey, config, initializeEditor, cleanupEditor, onError, createEventHandlers]); return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: containerRef, className: className, id: "onlyoffice-iframe", style: { width: '100%', height: '100%', position: 'relative', ...style } }), jsxRuntime.jsx(VersionSelectDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelectVersion: (version) => { if (dialogCallback) { dialogCallback(version); } setDialogOpen(false); }, versions: dialogVersions, title: "Ch\u1ECDn phi\u00EAn b\u1EA3n t\u00E0i li\u1EC7u" })] })); }); /** * Custom hook for managing OnlyOffice editor state and operations */ const useOnlyOfficeEditor = () => { const [isLoading, setIsLoading] = react.useState(false); const [error, setError] = react.useState(null); const editorInstanceRef = react.useRef(null); /** * Get editor instance from global window */ const getEditorInstance = react.useCallback((editorKey = 'default') => { return window.DocEditor?.instances?.[`editor-${editorKey}`] || null; }, []); /** * Set editor instance reference */ const setEditorInstance = react.useCallback((instance, editorKey = 'default') => { editorInstanceRef.current = instance; if (!window.DocEditor) { window.DocEditor = { instances: {} }; } if (window.DocEditor.instances) { window.DocEditor.instances[`editor-${editorKey}`] = instance; } }, []); /** * Refresh version history in editor */ const refreshHistory = react.useCallback((history, message = 'History updated', editorKey = 'default') => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.refreshHistory === 'function') { editor.refreshHistory(history, message); } else { console.warn('Editor instance not found or refreshHistory method not available'); } }, [getEditorInstance]); /** * Set history data in editor */ const setHistoryData = react.useCallback((data, editorKey = 'default') => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.setHistoryData === 'function') { editor.setHistoryData(data); } else { console.warn('Editor instance not found or setHistoryData method not available'); } }, [getEditorInstance]); /** * Request editor to close */ const requestClose = react.useCallback((editorKey = 'default') => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.requestClose === 'function') { editor.requestClose(); } else { console.warn('Editor instance not found or requestClose method not available'); } }, [getEditorInstance]); /** * Destroy editor instance */ const destroyEditor = react.useCallback((editorKey = 'default') => { const editor = getEditorInstance(editorKey); if (editor && typeof editor.destroyEditor === 'function') { editor.destroyEditor(); } // Remove from global instances if (window.DocEditor?.instances?.[`editor-${editorKey}`]) { delete window.DocEditor.instances[`editor-${editorKey}`]; } editorInstanceRef.current = null; }, [getEditorInstance]); /** * Create default event handlers */ const createEventHandlers = react.useCallback((options = {}) => { const { onHistoryRequest, onHistoryDataRequest, onHistoryClose, onClose, onRestore, fileId } = options; return { onDocumentReady: (event) => { console.log("Document is loaded"); }, onRequestHistory: async (event) => { if (!onHistoryRequest || !fileId) { console.warn("History request handler or fileId not provided"); return; } try { setIsLoading(true); const history = await onHistoryRequest(fileId); refreshHistory(history, "History loaded successfully"); } catch (error) { console.error("Error fetching version history:", error); setError(error instanceof Error ? error : new Error(String(error))); refreshHistory({ currentVersion: "1", history: [] }, "Failed to load history"); } finally { setIsLoading(false); } }, onRequestHistoryData: async (event) => { if (!onHistoryDataRequest || !fileId) { console.warn("History data request handler or fileId not provided"); return; } const version = event.data; try { setIsLoading(true); const data = await onHistoryDataRequest(fileId, version); setHistoryData(data); } catch (error) { console.error("Error fetching history data:", error); setError(error instanceof Error ? error : new Error(String(error))); setHistoryData({ error: error instanceof Error ? error.message : String(error), version }); } finally { setIsLoading(false); } }, onRequestHistoryClose: (event) => { if (onHistoryClose) { onHistoryClose(); } }, onRequestClose: () => { if (onClose) { onClose(); } else { requestClose(); } }, onRequestRestore: async (event) => { if (!onRestore || !fileId) { console.warn("Restore handler or fileId not provided"); return; } const version = event.data.version; try { setIsLoading(true); const result = await onRestore(fileId, version); refreshHistory(result, "Version restored successfully"); } catch (error) { console.error("Error restoring version:", error); setError(error instanceof Error ? error : new Error(String(error))); refreshHistory({ currentVersion: "1", history: [] }, "Failed to restore version"); } finally { setIsLoading(false); } }, onLoadComponentError: (errorCode, errorDescription) => { const error = new Error(`OnlyOffice Error ${errorCode}: ${errorDescription}`); setError(error); console.error(error.message); } }; }, [refreshHistory, setHistoryData, requestClose]); return { isLoading, error, setError, getEditorInstance, setEditorInstance, refreshHistory, setHistoryData, requestClose, destroyEditor, createEventHandlers }; }; exports.React = react; exports.DocumentEditor = DocumentEditor; exports.destroyEditor = destroyEditor; exports.getApiInstance = getApiInstance; exports.getEditorInstance = getEditorInstance; exports.getEditorStatus = getEditorStatus; exports.getScriptState = getScriptState; exports.isEditorReady = isEditorReady; exports.isOnlyOfficeScriptLoaded = isOnlyOfficeScriptLoaded; exports.loadOnlyOfficeScript = loadOnlyOfficeScript; exports.mapDocumentDataToEditor = mapDocumentDataToEditor; exports.refreshHistory = refreshHistory; exports.requestClose = requestClose; exports.resetScriptState = resetScriptState; exports.setDataDialog = setDataDialog; exports.setHistoryData = setHistoryData; exports.setRequestedDocument = setRequestedDocument; exports.useOnlyOfficeEditor = useOnlyOfficeEditor; //# sourceMappingURL=index.js.map