documentation-hub 5.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEditorModal - Fullscreen modal overlay for document editing
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Full editing capability for document content
|
|
6
|
+
* - Quick action buttons for formatting and table operations
|
|
7
|
+
* - Session-configured shading application
|
|
8
|
+
* - Save/Close with unsaved changes warning
|
|
9
|
+
* - Undo/Redo support
|
|
10
|
+
* - Integration with docxmlater for document manipulation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
14
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
15
|
+
import { createPortal } from 'react-dom';
|
|
16
|
+
import type {
|
|
17
|
+
QuickActionId,
|
|
18
|
+
EditorState,
|
|
19
|
+
CellSelection,
|
|
20
|
+
EditorSelection,
|
|
21
|
+
EditorAction,
|
|
22
|
+
} from '@/types/editor';
|
|
23
|
+
import { EditorToolbar } from './EditorToolbar';
|
|
24
|
+
import { EditorQuickActions } from './EditorQuickActions';
|
|
25
|
+
import { DocumentEditor, DocumentEditorRef } from './DocumentEditor';
|
|
26
|
+
import { Loader2, AlertTriangle } from 'lucide-react';
|
|
27
|
+
import { Document, Paragraph, Table } from 'docxmlater';
|
|
28
|
+
|
|
29
|
+
// Use 'any' for internal document element types to avoid type conflicts
|
|
30
|
+
// DocumentEditor has its own type definitions that we pass through
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sync editor bodyElements changes back to docxmlater Document
|
|
34
|
+
* Maps the editor's internal format back to docxmlater API calls
|
|
35
|
+
*/
|
|
36
|
+
async function syncBodyElementsToDocument(
|
|
37
|
+
doc: Document,
|
|
38
|
+
bodyElements: any[]
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
const docElements = doc.getBodyElements();
|
|
41
|
+
|
|
42
|
+
// Track which paragraphs/tables have been modified
|
|
43
|
+
let paragraphIndex = 0;
|
|
44
|
+
let tableIndex = 0;
|
|
45
|
+
|
|
46
|
+
for (const element of bodyElements) {
|
|
47
|
+
if (element.type === 'paragraph') {
|
|
48
|
+
// Find corresponding paragraph in document
|
|
49
|
+
let docParagraphIndex = 0;
|
|
50
|
+
for (const docElement of docElements) {
|
|
51
|
+
if (docElement instanceof Paragraph) {
|
|
52
|
+
if (docParagraphIndex === paragraphIndex) {
|
|
53
|
+
// Sync text changes - update runs
|
|
54
|
+
const editorPara = element.paragraph;
|
|
55
|
+
const docPara = docElement;
|
|
56
|
+
|
|
57
|
+
// Clear existing runs and rebuild from editor state
|
|
58
|
+
if (editorPara.runs && editorPara.runs.length > 0) {
|
|
59
|
+
const docRuns = docPara.getRuns();
|
|
60
|
+
|
|
61
|
+
// Update each run's text and formatting
|
|
62
|
+
for (let i = 0; i < Math.min(docRuns.length, editorPara.runs.length); i++) {
|
|
63
|
+
const editorRun = editorPara.runs[i];
|
|
64
|
+
const docRun = docRuns[i];
|
|
65
|
+
|
|
66
|
+
if (editorRun.text !== docRun.getText()) {
|
|
67
|
+
docRun.setText(editorRun.text);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Sync formatting if changed
|
|
71
|
+
if (editorRun.bold !== undefined) docRun.setBold(editorRun.bold);
|
|
72
|
+
if (editorRun.italic !== undefined) docRun.setItalic(editorRun.italic);
|
|
73
|
+
if (editorRun.underline !== undefined) {
|
|
74
|
+
docRun.setUnderline(editorRun.underline ? 'single' : false);
|
|
75
|
+
}
|
|
76
|
+
if (editorRun.color) docRun.setColor(editorRun.color.replace('#', ''));
|
|
77
|
+
if (editorRun.font) docRun.setFont(editorRun.font);
|
|
78
|
+
if (editorRun.size) docRun.setSize(editorRun.size);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sync paragraph formatting
|
|
83
|
+
if (editorPara.alignment) {
|
|
84
|
+
docPara.setAlignment(editorPara.alignment);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
docParagraphIndex++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
paragraphIndex++;
|
|
93
|
+
} else if (element.type === 'table') {
|
|
94
|
+
// Find corresponding table in document
|
|
95
|
+
let docTableIndex = 0;
|
|
96
|
+
for (const docElement of docElements) {
|
|
97
|
+
if (docElement instanceof Table) {
|
|
98
|
+
if (docTableIndex === tableIndex) {
|
|
99
|
+
const editorTable = element.table;
|
|
100
|
+
const docTable = docElement;
|
|
101
|
+
const docRows = docTable.getRows();
|
|
102
|
+
|
|
103
|
+
// Sync each row/cell
|
|
104
|
+
for (let rowIdx = 0; rowIdx < Math.min(docRows.length, editorTable.rows.length); rowIdx++) {
|
|
105
|
+
const editorRow = editorTable.rows[rowIdx];
|
|
106
|
+
const docRow = docRows[rowIdx];
|
|
107
|
+
const docCells = docRow.getCells();
|
|
108
|
+
|
|
109
|
+
for (let cellIdx = 0; cellIdx < Math.min(docCells.length, editorRow.cells.length); cellIdx++) {
|
|
110
|
+
const editorCell = editorRow.cells[cellIdx];
|
|
111
|
+
const docCell = docCells[cellIdx];
|
|
112
|
+
const docCellParas = docCell.getParagraphs();
|
|
113
|
+
|
|
114
|
+
// Sync cell shading
|
|
115
|
+
if (editorCell.shading) {
|
|
116
|
+
docCell.setShading(editorCell.shading.replace('#', ''));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sync cell paragraphs
|
|
120
|
+
for (let paraIdx = 0; paraIdx < Math.min(docCellParas.length, editorCell.paragraphs.length); paraIdx++) {
|
|
121
|
+
const editorCellPara = editorCell.paragraphs[paraIdx];
|
|
122
|
+
const docCellPara = docCellParas[paraIdx];
|
|
123
|
+
const docCellRuns = docCellPara.getRuns();
|
|
124
|
+
|
|
125
|
+
// Sync runs
|
|
126
|
+
for (let runIdx = 0; runIdx < Math.min(docCellRuns.length, (editorCellPara.runs || []).length); runIdx++) {
|
|
127
|
+
const editorRun = editorCellPara.runs[runIdx];
|
|
128
|
+
const docRun = docCellRuns[runIdx];
|
|
129
|
+
|
|
130
|
+
if (editorRun.text !== docRun.getText()) {
|
|
131
|
+
docRun.setText(editorRun.text);
|
|
132
|
+
}
|
|
133
|
+
if (editorRun.bold !== undefined) docRun.setBold(editorRun.bold);
|
|
134
|
+
if (editorRun.italic !== undefined) docRun.setItalic(editorRun.italic);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Sync paragraph alignment
|
|
138
|
+
if (editorCellPara.alignment) {
|
|
139
|
+
docCellPara.setAlignment(editorCellPara.alignment);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
docTableIndex++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
tableIndex++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Convert docxmlater Document body to editor-compatible format
|
|
157
|
+
* Returns any[] to allow passing to DocumentEditor which has its own types
|
|
158
|
+
*/
|
|
159
|
+
function documentToBodyElements(doc: Document): any[] {
|
|
160
|
+
const elements: any[] = [];
|
|
161
|
+
const docElements = doc.getBodyElements();
|
|
162
|
+
|
|
163
|
+
for (const element of docElements) {
|
|
164
|
+
if (element instanceof Paragraph) {
|
|
165
|
+
const para = element;
|
|
166
|
+
const runs: any[] = [];
|
|
167
|
+
|
|
168
|
+
for (const run of para.getRuns() || []) {
|
|
169
|
+
const runFormatting = run.getFormatting();
|
|
170
|
+
runs.push({
|
|
171
|
+
text: run.getText() || '',
|
|
172
|
+
bold: runFormatting.bold,
|
|
173
|
+
italic: runFormatting.italic,
|
|
174
|
+
underline: runFormatting.underline,
|
|
175
|
+
strike: runFormatting.strike,
|
|
176
|
+
color: runFormatting.color,
|
|
177
|
+
font: runFormatting.font,
|
|
178
|
+
size: runFormatting.size,
|
|
179
|
+
highlight: runFormatting.highlight,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const styleId = para.getStyle();
|
|
184
|
+
const paraFormatting = para.getFormatting();
|
|
185
|
+
elements.push({
|
|
186
|
+
type: 'paragraph',
|
|
187
|
+
paragraph: {
|
|
188
|
+
text: para.getText() || '',
|
|
189
|
+
runs,
|
|
190
|
+
alignment: paraFormatting.alignment,
|
|
191
|
+
style: styleId,
|
|
192
|
+
isHeading: styleId?.includes('Heading'),
|
|
193
|
+
headingLevel: styleId?.match(/Heading(\d)/)?.[1]
|
|
194
|
+
? parseInt(styleId.match(/Heading(\d)/)![1])
|
|
195
|
+
: undefined,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
} else if (element instanceof Table) {
|
|
199
|
+
const table = element;
|
|
200
|
+
const rows: any[] = [];
|
|
201
|
+
|
|
202
|
+
for (const row of table.getRows() || []) {
|
|
203
|
+
const cells: any[] = [];
|
|
204
|
+
|
|
205
|
+
for (const cell of row.getCells() || []) {
|
|
206
|
+
const paragraphs: any[] = [];
|
|
207
|
+
|
|
208
|
+
for (const cellPara of cell.getParagraphs() || []) {
|
|
209
|
+
const cellRuns: any[] = [];
|
|
210
|
+
|
|
211
|
+
for (const run of cellPara.getRuns() || []) {
|
|
212
|
+
const runFormatting = run.getFormatting();
|
|
213
|
+
cellRuns.push({
|
|
214
|
+
text: run.getText() || '',
|
|
215
|
+
bold: runFormatting.bold,
|
|
216
|
+
italic: runFormatting.italic,
|
|
217
|
+
underline: runFormatting.underline,
|
|
218
|
+
color: runFormatting.color,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const cellParaFormatting = cellPara.getFormatting();
|
|
223
|
+
paragraphs.push({
|
|
224
|
+
text: cellPara.getText() || '',
|
|
225
|
+
runs: cellRuns,
|
|
226
|
+
alignment: cellParaFormatting.alignment,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const cellFormatting = cell.getFormatting();
|
|
231
|
+
cells.push({
|
|
232
|
+
paragraphs:
|
|
233
|
+
paragraphs.length > 0 ? paragraphs : [{ text: '', runs: [] }],
|
|
234
|
+
shading: cellFormatting.shading?.fill,
|
|
235
|
+
verticalMerge: cellFormatting.vMerge,
|
|
236
|
+
columnSpan: cellFormatting.columnSpan,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
rows.push({ cells });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
elements.push({
|
|
244
|
+
type: 'table',
|
|
245
|
+
table: { rows },
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return elements;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface DocumentEditorModalProps {
|
|
254
|
+
/** Whether the modal is open */
|
|
255
|
+
isOpen: boolean;
|
|
256
|
+
/** Close handler */
|
|
257
|
+
onClose: () => void;
|
|
258
|
+
/** Save handler - receives the modified document */
|
|
259
|
+
onSave: (documentBuffer: ArrayBuffer) => Promise<void>;
|
|
260
|
+
/** Session ID */
|
|
261
|
+
sessionId: string;
|
|
262
|
+
/** Document ID */
|
|
263
|
+
documentId: string;
|
|
264
|
+
/** Document name for display */
|
|
265
|
+
documentName: string;
|
|
266
|
+
/** Original document buffer */
|
|
267
|
+
documentBuffer: ArrayBuffer | null;
|
|
268
|
+
/** Table shading settings from session */
|
|
269
|
+
tableShadingSettings?: {
|
|
270
|
+
header2Shading: string;
|
|
271
|
+
otherShading: string;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Main DocumentEditorModal component
|
|
277
|
+
*/
|
|
278
|
+
export function DocumentEditorModal({
|
|
279
|
+
isOpen,
|
|
280
|
+
onClose,
|
|
281
|
+
onSave,
|
|
282
|
+
sessionId,
|
|
283
|
+
documentId,
|
|
284
|
+
documentName,
|
|
285
|
+
documentBuffer,
|
|
286
|
+
tableShadingSettings,
|
|
287
|
+
}: DocumentEditorModalProps) {
|
|
288
|
+
// Editor state
|
|
289
|
+
const [editorState, setEditorState] = useState<EditorState>({
|
|
290
|
+
isDirty: false,
|
|
291
|
+
selection: null,
|
|
292
|
+
tableSelection: null,
|
|
293
|
+
selectedElementType: null,
|
|
294
|
+
selectedParagraphIndex: null,
|
|
295
|
+
selectedTableIndex: null,
|
|
296
|
+
undoStack: [],
|
|
297
|
+
redoStack: [],
|
|
298
|
+
activeQuickAction: null,
|
|
299
|
+
isLoading: false,
|
|
300
|
+
error: null,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
304
|
+
const [bodyElements, setBodyElements] = useState<any[]>([]);
|
|
305
|
+
const [docInstance, setDocInstance] = useState<Document | null>(null);
|
|
306
|
+
|
|
307
|
+
// Refs
|
|
308
|
+
const documentEditorRef = useRef<DocumentEditorRef>(null);
|
|
309
|
+
|
|
310
|
+
// Load document from buffer when opened
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
if (!isOpen || !documentBuffer) {
|
|
313
|
+
setBodyElements([]);
|
|
314
|
+
setDocInstance(null);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const loadDocument = async () => {
|
|
319
|
+
setEditorState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
// Convert ArrayBuffer to Buffer for docxmlater
|
|
323
|
+
const buffer = Buffer.from(documentBuffer);
|
|
324
|
+
const doc = await Document.loadFromBuffer(buffer);
|
|
325
|
+
setDocInstance(doc);
|
|
326
|
+
|
|
327
|
+
const elements = documentToBodyElements(doc);
|
|
328
|
+
setBodyElements(elements);
|
|
329
|
+
|
|
330
|
+
setEditorState((prev) => ({ ...prev, isLoading: false }));
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('Failed to load document:', error);
|
|
333
|
+
setEditorState((prev) => ({
|
|
334
|
+
...prev,
|
|
335
|
+
isLoading: false,
|
|
336
|
+
error: error instanceof Error ? error.message : 'Failed to load document',
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
loadDocument();
|
|
342
|
+
|
|
343
|
+
// Cleanup on unmount
|
|
344
|
+
return () => {
|
|
345
|
+
if (docInstance) {
|
|
346
|
+
docInstance.dispose?.();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}, [isOpen, documentBuffer]);
|
|
350
|
+
|
|
351
|
+
// Handle selection change
|
|
352
|
+
const handleSelectionChange = useCallback((selection: EditorSelection | null) => {
|
|
353
|
+
setEditorState((prev) => ({
|
|
354
|
+
...prev,
|
|
355
|
+
selection,
|
|
356
|
+
selectedElementType: selection ? 'paragraph' : prev.selectedElementType,
|
|
357
|
+
selectedParagraphIndex: selection?.paragraphIndex ?? prev.selectedParagraphIndex,
|
|
358
|
+
}));
|
|
359
|
+
}, []);
|
|
360
|
+
|
|
361
|
+
// Handle table selection change
|
|
362
|
+
const handleTableSelectionChange = useCallback(
|
|
363
|
+
(selection: CellSelection | null, tableIndex: number | null) => {
|
|
364
|
+
setEditorState((prev) => ({
|
|
365
|
+
...prev,
|
|
366
|
+
tableSelection: selection,
|
|
367
|
+
selectedElementType: selection ? 'table' : prev.selectedElementType,
|
|
368
|
+
selectedTableIndex: tableIndex,
|
|
369
|
+
}));
|
|
370
|
+
},
|
|
371
|
+
[]
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Handle body elements change from editor
|
|
375
|
+
const handleBodyElementsChange = useCallback(
|
|
376
|
+
(newElements: any[], action: EditorAction) => {
|
|
377
|
+
setBodyElements(newElements);
|
|
378
|
+
|
|
379
|
+
// Add to undo stack
|
|
380
|
+
setEditorState((prev) => ({
|
|
381
|
+
...prev,
|
|
382
|
+
isDirty: true,
|
|
383
|
+
undoStack: [...prev.undoStack, action],
|
|
384
|
+
redoStack: [], // Clear redo stack on new change
|
|
385
|
+
}));
|
|
386
|
+
},
|
|
387
|
+
[]
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Handle save - sync changes to document and save
|
|
391
|
+
const handleSave = useCallback(async () => {
|
|
392
|
+
if (!docInstance) return;
|
|
393
|
+
|
|
394
|
+
setIsSaving(true);
|
|
395
|
+
try {
|
|
396
|
+
// Sync editor bodyElements changes back to docxmlater document
|
|
397
|
+
await syncBodyElementsToDocument(docInstance, bodyElements);
|
|
398
|
+
|
|
399
|
+
// Save the document with synced changes
|
|
400
|
+
const buffer = await docInstance.toBuffer();
|
|
401
|
+
// Convert Buffer to ArrayBuffer for onSave callback
|
|
402
|
+
const arrayBuffer = buffer.buffer.slice(
|
|
403
|
+
buffer.byteOffset,
|
|
404
|
+
buffer.byteOffset + buffer.byteLength
|
|
405
|
+
) as ArrayBuffer;
|
|
406
|
+
await onSave(arrayBuffer);
|
|
407
|
+
setEditorState((prev) => ({ ...prev, isDirty: false }));
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error('Failed to save document:', error);
|
|
410
|
+
setEditorState((prev) => ({
|
|
411
|
+
...prev,
|
|
412
|
+
error: error instanceof Error ? error.message : 'Failed to save document',
|
|
413
|
+
}));
|
|
414
|
+
} finally {
|
|
415
|
+
setIsSaving(false);
|
|
416
|
+
}
|
|
417
|
+
}, [docInstance, bodyElements, onSave]);
|
|
418
|
+
|
|
419
|
+
// Handle undo
|
|
420
|
+
const handleUndo = useCallback(() => {
|
|
421
|
+
const { undoStack, redoStack } = editorState;
|
|
422
|
+
if (undoStack.length === 0) return;
|
|
423
|
+
|
|
424
|
+
const action = undoStack[undoStack.length - 1];
|
|
425
|
+
action.undo();
|
|
426
|
+
|
|
427
|
+
setEditorState((prev) => ({
|
|
428
|
+
...prev,
|
|
429
|
+
undoStack: prev.undoStack.slice(0, -1),
|
|
430
|
+
redoStack: [...prev.redoStack, action],
|
|
431
|
+
isDirty: true,
|
|
432
|
+
}));
|
|
433
|
+
}, [editorState]);
|
|
434
|
+
|
|
435
|
+
// Handle redo
|
|
436
|
+
const handleRedo = useCallback(() => {
|
|
437
|
+
const { redoStack } = editorState;
|
|
438
|
+
if (redoStack.length === 0) return;
|
|
439
|
+
|
|
440
|
+
const action = redoStack[redoStack.length - 1];
|
|
441
|
+
action.redo();
|
|
442
|
+
|
|
443
|
+
setEditorState((prev) => ({
|
|
444
|
+
...prev,
|
|
445
|
+
redoStack: prev.redoStack.slice(0, -1),
|
|
446
|
+
undoStack: [...prev.undoStack, action],
|
|
447
|
+
isDirty: true,
|
|
448
|
+
}));
|
|
449
|
+
}, [editorState]);
|
|
450
|
+
|
|
451
|
+
// Handle quick action
|
|
452
|
+
const handleQuickAction = useCallback((actionId: QuickActionId) => {
|
|
453
|
+
console.log('Quick action triggered:', actionId);
|
|
454
|
+
|
|
455
|
+
// Mark as dirty for any action
|
|
456
|
+
setEditorState((prev) => ({
|
|
457
|
+
...prev,
|
|
458
|
+
activeQuickAction: actionId,
|
|
459
|
+
isDirty: true,
|
|
460
|
+
}));
|
|
461
|
+
|
|
462
|
+
// The actual action implementation would go here
|
|
463
|
+
// For now, just clear the active action after a brief delay
|
|
464
|
+
setTimeout(() => {
|
|
465
|
+
setEditorState((prev) => ({
|
|
466
|
+
...prev,
|
|
467
|
+
activeQuickAction: null,
|
|
468
|
+
}));
|
|
469
|
+
}, 100);
|
|
470
|
+
}, []);
|
|
471
|
+
|
|
472
|
+
// Handle close with unsaved changes check
|
|
473
|
+
const handleClose = useCallback(() => {
|
|
474
|
+
if (editorState.isDirty) {
|
|
475
|
+
const confirmed = window.confirm(
|
|
476
|
+
'You have unsaved changes. Are you sure you want to close without saving?'
|
|
477
|
+
);
|
|
478
|
+
if (!confirmed) return;
|
|
479
|
+
}
|
|
480
|
+
onClose();
|
|
481
|
+
}, [editorState.isDirty, onClose]);
|
|
482
|
+
|
|
483
|
+
// Handle keyboard shortcuts
|
|
484
|
+
useEffect(() => {
|
|
485
|
+
if (!isOpen) return;
|
|
486
|
+
|
|
487
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
488
|
+
// Escape to close
|
|
489
|
+
if (e.key === 'Escape') {
|
|
490
|
+
handleClose();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Ctrl/Cmd+S to save
|
|
495
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
496
|
+
e.preventDefault();
|
|
497
|
+
handleSave();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Ctrl/Cmd+Z to undo
|
|
502
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
|
503
|
+
e.preventDefault();
|
|
504
|
+
handleUndo();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Ctrl/Cmd+Y or Ctrl/Cmd+Shift+Z to redo
|
|
509
|
+
if (
|
|
510
|
+
(e.ctrlKey || e.metaKey) &&
|
|
511
|
+
(e.key === 'y' || (e.key === 'z' && e.shiftKey))
|
|
512
|
+
) {
|
|
513
|
+
e.preventDefault();
|
|
514
|
+
handleRedo();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Ctrl/Cmd+B for bold
|
|
519
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
|
|
520
|
+
e.preventDefault();
|
|
521
|
+
handleQuickAction('bold');
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Ctrl/Cmd+I for italic
|
|
526
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'i') {
|
|
527
|
+
e.preventDefault();
|
|
528
|
+
handleQuickAction('italic');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Ctrl/Cmd+U for underline
|
|
533
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'u') {
|
|
534
|
+
e.preventDefault();
|
|
535
|
+
handleQuickAction('underline');
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
541
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
542
|
+
}, [isOpen, handleClose, handleSave, handleUndo, handleRedo, handleQuickAction]);
|
|
543
|
+
|
|
544
|
+
// Prevent body scroll when modal is open
|
|
545
|
+
useEffect(() => {
|
|
546
|
+
if (isOpen) {
|
|
547
|
+
document.body.style.overflow = 'hidden';
|
|
548
|
+
} else {
|
|
549
|
+
document.body.style.overflow = '';
|
|
550
|
+
}
|
|
551
|
+
return () => {
|
|
552
|
+
document.body.style.overflow = '';
|
|
553
|
+
};
|
|
554
|
+
}, [isOpen]);
|
|
555
|
+
|
|
556
|
+
// Don't render if not open
|
|
557
|
+
if (!isOpen) return null;
|
|
558
|
+
|
|
559
|
+
const modalContent = (
|
|
560
|
+
<AnimatePresence>
|
|
561
|
+
<motion.div
|
|
562
|
+
initial={{ opacity: 0 }}
|
|
563
|
+
animate={{ opacity: 1 }}
|
|
564
|
+
exit={{ opacity: 0 }}
|
|
565
|
+
transition={{ duration: 0.2 }}
|
|
566
|
+
className="fixed inset-0 z-50 flex flex-col bg-background"
|
|
567
|
+
>
|
|
568
|
+
{/* Backdrop blur effect */}
|
|
569
|
+
<div className="absolute inset-0 bg-background/95 backdrop-blur-sm" />
|
|
570
|
+
|
|
571
|
+
{/* Modal content */}
|
|
572
|
+
<div className="relative flex flex-col h-full">
|
|
573
|
+
{/* Toolbar */}
|
|
574
|
+
<EditorToolbar
|
|
575
|
+
documentName={documentName}
|
|
576
|
+
isDirty={editorState.isDirty}
|
|
577
|
+
isSaving={isSaving}
|
|
578
|
+
canUndo={editorState.undoStack.length > 0}
|
|
579
|
+
canRedo={editorState.redoStack.length > 0}
|
|
580
|
+
onClose={handleClose}
|
|
581
|
+
onSave={handleSave}
|
|
582
|
+
onUndo={handleUndo}
|
|
583
|
+
onRedo={handleRedo}
|
|
584
|
+
onQuickAction={handleQuickAction}
|
|
585
|
+
/>
|
|
586
|
+
|
|
587
|
+
{/* Main content area */}
|
|
588
|
+
<div className="flex flex-1 overflow-hidden">
|
|
589
|
+
{/* Document editor */}
|
|
590
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
591
|
+
{editorState.isLoading ? (
|
|
592
|
+
<div className="flex-1 flex items-center justify-center">
|
|
593
|
+
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
|
594
|
+
<span className="ml-2">Loading document...</span>
|
|
595
|
+
</div>
|
|
596
|
+
) : editorState.error ? (
|
|
597
|
+
<div className="flex-1 flex flex-col items-center justify-center text-destructive gap-2">
|
|
598
|
+
<AlertTriangle className="w-12 h-12" />
|
|
599
|
+
<p className="text-lg font-medium">Failed to load document</p>
|
|
600
|
+
<p className="text-sm">{editorState.error}</p>
|
|
601
|
+
</div>
|
|
602
|
+
) : (
|
|
603
|
+
<DocumentEditor
|
|
604
|
+
ref={documentEditorRef}
|
|
605
|
+
bodyElements={bodyElements}
|
|
606
|
+
onChange={handleBodyElementsChange}
|
|
607
|
+
onSelectionChange={handleSelectionChange}
|
|
608
|
+
onTableSelectionChange={handleTableSelectionChange}
|
|
609
|
+
selection={editorState.selection}
|
|
610
|
+
tableSelection={editorState.tableSelection}
|
|
611
|
+
selectedTableIndex={editorState.selectedTableIndex}
|
|
612
|
+
activeQuickAction={editorState.activeQuickAction}
|
|
613
|
+
tableShadingSettings={tableShadingSettings}
|
|
614
|
+
readOnly={isSaving}
|
|
615
|
+
/>
|
|
616
|
+
)}
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
{/* Quick actions sidebar */}
|
|
620
|
+
<div className="w-64 flex-shrink-0">
|
|
621
|
+
<EditorQuickActions
|
|
622
|
+
onAction={handleQuickAction}
|
|
623
|
+
hasTableSelection={editorState.tableSelection !== null}
|
|
624
|
+
hasTextSelection={editorState.selection !== null}
|
|
625
|
+
cellSelection={editorState.tableSelection}
|
|
626
|
+
tableShadingSettings={tableShadingSettings}
|
|
627
|
+
disabled={isSaving}
|
|
628
|
+
/>
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</motion.div>
|
|
633
|
+
</AnimatePresence>
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// Render in portal to avoid z-index issues
|
|
637
|
+
return createPortal(modalContent, document.body);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export default DocumentEditorModal;
|