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,402 @@
1
+ /**
2
+ * DictionaryService - Main Process
3
+ * Handles local SQLite database for document dictionary lookups
4
+ *
5
+ * This service runs in the Electron main process and provides O(1)
6
+ * lookup performance for 100-200k+ document entries.
7
+ *
8
+ * @architecture Main Process Service
9
+ * @security Context isolation compliant - no renderer access
10
+ * @performance SQLite with WAL mode for fast reads
11
+ */
12
+
13
+ import * as path from 'path';
14
+ import * as fs from 'fs/promises';
15
+ import { app } from 'electron';
16
+ import Database from 'better-sqlite3';
17
+ import { logger } from '../../src/utils/logger';
18
+ import type {
19
+ DictionaryEntry,
20
+ DictionaryLookupResult,
21
+ DictionarySyncStatus,
22
+ } from '../../src/types/dictionary';
23
+
24
+ const log = logger.namespace('DictionaryService');
25
+
26
+ /**
27
+ * Service for managing local document dictionary database
28
+ */
29
+ export class DictionaryService {
30
+ private db: Database.Database | null = null;
31
+ private dbPath: string;
32
+ private initialized: boolean = false;
33
+ private syncStatus: DictionarySyncStatus = {
34
+ enabled: false,
35
+ lastSyncTime: null,
36
+ lastSyncSuccess: false,
37
+ totalEntries: 0,
38
+ syncInProgress: false,
39
+ syncProgress: 0,
40
+ syncError: null,
41
+ nextScheduledSync: null,
42
+ fileHash: null,
43
+ };
44
+
45
+ constructor() {
46
+ // Store database in app data directory
47
+ this.dbPath = path.join(app.getPath('userData'), 'dictionary.db');
48
+ }
49
+
50
+ /**
51
+ * Initialize the database and create tables if needed
52
+ */
53
+ async initialize(): Promise<{ success: boolean; totalEntries: number; error?: string }> {
54
+ try {
55
+ if (this.initialized && this.db) {
56
+ const count = this.getEntryCount();
57
+ return { success: true, totalEntries: count };
58
+ }
59
+
60
+ // Ensure directory exists
61
+ await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
62
+
63
+ // Open database with WAL mode for better performance
64
+ this.db = new Database(this.dbPath);
65
+ this.db.pragma('journal_mode = WAL');
66
+ this.db.pragma('synchronous = NORMAL');
67
+ this.db.pragma('cache_size = -64000'); // 64MB cache
68
+
69
+ // Create tables
70
+ this.db.exec(`
71
+ CREATE TABLE IF NOT EXISTS dictionary (
72
+ Document_ID TEXT PRIMARY KEY,
73
+ Content_ID TEXT,
74
+ Title TEXT,
75
+ Summary TEXT,
76
+ Type TEXT,
77
+ Release_Date TEXT,
78
+ Expiration_Date TEXT,
79
+ Status TEXT,
80
+ Owner TEXT,
81
+ BPO TEXT,
82
+ LOB TEXT,
83
+ Last_Published_By TEXT
84
+ );
85
+
86
+ CREATE INDEX IF NOT EXISTS idx_content_id ON dictionary(Content_ID);
87
+ CREATE INDEX IF NOT EXISTS idx_status ON dictionary(Status);
88
+
89
+ CREATE TABLE IF NOT EXISTS sync_metadata (
90
+ key TEXT PRIMARY KEY,
91
+ value TEXT
92
+ );
93
+ `);
94
+
95
+ this.initialized = true;
96
+ const count = this.getEntryCount();
97
+ this.syncStatus.totalEntries = count;
98
+
99
+ // Load sync metadata
100
+ await this.loadSyncMetadata();
101
+
102
+ log.info('Dictionary database initialized', { path: this.dbPath, entries: count });
103
+ return { success: true, totalEntries: count };
104
+ } catch (error) {
105
+ const message = error instanceof Error ? error.message : 'Unknown error';
106
+ log.error('Failed to initialize dictionary database', { error: message });
107
+ return { success: false, totalEntries: 0, error: message };
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Lookup a single entry by Document_ID or Content_ID
113
+ */
114
+ lookup(lookupId: string): DictionaryLookupResult {
115
+ if (!this.db || !this.initialized) {
116
+ return { found: false, lookupId, lookupType: 'Document_ID' };
117
+ }
118
+
119
+ try {
120
+ // First try Document_ID (primary key - fastest)
121
+ let stmt = this.db.prepare('SELECT * FROM dictionary WHERE Document_ID = ?');
122
+ let row = stmt.get(lookupId) as DictionaryEntry | undefined;
123
+
124
+ if (row) {
125
+ return {
126
+ found: true,
127
+ entry: row,
128
+ lookupId,
129
+ lookupType: 'Document_ID',
130
+ };
131
+ }
132
+
133
+ // Try Content_ID (indexed)
134
+ stmt = this.db.prepare('SELECT * FROM dictionary WHERE Content_ID = ?');
135
+ row = stmt.get(lookupId) as DictionaryEntry | undefined;
136
+
137
+ if (row) {
138
+ return {
139
+ found: true,
140
+ entry: row,
141
+ lookupId,
142
+ lookupType: 'Content_ID',
143
+ };
144
+ }
145
+
146
+ return { found: false, lookupId, lookupType: 'Document_ID' };
147
+ } catch (error) {
148
+ log.error('Lookup failed', { lookupId, error });
149
+ return { found: false, lookupId, lookupType: 'Document_ID' };
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Batch lookup multiple IDs for performance
155
+ */
156
+ batchLookup(lookupIds: string[]): Map<string, DictionaryLookupResult> {
157
+ const results = new Map<string, DictionaryLookupResult>();
158
+
159
+ if (!this.db || !this.initialized || lookupIds.length === 0) {
160
+ lookupIds.forEach((id) => {
161
+ results.set(id, { found: false, lookupId: id, lookupType: 'Document_ID' });
162
+ });
163
+ return results;
164
+ }
165
+
166
+ try {
167
+ // Prepare statements once for reuse
168
+ const docIdStmt = this.db.prepare('SELECT * FROM dictionary WHERE Document_ID = ?');
169
+ const contentIdStmt = this.db.prepare('SELECT * FROM dictionary WHERE Content_ID = ?');
170
+
171
+ for (const lookupId of lookupIds) {
172
+ // Try Document_ID first
173
+ let row = docIdStmt.get(lookupId) as DictionaryEntry | undefined;
174
+
175
+ if (row) {
176
+ results.set(lookupId, {
177
+ found: true,
178
+ entry: row,
179
+ lookupId,
180
+ lookupType: 'Document_ID',
181
+ });
182
+ continue;
183
+ }
184
+
185
+ // Try Content_ID
186
+ row = contentIdStmt.get(lookupId) as DictionaryEntry | undefined;
187
+
188
+ if (row) {
189
+ results.set(lookupId, {
190
+ found: true,
191
+ entry: row,
192
+ lookupId,
193
+ lookupType: 'Content_ID',
194
+ });
195
+ continue;
196
+ }
197
+
198
+ // Not found
199
+ results.set(lookupId, { found: false, lookupId, lookupType: 'Document_ID' });
200
+ }
201
+
202
+ return results;
203
+ } catch (error) {
204
+ log.error('Batch lookup failed', { count: lookupIds.length, error });
205
+ lookupIds.forEach((id) => {
206
+ if (!results.has(id)) {
207
+ results.set(id, { found: false, lookupId: id, lookupType: 'Document_ID' });
208
+ }
209
+ });
210
+ return results;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Import entries from parsed Excel data
216
+ * Uses transaction for performance with large datasets
217
+ */
218
+ importEntries(
219
+ entries: DictionaryEntry[],
220
+ onProgress?: (processed: number, total: number) => void
221
+ ): { success: boolean; imported: number; error?: string } {
222
+ if (!this.db || !this.initialized) {
223
+ return { success: false, imported: 0, error: 'Database not initialized' };
224
+ }
225
+
226
+ try {
227
+ const insertStmt = this.db.prepare(`
228
+ INSERT OR REPLACE INTO dictionary (
229
+ Document_ID, Content_ID, Title, Summary, Type,
230
+ Release_Date, Expiration_Date, Status, Owner, BPO, LOB, Last_Published_By
231
+ ) VALUES (
232
+ @Document_ID, @Content_ID, @Title, @Summary, @Type,
233
+ @Release_Date, @Expiration_Date, @Status, @Owner, @BPO, @LOB, @Last_Published_By
234
+ )
235
+ `);
236
+
237
+ // Use transaction for performance
238
+ const importMany = this.db.transaction((items: DictionaryEntry[]) => {
239
+ let count = 0;
240
+ for (const entry of items) {
241
+ insertStmt.run(entry);
242
+ count++;
243
+ if (onProgress && count % 1000 === 0) {
244
+ onProgress(count, items.length);
245
+ }
246
+ }
247
+ return count;
248
+ });
249
+
250
+ const imported = importMany(entries);
251
+
252
+ // Update sync status
253
+ this.syncStatus.totalEntries = this.getEntryCount();
254
+ this.syncStatus.lastSyncTime = new Date().toISOString();
255
+ this.syncStatus.lastSyncSuccess = true;
256
+ this.syncStatus.syncError = null;
257
+
258
+ // Save sync metadata
259
+ this.saveSyncMetadata();
260
+
261
+ log.info('Imported dictionary entries', { imported, total: this.syncStatus.totalEntries });
262
+ return { success: true, imported };
263
+ } catch (error) {
264
+ const message = error instanceof Error ? error.message : 'Unknown error';
265
+ log.error('Import failed', { error: message, entriesCount: entries.length });
266
+ this.syncStatus.syncError = message;
267
+ this.syncStatus.lastSyncSuccess = false;
268
+ return { success: false, imported: 0, error: message };
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Clear all entries from the dictionary
274
+ */
275
+ clearEntries(): { success: boolean; error?: string } {
276
+ if (!this.db || !this.initialized) {
277
+ return { success: false, error: 'Database not initialized' };
278
+ }
279
+
280
+ try {
281
+ this.db.exec('DELETE FROM dictionary');
282
+ this.syncStatus.totalEntries = 0;
283
+ log.info('Cleared all dictionary entries');
284
+ return { success: true };
285
+ } catch (error) {
286
+ const message = error instanceof Error ? error.message : 'Unknown error';
287
+ log.error('Clear failed', { error: message });
288
+ return { success: false, error: message };
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Get current sync status
294
+ */
295
+ getSyncStatus(): DictionarySyncStatus {
296
+ return { ...this.syncStatus };
297
+ }
298
+
299
+ /**
300
+ * Update sync status
301
+ */
302
+ updateSyncStatus(updates: Partial<DictionarySyncStatus>): void {
303
+ this.syncStatus = { ...this.syncStatus, ...updates };
304
+ }
305
+
306
+ /**
307
+ * Get entry count
308
+ */
309
+ getEntryCount(): number {
310
+ if (!this.db || !this.initialized) {
311
+ return 0;
312
+ }
313
+
314
+ try {
315
+ const result = this.db.prepare('SELECT COUNT(*) as count FROM dictionary').get() as {
316
+ count: number;
317
+ };
318
+ return result.count;
319
+ } catch {
320
+ return 0;
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Set metadata value
326
+ */
327
+ setMetadata(key: string, value: string): void {
328
+ if (!this.db || !this.initialized) return;
329
+
330
+ try {
331
+ const stmt = this.db.prepare(
332
+ 'INSERT OR REPLACE INTO sync_metadata (key, value) VALUES (?, ?)'
333
+ );
334
+ stmt.run(key, value);
335
+ } catch (error) {
336
+ log.error('Failed to set metadata', { key, error });
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Get metadata value
342
+ */
343
+ getMetadata(key: string): string | null {
344
+ if (!this.db || !this.initialized) return null;
345
+
346
+ try {
347
+ const stmt = this.db.prepare('SELECT value FROM sync_metadata WHERE key = ?');
348
+ const row = stmt.get(key) as { value: string } | undefined;
349
+ return row?.value ?? null;
350
+ } catch {
351
+ return null;
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Close database connection
357
+ */
358
+ close(): void {
359
+ if (this.db) {
360
+ this.db.close();
361
+ this.db = null;
362
+ this.initialized = false;
363
+ log.info('Dictionary database closed');
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Load sync metadata from database
369
+ */
370
+ private async loadSyncMetadata(): Promise<void> {
371
+ const lastSyncTime = this.getMetadata('lastSyncTime');
372
+ const lastSyncSuccess = this.getMetadata('lastSyncSuccess');
373
+ const fileHash = this.getMetadata('fileHash');
374
+
375
+ if (lastSyncTime) this.syncStatus.lastSyncTime = lastSyncTime;
376
+ if (lastSyncSuccess) this.syncStatus.lastSyncSuccess = lastSyncSuccess === 'true';
377
+ if (fileHash) this.syncStatus.fileHash = fileHash;
378
+ }
379
+
380
+ /**
381
+ * Save sync metadata to database
382
+ */
383
+ private saveSyncMetadata(): void {
384
+ if (this.syncStatus.lastSyncTime) {
385
+ this.setMetadata('lastSyncTime', this.syncStatus.lastSyncTime);
386
+ }
387
+ this.setMetadata('lastSyncSuccess', String(this.syncStatus.lastSyncSuccess));
388
+ if (this.syncStatus.fileHash) {
389
+ this.setMetadata('fileHash', this.syncStatus.fileHash);
390
+ }
391
+ }
392
+ }
393
+
394
+ // Singleton instance
395
+ let dictionaryServiceInstance: DictionaryService | null = null;
396
+
397
+ export function getDictionaryService(): DictionaryService {
398
+ if (!dictionaryServiceInstance) {
399
+ dictionaryServiceInstance = new DictionaryService();
400
+ }
401
+ return dictionaryServiceInstance;
402
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * LocalDictionaryLookupService - Main Process
3
+ * Bridge service that maps dictionary lookups to hyperlink API format
4
+ *
5
+ * This service provides the same result format as the Power Automate API
6
+ * so that the renderer's HyperlinkService can use local dictionary
7
+ * lookups without changing its processing logic.
8
+ *
9
+ * @architecture Main Process Service
10
+ * @security Context isolation compliant
11
+ */
12
+
13
+ import { logger } from '../../src/utils/logger';
14
+ import { getDictionaryService } from './DictionaryService';
15
+ import type { DictionaryEntry, DictionaryLookupResult } from '../../src/types/dictionary';
16
+
17
+ const log = logger.namespace('LocalDictionaryLookupService');
18
+
19
+ /**
20
+ * Result format matching the Power Automate API response
21
+ */
22
+ export interface HyperlinkLookupResult {
23
+ Document_ID: string;
24
+ Content_ID: string;
25
+ Title: string;
26
+ Status: 'Active' | 'Deprecated' | 'Expired' | 'Moved' | 'Not_Found';
27
+ }
28
+
29
+ /**
30
+ * Service for performing hyperlink lookups against local dictionary
31
+ */
32
+ export class LocalDictionaryLookupService {
33
+ /**
34
+ * Lookup a single ID and return result in API-compatible format
35
+ */
36
+ lookup(lookupId: string): HyperlinkLookupResult {
37
+ const dictionaryService = getDictionaryService();
38
+ const result = dictionaryService.lookup(lookupId);
39
+
40
+ return this.mapToHyperlinkResult(result);
41
+ }
42
+
43
+ /**
44
+ * Batch lookup multiple IDs for performance
45
+ * Returns results in the same format as the Power Automate API
46
+ */
47
+ batchLookup(lookupIds: string[]): HyperlinkLookupResult[] {
48
+ if (lookupIds.length === 0) {
49
+ return [];
50
+ }
51
+
52
+ const dictionaryService = getDictionaryService();
53
+ const results = dictionaryService.batchLookup(lookupIds);
54
+ const mappedResults: HyperlinkLookupResult[] = [];
55
+
56
+ for (const [, result] of results) {
57
+ mappedResults.push(this.mapToHyperlinkResult(result));
58
+ }
59
+
60
+ log.debug('Batch lookup completed', {
61
+ requested: lookupIds.length,
62
+ found: mappedResults.filter((r) => r.Status !== 'Not_Found').length,
63
+ });
64
+
65
+ return mappedResults;
66
+ }
67
+
68
+ /**
69
+ * Map dictionary lookup result to API-compatible format
70
+ */
71
+ private mapToHyperlinkResult(result: DictionaryLookupResult): HyperlinkLookupResult {
72
+ if (!result.found || !result.entry) {
73
+ return {
74
+ Document_ID: result.lookupId,
75
+ Content_ID: '',
76
+ Title: '',
77
+ Status: 'Not_Found',
78
+ };
79
+ }
80
+
81
+ const entry = result.entry;
82
+
83
+ // Map the status from dictionary to API format
84
+ const status = this.mapStatus(entry.Status);
85
+
86
+ return {
87
+ Document_ID: entry.Document_ID,
88
+ Content_ID: entry.Content_ID,
89
+ Title: entry.Title,
90
+ Status: status,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Map dictionary status to API status format
96
+ */
97
+ private mapStatus(
98
+ dictionaryStatus: string
99
+ ): 'Active' | 'Deprecated' | 'Expired' | 'Moved' | 'Not_Found' {
100
+ const status = dictionaryStatus.toLowerCase().trim();
101
+
102
+ switch (status) {
103
+ case 'active':
104
+ case 'published':
105
+ case 'current':
106
+ return 'Active';
107
+
108
+ case 'deprecated':
109
+ case 'obsolete':
110
+ return 'Deprecated';
111
+
112
+ case 'expired':
113
+ case 'retired':
114
+ case 'archived':
115
+ return 'Expired';
116
+
117
+ case 'moved':
118
+ case 'relocated':
119
+ case 'redirected':
120
+ return 'Moved';
121
+
122
+ default:
123
+ // If status is empty or unknown, assume active
124
+ return status === '' ? 'Active' : 'Active';
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Get detailed entry information (includes all fields)
130
+ */
131
+ getDetailedEntry(lookupId: string): DictionaryEntry | null {
132
+ const dictionaryService = getDictionaryService();
133
+ const result = dictionaryService.lookup(lookupId);
134
+
135
+ return result.found && result.entry ? result.entry : null;
136
+ }
137
+ }
138
+
139
+ // Singleton instance
140
+ let localDictionaryLookupServiceInstance: LocalDictionaryLookupService | null = null;
141
+
142
+ export function getLocalDictionaryLookupService(): LocalDictionaryLookupService {
143
+ if (!localDictionaryLookupServiceInstance) {
144
+ localDictionaryLookupServiceInstance = new LocalDictionaryLookupService();
145
+ }
146
+ return localDictionaryLookupServiceInstance;
147
+ }