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,347 @@
1
+ /**
2
+ * Diff Utilities - Word-level text comparison for document comparison views
3
+ *
4
+ * Uses the 'diff' library (already installed) for generating word-level
5
+ * differences between pre-processing and post-processing document content.
6
+ */
7
+
8
+ import { diffWords, diffLines, Change } from 'diff';
9
+ import type { DiffSegment, ParagraphDiff, DocumentDiff } from '@/types/editor';
10
+
11
+ /**
12
+ * Generate word-level diff between two strings
13
+ *
14
+ * @param original - Original text (before processing)
15
+ * @param modified - Modified text (after processing)
16
+ * @returns Arrays of diff segments for each side
17
+ */
18
+ export function generateWordDiff(
19
+ original: string,
20
+ modified: string
21
+ ): { originalSegments: DiffSegment[]; modifiedSegments: DiffSegment[] } {
22
+ const changes = diffWords(original, modified);
23
+
24
+ const originalSegments: DiffSegment[] = [];
25
+ const modifiedSegments: DiffSegment[] = [];
26
+
27
+ for (const change of changes) {
28
+ if (change.added) {
29
+ // Text was added (only in modified)
30
+ modifiedSegments.push({
31
+ text: change.value,
32
+ type: 'added',
33
+ });
34
+ } else if (change.removed) {
35
+ // Text was removed (only in original)
36
+ originalSegments.push({
37
+ text: change.value,
38
+ type: 'removed',
39
+ });
40
+ } else {
41
+ // Text is unchanged (in both)
42
+ originalSegments.push({
43
+ text: change.value,
44
+ type: 'unchanged',
45
+ });
46
+ modifiedSegments.push({
47
+ text: change.value,
48
+ type: 'unchanged',
49
+ });
50
+ }
51
+ }
52
+
53
+ return { originalSegments, modifiedSegments };
54
+ }
55
+
56
+ /**
57
+ * Generate a complete document diff from paragraph arrays
58
+ *
59
+ * @param original - Array of original paragraph texts
60
+ * @param modified - Array of modified paragraph texts
61
+ * @returns Complete document diff with per-paragraph analysis
62
+ */
63
+ export function generateDocumentDiff(
64
+ original: string[],
65
+ modified: string[]
66
+ ): DocumentDiff {
67
+ const paragraphDiffs: ParagraphDiff[] = [];
68
+
69
+ let wordsAdded = 0;
70
+ let wordsRemoved = 0;
71
+ let wordsModified = 0;
72
+ let changedParagraphs = 0;
73
+ let addedParagraphs = 0;
74
+ let removedParagraphs = 0;
75
+
76
+ // Use line-level diff first to align paragraphs
77
+ const lineDiffs = diffLines(original.join('\n'), modified.join('\n'));
78
+
79
+ let originalIndex = 0;
80
+ let modifiedIndex = 0;
81
+ let paragraphIndex = 0;
82
+
83
+ for (const lineDiff of lineDiffs) {
84
+ const lines = lineDiff.value.split('\n').filter((line) => line !== '');
85
+
86
+ if (lineDiff.added) {
87
+ // Paragraphs were added
88
+ for (const line of lines) {
89
+ paragraphDiffs.push({
90
+ index: paragraphIndex++,
91
+ original: '',
92
+ modified: line,
93
+ originalSegments: [],
94
+ modifiedSegments: [{ text: line, type: 'added' }],
95
+ hasChanges: true,
96
+ });
97
+ addedParagraphs++;
98
+ wordsAdded += countWords(line);
99
+ modifiedIndex++;
100
+ }
101
+ } else if (lineDiff.removed) {
102
+ // Paragraphs were removed
103
+ for (const line of lines) {
104
+ paragraphDiffs.push({
105
+ index: paragraphIndex++,
106
+ original: line,
107
+ modified: '',
108
+ originalSegments: [{ text: line, type: 'removed' }],
109
+ modifiedSegments: [],
110
+ hasChanges: true,
111
+ });
112
+ removedParagraphs++;
113
+ wordsRemoved += countWords(line);
114
+ originalIndex++;
115
+ }
116
+ } else {
117
+ // Paragraphs match - but content might differ
118
+ for (const line of lines) {
119
+ const origText = original[originalIndex] || '';
120
+ const modText = modified[modifiedIndex] || '';
121
+
122
+ if (origText === modText) {
123
+ // Exact match
124
+ paragraphDiffs.push({
125
+ index: paragraphIndex++,
126
+ original: origText,
127
+ modified: modText,
128
+ originalSegments: [{ text: origText, type: 'unchanged' }],
129
+ modifiedSegments: [{ text: modText, type: 'unchanged' }],
130
+ hasChanges: false,
131
+ });
132
+ } else {
133
+ // Content changed within paragraph
134
+ const { originalSegments, modifiedSegments } = generateWordDiff(
135
+ origText,
136
+ modText
137
+ );
138
+
139
+ paragraphDiffs.push({
140
+ index: paragraphIndex++,
141
+ original: origText,
142
+ modified: modText,
143
+ originalSegments,
144
+ modifiedSegments,
145
+ hasChanges: true,
146
+ });
147
+ changedParagraphs++;
148
+
149
+ // Count word changes
150
+ const origWords = countWords(origText);
151
+ const modWords = countWords(modText);
152
+ wordsModified += Math.abs(modWords - origWords);
153
+ }
154
+
155
+ originalIndex++;
156
+ modifiedIndex++;
157
+ }
158
+ }
159
+ }
160
+
161
+ // Handle any remaining paragraphs
162
+ while (originalIndex < original.length) {
163
+ const origText = original[originalIndex];
164
+ paragraphDiffs.push({
165
+ index: paragraphIndex++,
166
+ original: origText,
167
+ modified: '',
168
+ originalSegments: [{ text: origText, type: 'removed' }],
169
+ modifiedSegments: [],
170
+ hasChanges: true,
171
+ });
172
+ removedParagraphs++;
173
+ wordsRemoved += countWords(origText);
174
+ originalIndex++;
175
+ }
176
+
177
+ while (modifiedIndex < modified.length) {
178
+ const modText = modified[modifiedIndex];
179
+ paragraphDiffs.push({
180
+ index: paragraphIndex++,
181
+ original: '',
182
+ modified: modText,
183
+ originalSegments: [],
184
+ modifiedSegments: [{ text: modText, type: 'added' }],
185
+ hasChanges: true,
186
+ });
187
+ addedParagraphs++;
188
+ wordsAdded += countWords(modText);
189
+ modifiedIndex++;
190
+ }
191
+
192
+ return {
193
+ original,
194
+ modified,
195
+ paragraphDiffs,
196
+ stats: {
197
+ totalParagraphs: paragraphDiffs.length,
198
+ changedParagraphs,
199
+ addedParagraphs,
200
+ removedParagraphs,
201
+ wordsAdded,
202
+ wordsRemoved,
203
+ wordsModified,
204
+ },
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Count words in a string
210
+ */
211
+ function countWords(text: string): number {
212
+ return text
213
+ .trim()
214
+ .split(/\s+/)
215
+ .filter((word) => word.length > 0).length;
216
+ }
217
+
218
+ /**
219
+ * Simplify diff result by merging consecutive segments of the same type
220
+ *
221
+ * @param segments - Array of diff segments
222
+ * @returns Merged segments
223
+ */
224
+ export function mergeConsecutiveSegments(segments: DiffSegment[]): DiffSegment[] {
225
+ if (segments.length === 0) return segments;
226
+
227
+ const merged: DiffSegment[] = [];
228
+ let current = { ...segments[0] };
229
+
230
+ for (let i = 1; i < segments.length; i++) {
231
+ const segment = segments[i];
232
+ if (segment.type === current.type) {
233
+ current.text += segment.text;
234
+ } else {
235
+ merged.push(current);
236
+ current = { ...segment };
237
+ }
238
+ }
239
+
240
+ merged.push(current);
241
+ return merged;
242
+ }
243
+
244
+ /**
245
+ * Calculate similarity percentage between two texts
246
+ *
247
+ * @param original - Original text
248
+ * @param modified - Modified text
249
+ * @returns Percentage similarity (0-100)
250
+ */
251
+ export function calculateSimilarity(original: string, modified: string): number {
252
+ if (original === modified) return 100;
253
+ if (original.length === 0 && modified.length === 0) return 100;
254
+ if (original.length === 0 || modified.length === 0) return 0;
255
+
256
+ const changes = diffWords(original, modified);
257
+ let unchangedLength = 0;
258
+ let totalLength = 0;
259
+
260
+ for (const change of changes) {
261
+ totalLength += change.value.length;
262
+ if (!change.added && !change.removed) {
263
+ unchangedLength += change.value.length;
264
+ }
265
+ }
266
+
267
+ return totalLength > 0 ? Math.round((unchangedLength / totalLength) * 100) : 0;
268
+ }
269
+
270
+ /**
271
+ * Get a summary of changes between two paragraph arrays
272
+ *
273
+ * @param original - Original paragraphs
274
+ * @param modified - Modified paragraphs
275
+ * @returns Human-readable summary
276
+ */
277
+ export function getDiffSummary(original: string[], modified: string[]): string {
278
+ const diff = generateDocumentDiff(original, modified);
279
+ const { stats } = diff;
280
+
281
+ const parts: string[] = [];
282
+
283
+ if (stats.changedParagraphs > 0) {
284
+ parts.push(`${stats.changedParagraphs} modified`);
285
+ }
286
+ if (stats.addedParagraphs > 0) {
287
+ parts.push(`${stats.addedParagraphs} added`);
288
+ }
289
+ if (stats.removedParagraphs > 0) {
290
+ parts.push(`${stats.removedParagraphs} removed`);
291
+ }
292
+
293
+ if (parts.length === 0) {
294
+ return 'No changes';
295
+ }
296
+
297
+ return `${parts.join(', ')} paragraphs`;
298
+ }
299
+
300
+ /**
301
+ * Filter paragraph diffs to only show changed paragraphs
302
+ *
303
+ * @param diffs - All paragraph diffs
304
+ * @param includeContext - Number of unchanged paragraphs to include around changes
305
+ * @returns Filtered diffs with context markers
306
+ */
307
+ export function filterChangedParagraphs(
308
+ diffs: ParagraphDiff[],
309
+ includeContext: number = 2
310
+ ): ParagraphDiff[] {
311
+ if (includeContext === 0) {
312
+ return diffs.filter((d) => d.hasChanges);
313
+ }
314
+
315
+ const result: ParagraphDiff[] = [];
316
+ const showIndices = new Set<number>();
317
+
318
+ // Mark all changed paragraphs and their context
319
+ diffs.forEach((diff, index) => {
320
+ if (diff.hasChanges) {
321
+ for (
322
+ let i = Math.max(0, index - includeContext);
323
+ i <= Math.min(diffs.length - 1, index + includeContext);
324
+ i++
325
+ ) {
326
+ showIndices.add(i);
327
+ }
328
+ }
329
+ });
330
+
331
+ // Build result with indices in order
332
+ const sortedIndices = Array.from(showIndices).sort((a, b) => a - b);
333
+ for (const index of sortedIndices) {
334
+ result.push(diffs[index]);
335
+ }
336
+
337
+ return result;
338
+ }
339
+
340
+ export default {
341
+ generateWordDiff,
342
+ generateDocumentDiff,
343
+ mergeConsecutiveSegments,
344
+ calculateSimilarity,
345
+ getDiffSummary,
346
+ filterChangedParagraphs,
347
+ };
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Document Utility Functions for DocXMLater
3
+ *
4
+ * Provides safe wrappers around DocXMLater operations with guaranteed resource cleanup.
5
+ * These utilities ensure documents are always disposed, preventing memory leaks.
6
+ *
7
+ * @module documentUtils
8
+ */
9
+
10
+ import { Document } from 'docxmlater';
11
+
12
+ /**
13
+ * Options for document loading
14
+ */
15
+ export interface DocumentLoadOptions {
16
+ /** Enable strict XML parsing (default: false for compatibility) */
17
+ strictParsing?: boolean;
18
+ /** How to handle tracked changes: 'preserve' keeps them, 'accept' accepts them */
19
+ revisionHandling?: 'preserve' | 'accept';
20
+ /** Whether to accept all revisions on load (default: false) */
21
+ acceptRevisions?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Execute an operation on a document loaded from file with guaranteed disposal.
26
+ *
27
+ * This function ensures that the document is always disposed, even if the operation
28
+ * throws an error. Use this for any document processing that doesn't need to keep
29
+ * the document open after the operation completes.
30
+ *
31
+ * @template T - The return type of the operation
32
+ * @param filePath - Path to the DOCX file
33
+ * @param operation - Async function that receives the loaded document
34
+ * @param options - Optional loading configuration
35
+ * @returns Promise resolving to the operation result
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // Extract all text from a document
40
+ * const text = await withDocumentFromFile('report.docx', async (doc) => {
41
+ * const paragraphs = doc.getAllParagraphs();
42
+ * return paragraphs.map(p => p.getText()).join('\n');
43
+ * });
44
+ *
45
+ * // Count hyperlinks in a document
46
+ * const count = await withDocumentFromFile('document.docx', async (doc) => {
47
+ * const paragraphs = doc.getAllParagraphs();
48
+ * let hyperlinkCount = 0;
49
+ * for (const para of paragraphs) {
50
+ * for (const item of para.getContent()) {
51
+ * if (typeof item.getUrl === 'function') hyperlinkCount++;
52
+ * }
53
+ * }
54
+ * return hyperlinkCount;
55
+ * });
56
+ * ```
57
+ */
58
+ export async function withDocumentFromFile<T>(
59
+ filePath: string,
60
+ operation: (doc: Document) => Promise<T>,
61
+ options?: DocumentLoadOptions
62
+ ): Promise<T> {
63
+ const doc = await Document.load(filePath, {
64
+ strictParsing: options?.strictParsing ?? false,
65
+ revisionHandling: options?.revisionHandling,
66
+ acceptRevisions: options?.acceptRevisions,
67
+ });
68
+
69
+ try {
70
+ return await operation(doc);
71
+ } finally {
72
+ try {
73
+ doc.dispose();
74
+ } catch {
75
+ // Ignore disposal errors - document may already be disposed
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Execute an operation on a document loaded from buffer with guaranteed disposal.
82
+ *
83
+ * Similar to withDocumentFromFile, but loads from a Buffer instead of a file path.
84
+ * Useful for processing documents from HTTP responses, databases, or other in-memory sources.
85
+ *
86
+ * @template T - The return type of the operation
87
+ * @param buffer - Buffer containing the DOCX file data
88
+ * @param operation - Async function that receives the loaded document
89
+ * @returns Promise resolving to the operation result
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // Process document from HTTP response
94
+ * const response = await fetch('https://example.com/document.docx');
95
+ * const buffer = Buffer.from(await response.arrayBuffer());
96
+ *
97
+ * const result = await withDocumentFromBuffer(buffer, async (doc) => {
98
+ * // Process document...
99
+ * return { pageCount: doc.getParagraphs().length };
100
+ * });
101
+ * ```
102
+ */
103
+ export async function withDocumentFromBuffer<T>(
104
+ buffer: Buffer,
105
+ operation: (doc: Document) => Promise<T>
106
+ ): Promise<T> {
107
+ const doc = await Document.loadFromBuffer(buffer);
108
+
109
+ try {
110
+ return await operation(doc);
111
+ } finally {
112
+ try {
113
+ doc.dispose();
114
+ } catch {
115
+ // Ignore disposal errors - document may already be disposed
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Execute an operation on a document and save it with guaranteed disposal.
122
+ *
123
+ * This is a convenience wrapper for the common pattern of loading a document,
124
+ * modifying it, and saving the result. The document is disposed after saving.
125
+ *
126
+ * @template T - The return type of the operation (in addition to saving)
127
+ * @param inputPath - Path to the input DOCX file
128
+ * @param outputPath - Path where the modified document should be saved
129
+ * @param operation - Async function that modifies the document and optionally returns data
130
+ * @param options - Optional loading configuration
131
+ * @returns Promise resolving to the operation result
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * // Modify document and save
136
+ * await withDocumentModify(
137
+ * 'input.docx',
138
+ * 'output.docx',
139
+ * async (doc) => {
140
+ * const paragraphs = doc.getAllParagraphs();
141
+ * for (const para of paragraphs) {
142
+ * for (const run of para.getRuns()) {
143
+ * run.setFont('Arial');
144
+ * }
145
+ * }
146
+ * }
147
+ * );
148
+ * ```
149
+ */
150
+ export async function withDocumentModify<T = void>(
151
+ inputPath: string,
152
+ outputPath: string,
153
+ operation: (doc: Document) => Promise<T>,
154
+ options?: DocumentLoadOptions
155
+ ): Promise<T> {
156
+ const doc = await Document.load(inputPath, {
157
+ strictParsing: options?.strictParsing ?? false,
158
+ revisionHandling: options?.revisionHandling,
159
+ acceptRevisions: options?.acceptRevisions,
160
+ });
161
+
162
+ try {
163
+ const result = await operation(doc);
164
+ await doc.save(outputPath);
165
+ return result;
166
+ } finally {
167
+ try {
168
+ doc.dispose();
169
+ } catch {
170
+ // Ignore disposal errors
171
+ }
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Safely dispose a document, catching any errors.
177
+ *
178
+ * Use this when you need to manually dispose a document and want to ensure
179
+ * no exceptions are thrown, even if the document is already disposed or invalid.
180
+ *
181
+ * @param doc - The document to dispose, or null/undefined
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * let doc: Document | null = null;
186
+ * try {
187
+ * doc = await Document.load('file.docx');
188
+ * // ... operations ...
189
+ * } finally {
190
+ * safeDispose(doc);
191
+ * }
192
+ * ```
193
+ */
194
+ export function safeDispose(doc: Document | null | undefined): void {
195
+ if (doc) {
196
+ try {
197
+ doc.dispose();
198
+ } catch {
199
+ // Ignore disposal errors
200
+ }
201
+ }
202
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Electron API Guard Utilities
3
+ *
4
+ * Provides safe access to the Electron API exposed via contextBridge.
5
+ * These utilities handle cases where the API might not be available:
6
+ * - Running in a browser instead of Electron
7
+ * - During initial page load before preload completes
8
+ * - In test environments
9
+ * - During hot module replacement (HMR) cycles
10
+ */
11
+
12
+ import type { ElectronAPI } from '@/global.d';
13
+
14
+ /**
15
+ * Check if the code is running in an Electron environment.
16
+ *
17
+ * @returns true if window.electronAPI is available
18
+ */
19
+ export function isElectronEnvironment(): boolean {
20
+ return typeof window !== 'undefined' && typeof window.electronAPI !== 'undefined';
21
+ }
22
+
23
+ /**
24
+ * Get the Electron API, throwing if unavailable.
25
+ *
26
+ * @throws Error if not running in Electron environment
27
+ * @returns The electronAPI object
28
+ */
29
+ export function getElectronAPI(): ElectronAPI {
30
+ if (!isElectronEnvironment()) {
31
+ throw new Error(
32
+ 'Electron API not available. This feature requires running in the Electron desktop application.'
33
+ );
34
+ }
35
+ return window.electronAPI;
36
+ }
37
+
38
+ /**
39
+ * Get the Electron API with a feature-specific error message.
40
+ *
41
+ * @param feature - Name of the feature requiring Electron (for error message)
42
+ * @throws Error if not running in Electron environment
43
+ * @returns The electronAPI object
44
+ */
45
+ export function requireElectronAPI(feature: string): ElectronAPI {
46
+ if (!isElectronEnvironment()) {
47
+ throw new Error(
48
+ `Electron API not available - ${feature} requires running in the Electron desktop application.`
49
+ );
50
+ }
51
+ return window.electronAPI;
52
+ }
53
+
54
+ /**
55
+ * Safely check if a specific Electron API method is available.
56
+ *
57
+ * @param method - The method name to check (e.g., 'callPowerAutomateApi')
58
+ * @returns true if the method exists and is callable
59
+ */
60
+ export function hasElectronMethod(method: keyof ElectronAPI): boolean {
61
+ return isElectronEnvironment() && typeof window.electronAPI[method] === 'function';
62
+ }