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,176 @@
1
+ /**
2
+ * StructureProcessor - Document structure operations
3
+ *
4
+ * Handles:
5
+ * - Blank paragraph removal
6
+ * - Structure blank line insertion (after lists, tables)
7
+ * - Italic formatting removal
8
+ * - Headers and footers removal
9
+ * - Document warnings
10
+ */
11
+
12
+ import { Document, Paragraph, Run, Hyperlink, Image } from "docxmlater";
13
+ import { logger } from "@/utils/logger";
14
+
15
+ const log = logger.namespace("StructureProcessor");
16
+
17
+ /**
18
+ * Structure processing service
19
+ */
20
+ export class StructureProcessor {
21
+ private readonly DEBUG = process.env.NODE_ENV !== "production";
22
+
23
+ /**
24
+ * Remove italic formatting from all text runs
25
+ */
26
+ async removeItalicFormatting(doc: Document): Promise<number> {
27
+ let removedCount = 0;
28
+ const paragraphs = doc.getAllParagraphs();
29
+
30
+ for (const para of paragraphs) {
31
+ const runs = para.getRuns();
32
+ for (const run of runs) {
33
+ const formatting = run.getFormatting();
34
+ if (formatting?.italic) {
35
+ run.setItalic(false);
36
+ removedCount++;
37
+ }
38
+ }
39
+ }
40
+
41
+ log.info(`Removed italic formatting from ${removedCount} runs`);
42
+ return removedCount;
43
+ }
44
+
45
+ /**
46
+ * Remove headers and footers from document
47
+ */
48
+ async removeHeadersFooters(doc: Document): Promise<number> {
49
+ try {
50
+ const removedCount = doc.removeAllHeadersFooters();
51
+ log.info(`Removed ${removedCount} headers/footers`);
52
+ return removedCount;
53
+ } catch (error) {
54
+ log.error(`Error removing headers/footers: ${error}`);
55
+ return 0;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Add document warning at end of document
61
+ */
62
+ async addDocumentWarning(doc: Document): Promise<boolean> {
63
+ try {
64
+ // Check if warning already exists
65
+ const paragraphs = doc.getAllParagraphs();
66
+ const searchStartIndex = Math.max(0, paragraphs.length - 10);
67
+
68
+ for (let i = paragraphs.length - 1; i >= searchStartIndex; i--) {
69
+ const text = paragraphs[i].getText() || "";
70
+ if (
71
+ text.includes("electronic data") ||
72
+ text.includes("not to be reproduced")
73
+ ) {
74
+ log.debug("Document warning already exists");
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // Create warning paragraphs
80
+ const warningParagraph1 = Paragraph.create("This is electronic data and is not to be reproduced, copied, or distributed.");
81
+ warningParagraph1.setAlignment("center");
82
+ warningParagraph1.setSpaceBefore(240);
83
+ warningParagraph1.setSpaceAfter(0);
84
+
85
+ const warningParagraph2 = Paragraph.create("For internal use only.");
86
+ warningParagraph2.setAlignment("center");
87
+ warningParagraph2.setSpaceBefore(0);
88
+ warningParagraph2.setSpaceAfter(0);
89
+
90
+ // Apply italic formatting to runs
91
+ for (const run of warningParagraph1.getRuns()) {
92
+ run.setItalic(true);
93
+ run.setFont("Verdana");
94
+ run.setSize(10);
95
+ }
96
+
97
+ for (const run of warningParagraph2.getRuns()) {
98
+ run.setItalic(true);
99
+ run.setFont("Verdana");
100
+ run.setSize(10);
101
+ }
102
+
103
+ // Append to document
104
+ doc.addParagraph(warningParagraph1);
105
+ doc.addParagraph(warningParagraph2);
106
+
107
+ log.info("Added document warning");
108
+ return true;
109
+ } catch (error) {
110
+ log.error(`Error adding document warning: ${error}`);
111
+ return false;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Check if a paragraph is truly empty
117
+ */
118
+ isParagraphTrulyEmpty(para: Paragraph): boolean {
119
+ try {
120
+ // Check for numbering (list item)
121
+ const numbering = para.getNumbering();
122
+ if (numbering) {
123
+ return false;
124
+ }
125
+
126
+ // Check for content
127
+ const content = para.getContent();
128
+
129
+ if (content.length === 0) {
130
+ return true;
131
+ }
132
+
133
+ // Check for hyperlinks or images
134
+ for (const item of content) {
135
+ if (item instanceof Hyperlink || item instanceof Image) {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ // Check if all runs are empty
141
+ const allRunsEmpty = content.every((item) => {
142
+ if (item instanceof Run) {
143
+ const text = (item.getText() || "").trim();
144
+ return text === "";
145
+ }
146
+ return false;
147
+ });
148
+
149
+ return allRunsEmpty;
150
+ } catch (error) {
151
+ // Default to NOT empty - safer than deleting
152
+ return false;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Find nearest Header2 text for a given paragraph index
158
+ */
159
+ findNearestHeader2(doc: Document, paragraphIndex: number): string | null {
160
+ const paragraphs = doc.getAllParagraphs();
161
+
162
+ // Search backwards from the given index
163
+ for (let i = paragraphIndex; i >= 0; i--) {
164
+ const para = paragraphs[i];
165
+ const style = para.getStyle();
166
+
167
+ if (style === "Heading2" || style === "Heading 2") {
168
+ return para.getText() || null;
169
+ }
170
+ }
171
+
172
+ return null;
173
+ }
174
+ }
175
+
176
+ export const structureProcessor = new StructureProcessor();
@@ -0,0 +1,389 @@
1
+ /**
2
+ * StyleProcessor - Style assignment and text formatting operations
3
+ *
4
+ * Handles:
5
+ * - Heading style application (H1, H2, H3)
6
+ * - Normal paragraph styling
7
+ * - Font, size, color standardization
8
+ * - Paragraph spacing and alignment
9
+ * - Style definition management
10
+ */
11
+
12
+ import { Document, Hyperlink, Paragraph, Run, Style, pointsToTwips, isRevision } from "docxmlater";
13
+ import { logger } from "@/utils/logger";
14
+
15
+ const log = logger.namespace("StyleProcessor");
16
+
17
+ /**
18
+ * Style configuration from session
19
+ */
20
+ export interface SessionStyle {
21
+ id: string;
22
+ name: string;
23
+ fontFamily: string;
24
+ fontSize: number;
25
+ bold: boolean;
26
+ italic: boolean;
27
+ underline: boolean;
28
+ preserveBold?: boolean;
29
+ preserveItalic?: boolean;
30
+ preserveUnderline?: boolean;
31
+ alignment: "left" | "center" | "right" | "justify";
32
+ color: string;
33
+ spaceBefore: number;
34
+ spaceAfter: number;
35
+ lineSpacing: number;
36
+ noSpaceBetweenSame?: boolean;
37
+ indentation?: {
38
+ left?: number;
39
+ firstLine?: number;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Result of style application
45
+ */
46
+ export interface StyleApplicationResult {
47
+ heading1: number;
48
+ heading2: number;
49
+ heading3: number;
50
+ normal: number;
51
+ listParagraph: number;
52
+ }
53
+
54
+ /**
55
+ * Table shading configuration
56
+ */
57
+ export interface TableShadingSettings {
58
+ header2Shading: string;
59
+ otherShading: string;
60
+ }
61
+
62
+ /**
63
+ * Style processing service
64
+ */
65
+ export class StyleProcessor {
66
+ private readonly DEBUG = process.env.NODE_ENV !== "production";
67
+
68
+ /**
69
+ * Apply custom styles to document based on session configuration
70
+ */
71
+ async applyCustomStyles(
72
+ doc: Document,
73
+ styles: SessionStyle[],
74
+ tableShadingSettings?: TableShadingSettings,
75
+ preserveBlankLinesAfterHeader2Tables?: boolean
76
+ ): Promise<StyleApplicationResult> {
77
+ const result: StyleApplicationResult = {
78
+ heading1: 0,
79
+ heading2: 0,
80
+ heading3: 0,
81
+ normal: 0,
82
+ listParagraph: 0,
83
+ };
84
+
85
+ // Find configured styles
86
+ const header1Style = styles.find((s) => s.id === "header1");
87
+ const header2Style = styles.find((s) => s.id === "header2");
88
+ const header3Style = styles.find((s) => s.id === "header3");
89
+ const normalStyle = styles.find((s) => s.id === "normal");
90
+ const listParagraphStyle = styles.find((s) => s.id === "listParagraph");
91
+
92
+ // Use docxmlater's applyStyles if available
93
+ if (typeof (doc as any).applyStyles === "function") {
94
+ const config = this.convertStylesToDocXMLaterConfig(
95
+ styles,
96
+ tableShadingSettings
97
+ );
98
+
99
+ try {
100
+ const docResult = (doc as any).applyStyles(config);
101
+ result.heading1 = docResult?.heading1 || 0;
102
+ result.heading2 = docResult?.heading2 || 0;
103
+ result.heading3 = docResult?.heading3 || 0;
104
+ result.normal = docResult?.normal || 0;
105
+ result.listParagraph = docResult?.listParagraph || 0;
106
+
107
+ log.info(
108
+ `Applied styles via docxmlater: H1=${result.heading1}, H2=${result.heading2}, ` +
109
+ `H3=${result.heading3}, Normal=${result.normal}, ListParagraph=${result.listParagraph}`
110
+ );
111
+
112
+ return result;
113
+ } catch (error) {
114
+ log.warn(`docxmlater applyStyles failed, falling back to manual: ${error}`);
115
+ }
116
+ }
117
+
118
+ // Manual style application fallback
119
+ const paragraphs = doc.getAllParagraphs();
120
+
121
+ for (const para of paragraphs) {
122
+ let styleToApply: SessionStyle | null = null;
123
+ let styleType: keyof StyleApplicationResult | null = null;
124
+
125
+ const currentStyle = para.getStyle();
126
+
127
+ // Determine which style to apply
128
+ if (currentStyle === "Heading1" || currentStyle === "Heading 1") {
129
+ if (header1Style) {
130
+ styleToApply = header1Style;
131
+ styleType = "heading1";
132
+ }
133
+ } else if (currentStyle === "Heading2" || currentStyle === "Heading 2") {
134
+ if (header2Style) {
135
+ styleToApply = header2Style;
136
+ styleType = "heading2";
137
+ }
138
+ } else if (currentStyle === "Heading3" || currentStyle === "Heading 3") {
139
+ if (header3Style) {
140
+ styleToApply = header3Style;
141
+ styleType = "heading3";
142
+ }
143
+ } else if (currentStyle === "ListParagraph" || currentStyle === "List Paragraph") {
144
+ if (listParagraphStyle) {
145
+ styleToApply = listParagraphStyle;
146
+ styleType = "listParagraph";
147
+ } else if (normalStyle) {
148
+ // Fallback to Normal if List Paragraph style not defined
149
+ styleToApply = normalStyle;
150
+ styleType = "listParagraph";
151
+ }
152
+ } else if (!currentStyle || currentStyle === "Normal") {
153
+ // Apply Normal style to ALL paragraphs (including empty/blank lines)
154
+ if (normalStyle) {
155
+ styleToApply = normalStyle;
156
+ styleType = "normal";
157
+ }
158
+ }
159
+
160
+ if (styleToApply && styleType) {
161
+ this.applyStyleToParagraph(para, styleToApply);
162
+ result[styleType]++;
163
+ }
164
+ }
165
+
166
+ log.info(
167
+ `Applied styles manually: H1=${result.heading1}, H2=${result.heading2}, ` +
168
+ `H3=${result.heading3}, Normal=${result.normal}, ListParagraph=${result.listParagraph}`
169
+ );
170
+
171
+ return result;
172
+ }
173
+
174
+ /**
175
+ * Apply a single style to a paragraph
176
+ */
177
+ private applyStyleToParagraph(para: Paragraph, style: SessionStyle): void {
178
+ // Apply paragraph formatting
179
+ // PRESERVE center alignment if it already exists in the document
180
+ // This prevents overriding intentional center formatting (like image captions)
181
+ const existingAlignment = para.getFormatting().alignment;
182
+ if (existingAlignment === 'center') {
183
+ log.debug(`Preserving center alignment for: "${para.getText().substring(0, 30)}..."`);
184
+ // Don't change alignment - keep center
185
+ } else {
186
+ para.setAlignment(style.alignment);
187
+ }
188
+ para.setSpaceBefore(pointsToTwips(style.spaceBefore));
189
+ para.setSpaceAfter(pointsToTwips(style.spaceAfter));
190
+
191
+ if (style.lineSpacing) {
192
+ para.setLineSpacing(pointsToTwips(style.lineSpacing * 12));
193
+ }
194
+
195
+ if (style.indentation) {
196
+ if (style.indentation.left !== undefined) {
197
+ para.setLeftIndent(pointsToTwips(style.indentation.left));
198
+ }
199
+ if (style.indentation.firstLine !== undefined) {
200
+ para.setFirstLineIndent(pointsToTwips(style.indentation.firstLine));
201
+ }
202
+ }
203
+
204
+ // Build a set of runs that belong to real Hyperlink elements.
205
+ // para.getRuns() includes runs from Hyperlink children, so we must distinguish
206
+ // real hyperlink runs from false-hyperlink runs (runs with Hyperlink character
207
+ // style that are NOT inside a w:hyperlink element).
208
+ const realHyperlinkRuns = new Set<Run>();
209
+ for (const item of para.getContent()) {
210
+ if (item instanceof Hyperlink) {
211
+ const hRun = item.getRun();
212
+ if (hRun) realHyperlinkRuns.add(hRun);
213
+ }
214
+ }
215
+
216
+ // Apply text formatting to all runs
217
+ const runs = para.getRuns();
218
+ for (const run of runs) {
219
+ // Skip runs from real Hyperlink elements — preserve their formatting
220
+ if (realHyperlinkRuns.has(run)) {
221
+ continue;
222
+ }
223
+ // If the run has Hyperlink character style but is NOT a real hyperlink,
224
+ // strip the false character style so it gets proper paragraph formatting
225
+ if (typeof run.isHyperlinkStyled === 'function' && run.isHyperlinkStyled()) {
226
+ run.setCharacterStyle(undefined as unknown as string);
227
+ log.debug(
228
+ `[FalseHyperlink] Stripped Hyperlink character style from run: ` +
229
+ `"${run.getText()?.substring(0, 40) || ''}"`
230
+ );
231
+ }
232
+
233
+ run.setFont(style.fontFamily);
234
+ run.setSize(style.fontSize);
235
+
236
+ if (!style.preserveBold) {
237
+ run.setBold(style.bold);
238
+ }
239
+ if (!style.preserveItalic) {
240
+ run.setItalic(style.italic);
241
+ }
242
+ if (!style.preserveUnderline) {
243
+ run.setUnderline(style.underline ? "single" : false);
244
+ }
245
+
246
+ // Preserve white font - don't change color if run is white (FFFFFF)
247
+ const currentColor = run.getFormatting().color?.toUpperCase();
248
+ if (currentColor !== 'FFFFFF') {
249
+ run.setColor(style.color.replace("#", ""));
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Update hyperlink style definition to use Verdana
256
+ */
257
+ async updateHyperlinkStyleDefinition(doc: Document): Promise<boolean> {
258
+ try {
259
+ const hyperlinkStyle = Style.create({
260
+ styleId: "Hyperlink",
261
+ name: "Hyperlink",
262
+ type: "character",
263
+ runFormatting: {
264
+ font: "Verdana",
265
+ size: 12,
266
+ color: "0000FF",
267
+ underline: "single",
268
+ bold: false,
269
+ italic: false,
270
+ },
271
+ });
272
+
273
+ doc.addStyle(hyperlinkStyle);
274
+ log.info("Updated Hyperlink style to use Verdana 12pt");
275
+ return true;
276
+ } catch (error) {
277
+ log.warn("Failed to update Hyperlink style:", error);
278
+ return false;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Convert session styles to docxmlater format
284
+ */
285
+ private convertStylesToDocXMLaterConfig(
286
+ styles: SessionStyle[],
287
+ tableShadingSettings?: TableShadingSettings
288
+ ): Record<string, unknown> {
289
+ const config: Record<string, unknown> = {
290
+ preserveWhiteFont: true, // Always preserve white font (FFFFFF) during style application
291
+ };
292
+
293
+ for (const style of styles) {
294
+ // Use 'run' property name as expected by docxmlater applyStyles
295
+ const run = {
296
+ font: style.fontFamily,
297
+ size: style.fontSize,
298
+ bold: style.bold,
299
+ italic: style.italic,
300
+ underline: style.underline,
301
+ color: style.color.replace("#", ""),
302
+ preserveBold: style.preserveBold,
303
+ preserveItalic: style.preserveItalic,
304
+ preserveUnderline: style.preserveUnderline,
305
+ };
306
+
307
+ // Use 'paragraph' property name with nested 'spacing' as expected by docxmlater
308
+ const paragraph: Record<string, unknown> = {
309
+ alignment: style.alignment,
310
+ spacing: {
311
+ before: pointsToTwips(style.spaceBefore),
312
+ after: pointsToTwips(style.spaceAfter),
313
+ line: style.lineSpacing ? pointsToTwips(style.lineSpacing * 12) : undefined,
314
+ },
315
+ };
316
+
317
+ // Add indentation if provided
318
+ if (style.indentation) {
319
+ paragraph.indentation = style.indentation;
320
+ }
321
+
322
+ const styleConfig: Record<string, unknown> = {
323
+ run,
324
+ paragraph,
325
+ };
326
+
327
+ if (style.noSpaceBetweenSame !== undefined) {
328
+ (styleConfig as any).noSpaceBetweenSame = style.noSpaceBetweenSame;
329
+ }
330
+
331
+ // Map style IDs to docxmlater style names
332
+ switch (style.id) {
333
+ case "header1":
334
+ config.heading1 = styleConfig;
335
+ break;
336
+ case "header2":
337
+ config.heading2 = {
338
+ ...styleConfig,
339
+ tableOptions: tableShadingSettings?.header2Shading
340
+ ? { shading: tableShadingSettings.header2Shading }
341
+ : undefined,
342
+ };
343
+ break;
344
+ case "header3":
345
+ config.heading3 = styleConfig;
346
+ break;
347
+ case "normal":
348
+ config.normal = {
349
+ ...styleConfig,
350
+ preserveCenterAlignment: true, // Preserve center alignment for captions, etc.
351
+ };
352
+ break;
353
+ case "listParagraph":
354
+ config.listParagraph = styleConfig;
355
+ break;
356
+ }
357
+ }
358
+
359
+ return config;
360
+ }
361
+
362
+ /**
363
+ * Get all runs from a paragraph including those in revisions
364
+ */
365
+ getAllRunsFromParagraph(para: Paragraph): Run[] {
366
+ const runs: Run[] = [];
367
+
368
+ try {
369
+ // Get direct runs
370
+ const directRuns = para.getRuns();
371
+ runs.push(...directRuns);
372
+
373
+ // Try to get runs from revisions if available
374
+ const content = para.getContent();
375
+ for (const item of content) {
376
+ if (isRevision(item)) {
377
+ const revisionRuns = item.getRuns();
378
+ runs.push(...revisionRuns);
379
+ }
380
+ }
381
+ } catch (error) {
382
+ log.debug(`Error getting runs from paragraph: ${error}`);
383
+ }
384
+
385
+ return runs;
386
+ }
387
+ }
388
+
389
+ export const styleProcessor = new StyleProcessor();