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.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- 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
|
+
}
|