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,496 @@
1
+ # Hyperlink Management in .docx Files: Technical Documentation
2
+
3
+ ## Architecture Overview
4
+
5
+ In OpenXML, hyperlinks use a two-part reference system:
6
+
7
+ - `<w:hyperlink>` element in document.xml contains an `r:id` attribute
8
+ - `r:id` references a relationship entry in document.xml.rels with the target URL
9
+
10
+ ```xml
11
+ <!-- document.xml -->
12
+ <w:hyperlink r:id="rId4" w:history="1">
13
+ <w:r>
14
+ <w:t>Link text</w:t>
15
+ </w:r>
16
+ </w:hyperlink>
17
+
18
+ <!-- document.xml.rels -->
19
+ <Relationship
20
+ Id="rId4"
21
+ Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
22
+ Target="https://example.com"
23
+ TargetMode="External"/>
24
+ ```
25
+
26
+ ## TypeScript Implementation
27
+
28
+ ### Basic Hyperlink Manager
29
+
30
+ ```typescript
31
+ import { Document, ExternalHyperlink, InternalHyperlink, TextRun, Paragraph } from 'docx';
32
+ import JSZip from 'jszip';
33
+ import { parseStringPromise, Builder } from 'xml2js';
34
+
35
+ interface HyperlinkData {
36
+ id: string;
37
+ type: 'external' | 'internal';
38
+ target: string;
39
+ text: string;
40
+ created: Date;
41
+ modified?: Date;
42
+ }
43
+
44
+ export class HyperlinkManager {
45
+ private relationships = new Map<string, HyperlinkData>();
46
+ private nextId = 1;
47
+
48
+ createExternal(text: string, url: string): ExternalHyperlink {
49
+ const id = `rId${this.nextId++}`;
50
+
51
+ this.relationships.set(id, {
52
+ id,
53
+ type: 'external',
54
+ target: url,
55
+ text,
56
+ created: new Date(),
57
+ });
58
+
59
+ return new ExternalHyperlink({
60
+ link: url,
61
+ children: [
62
+ new TextRun({
63
+ text,
64
+ style: 'Hyperlink',
65
+ }),
66
+ ],
67
+ });
68
+ }
69
+
70
+ createInternal(text: string, bookmarkId: string): InternalHyperlink {
71
+ const id = `rId${this.nextId++}`;
72
+
73
+ this.relationships.set(id, {
74
+ id,
75
+ type: 'internal',
76
+ target: `#${bookmarkId}`,
77
+ text,
78
+ created: new Date(),
79
+ });
80
+
81
+ return new InternalHyperlink({
82
+ anchor: bookmarkId,
83
+ children: [
84
+ new TextRun({
85
+ text,
86
+ style: 'Hyperlink',
87
+ }),
88
+ ],
89
+ });
90
+ }
91
+
92
+ getRelationships(): HyperlinkData[] {
93
+ return Array.from(this.relationships.values());
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### Modifying Existing Hyperlinks
99
+
100
+ ```typescript
101
+ export class HyperlinkModifier {
102
+ async updateUrl(documentPath: string, oldUrl: string, newUrl: string): Promise<UpdateResult> {
103
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
104
+ const changes: Change[] = [];
105
+
106
+ // Parse relationships
107
+ const relsFile = zip.file('word/_rels/document.xml.rels');
108
+ if (!relsFile) throw new Error('Relationships file not found');
109
+
110
+ const relsXml = await relsFile.async('string');
111
+ const parsed = await parseStringPromise(relsXml);
112
+
113
+ // Update matching relationships
114
+ const relationships = parsed.Relationships.Relationship;
115
+ for (const rel of relationships) {
116
+ if (rel.$.Target === oldUrl) {
117
+ changes.push({
118
+ relationshipId: rel.$.Id,
119
+ oldTarget: rel.$.Target,
120
+ newTarget: newUrl,
121
+ });
122
+ rel.$.Target = newUrl;
123
+ }
124
+ }
125
+
126
+ // Save changes
127
+ const builder = new Builder();
128
+ const updatedXml = builder.buildObject(parsed);
129
+ zip.file('word/_rels/document.xml.rels', updatedXml);
130
+
131
+ const buffer = await zip.generateAsync({ type: 'nodebuffer' });
132
+ await fs.writeFile(documentPath, buffer);
133
+
134
+ return { success: true, changes };
135
+ }
136
+
137
+ async updateText(documentPath: string, relationshipId: string, newText: string): Promise<void> {
138
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
139
+
140
+ const docFile = zip.file('word/document.xml');
141
+ if (!docFile) throw new Error('Document file not found');
142
+
143
+ const docXml = await docFile.async('string');
144
+ const parsed = await parseStringPromise(docXml);
145
+
146
+ // Find and update hyperlink text
147
+ this.findAndUpdateHyperlink(parsed, relationshipId, newText);
148
+
149
+ const builder = new Builder();
150
+ zip.file('word/document.xml', builder.buildObject(parsed));
151
+
152
+ const buffer = await zip.generateAsync({ type: 'nodebuffer' });
153
+ await fs.writeFile(documentPath, buffer);
154
+ }
155
+
156
+ private findAndUpdateHyperlink(doc: any, relationshipId: string, newText: string): void {
157
+ const traverse = (node: any): void => {
158
+ if (!node || typeof node !== 'object') return;
159
+
160
+ if (node['w:hyperlink']) {
161
+ const hyperlinks = Array.isArray(node['w:hyperlink'])
162
+ ? node['w:hyperlink']
163
+ : [node['w:hyperlink']];
164
+
165
+ for (const hyperlink of hyperlinks) {
166
+ if (hyperlink.$?.['r:id'] === relationshipId) {
167
+ // Update text in runs
168
+ const runs = hyperlink['w:r'] || [];
169
+ for (const run of runs) {
170
+ if (run['w:t']) {
171
+ run['w:t'] = [newText];
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ Object.values(node).forEach((child) => traverse(child));
179
+ };
180
+
181
+ traverse(doc);
182
+ }
183
+ }
184
+ ```
185
+
186
+ ## Change Tracking Implementation
187
+
188
+ Since `docx` library doesn't support native Word track changes, implement custom tracking:
189
+
190
+ ```typescript
191
+ interface TrackedChange {
192
+ id: string;
193
+ type: 'insert' | 'delete' | 'modify';
194
+ elementType: 'hyperlink';
195
+ author: string;
196
+ timestamp: Date;
197
+ before?: HyperlinkData;
198
+ after?: HyperlinkData;
199
+ }
200
+
201
+ export class ChangeTracker {
202
+ private changes: TrackedChange[] = [];
203
+ private snapshots = new Map<string, HyperlinkData>();
204
+
205
+ async initialize(documentPath: string): Promise<void> {
206
+ const hyperlinks = await this.extractHyperlinks(documentPath);
207
+ hyperlinks.forEach((h) => this.snapshots.set(h.id, { ...h }));
208
+ }
209
+
210
+ track(
211
+ operation: 'insert' | 'delete' | 'modify',
212
+ data: {
213
+ id: string;
214
+ before?: HyperlinkData;
215
+ after?: HyperlinkData;
216
+ }
217
+ ): void {
218
+ this.changes.push({
219
+ id: crypto.randomUUID(),
220
+ type: operation,
221
+ elementType: 'hyperlink',
222
+ author: process.env.USER || 'unknown',
223
+ timestamp: new Date(),
224
+ before: data.before,
225
+ after: data.after,
226
+ });
227
+
228
+ // Update snapshot
229
+ if (operation === 'delete') {
230
+ this.snapshots.delete(data.id);
231
+ } else if (data.after) {
232
+ this.snapshots.set(data.id, data.after);
233
+ }
234
+ }
235
+
236
+ getChanges(): TrackedChange[] {
237
+ return [...this.changes];
238
+ }
239
+
240
+ async exportReport(outputPath: string): Promise<void> {
241
+ const doc = new Document({
242
+ sections: [
243
+ {
244
+ children: [
245
+ new Paragraph({
246
+ text: 'Change History',
247
+ heading: HeadingLevel.HEADING_1,
248
+ }),
249
+ ...this.changes.map(
250
+ (change) =>
251
+ new Paragraph({
252
+ text: `${change.timestamp.toISOString()}: ${change.type} by ${change.author}`,
253
+ bullet: { level: 0 },
254
+ })
255
+ ),
256
+ ],
257
+ },
258
+ ],
259
+ });
260
+
261
+ const buffer = await Packer.toBuffer(doc);
262
+ await fs.writeFile(outputPath, buffer);
263
+ }
264
+ }
265
+ ```
266
+
267
+ ## Helper Utilities
268
+
269
+ ```typescript
270
+ export class HyperlinkUtils {
271
+ // Remove orphaned relationships
272
+ static async cleanupOrphaned(documentPath: string): Promise<number> {
273
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
274
+
275
+ // Get used relationship IDs from document
276
+ const docXml = await zip.file('word/document.xml')?.async('string');
277
+ const usedIds = this.extractUsedRelationshipIds(docXml);
278
+
279
+ // Get all relationship IDs
280
+ const relsXml = await zip.file('word/_rels/document.xml.rels')?.async('string');
281
+ const parsed = await parseStringPromise(relsXml);
282
+
283
+ // Remove orphaned
284
+ let removed = 0;
285
+ parsed.Relationships.Relationship = parsed.Relationships.Relationship.filter((rel: any) => {
286
+ if (!usedIds.has(rel.$.Id)) {
287
+ removed++;
288
+ return false;
289
+ }
290
+ return true;
291
+ });
292
+
293
+ if (removed > 0) {
294
+ const builder = new Builder();
295
+ zip.file('word/_rels/document.xml.rels', builder.buildObject(parsed));
296
+ const buffer = await zip.generateAsync({ type: 'nodebuffer' });
297
+ await fs.writeFile(documentPath, buffer);
298
+ }
299
+
300
+ return removed;
301
+ }
302
+
303
+ // Consolidate duplicate URLs
304
+ static async consolidateDuplicates(documentPath: string): Promise<ConsolidationResult> {
305
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
306
+ const relsXml = await zip.file('word/_rels/document.xml.rels')?.async('string');
307
+ const parsed = await parseStringPromise(relsXml);
308
+
309
+ const urlMap = new Map<string, string[]>();
310
+
311
+ // Group by URL
312
+ for (const rel of parsed.Relationships.Relationship) {
313
+ const url = rel.$.Target;
314
+ const ids = urlMap.get(url) || [];
315
+ ids.push(rel.$.Id);
316
+ urlMap.set(url, ids);
317
+ }
318
+
319
+ const consolidations: Array<{ url: string; kept: string; removed: string[] }> = [];
320
+
321
+ // Process duplicates
322
+ for (const [url, ids] of urlMap.entries()) {
323
+ if (ids.length > 1) {
324
+ const [kept, ...removed] = ids;
325
+ consolidations.push({ url, kept, removed });
326
+
327
+ // Update document references
328
+ await this.updateReferences(zip, removed, kept);
329
+
330
+ // Remove duplicate relationships
331
+ parsed.Relationships.Relationship = parsed.Relationships.Relationship.filter(
332
+ (rel: any) => !removed.includes(rel.$.Id)
333
+ );
334
+ }
335
+ }
336
+
337
+ if (consolidations.length > 0) {
338
+ const builder = new Builder();
339
+ zip.file('word/_rels/document.xml.rels', builder.buildObject(parsed));
340
+ const buffer = await zip.generateAsync({ type: 'nodebuffer' });
341
+ await fs.writeFile(documentPath, buffer);
342
+ }
343
+
344
+ return { consolidations, saved: consolidations.length };
345
+ }
346
+
347
+ // Validate all hyperlinks
348
+ static async validate(documentPath: string): Promise<ValidationResult> {
349
+ const issues: ValidationIssue[] = [];
350
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
351
+
352
+ const relsXml = await zip.file('word/_rels/document.xml.rels')?.async('string');
353
+ const parsed = await parseStringPromise(relsXml);
354
+
355
+ for (const rel of parsed.Relationships.Relationship) {
356
+ if (rel.$.Type?.includes('hyperlink')) {
357
+ // Check URL validity
358
+ try {
359
+ new URL(rel.$.Target);
360
+ } catch {
361
+ issues.push({
362
+ id: rel.$.Id,
363
+ type: 'invalid_url',
364
+ message: `Invalid URL: ${rel.$.Target}`,
365
+ });
366
+ }
367
+
368
+ // Check for broken internal links
369
+ if (rel.$.Target?.startsWith('#')) {
370
+ const bookmarkId = rel.$.Target.substring(1);
371
+ if (!(await this.bookmarkExists(zip, bookmarkId))) {
372
+ issues.push({
373
+ id: rel.$.Id,
374
+ type: 'broken_bookmark',
375
+ message: `Bookmark not found: ${bookmarkId}`,
376
+ });
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ return {
383
+ valid: issues.length === 0,
384
+ issues,
385
+ };
386
+ }
387
+ }
388
+ ```
389
+
390
+ ## Performance Optimization
391
+
392
+ ```typescript
393
+ export class HyperlinkProcessor {
394
+ private cache = new Map<string, HyperlinkData>();
395
+
396
+ async processBatch(documentPath: string, operations: Operation[]): Promise<BatchResult> {
397
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
398
+ const results: OperationResult[] = [];
399
+
400
+ // Batch all changes before writing
401
+ for (const op of operations) {
402
+ try {
403
+ await this.applyOperation(zip, op);
404
+ results.push({ success: true, operation: op });
405
+ } catch (error) {
406
+ results.push({
407
+ success: false,
408
+ operation: op,
409
+ error: error.message,
410
+ });
411
+ }
412
+ }
413
+
414
+ // Write once
415
+ const buffer = await zip.generateAsync({ type: 'nodebuffer' });
416
+ await fs.writeFile(documentPath, buffer);
417
+
418
+ return {
419
+ total: operations.length,
420
+ successful: results.filter((r) => r.success).length,
421
+ failed: results.filter((r) => !r.success).length,
422
+ results,
423
+ };
424
+ }
425
+
426
+ // Stream processing for large documents
427
+ async *streamHyperlinks(documentPath: string): AsyncGenerator<HyperlinkData> {
428
+ const zip = await JSZip.loadAsync(await fs.readFile(documentPath));
429
+ const docXml = await zip.file('word/document.xml')?.async('string');
430
+
431
+ // Parse in chunks to avoid memory issues
432
+ const chunks = this.splitIntoChunks(docXml, 1000);
433
+
434
+ for (const chunk of chunks) {
435
+ const hyperlinks = this.extractHyperlinksFromChunk(chunk);
436
+ for (const hyperlink of hyperlinks) {
437
+ yield hyperlink;
438
+ }
439
+ }
440
+ }
441
+ }
442
+ ```
443
+
444
+ ## Best Practices Summary
445
+
446
+ 1. **Always preserve relationship IDs** when modifying hyperlinks
447
+ 2. **Track all changes** for audit trails
448
+ 3. **Validate URLs** before saving
449
+ 4. **Batch operations** to minimize file I/O
450
+ 5. **Cache frequently accessed data** for performance
451
+ 6. **Use TypeScript strict mode** for type safety
452
+
453
+ ## Limitations
454
+
455
+ - `docx` library cannot create native Word tracked changes
456
+ - Real-time collaboration requires server infrastructure
457
+ - Complex field codes require low-level XML manipulation
458
+ - Digital signatures need special handling after modifications
459
+
460
+ ## Complete Example
461
+
462
+ ```typescript
463
+ async function processDocument(inputPath: string, outputPath: string) {
464
+ const manager = new HyperlinkManager();
465
+ const modifier = new HyperlinkModifier();
466
+ const tracker = new ChangeTracker();
467
+
468
+ // Initialize tracking
469
+ await tracker.initialize(inputPath);
470
+
471
+ // Update URL
472
+ const result = await modifier.updateUrl(inputPath, 'http://old-url.com', 'https://new-url.com');
473
+
474
+ // Track changes
475
+ result.changes.forEach((change) => {
476
+ tracker.track('modify', {
477
+ id: change.relationshipId,
478
+ before: { target: change.oldTarget },
479
+ after: { target: change.newTarget },
480
+ });
481
+ });
482
+
483
+ // Cleanup orphaned relationships
484
+ const removed = await HyperlinkUtils.cleanupOrphaned(inputPath);
485
+ console.log(`Removed ${removed} orphaned relationships`);
486
+
487
+ // Validate
488
+ const validation = await HyperlinkUtils.validate(inputPath);
489
+ if (!validation.valid) {
490
+ console.error('Validation issues:', validation.issues);
491
+ }
492
+
493
+ // Export change report
494
+ await tracker.exportReport(outputPath);
495
+ }
496
+ ```