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.
Files changed (271) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +64 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/Current.md +97 -0
  6. package/DocHub_Image.png +0 -0
  7. package/README.md +666 -0
  8. package/USER_GUIDE.md +1173 -0
  9. package/Updater.md +311 -0
  10. package/build/256x256.png +0 -0
  11. package/build/512x512.png +0 -0
  12. package/build/app-update.yml +4 -0
  13. package/build/create-icon.js +208 -0
  14. package/build/icon.ico +0 -0
  15. package/build/icon.png +0 -0
  16. package/build/icon_1024x1024.png +0 -0
  17. package/dist/assets/Analytics-BpsG9895.js +1 -0
  18. package/dist/assets/Card-IAZin8kp.js +1 -0
  19. package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
  20. package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
  21. package/dist/assets/Documents-CqZ25axS.js +1 -0
  22. package/dist/assets/Input-l89xwXBi.js +1 -0
  23. package/dist/assets/Reporting-DqdHJY_a.js +1 -0
  24. package/dist/assets/Search-XNbu5z_3.js +1 -0
  25. package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
  26. package/dist/assets/Sessions-ClZOPYNc.js +1 -0
  27. package/dist/assets/Settings-DUEHGURa.js +11 -0
  28. package/dist/assets/index-8xUe8ptc.js +24 -0
  29. package/dist/assets/index-RYyJqF7O.css +1 -0
  30. package/dist/assets/path-BkOl0AGO.js +1 -0
  31. package/dist/assets/promises-ID_B9S-h.js +1 -0
  32. package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
  33. package/dist/assets/useToast-yRSO1dkm.js +1 -0
  34. package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
  35. package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
  36. package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
  37. package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
  38. package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
  39. package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
  40. package/dist/electron/main-CXkNtyv-.js +19789 -0
  41. package/dist/electron/main.js +5 -0
  42. package/dist/electron/preload.js +1 -0
  43. package/dist/icon.png +0 -0
  44. package/dist/index.html +27 -0
  45. package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
  46. package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
  47. package/docs/README.md +115 -0
  48. package/docs/TOC_WIRING_GUIDE.md +344 -0
  49. package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
  50. package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
  51. package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
  52. package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
  53. package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
  54. package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
  55. package/docs/analysis/List_Implementation.md +206 -0
  56. package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
  57. package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
  58. package/docs/analysis/RefactorStyles.md +852 -0
  59. package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
  60. package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
  61. package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
  62. package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
  63. package/docs/analysis/github-issues-to-create.md +237 -0
  64. package/docs/api/API_README.md +538 -0
  65. package/docs/api/API_REFERENCE.md +751 -0
  66. package/docs/api/TYPE_DEFINITIONS.md +869 -0
  67. package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
  68. package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
  69. package/docs/docxmlater-readme.md +1341 -0
  70. package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
  71. package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
  72. package/docs/fixes/README.md +37 -0
  73. package/docs/github-issues/issue-1-body.md +125 -0
  74. package/docs/github-issues/issue-10-body.md +850 -0
  75. package/docs/github-issues/issue-2-body.md +200 -0
  76. package/docs/github-issues/issue-3-body.md +270 -0
  77. package/docs/github-issues/issue-4-body.md +169 -0
  78. package/docs/github-issues/issue-5-body.md +173 -0
  79. package/docs/github-issues/issue-6-body.md +158 -0
  80. package/docs/github-issues/issue-7-body.md +171 -0
  81. package/docs/github-issues/issue-8-body.md +407 -0
  82. package/docs/github-issues/issue-9-body.md +515 -0
  83. package/docs/github-issues/issue-tracker.md +274 -0
  84. package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
  85. package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
  86. package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
  87. package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
  88. package/docs/implementation/RefactorStyles.md +588 -0
  89. package/docs/implementation/implement-plan.md +489 -0
  90. package/docs/implementation/missing-helpers-implementation.md +391 -0
  91. package/docs/implementation/refactor-plan.md +520 -0
  92. package/docs/implementation/session-implementation-complete.md +233 -0
  93. package/docs/implementation/session-management-plan.md +250 -0
  94. package/docs/setup-checklist.md +77 -0
  95. package/docs/versions/changelog.md +345 -0
  96. package/electron/customUpdater.ts +656 -0
  97. package/electron/main.ts +2441 -0
  98. package/electron/memoryConfig.ts +187 -0
  99. package/electron/preload.ts +394 -0
  100. package/electron/proxyConfig.ts +340 -0
  101. package/electron/services/BackupService.ts +452 -0
  102. package/electron/services/DictionaryService.ts +402 -0
  103. package/electron/services/LocalDictionaryLookupService.ts +147 -0
  104. package/electron/services/PowerAutomateApiService.ts +231 -0
  105. package/electron/services/SharePointSyncService.ts +474 -0
  106. package/electron/windowsCertStore.ts +427 -0
  107. package/electron/zscalerConfig.ts +381 -0
  108. package/eslint.config.js +92 -0
  109. package/jest.config.js +52 -0
  110. package/package.json +214 -0
  111. package/postcss.config.mjs +6 -0
  112. package/public/icon.png +0 -0
  113. package/publish-release.ps1 +5 -0
  114. package/renovate.json +30 -0
  115. package/src/App.tsx +216 -0
  116. package/src/__mocks__/p-limit.js +12 -0
  117. package/src/__mocks__/styleMock.js +1 -0
  118. package/src/components/common/BugReportButton.tsx +44 -0
  119. package/src/components/common/BugReportDialog.tsx +193 -0
  120. package/src/components/common/Button.tsx +153 -0
  121. package/src/components/common/Card.tsx +86 -0
  122. package/src/components/common/ColorPickerDialog.tsx +177 -0
  123. package/src/components/common/ConfirmDialog.tsx +96 -0
  124. package/src/components/common/DebugConsole.tsx +275 -0
  125. package/src/components/common/EmptyState.tsx +183 -0
  126. package/src/components/common/ErrorBoundary.tsx +98 -0
  127. package/src/components/common/ErrorDetailsDialog.tsx +153 -0
  128. package/src/components/common/ErrorFallback.tsx +218 -0
  129. package/src/components/common/Input.tsx +109 -0
  130. package/src/components/common/Skeleton.tsx +184 -0
  131. package/src/components/common/SplashScreen.tsx +81 -0
  132. package/src/components/common/Toast.tsx +155 -0
  133. package/src/components/common/Tooltip.tsx +79 -0
  134. package/src/components/common/UpdateNotification.tsx +320 -0
  135. package/src/components/comparison/ComparisonWindow.tsx +374 -0
  136. package/src/components/comparison/SideBySideDiff.tsx +486 -0
  137. package/src/components/comparison/index.ts +8 -0
  138. package/src/components/document/DocumentUploader.tsx +288 -0
  139. package/src/components/document/HyperlinkPreview.tsx +430 -0
  140. package/src/components/document/HyperlinkService.md +1484 -0
  141. package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
  142. package/src/components/document/InlineChangesView.tsx +707 -0
  143. package/src/components/document/ProcessingProgress.tsx +303 -0
  144. package/src/components/document/ProcessingResults.tsx +256 -0
  145. package/src/components/document/TrackedChangesDetail.tsx +530 -0
  146. package/src/components/document/TrackedChangesPanel.tsx +546 -0
  147. package/src/components/document/VirtualDocumentList.tsx +240 -0
  148. package/src/components/editor/DocumentEditor.tsx +723 -0
  149. package/src/components/editor/DocumentEditorModal.tsx +640 -0
  150. package/src/components/editor/EditorQuickActions.tsx +502 -0
  151. package/src/components/editor/EditorToolbar.tsx +312 -0
  152. package/src/components/editor/TableEditor.tsx +926 -0
  153. package/src/components/editor/index.ts +18 -0
  154. package/src/components/layout/Header.tsx +190 -0
  155. package/src/components/layout/Sidebar.tsx +313 -0
  156. package/src/components/layout/TitleBar.tsx +190 -0
  157. package/src/components/navigation/CommandPalette.tsx +233 -0
  158. package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
  159. package/src/components/sessions/ChangeItem.tsx +408 -0
  160. package/src/components/sessions/ChangeViewer.tsx +1155 -0
  161. package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
  162. package/src/components/sessions/ProcessingOptions.tsx +297 -0
  163. package/src/components/sessions/ReplacementsTab.tsx +438 -0
  164. package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
  165. package/src/components/sessions/SessionManager.tsx +188 -0
  166. package/src/components/sessions/StylesEditor.tsx +1335 -0
  167. package/src/components/sessions/TabContainer.tsx +151 -0
  168. package/src/components/sessions/VirtualSessionList.tsx +157 -0
  169. package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
  170. package/src/components/settings/CertificateManager.tsx +410 -0
  171. package/src/components/settings/SegmentedControl.tsx +88 -0
  172. package/src/components/settings/SettingRow.tsx +52 -0
  173. package/src/contexts/GlobalStatsContext.tsx +396 -0
  174. package/src/contexts/SessionContext.tsx +2129 -0
  175. package/src/contexts/ThemeContext.tsx +428 -0
  176. package/src/contexts/UserSettingsContext.tsx +290 -0
  177. package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
  178. package/src/global.d.ts +273 -0
  179. package/src/hooks/useDocumentQueue.tsx +210 -0
  180. package/src/hooks/useToast.tsx +55 -0
  181. package/src/main.tsx +10 -0
  182. package/src/pages/Analytics.tsx +386 -0
  183. package/src/pages/CurrentSession.tsx +1174 -0
  184. package/src/pages/Dashboard.tsx +319 -0
  185. package/src/pages/Documents.tsx +317 -0
  186. package/src/pages/Projects.tsx +250 -0
  187. package/src/pages/Reporting.tsx +386 -0
  188. package/src/pages/Search.tsx +349 -0
  189. package/src/pages/Sessions.tsx +285 -0
  190. package/src/pages/Settings.tsx +2662 -0
  191. package/src/services/HyperlinkService.ts +1085 -0
  192. package/src/services/document/DocXMLaterProcessor.ts +617 -0
  193. package/src/services/document/DocumentProcessingComparison.ts +856 -0
  194. package/src/services/document/DocumentSnapshotService.ts +575 -0
  195. package/src/services/document/WordDocumentProcessor.ts +10509 -0
  196. package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
  197. package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
  198. package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
  199. package/src/services/document/blanklines/BlankLineManager.ts +658 -0
  200. package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
  201. package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
  202. package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
  203. package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
  204. package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
  205. package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
  206. package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
  207. package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
  208. package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
  209. package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
  210. package/src/services/document/blanklines/index.ts +67 -0
  211. package/src/services/document/blanklines/rules/additionRules.ts +337 -0
  212. package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
  213. package/src/services/document/blanklines/rules/removalRules.ts +362 -0
  214. package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
  215. package/src/services/document/blanklines/types.ts +29 -0
  216. package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
  217. package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
  218. package/src/services/document/helpers/whitespace.ts +117 -0
  219. package/src/services/document/list/ListNormalizer.ts +947 -0
  220. package/src/services/document/list/index.ts +45 -0
  221. package/src/services/document/list/list-detection.ts +275 -0
  222. package/src/services/document/list/list-types.ts +162 -0
  223. package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
  224. package/src/services/document/processors/ListProcessor.ts +257 -0
  225. package/src/services/document/processors/StructureProcessor.ts +176 -0
  226. package/src/services/document/processors/StyleProcessor.ts +389 -0
  227. package/src/services/document/processors/TableProcessor.ts +2238 -0
  228. package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
  229. package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
  230. package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
  231. package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
  232. package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
  233. package/src/services/document/processors/index.ts +28 -0
  234. package/src/services/document/types/docx-processing.ts +310 -0
  235. package/src/services/editor/EditorActionHandlers.ts +901 -0
  236. package/src/services/editor/index.ts +13 -0
  237. package/src/setupTests.ts +47 -0
  238. package/src/styles/global.css +782 -0
  239. package/src/types/backup.ts +132 -0
  240. package/src/types/dictionary.ts +125 -0
  241. package/src/types/document-processing.ts +331 -0
  242. package/src/types/docxmlater-augments.d.ts +142 -0
  243. package/src/types/editor.ts +280 -0
  244. package/src/types/electron.ts +340 -0
  245. package/src/types/globalStats.ts +155 -0
  246. package/src/types/hyperlink.ts +471 -0
  247. package/src/types/operations.ts +354 -0
  248. package/src/types/session.ts +427 -0
  249. package/src/types/settings.ts +112 -0
  250. package/src/utils/MemoryMonitor.ts +248 -0
  251. package/src/utils/cn.ts +6 -0
  252. package/src/utils/colorConvert.ts +306 -0
  253. package/src/utils/diffUtils.ts +347 -0
  254. package/src/utils/documentUtils.ts +202 -0
  255. package/src/utils/electronGuard.ts +62 -0
  256. package/src/utils/indexedDB.ts +915 -0
  257. package/src/utils/logger.ts +717 -0
  258. package/src/utils/pathSecurity.ts +232 -0
  259. package/src/utils/pathValidator.ts +236 -0
  260. package/src/utils/processingTimeEstimator.ts +153 -0
  261. package/src/utils/safeJsonParse.ts +62 -0
  262. package/src/utils/textSanitizer.ts +162 -0
  263. package/src/utils/urlHelpers.ts +304 -0
  264. package/src/utils/urlPatterns.ts +198 -0
  265. package/src/utils/urlSanitizer.ts +152 -0
  266. package/src/vite-env.d.ts +11 -0
  267. package/tsconfig.electron.json +19 -0
  268. package/tsconfig.json +36 -0
  269. package/tsconfig.node.json +12 -0
  270. package/typedoc.json +45 -0
  271. 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;