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,121 @@
1
+ /**
2
+ * Clears Structured Document Tags (SDTs) from a document, unwrapping their content.
3
+ * Ported from Document.ts clearCustom(), clearCustomInTable(),
4
+ * unwrapNestedStructuredDocumentTags(), and sanitizeTableRowExceptions().
5
+ *
6
+ * SDTs are wrappers added by tools like Google Docs that can interfere with
7
+ * document processing. This function removes them while preserving content.
8
+ */
9
+
10
+ import {
11
+ Document,
12
+ Paragraph,
13
+ Table,
14
+ TableCell,
15
+ StructuredDocumentTag,
16
+ } from "docxmlater";
17
+
18
+ // Use any[] for unwrapped body since getBodyElements() may return
19
+ // types beyond Paragraph | Table | StructuredDocumentTag (e.g., TableOfContentsElement)
20
+ type BodyElement = any;
21
+
22
+ /**
23
+ * Removes all SDT wrappers from the document, unwrapping their content
24
+ * into the body elements array. Also sanitizes table row exceptions
25
+ * that would otherwise cause formatting leakage outside the SDT context.
26
+ *
27
+ * Ported from Document.ts clearCustom() (lines 15119-15154)
28
+ */
29
+ export function clearCustom(doc: Document): void {
30
+ const bodyElements = doc.getBodyElements();
31
+ const unwrappedBody: BodyElement[] = [];
32
+
33
+ for (const element of bodyElements) {
34
+ if (element instanceof StructuredDocumentTag) {
35
+ // Unwrap SDT: add its content directly to the body
36
+ const sdtContent = element.getContent();
37
+ for (const item of sdtContent) {
38
+ if (item instanceof Table) {
39
+ // Sanitize tblPrEx from table rows when coming out of SDT
40
+ sanitizeTableRowExceptions(item);
41
+ unwrappedBody.push(item);
42
+ } else if (item instanceof Paragraph) {
43
+ unwrappedBody.push(item);
44
+ } else if (item instanceof StructuredDocumentTag) {
45
+ // Recursively handle nested SDTs
46
+ unwrapNestedSDTs(item, unwrappedBody);
47
+ }
48
+ }
49
+ } else if (element instanceof Table) {
50
+ // Process table: unwrap SDTs inside cells
51
+ clearCustomInTable(element);
52
+ unwrappedBody.push(element);
53
+ } else {
54
+ unwrappedBody.push(element);
55
+ }
56
+ }
57
+
58
+ // Replace body elements with unwrapped content
59
+ doc.setBodyElements(unwrappedBody);
60
+ }
61
+
62
+ /**
63
+ * Sanitizes table property exceptions from all rows in a table.
64
+ * Clears tblPrEx (row-level table property overrides) to prevent formatting
65
+ * from leaking when tables are relocated outside SDT context.
66
+ *
67
+ * Ported from Document.ts sanitizeTableRowExceptions() (lines 15169-15182)
68
+ */
69
+ function sanitizeTableRowExceptions(table: Table): void {
70
+ const rows = table.getRows();
71
+
72
+ for (const row of rows) {
73
+ const exceptions = row.getTablePropertyExceptions();
74
+
75
+ if (exceptions && Object.keys(exceptions).length > 0) {
76
+ row.setTablePropertyExceptions(undefined as any);
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Recursively unwraps nested SDTs, adding their content to the target array.
83
+ *
84
+ * Ported from Document.ts unwrapNestedStructuredDocumentTags() (lines 15188-15202)
85
+ */
86
+ function unwrapNestedSDTs(
87
+ sdt: StructuredDocumentTag,
88
+ targetArray: BodyElement[]
89
+ ): void {
90
+ const content = sdt.getContent();
91
+
92
+ for (const item of content) {
93
+ if (item instanceof Paragraph || item instanceof Table) {
94
+ targetArray.push(item);
95
+ } else if (item instanceof StructuredDocumentTag) {
96
+ unwrapNestedSDTs(item, targetArray);
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Processes a table's cells to clear SDTs inside them.
103
+ *
104
+ * Ported from Document.ts clearCustomInTable() (lines 15209-15233)
105
+ * Note: The original implementation's inner loop body was empty.
106
+ */
107
+ function clearCustomInTable(table: Table): void {
108
+ const rows = table.getRows();
109
+
110
+ for (const row of rows) {
111
+ const cells = row.getCells();
112
+
113
+ for (const cell of cells) {
114
+ if (!(cell instanceof TableCell)) {
115
+ continue;
116
+ }
117
+ // Original implementation iterates cell paragraphs but doesn't modify them.
118
+ // Nested tables in cells are handled separately via getAllTables().
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Functions for checking paragraph context (list context, etc.).
3
+ * These need access to body elements via the Document public API.
4
+ * Ported from docxmlater Document.ts private methods.
5
+ */
6
+
7
+ import { Document, Paragraph, Table, TableCell } from "docxmlater";
8
+
9
+ /**
10
+ * Checks if a non-list paragraph is "within" a list context.
11
+ * A paragraph is within a list context if it has no numbering,
12
+ * and the previous and next list items share the same numId.
13
+ * Ported from Document.ts:8302-8348
14
+ */
15
+ export function isWithinListContext(doc: Document, index: number): boolean {
16
+ const current = doc.getBodyElementAt(index);
17
+ if (!(current instanceof Paragraph)) {
18
+ return false;
19
+ }
20
+
21
+ // If current is a list item, it's not "within" - it IS the list
22
+ const currentNum = current.getNumbering();
23
+ if (currentNum) {
24
+ return false;
25
+ }
26
+
27
+ // Find previous list item (scanning backwards)
28
+ let prevNumId: number | undefined;
29
+ for (let i = index - 1; i >= 0; i--) {
30
+ const el = doc.getBodyElementAt(i);
31
+ if (el instanceof Paragraph) {
32
+ const num = el.getNumbering();
33
+ if (num) {
34
+ prevNumId = num.numId;
35
+ break;
36
+ }
37
+ } else if (el instanceof Table) {
38
+ // Stop at table boundaries
39
+ break;
40
+ }
41
+ }
42
+
43
+ // Find next list item (scanning forwards)
44
+ let nextNumId: number | undefined;
45
+ const count = doc.getBodyElementCount();
46
+ for (let i = index + 1; i < count; i++) {
47
+ const el = doc.getBodyElementAt(i);
48
+ if (el instanceof Paragraph) {
49
+ const num = el.getNumbering();
50
+ if (num) {
51
+ nextNumId = num.numId;
52
+ break;
53
+ }
54
+ } else if (el instanceof Table) {
55
+ // Stop at table boundaries
56
+ break;
57
+ }
58
+ }
59
+
60
+ // Within list context only if both prev and next are same list
61
+ return (
62
+ prevNumId !== undefined &&
63
+ nextNumId !== undefined &&
64
+ prevNumId === nextNumId
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Checks if a paragraph is within a list context inside a table cell.
70
+ * Ported from Document.ts:8359-8389
71
+ */
72
+ export function isWithinListContextInCell(
73
+ cell: TableCell,
74
+ paraIndex: number
75
+ ): boolean {
76
+ const cellParas = cell.getParagraphs();
77
+ const current = cellParas[paraIndex];
78
+ if (!current) return false;
79
+
80
+ // If current is a list item, it's not "within" - it IS the list
81
+ const currentNum = current.getNumbering();
82
+ if (currentNum) {
83
+ return false;
84
+ }
85
+
86
+ // Find previous list item in cell
87
+ let prevNumId: number | undefined;
88
+ for (let i = paraIndex - 1; i >= 0; i--) {
89
+ const para = cellParas[i];
90
+ if (para) {
91
+ const num = para.getNumbering();
92
+ if (num) {
93
+ prevNumId = num.numId;
94
+ break;
95
+ }
96
+ }
97
+ }
98
+
99
+ // Find next list item in cell
100
+ let nextNumId: number | undefined;
101
+ for (let i = paraIndex + 1; i < cellParas.length; i++) {
102
+ const para = cellParas[i];
103
+ if (para) {
104
+ const num = para.getNumbering();
105
+ if (num) {
106
+ nextNumId = num.numId;
107
+ break;
108
+ }
109
+ }
110
+ }
111
+
112
+ return (
113
+ prevNumId !== undefined &&
114
+ nextNumId !== undefined &&
115
+ prevNumId === nextNumId
116
+ );
117
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pure functions for checking image-related paragraph characteristics.
3
+ * Ported from docxmlater Document.ts public/private methods.
4
+ */
5
+
6
+ import { Paragraph, ImageRun, Image, Revision } from "docxmlater";
7
+
8
+ const EMU_PER_PIXEL = 9525; // At 96 DPI
9
+
10
+ /**
11
+ * Checks if an image is "small" (both dimensions < 100 pixels).
12
+ * Ported from Document.ts:8423-8428
13
+ */
14
+ export function isImageSmall(image: Image): boolean {
15
+ const widthPx = image.getWidth() / EMU_PER_PIXEL;
16
+ const heightPx = image.getHeight() / EMU_PER_PIXEL;
17
+ return widthPx < 100 && heightPx < 100;
18
+ }
19
+
20
+ /**
21
+ * Checks if a paragraph contains a small image (< 100x100 pixels).
22
+ * Ported from Document.ts:8448-8453
23
+ */
24
+ export function isSmallImageParagraph(para: Paragraph): boolean {
25
+ const imageRun = getImageRunFromParagraph(para);
26
+ if (!imageRun) return false;
27
+ const image = imageRun.getImageElement();
28
+ return isImageSmall(image);
29
+ }
30
+
31
+ /**
32
+ * Extracts the first ImageRun from a paragraph, including from Revision elements.
33
+ * Ported from Document.ts:8474-8491
34
+ */
35
+ export function getImageRunFromParagraph(para: Paragraph): ImageRun | null {
36
+ const content = para.getContent();
37
+ for (const item of content) {
38
+ if (item instanceof ImageRun) {
39
+ return item;
40
+ }
41
+ // Also check inside Revision objects (tracked changes)
42
+ if (item instanceof Revision) {
43
+ for (const revContent of item.getContent()) {
44
+ if (revContent instanceof ImageRun) {
45
+ return revContent;
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return null;
51
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Pure functions for checking paragraph content characteristics.
3
+ * Ported from docxmlater Document.ts private methods.
4
+ * Most operate only on Paragraph/Run/etc. instances with no Document dependency.
5
+ * Exception: getEffectiveLeftIndent() requires Document to resolve style-inherited indentation.
6
+ */
7
+
8
+ import {
9
+ Document,
10
+ Paragraph,
11
+ Run,
12
+ Hyperlink,
13
+ ImageRun,
14
+ Shape,
15
+ TextBox,
16
+ Field,
17
+ Revision,
18
+ } from "docxmlater";
19
+
20
+ /**
21
+ * Checks if a paragraph is blank (no meaningful content).
22
+ * Ported from Document.ts:8146-8215
23
+ */
24
+ export function isParagraphBlank(para: Paragraph): boolean {
25
+ const content = para.getContent();
26
+
27
+ // No content at all
28
+ if (!content || content.length === 0) {
29
+ return true;
30
+ }
31
+
32
+ // Check all content items
33
+ for (const item of content) {
34
+ // Hyperlinks count as content
35
+ if (item instanceof Hyperlink) {
36
+ return false;
37
+ }
38
+
39
+ // ImageRun (images embedded in runs) count as content
40
+ // IMPORTANT: Check ImageRun BEFORE Run since ImageRun extends Run
41
+ if (item instanceof ImageRun) {
42
+ return false;
43
+ }
44
+
45
+ // Shapes count as content
46
+ if (item instanceof Shape) {
47
+ return false;
48
+ }
49
+
50
+ // TextBox count as content
51
+ if (item instanceof TextBox) {
52
+ return false;
53
+ }
54
+
55
+ // Fields count as content
56
+ if (item instanceof Field) {
57
+ return false;
58
+ }
59
+
60
+ // Revisions (track changes) - check nested content for text and hyperlinks
61
+ if (item instanceof Revision) {
62
+ const revisionText = item.getText().trim();
63
+ if (revisionText !== "") {
64
+ return false;
65
+ }
66
+ // Also check if revision contains hyperlinks (may have empty display text)
67
+ for (const revContent of item.getContent()) {
68
+ if (revContent instanceof Hyperlink) {
69
+ return false;
70
+ }
71
+ }
72
+ continue; // Already checked, move to next item
73
+ }
74
+
75
+ // Check runs for non-whitespace text
76
+ if ((item as any).getText) {
77
+ const text = (item as any).getText().trim();
78
+ if (text !== "") {
79
+ return false;
80
+ }
81
+ }
82
+ }
83
+
84
+ // Check for bookmarks
85
+ if (
86
+ para.getBookmarksStart().length > 0 ||
87
+ para.getBookmarksEnd().length > 0
88
+ ) {
89
+ return false;
90
+ }
91
+
92
+ return true;
93
+ }
94
+
95
+ /**
96
+ * Checks if a paragraph starts with bold text and has a colon within the first 55 characters.
97
+ * Examples: "Note:", "Warning:", "Note: This can include the following:"
98
+ * Ported from Document.ts:8267-8287
99
+ */
100
+ export function startsWithBoldColon(para: Paragraph): boolean {
101
+ const content = para.getContent();
102
+ if (!content || content.length === 0) return false;
103
+
104
+ // Get first content item that is a Run (skip any non-Run items at start)
105
+ const firstRun = content.find((item) => item instanceof Run) as
106
+ | Run
107
+ | undefined;
108
+ if (!firstRun) return false;
109
+
110
+ // Check if first run is bold
111
+ const formatting = firstRun.getFormatting();
112
+ if (!formatting.bold) return false;
113
+
114
+ // Check if colon exists within first 55 characters of paragraph text
115
+ const fullText = para.getText();
116
+ if (!fullText) return false;
117
+
118
+ const first55 = fullText.substring(0, 55);
119
+ return first55.includes(":");
120
+ }
121
+
122
+ /**
123
+ * Checks if a paragraph is centered bold text (all text runs are bold and centered).
124
+ * Ported from Document.ts:8499-8523
125
+ */
126
+ export function isCenteredBoldText(para: Paragraph): boolean {
127
+ if (para.getAlignment() !== "center") return false;
128
+
129
+ const content = para.getContent();
130
+ if (!content || content.length === 0) return false;
131
+
132
+ let hasTextRun = false;
133
+
134
+ for (const item of content) {
135
+ if (item instanceof Run) {
136
+ const text = item.getText();
137
+ if (text && text.trim() !== "") {
138
+ hasTextRun = true;
139
+ const formatting = item.getFormatting();
140
+ if (!formatting.bold) return false;
141
+ }
142
+ }
143
+ }
144
+
145
+ return hasTextRun;
146
+ }
147
+
148
+ /**
149
+ * Checks if a paragraph contains text but no media elements (images, shapes, textboxes).
150
+ * Ported from Document.ts:8531-8548
151
+ */
152
+ export function isTextOnlyParagraph(para: Paragraph): boolean {
153
+ if (isParagraphBlank(para)) return false;
154
+
155
+ const content = para.getContent();
156
+ for (const item of content) {
157
+ if (item instanceof ImageRun) return false;
158
+ if (item instanceof Shape) return false;
159
+ if (item instanceof TextBox) return false;
160
+ }
161
+
162
+ const text = para.getText();
163
+ return !!text && text.trim() !== "";
164
+ }
165
+
166
+ /**
167
+ * Detects Table of Contents paragraphs by style.
168
+ * Ported from Document.ts:8397-8401
169
+ */
170
+ export function isTocParagraph(para: Paragraph): boolean {
171
+ const styleId = para.getStyle();
172
+ if (!styleId) return false;
173
+ const lower = styleId.toLowerCase();
174
+ return /^toc\s?\d$/i.test(lower) || lower.startsWith("toc");
175
+ }
176
+
177
+ /**
178
+ * Checks if a paragraph contains a navigation hyperlink
179
+ * (display text starts with "Top of" or "Return to", case insensitive).
180
+ */
181
+ export function hasNavigationHyperlink(para: Paragraph): boolean {
182
+ const content = para.getContent();
183
+ if (!content) return false;
184
+
185
+ for (const item of content) {
186
+ if (item instanceof Hyperlink) {
187
+ const text = item.getText().toLowerCase().trim();
188
+ if (text.startsWith("top of") || text.startsWith("return to")) {
189
+ return true;
190
+ }
191
+ }
192
+ }
193
+ return false;
194
+ }
195
+
196
+ /**
197
+ * Get the effective left indentation of a paragraph, resolving style-inherited
198
+ * indentation when the paragraph has no direct indentation set.
199
+ *
200
+ * In OOXML, a paragraph's visual indentation can come from:
201
+ * 1. Direct paragraph properties (<w:pPr><w:ind w:left="..."/>)
202
+ * 2. The paragraph's style definition (e.g., ListParagraph defines w:left="720")
203
+ *
204
+ * Paragraph.getFormatting()?.indentation?.left only returns (1).
205
+ * This helper also checks (2) via the Document's style definitions.
206
+ *
207
+ * NOTE: docxmlater currently parses style-level indentation as an empty object
208
+ * (keys exist but values are undefined), so we also check for known indented
209
+ * styles like ListParagraph which defines w:ind w:left="720" in its XML.
210
+ */
211
+ export function getEffectiveLeftIndent(para: Paragraph, doc: Document): number {
212
+ // 1. Check direct paragraph indentation
213
+ const directIndent = para.getFormatting()?.indentation?.left;
214
+ if (directIndent && directIndent > 0) return directIndent;
215
+
216
+ // 2. Check style-inherited indentation
217
+ const styleId = para.getStyle();
218
+ if (styleId) {
219
+ const style = doc.getStyle(styleId);
220
+ if (style) {
221
+ const stylePf = style.getParagraphFormatting();
222
+ const styleIndent = stylePf?.indentation?.left;
223
+ if (styleIndent && styleIndent > 0) return styleIndent;
224
+
225
+ // Fallback: docxmlater may create an indentation object with undefined values
226
+ // for styles that DO have w:ind in their XML (e.g., ListParagraph w:left="720").
227
+ // If the style has an indentation object at all, it likely defines indentation.
228
+ // ListParagraph is the most common case: Word's built-in style with w:left="720".
229
+ if (stylePf?.indentation && styleId === "ListParagraph") {
230
+ return 720; // Standard ListParagraph indentation
231
+ }
232
+ }
233
+ }
234
+
235
+ return 0;
236
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Removes blank paragraphs between consecutive list items.
3
+ * Ported from Document.ts removeBlanksBetweenListItems() (lines 7159-7216)
4
+ */
5
+
6
+ import { Document, Paragraph, Table } from "docxmlater";
7
+ import { isParagraphBlank } from "./paragraphChecks";
8
+ import { tableHasNestedContent } from "./tableGuards";
9
+
10
+ /**
11
+ * Removes blank paragraphs between consecutive list items in both
12
+ * body elements and table cells.
13
+ *
14
+ * This should be called AFTER list normalization (e.g., after converting
15
+ * typed list prefixes like "1.", "a." to proper Word numbering) to clean up
16
+ * blanks that were missed because items didn't have Word numbering yet.
17
+ *
18
+ * @param doc - The document to process
19
+ * @returns Number of blank paragraphs removed
20
+ */
21
+ export function removeBlanksBetweenListItems(doc: Document): number {
22
+ let removed = 0;
23
+
24
+ // Process table cells
25
+ // Skip tables with nested content to avoid violating ECMA-376 cell structure
26
+ for (const table of doc.getAllTables()) {
27
+ if (tableHasNestedContent(table)) continue;
28
+ for (const row of table.getRows()) {
29
+ for (const cell of row.getCells()) {
30
+ let cellParas = cell.getParagraphs();
31
+ for (let ci = 1; ci < cellParas.length - 1; ci++) {
32
+ const prev = cellParas[ci - 1];
33
+ const current = cellParas[ci];
34
+ const next = cellParas[ci + 1];
35
+
36
+ if (!current || !isParagraphBlank(current)) continue;
37
+
38
+ const prevNumbering = prev?.getNumbering();
39
+ const nextNumbering = next?.getNumbering();
40
+
41
+ if (prevNumbering && nextNumbering) {
42
+ cell.removeParagraph(ci);
43
+ ci--;
44
+ removed++;
45
+ cellParas = cell.getParagraphs();
46
+ } else if (prevNumbering && !nextNumbering) {
47
+ // Also remove blank between list item and indented continuation
48
+ const nextIndent = next?.getFormatting()?.indentation?.left || 0;
49
+ if (nextIndent > 0) {
50
+ cell.removeParagraph(ci);
51
+ ci--;
52
+ removed++;
53
+ cellParas = cell.getParagraphs();
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ // Process body-level list items
62
+ for (let bi = 1; bi < doc.getBodyElementCount() - 1; bi++) {
63
+ const prev = doc.getBodyElementAt(bi - 1);
64
+ const current = doc.getBodyElementAt(bi);
65
+ const next = doc.getBodyElementAt(bi + 1);
66
+
67
+ if (!(current instanceof Paragraph) || !isParagraphBlank(current)) continue;
68
+ if (prev instanceof Table || next instanceof Table) continue;
69
+
70
+ if (prev instanceof Paragraph && next instanceof Paragraph) {
71
+ const prevNumbering = prev.getNumbering();
72
+ const nextNumbering = next.getNumbering();
73
+
74
+ if (prevNumbering && nextNumbering) {
75
+ doc.removeBodyElementAt(bi);
76
+ bi--;
77
+ removed++;
78
+ } else if (prevNumbering && !nextNumbering) {
79
+ // Also remove blank between list item and indented continuation
80
+ const nextIndent = next.getFormatting()?.indentation?.left || 0;
81
+ if (nextIndent > 0) {
82
+ doc.removeBodyElementAt(bi);
83
+ bi--;
84
+ removed++;
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ return removed;
91
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Removes trailing blank paragraphs from all table cells in the document.
3
+ * Ported from Document.ts removeTrailingBlanksInTableCells() (lines 5485-5502)
4
+ */
5
+
6
+ import { Document } from "docxmlater";
7
+
8
+ /**
9
+ * Removes trailing blank paragraphs from all table cells.
10
+ * A trailing blank is a blank paragraph at the END of a cell, after all content.
11
+ * Respects the ECMA-376 requirement that each cell must have at least one paragraph.
12
+ *
13
+ * @param doc - The document to process
14
+ * @param options.ignorePreserveFlag - If true, removes trailing blanks even if marked preserved (default: true)
15
+ * @returns Total number of paragraphs removed across all cells
16
+ */
17
+ export function removeTrailingBlanksInTableCells(
18
+ doc: Document,
19
+ options?: { ignorePreserveFlag?: boolean }
20
+ ): number {
21
+ let totalRemoved = 0;
22
+ const ignorePreserve = options?.ignorePreserveFlag ?? true;
23
+
24
+ for (const table of doc.getAllTables()) {
25
+ for (const row of table.getRows()) {
26
+ for (const cell of row.getCells()) {
27
+ totalRemoved += cell.removeTrailingBlankParagraphs({
28
+ ignorePreserveFlag: ignorePreserve,
29
+ });
30
+ }
31
+ }
32
+ }
33
+
34
+ return totalRemoved;
35
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Guard functions for skipping tables with nested content during blank line processing.
3
+ * Cells containing nested tables have rawNestedContent that must be preserved —
4
+ * removing paragraphs from these cells can violate ECMA-376 (every w:tc must end with w:p).
5
+ */
6
+
7
+ import { Table } from "docxmlater";
8
+
9
+ /**
10
+ * Returns true if the table contains any cells with nested tables.
11
+ * Tables with nested content should be skipped during blank line
12
+ * removal to avoid corrupting the cell structure.
13
+ */
14
+ export function tableHasNestedContent(table: Table): boolean {
15
+ for (const row of table.getRows()) {
16
+ for (const cell of row.getCells()) {
17
+ if (cell.hasNestedTables()) return true;
18
+ }
19
+ }
20
+ return false;
21
+ }