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,438 @@
1
+ import { Button } from '@/components/common/Button';
2
+ import { useSession } from '@/contexts/SessionContext';
3
+ import { ReplacementRule } from '@/types/session';
4
+ import { cn } from '@/utils/cn';
5
+ import { validateUrlScheme } from '@/utils/urlHelpers';
6
+ import { AnimatePresence, motion } from 'framer-motion';
7
+ import { Check, Plus, Trash2 } from 'lucide-react';
8
+ import { useEffect, useState } from 'react';
9
+
10
+ interface HyperlinkRule {
11
+ id: string;
12
+ enabled: boolean;
13
+ oldHyperlink: string;
14
+ newContentId: string;
15
+ }
16
+
17
+ interface TextRule {
18
+ id: string;
19
+ enabled: boolean;
20
+ oldText: string;
21
+ newText: string;
22
+ }
23
+
24
+ interface ReplacementsTabProps {
25
+ sessionId?: string;
26
+ }
27
+
28
+ export function ReplacementsTab({ sessionId }: ReplacementsTabProps) {
29
+ const { sessions, updateSessionReplacements } = useSession();
30
+ const [replaceHyperlinksEnabled, setReplaceHyperlinksEnabled] = useState(false);
31
+ const [replaceTextEnabled, setReplaceTextEnabled] = useState(false);
32
+ const [hyperlinkRules, setHyperlinkRules] = useState<HyperlinkRule[]>([]);
33
+ const [textRules, setTextRules] = useState<TextRule[]>([]);
34
+ // SECURITY: Track URL validation errors for user feedback
35
+ const [urlValidationErrors, setUrlValidationErrors] = useState<Record<string, string>>({});
36
+
37
+ // Load existing replacements from session on mount
38
+ useEffect(() => {
39
+ if (!sessionId) return;
40
+
41
+ const session = sessions.find((s) => s.id === sessionId);
42
+ if (session?.replacements) {
43
+ // Convert ReplacementRule[] to HyperlinkRule[] and TextRule[]
44
+ const hyperlinks: HyperlinkRule[] = [];
45
+ const texts: TextRule[] = [];
46
+
47
+ session.replacements.forEach((rule) => {
48
+ if (rule.type === 'hyperlink') {
49
+ hyperlinks.push({
50
+ id: rule.id,
51
+ enabled: rule.enabled,
52
+ oldHyperlink: rule.pattern,
53
+ newContentId: rule.replacement,
54
+ });
55
+ } else if (rule.type === 'text') {
56
+ texts.push({
57
+ id: rule.id,
58
+ enabled: rule.enabled,
59
+ oldText: rule.pattern,
60
+ newText: rule.replacement,
61
+ });
62
+ }
63
+ });
64
+
65
+ setHyperlinkRules(hyperlinks);
66
+ setTextRules(texts);
67
+ setReplaceHyperlinksEnabled(hyperlinks.some((r) => r.enabled));
68
+ setReplaceTextEnabled(texts.some((r) => r.enabled));
69
+ }
70
+ }, [sessionId, sessions]);
71
+
72
+ // Save changes to session whenever rules change
73
+ const saveRulesToSession = (hyperlinks: HyperlinkRule[], texts: TextRule[]) => {
74
+ if (!sessionId) return;
75
+
76
+ // Convert back to ReplacementRule[]
77
+ const replacements: ReplacementRule[] = [
78
+ ...hyperlinks.map((h) => ({
79
+ id: h.id,
80
+ enabled: h.enabled,
81
+ type: 'hyperlink' as const,
82
+ pattern: h.oldHyperlink,
83
+ replacement: h.newContentId,
84
+ })),
85
+ ...texts.map((t) => ({
86
+ id: t.id,
87
+ enabled: t.enabled,
88
+ type: 'text' as const,
89
+ pattern: t.oldText,
90
+ replacement: t.newText,
91
+ })),
92
+ ];
93
+
94
+ updateSessionReplacements(sessionId, replacements);
95
+ };
96
+
97
+ const addHyperlinkRule = () => {
98
+ const newRule: HyperlinkRule = {
99
+ id: `hyperlink-${Date.now()}`,
100
+ enabled: true,
101
+ oldHyperlink: '',
102
+ newContentId: '',
103
+ };
104
+ const updatedRules = [...hyperlinkRules, newRule];
105
+ setHyperlinkRules(updatedRules);
106
+ saveRulesToSession(updatedRules, textRules);
107
+ };
108
+
109
+ const updateHyperlinkRule = (id: string, updates: Partial<HyperlinkRule>) => {
110
+ // SECURITY FIX: Validate URL scheme for newContentId to prevent XSS-like attacks
111
+ if (updates.newContentId !== undefined) {
112
+ const validation = validateUrlScheme(updates.newContentId);
113
+
114
+ if (!validation.valid) {
115
+ // Store validation error to show to user
116
+ setUrlValidationErrors((prev) => ({
117
+ ...prev,
118
+ [id]: validation.error || 'Invalid URL',
119
+ }));
120
+
121
+ // Still update the field value (for user to see and correct)
122
+ // but don't save to session until valid
123
+ const updatedRules = hyperlinkRules.map((rule) =>
124
+ rule.id === id ? { ...rule, ...updates } : rule
125
+ );
126
+ setHyperlinkRules(updatedRules);
127
+ return; // Don't save to session with invalid URL
128
+ } else {
129
+ // Clear any previous validation error for this field
130
+ setUrlValidationErrors((prev) => {
131
+ const newErrors = { ...prev };
132
+ delete newErrors[id];
133
+ return newErrors;
134
+ });
135
+ }
136
+ }
137
+
138
+ // If validation passed or update doesn't include newContentId, proceed normally
139
+ const updatedRules = hyperlinkRules.map((rule) =>
140
+ rule.id === id ? { ...rule, ...updates } : rule
141
+ );
142
+ setHyperlinkRules(updatedRules);
143
+ saveRulesToSession(updatedRules, textRules);
144
+ };
145
+
146
+ const removeHyperlinkRule = (id: string) => {
147
+ const updatedRules = hyperlinkRules.filter((rule) => rule.id !== id);
148
+ setHyperlinkRules(updatedRules);
149
+ saveRulesToSession(updatedRules, textRules);
150
+ };
151
+
152
+ const addTextRule = () => {
153
+ const newRule: TextRule = {
154
+ id: `text-${Date.now()}`,
155
+ enabled: true,
156
+ oldText: '',
157
+ newText: '',
158
+ };
159
+ const updatedRules = [...textRules, newRule];
160
+ setTextRules(updatedRules);
161
+ saveRulesToSession(hyperlinkRules, updatedRules);
162
+ };
163
+
164
+ const updateTextRule = (id: string, updates: Partial<TextRule>) => {
165
+ const updatedRules = textRules.map((rule) => (rule.id === id ? { ...rule, ...updates } : rule));
166
+ setTextRules(updatedRules);
167
+ saveRulesToSession(hyperlinkRules, updatedRules);
168
+ };
169
+
170
+ const removeTextRule = (id: string) => {
171
+ const updatedRules = textRules.filter((rule) => rule.id !== id);
172
+ setTextRules(updatedRules);
173
+ saveRulesToSession(hyperlinkRules, updatedRules);
174
+ };
175
+
176
+ return (
177
+ <div className="space-y-6">
178
+ {/* Hyperlink Replacements */}
179
+ <div className="space-y-4">
180
+ <div className="flex items-center justify-between">
181
+ <div className="flex items-center gap-3">
182
+ <button
183
+ onClick={() => setReplaceHyperlinksEnabled(!replaceHyperlinksEnabled)}
184
+ className={cn(
185
+ 'relative w-12 h-6 rounded-full transition-colors',
186
+ replaceHyperlinksEnabled ? 'bg-primary' : 'bg-muted'
187
+ )}
188
+ >
189
+ <motion.div
190
+ className="absolute top-1 w-4 h-4 bg-white rounded-full shadow-xs"
191
+ animate={{ x: replaceHyperlinksEnabled ? 24 : 2 }}
192
+ transition={{ type: 'spring', stiffness: 500, damping: 30 }}
193
+ />
194
+ </button>
195
+ <div>
196
+ <h3 className="font-medium">Replace Hyperlinks</h3>
197
+ <p className="text-sm text-muted-foreground">
198
+ Replace hyperlink targets based on rules
199
+ </p>
200
+ </div>
201
+ </div>
202
+ <Button
203
+ size="sm"
204
+ variant="outline"
205
+ icon={<Plus className="w-4 h-4" />}
206
+ onClick={addHyperlinkRule}
207
+ disabled={!replaceHyperlinksEnabled}
208
+ >
209
+ Add Rule
210
+ </Button>
211
+ </div>
212
+
213
+ {hyperlinkRules.length > 0 && (
214
+ <div className="border border-border rounded-lg overflow-hidden">
215
+ <table className="w-full">
216
+ <thead className="bg-muted/50">
217
+ <tr>
218
+ <th className="px-4 py-3 text-left text-sm font-medium w-12">Enable</th>
219
+ <th className="px-4 py-3 text-left text-sm font-medium">Old Hyperlink Text</th>
220
+ <th className="px-4 py-3 text-left text-sm font-medium">New Content ID</th>
221
+ <th className="px-4 py-3 text-left text-sm font-medium w-12"></th>
222
+ </tr>
223
+ </thead>
224
+ <tbody>
225
+ <AnimatePresence>
226
+ {hyperlinkRules.map((rule) => (
227
+ <motion.tr
228
+ key={rule.id}
229
+ initial={{ opacity: 0, y: -10 }}
230
+ animate={{ opacity: 1, y: 0 }}
231
+ exit={{ opacity: 0, x: -20 }}
232
+ className={cn(
233
+ 'border-t border-border',
234
+ !replaceHyperlinksEnabled && 'opacity-50'
235
+ )}
236
+ >
237
+ <td className="px-4 py-3">
238
+ <label className="flex items-center cursor-pointer">
239
+ <div className="relative">
240
+ <input
241
+ type="checkbox"
242
+ checked={rule.enabled}
243
+ onChange={(e) =>
244
+ updateHyperlinkRule(rule.id, { enabled: e.target.checked })
245
+ }
246
+ disabled={!replaceHyperlinksEnabled}
247
+ className="sr-only"
248
+ />
249
+ <div
250
+ className={cn(
251
+ 'w-5 h-5 rounded border-2 flex items-center justify-center transition-all',
252
+ rule.enabled && replaceHyperlinksEnabled
253
+ ? 'bg-primary border-primary checkbox-checked'
254
+ : 'border-border'
255
+ )}
256
+ >
257
+ {rule.enabled && replaceHyperlinksEnabled && (
258
+ <Check className="w-3 h-3 text-primary-foreground checkbox-checkmark" />
259
+ )}
260
+ </div>
261
+ </div>
262
+ </label>
263
+ </td>
264
+ <td className="px-4 py-3">
265
+ <input
266
+ type="text"
267
+ value={rule.oldHyperlink}
268
+ onChange={(e) =>
269
+ updateHyperlinkRule(rule.id, { oldHyperlink: e.target.value })
270
+ }
271
+ disabled={!replaceHyperlinksEnabled}
272
+ placeholder="Enter old hyperlink text"
273
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-muted/30 focus:bg-background transition-colors disabled:opacity-50"
274
+ />
275
+ </td>
276
+ <td className="px-4 py-3">
277
+ <div className="space-y-1">
278
+ <input
279
+ type="text"
280
+ value={rule.newContentId}
281
+ onChange={(e) =>
282
+ updateHyperlinkRule(rule.id, { newContentId: e.target.value })
283
+ }
284
+ disabled={!replaceHyperlinksEnabled}
285
+ placeholder="Enter new content ID"
286
+ className={cn(
287
+ 'w-full px-3 py-1.5 text-sm border rounded-md bg-muted/30 focus:bg-background transition-colors disabled:opacity-50',
288
+ urlValidationErrors[rule.id]
289
+ ? 'border-red-500 focus:border-red-500'
290
+ : 'border-border'
291
+ )}
292
+ />
293
+ {urlValidationErrors[rule.id] && (
294
+ <p className="text-xs text-red-500 mt-1">
295
+ {urlValidationErrors[rule.id]}
296
+ </p>
297
+ )}
298
+ </div>
299
+ </td>
300
+ <td className="px-4 py-3">
301
+ <button
302
+ onClick={() => removeHyperlinkRule(rule.id)}
303
+ disabled={!replaceHyperlinksEnabled}
304
+ className="p-1.5 text-muted-foreground hover:text-red-500 transition-colors disabled:opacity-50"
305
+ >
306
+ <Trash2 className="w-4 h-4" />
307
+ </button>
308
+ </td>
309
+ </motion.tr>
310
+ ))}
311
+ </AnimatePresence>
312
+ </tbody>
313
+ </table>
314
+ </div>
315
+ )}
316
+ </div>
317
+
318
+ {/* Text Replacements */}
319
+ <div className="space-y-4">
320
+ <div className="flex items-center justify-between">
321
+ <div className="flex items-center gap-3">
322
+ <button
323
+ onClick={() => setReplaceTextEnabled(!replaceTextEnabled)}
324
+ className={cn(
325
+ 'relative w-12 h-6 rounded-full transition-colors',
326
+ replaceTextEnabled ? 'bg-primary' : 'bg-muted'
327
+ )}
328
+ >
329
+ <motion.div
330
+ className="absolute top-1 w-4 h-4 bg-white rounded-full shadow-xs"
331
+ animate={{ x: replaceTextEnabled ? 24 : 2 }}
332
+ transition={{ type: 'spring', stiffness: 500, damping: 30 }}
333
+ />
334
+ </button>
335
+ <div>
336
+ <h3 className="font-medium">Replace Text</h3>
337
+ <p className="text-sm text-muted-foreground">Replace text content based on rules</p>
338
+ </div>
339
+ </div>
340
+ <Button
341
+ size="sm"
342
+ variant="outline"
343
+ icon={<Plus className="w-4 h-4" />}
344
+ onClick={addTextRule}
345
+ disabled={!replaceTextEnabled}
346
+ >
347
+ Add Rule
348
+ </Button>
349
+ </div>
350
+
351
+ {textRules.length > 0 && (
352
+ <div className="border border-border rounded-lg overflow-hidden">
353
+ <table className="w-full">
354
+ <thead className="bg-muted/50">
355
+ <tr>
356
+ <th className="px-4 py-3 text-left text-sm font-medium w-12">Enable</th>
357
+ <th className="px-4 py-3 text-left text-sm font-medium">Old Text</th>
358
+ <th className="px-4 py-3 text-left text-sm font-medium">New Text</th>
359
+ <th className="px-4 py-3 text-left text-sm font-medium w-12"></th>
360
+ </tr>
361
+ </thead>
362
+ <tbody>
363
+ <AnimatePresence>
364
+ {textRules.map((rule) => (
365
+ <motion.tr
366
+ key={rule.id}
367
+ initial={{ opacity: 0, y: -10 }}
368
+ animate={{ opacity: 1, y: 0 }}
369
+ exit={{ opacity: 0, x: -20 }}
370
+ className={cn('border-t border-border', !replaceTextEnabled && 'opacity-50')}
371
+ >
372
+ <td className="px-4 py-3">
373
+ <label className="flex items-center cursor-pointer">
374
+ <div className="relative">
375
+ <input
376
+ type="checkbox"
377
+ checked={rule.enabled}
378
+ onChange={(e) =>
379
+ updateTextRule(rule.id, { enabled: e.target.checked })
380
+ }
381
+ disabled={!replaceTextEnabled}
382
+ className="sr-only"
383
+ />
384
+ <div
385
+ className={cn(
386
+ 'w-5 h-5 rounded border-2 flex items-center justify-center transition-all',
387
+ rule.enabled && replaceTextEnabled
388
+ ? 'bg-primary border-primary checkbox-checked'
389
+ : 'border-border'
390
+ )}
391
+ >
392
+ {rule.enabled && replaceTextEnabled && (
393
+ <Check className="w-3 h-3 text-primary-foreground checkbox-checkmark" />
394
+ )}
395
+ </div>
396
+ </div>
397
+ </label>
398
+ </td>
399
+ <td className="px-4 py-3">
400
+ <input
401
+ type="text"
402
+ value={rule.oldText}
403
+ onChange={(e) => updateTextRule(rule.id, { oldText: e.target.value })}
404
+ disabled={!replaceTextEnabled}
405
+ placeholder="Enter old text"
406
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-muted/30 focus:bg-background transition-colors disabled:opacity-50"
407
+ />
408
+ </td>
409
+ <td className="px-4 py-3">
410
+ <input
411
+ type="text"
412
+ value={rule.newText}
413
+ onChange={(e) => updateTextRule(rule.id, { newText: e.target.value })}
414
+ disabled={!replaceTextEnabled}
415
+ placeholder="Enter new text"
416
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-muted/30 focus:bg-background transition-colors disabled:opacity-50"
417
+ />
418
+ </td>
419
+ <td className="px-4 py-3">
420
+ <button
421
+ onClick={() => removeTextRule(rule.id)}
422
+ disabled={!replaceTextEnabled}
423
+ className="p-1.5 text-muted-foreground hover:text-red-500 transition-colors disabled:opacity-50"
424
+ >
425
+ <Trash2 className="w-4 h-4" />
426
+ </button>
427
+ </td>
428
+ </motion.tr>
429
+ ))}
430
+ </AnimatePresence>
431
+ </tbody>
432
+ </table>
433
+ </div>
434
+ )}
435
+ </div>
436
+ </div>
437
+ );
438
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * RevisionHandlingOptions - Controls for Word tracked changes handling
3
+ *
4
+ * All DocHub modifications are automatically tracked as Word revisions.
5
+ * This component provides the option to auto-accept revisions for clean output.
6
+ */
7
+
8
+ import { cn } from '@/utils/cn';
9
+ import { Check, FileText, GitBranch } from 'lucide-react';
10
+
11
+ interface RevisionHandlingOptionsProps {
12
+ autoAccept: boolean;
13
+ onAutoAcceptChange: (autoAccept: boolean) => void;
14
+ disabled?: boolean;
15
+ }
16
+
17
+ export function RevisionHandlingOptions({
18
+ autoAccept,
19
+ onAutoAcceptChange,
20
+ disabled = false,
21
+ }: RevisionHandlingOptionsProps) {
22
+ return (
23
+ <div className="space-y-4">
24
+ <div className="flex items-center gap-2 text-sm">
25
+ <GitBranch className="w-4 h-4 text-muted-foreground" />
26
+ <span className="font-medium">Track Changes</span>
27
+ </div>
28
+
29
+ <div className="p-3 bg-muted/30 rounded-lg border border-border">
30
+ <div className="flex items-start gap-2">
31
+ <FileText className="w-4 h-4 text-blue-500 mt-0.5 shrink-0" />
32
+ <div>
33
+ <p className="text-sm font-medium">All changes are tracked</p>
34
+ <p className="text-xs text-muted-foreground mt-1">
35
+ When enabled, DocHub records all document modifications as Word tracked changes (revisions).
36
+ These changes appear in the Document Changes tab and are visible when opening the file in Microsoft Word.
37
+ </p>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <label
43
+ className={cn(
44
+ 'flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors',
45
+ autoAccept
46
+ ? 'border-primary bg-primary/5'
47
+ : 'border-border hover:border-primary/50 hover:bg-muted/50',
48
+ disabled && 'opacity-50 cursor-not-allowed'
49
+ )}
50
+ >
51
+ <div className="relative mt-0.5">
52
+ <input
53
+ type="checkbox"
54
+ checked={autoAccept}
55
+ onChange={(e) => onAutoAcceptChange(e.target.checked)}
56
+ disabled={disabled}
57
+ className="sr-only"
58
+ />
59
+ <div
60
+ className={cn(
61
+ 'w-5 h-5 rounded border-2 flex items-center justify-center transition-all',
62
+ autoAccept
63
+ ? 'bg-primary border-primary'
64
+ : 'border-muted-foreground/50'
65
+ )}
66
+ >
67
+ {autoAccept && <Check className="w-3 h-3 text-primary-foreground" />}
68
+ </div>
69
+ </div>
70
+
71
+ <div className="flex-1 min-w-0">
72
+ <span className="font-medium text-sm">Auto-Accept Tracked Changes</span>
73
+ <p className="mt-1 text-xs text-muted-foreground leading-relaxed">
74
+ Creates a clean document without visible tracked changes.
75
+ Changes are still recorded in DocHub for review.
76
+ </p>
77
+ </div>
78
+ </label>
79
+
80
+ {autoAccept && (
81
+ <p className="text-xs text-blue-600 dark:text-blue-400">
82
+ Tracked changes will be auto-accepted and not visible in Microsoft Word.
83
+ </p>
84
+ )}
85
+ </div>
86
+ );
87
+ }