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,856 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentProcessingComparison - Compare document before and after processing
|
|
3
|
+
*
|
|
4
|
+
* This service captures the state of a document before processing,
|
|
5
|
+
* tracks all changes made during processing, and generates a comparison
|
|
6
|
+
* showing exactly what was modified.
|
|
7
|
+
*
|
|
8
|
+
* Used to show users what changes were made to their document during:
|
|
9
|
+
* - Hyperlink processing
|
|
10
|
+
* - Style application
|
|
11
|
+
* - Content ID appending
|
|
12
|
+
* - PowerAutomate API updates
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Document, Paragraph, Hyperlink } from 'docxmlater';
|
|
16
|
+
import { diffWords, Change } from 'diff';
|
|
17
|
+
import { sanitizeHyperlinkText, isTextCorrupted } from '../../utils/textSanitizer';
|
|
18
|
+
import { logger } from '../../utils/logger';
|
|
19
|
+
|
|
20
|
+
// Create namespaced logger for this module
|
|
21
|
+
const log = logger.namespace('DocumentProcessingComparison');
|
|
22
|
+
|
|
23
|
+
export interface ProcessingChange {
|
|
24
|
+
id: string;
|
|
25
|
+
type:
|
|
26
|
+
| 'hyperlink_url'
|
|
27
|
+
| 'hyperlink_text'
|
|
28
|
+
| 'style'
|
|
29
|
+
| 'content_added'
|
|
30
|
+
| 'content_removed'
|
|
31
|
+
| 'formatting';
|
|
32
|
+
location: string; // e.g., "Paragraph 5, Hyperlink 2"
|
|
33
|
+
before: any;
|
|
34
|
+
after: any;
|
|
35
|
+
timestamp: Date;
|
|
36
|
+
operation: string; // e.g., "PowerAutomate API Update", "Content ID Append"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ProcessingComparison {
|
|
40
|
+
documentPath: string;
|
|
41
|
+
processingStartTime: Date;
|
|
42
|
+
processingEndTime?: Date;
|
|
43
|
+
originalBuffer: Buffer;
|
|
44
|
+
processedBuffer?: Buffer;
|
|
45
|
+
changes: ProcessingChange[];
|
|
46
|
+
statistics: ProcessingStatistics;
|
|
47
|
+
hyperlinkChanges: HyperlinkChange[];
|
|
48
|
+
styleChanges: StyleChange[];
|
|
49
|
+
contentChanges: ContentChange[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ProcessingStatistics {
|
|
53
|
+
totalChanges: number;
|
|
54
|
+
hyperlinksModified: number;
|
|
55
|
+
urlsChanged: number;
|
|
56
|
+
displayTextsChanged: number;
|
|
57
|
+
stylesApplied: number;
|
|
58
|
+
contentIdsAppended: number;
|
|
59
|
+
processingDurationMs: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface HyperlinkChange {
|
|
63
|
+
paragraphIndex: number;
|
|
64
|
+
hyperlinkIndex: number;
|
|
65
|
+
originalUrl: string;
|
|
66
|
+
modifiedUrl: string;
|
|
67
|
+
originalText: string;
|
|
68
|
+
modifiedText: string;
|
|
69
|
+
changeReason: string; // e.g., "Content ID appended", "URL updated by API"
|
|
70
|
+
/** Status of the hyperlink source - 'not_found' for theSource links that couldn't be resolved */
|
|
71
|
+
status?: 'updated' | 'not_found' | 'expired';
|
|
72
|
+
/** Content ID associated with this hyperlink change (for theSource links) */
|
|
73
|
+
contentId?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface StyleChange {
|
|
77
|
+
paragraphIndex: number;
|
|
78
|
+
styleId: string;
|
|
79
|
+
styleName: string;
|
|
80
|
+
properties: Record<string, any>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ContentChange {
|
|
84
|
+
paragraphIndex: number;
|
|
85
|
+
type: 'added' | 'removed' | 'modified';
|
|
86
|
+
originalContent?: string;
|
|
87
|
+
modifiedContent?: string;
|
|
88
|
+
diff?: Change[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Service for tracking and comparing document changes during processing
|
|
93
|
+
*/
|
|
94
|
+
export class DocumentProcessingComparison {
|
|
95
|
+
private comparison: ProcessingComparison | null = null;
|
|
96
|
+
private originalHyperlinks: Map<string, HyperlinkSnapshot> = new Map();
|
|
97
|
+
private originalStyles: Map<number, any> = new Map();
|
|
98
|
+
private changeIdCounter = 0;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Start tracking a document for comparison
|
|
102
|
+
*/
|
|
103
|
+
async startTracking(documentPath: string, document: Document): Promise<void> {
|
|
104
|
+
const startTime = new Date();
|
|
105
|
+
|
|
106
|
+
// Capture original state
|
|
107
|
+
const originalBuffer = await document.toBuffer();
|
|
108
|
+
|
|
109
|
+
// Capture hyperlink snapshots
|
|
110
|
+
this.captureHyperlinks(document);
|
|
111
|
+
|
|
112
|
+
// Capture style snapshots
|
|
113
|
+
this.captureStyles(document);
|
|
114
|
+
|
|
115
|
+
// Initialize comparison
|
|
116
|
+
this.comparison = {
|
|
117
|
+
documentPath,
|
|
118
|
+
processingStartTime: startTime,
|
|
119
|
+
originalBuffer,
|
|
120
|
+
changes: [],
|
|
121
|
+
statistics: {
|
|
122
|
+
totalChanges: 0,
|
|
123
|
+
hyperlinksModified: 0,
|
|
124
|
+
urlsChanged: 0,
|
|
125
|
+
displayTextsChanged: 0,
|
|
126
|
+
stylesApplied: 0,
|
|
127
|
+
contentIdsAppended: 0,
|
|
128
|
+
processingDurationMs: 0,
|
|
129
|
+
},
|
|
130
|
+
hyperlinkChanges: [],
|
|
131
|
+
styleChanges: [],
|
|
132
|
+
contentChanges: [],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Capture current hyperlink state
|
|
138
|
+
*/
|
|
139
|
+
private captureHyperlinks(document: Document): void {
|
|
140
|
+
this.originalHyperlinks.clear();
|
|
141
|
+
const paragraphs = document.getAllParagraphs();
|
|
142
|
+
|
|
143
|
+
paragraphs.forEach((para, paraIndex) => {
|
|
144
|
+
const content = para.getContent();
|
|
145
|
+
let hyperlinkIndex = 0;
|
|
146
|
+
|
|
147
|
+
for (const item of content) {
|
|
148
|
+
if (item instanceof Hyperlink) {
|
|
149
|
+
const key = `${paraIndex}-${hyperlinkIndex}`;
|
|
150
|
+
const rawText = item.getText();
|
|
151
|
+
|
|
152
|
+
// Log if XML corruption detected
|
|
153
|
+
if (isTextCorrupted(rawText)) {
|
|
154
|
+
log.warn(
|
|
155
|
+
`XML corruption detected in hyperlink text at paragraph ${paraIndex}, hyperlink ${hyperlinkIndex}:`,
|
|
156
|
+
rawText
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.originalHyperlinks.set(key, {
|
|
161
|
+
paragraphIndex: paraIndex,
|
|
162
|
+
hyperlinkIndex,
|
|
163
|
+
url: item.getUrl() || '',
|
|
164
|
+
text: sanitizeHyperlinkText(rawText),
|
|
165
|
+
});
|
|
166
|
+
hyperlinkIndex++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Capture current style state
|
|
174
|
+
*/
|
|
175
|
+
private captureStyles(document: Document): void {
|
|
176
|
+
this.originalStyles.clear();
|
|
177
|
+
const paragraphs = document.getAllParagraphs();
|
|
178
|
+
|
|
179
|
+
paragraphs.forEach((para, index) => {
|
|
180
|
+
const formatting = para.getFormatting?.() || {};
|
|
181
|
+
this.originalStyles.set(index, formatting);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Record a hyperlink URL change
|
|
187
|
+
*/
|
|
188
|
+
recordHyperlinkUrlChange(
|
|
189
|
+
paragraphIndex: number,
|
|
190
|
+
hyperlinkIndex: number,
|
|
191
|
+
originalUrl: string,
|
|
192
|
+
newUrl: string,
|
|
193
|
+
reason: string
|
|
194
|
+
): void {
|
|
195
|
+
if (!this.comparison) return;
|
|
196
|
+
|
|
197
|
+
const changeId = `change-${++this.changeIdCounter}`;
|
|
198
|
+
const location = `Paragraph ${paragraphIndex + 1}, Hyperlink ${hyperlinkIndex + 1}`;
|
|
199
|
+
|
|
200
|
+
// Add to general changes
|
|
201
|
+
this.comparison.changes.push({
|
|
202
|
+
id: changeId,
|
|
203
|
+
type: 'hyperlink_url',
|
|
204
|
+
location,
|
|
205
|
+
before: originalUrl,
|
|
206
|
+
after: newUrl,
|
|
207
|
+
timestamp: new Date(),
|
|
208
|
+
operation: reason,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Find if there's an existing hyperlink change for this position
|
|
212
|
+
const existingChange = this.comparison.hyperlinkChanges.find(
|
|
213
|
+
(c) => c.paragraphIndex === paragraphIndex && c.hyperlinkIndex === hyperlinkIndex
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (existingChange) {
|
|
217
|
+
existingChange.modifiedUrl = newUrl;
|
|
218
|
+
existingChange.changeReason += `, ${reason}`;
|
|
219
|
+
} else {
|
|
220
|
+
// Get original state
|
|
221
|
+
const key = `${paragraphIndex}-${hyperlinkIndex}`;
|
|
222
|
+
const original = this.originalHyperlinks.get(key);
|
|
223
|
+
|
|
224
|
+
this.comparison.hyperlinkChanges.push({
|
|
225
|
+
paragraphIndex,
|
|
226
|
+
hyperlinkIndex,
|
|
227
|
+
originalUrl: original?.url || originalUrl,
|
|
228
|
+
modifiedUrl: newUrl,
|
|
229
|
+
originalText: original?.text || '',
|
|
230
|
+
modifiedText: original?.text || '',
|
|
231
|
+
changeReason: reason,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Update statistics
|
|
236
|
+
this.comparison.statistics.urlsChanged++;
|
|
237
|
+
this.comparison.statistics.hyperlinksModified++;
|
|
238
|
+
|
|
239
|
+
if (reason.includes('Content ID')) {
|
|
240
|
+
this.comparison.statistics.contentIdsAppended++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Record a hyperlink text change
|
|
246
|
+
* @param status - Optional status for theSource links ('not_found', 'expired')
|
|
247
|
+
* @param contentId - Optional content ID associated with this hyperlink
|
|
248
|
+
*/
|
|
249
|
+
recordHyperlinkTextChange(
|
|
250
|
+
paragraphIndex: number,
|
|
251
|
+
hyperlinkIndex: number,
|
|
252
|
+
originalText: string,
|
|
253
|
+
newText: string,
|
|
254
|
+
reason: string,
|
|
255
|
+
status?: 'updated' | 'not_found' | 'expired',
|
|
256
|
+
contentId?: string
|
|
257
|
+
): void {
|
|
258
|
+
if (!this.comparison) return;
|
|
259
|
+
|
|
260
|
+
const changeId = `change-${++this.changeIdCounter}`;
|
|
261
|
+
const location = `Paragraph ${paragraphIndex + 1}, Hyperlink ${hyperlinkIndex + 1}`;
|
|
262
|
+
|
|
263
|
+
// Add to general changes
|
|
264
|
+
this.comparison.changes.push({
|
|
265
|
+
id: changeId,
|
|
266
|
+
type: 'hyperlink_text',
|
|
267
|
+
location,
|
|
268
|
+
before: originalText,
|
|
269
|
+
after: newText,
|
|
270
|
+
timestamp: new Date(),
|
|
271
|
+
operation: reason,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Update hyperlink changes
|
|
275
|
+
const existingChange = this.comparison.hyperlinkChanges.find(
|
|
276
|
+
(c) => c.paragraphIndex === paragraphIndex && c.hyperlinkIndex === hyperlinkIndex
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (existingChange) {
|
|
280
|
+
existingChange.modifiedText = newText;
|
|
281
|
+
existingChange.changeReason += `, ${reason}`;
|
|
282
|
+
if (status) existingChange.status = status;
|
|
283
|
+
if (contentId) existingChange.contentId = contentId;
|
|
284
|
+
} else {
|
|
285
|
+
const key = `${paragraphIndex}-${hyperlinkIndex}`;
|
|
286
|
+
const original = this.originalHyperlinks.get(key);
|
|
287
|
+
|
|
288
|
+
this.comparison.hyperlinkChanges.push({
|
|
289
|
+
paragraphIndex,
|
|
290
|
+
hyperlinkIndex,
|
|
291
|
+
originalUrl: original?.url || '',
|
|
292
|
+
modifiedUrl: original?.url || '',
|
|
293
|
+
originalText: original?.text || originalText,
|
|
294
|
+
modifiedText: newText,
|
|
295
|
+
changeReason: reason,
|
|
296
|
+
status,
|
|
297
|
+
contentId,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Update statistics
|
|
302
|
+
this.comparison.statistics.displayTextsChanged++;
|
|
303
|
+
if (!existingChange) {
|
|
304
|
+
this.comparison.statistics.hyperlinksModified++;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Record a style change
|
|
310
|
+
*/
|
|
311
|
+
recordStyleChange(
|
|
312
|
+
paragraphIndex: number,
|
|
313
|
+
styleId: string,
|
|
314
|
+
styleName: string,
|
|
315
|
+
properties: Record<string, any>
|
|
316
|
+
): void {
|
|
317
|
+
if (!this.comparison) return;
|
|
318
|
+
|
|
319
|
+
const changeId = `change-${++this.changeIdCounter}`;
|
|
320
|
+
const location = `Paragraph ${paragraphIndex + 1}`;
|
|
321
|
+
|
|
322
|
+
// Add to general changes
|
|
323
|
+
this.comparison.changes.push({
|
|
324
|
+
id: changeId,
|
|
325
|
+
type: 'style',
|
|
326
|
+
location,
|
|
327
|
+
before: this.originalStyles.get(paragraphIndex) || {},
|
|
328
|
+
after: { styleId, ...properties },
|
|
329
|
+
timestamp: new Date(),
|
|
330
|
+
operation: `Applied style: ${styleName}`,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Add to style changes
|
|
334
|
+
this.comparison.styleChanges.push({
|
|
335
|
+
paragraphIndex,
|
|
336
|
+
styleId,
|
|
337
|
+
styleName,
|
|
338
|
+
properties,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Update statistics
|
|
342
|
+
this.comparison.statistics.stylesApplied++;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Complete tracking and generate final comparison
|
|
347
|
+
*/
|
|
348
|
+
async completeTracking(processedDocument: Document): Promise<ProcessingComparison | null> {
|
|
349
|
+
if (!this.comparison) return null;
|
|
350
|
+
|
|
351
|
+
// Capture processed state
|
|
352
|
+
this.comparison.processedBuffer = await processedDocument.toBuffer();
|
|
353
|
+
this.comparison.processingEndTime = new Date();
|
|
354
|
+
|
|
355
|
+
// Calculate processing duration
|
|
356
|
+
this.comparison.statistics.processingDurationMs =
|
|
357
|
+
this.comparison.processingEndTime.getTime() - this.comparison.processingStartTime.getTime();
|
|
358
|
+
|
|
359
|
+
// Compare final hyperlink state
|
|
360
|
+
this.compareFinalHyperlinks(processedDocument);
|
|
361
|
+
|
|
362
|
+
// Update total changes
|
|
363
|
+
this.comparison.statistics.totalChanges = this.comparison.changes.length;
|
|
364
|
+
|
|
365
|
+
return this.comparison;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Compare final hyperlink state with original
|
|
370
|
+
*/
|
|
371
|
+
private compareFinalHyperlinks(document: Document): void {
|
|
372
|
+
if (!this.comparison) return;
|
|
373
|
+
|
|
374
|
+
const paragraphs = document.getAllParagraphs();
|
|
375
|
+
|
|
376
|
+
paragraphs.forEach((para, paraIndex) => {
|
|
377
|
+
const content = para.getContent();
|
|
378
|
+
let hyperlinkIndex = 0;
|
|
379
|
+
|
|
380
|
+
for (const item of content) {
|
|
381
|
+
if (item instanceof Hyperlink) {
|
|
382
|
+
const key = `${paraIndex}-${hyperlinkIndex}`;
|
|
383
|
+
const original = this.originalHyperlinks.get(key);
|
|
384
|
+
|
|
385
|
+
if (original) {
|
|
386
|
+
const currentUrl = item.getUrl() || '';
|
|
387
|
+
const rawCurrentText = item.getText();
|
|
388
|
+
|
|
389
|
+
// Log if XML corruption detected
|
|
390
|
+
if (isTextCorrupted(rawCurrentText)) {
|
|
391
|
+
log.warn(
|
|
392
|
+
`XML corruption detected in current hyperlink text at paragraph ${paraIndex}, hyperlink ${hyperlinkIndex}:`,
|
|
393
|
+
rawCurrentText
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const currentText = sanitizeHyperlinkText(rawCurrentText);
|
|
398
|
+
|
|
399
|
+
// Check if this change was already recorded
|
|
400
|
+
const recorded = this.comparison!.hyperlinkChanges.find(
|
|
401
|
+
(c) => c.paragraphIndex === paraIndex && c.hyperlinkIndex === hyperlinkIndex
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
if (!recorded) {
|
|
405
|
+
// Detect untracked changes
|
|
406
|
+
if (currentUrl !== original.url || currentText !== original.text) {
|
|
407
|
+
this.comparison!.hyperlinkChanges.push({
|
|
408
|
+
paragraphIndex: paraIndex,
|
|
409
|
+
hyperlinkIndex,
|
|
410
|
+
originalUrl: original.url,
|
|
411
|
+
modifiedUrl: currentUrl,
|
|
412
|
+
originalText: original.text,
|
|
413
|
+
modifiedText: currentText,
|
|
414
|
+
changeReason: 'Automatic change detected',
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (currentUrl !== original.url) {
|
|
418
|
+
this.comparison!.statistics.urlsChanged++;
|
|
419
|
+
}
|
|
420
|
+
if (currentText !== original.text) {
|
|
421
|
+
this.comparison!.statistics.displayTextsChanged++;
|
|
422
|
+
}
|
|
423
|
+
this.comparison!.statistics.hyperlinksModified++;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
hyperlinkIndex++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Generate HTML report of changes
|
|
436
|
+
*/
|
|
437
|
+
generateHTMLReport(comparison: ProcessingComparison): string {
|
|
438
|
+
const html = `
|
|
439
|
+
<!DOCTYPE html>
|
|
440
|
+
<html>
|
|
441
|
+
<head>
|
|
442
|
+
<title>Document Processing Changes</title>
|
|
443
|
+
<style>
|
|
444
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
445
|
+
body {
|
|
446
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
447
|
+
background: #f8f9fa;
|
|
448
|
+
color: #212529;
|
|
449
|
+
}
|
|
450
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
451
|
+
|
|
452
|
+
.header {
|
|
453
|
+
background: white;
|
|
454
|
+
border-radius: 12px;
|
|
455
|
+
padding: 24px;
|
|
456
|
+
margin-bottom: 24px;
|
|
457
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.header h1 {
|
|
461
|
+
font-size: 24px;
|
|
462
|
+
font-weight: 600;
|
|
463
|
+
margin-bottom: 16px;
|
|
464
|
+
color: #0969da;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.meta-info {
|
|
468
|
+
display: grid;
|
|
469
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
470
|
+
gap: 12px;
|
|
471
|
+
color: #656d76;
|
|
472
|
+
font-size: 14px;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.statistics {
|
|
476
|
+
background: white;
|
|
477
|
+
border-radius: 12px;
|
|
478
|
+
padding: 20px;
|
|
479
|
+
margin-bottom: 24px;
|
|
480
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.stat-grid {
|
|
484
|
+
display: grid;
|
|
485
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
486
|
+
gap: 16px;
|
|
487
|
+
margin-top: 16px;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.stat-card {
|
|
491
|
+
background: #f6f8fa;
|
|
492
|
+
padding: 12px 16px;
|
|
493
|
+
border-radius: 8px;
|
|
494
|
+
border-left: 3px solid #0969da;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.stat-card .value {
|
|
498
|
+
font-size: 24px;
|
|
499
|
+
font-weight: 600;
|
|
500
|
+
color: #0969da;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.stat-card .label {
|
|
504
|
+
font-size: 12px;
|
|
505
|
+
color: #656d76;
|
|
506
|
+
text-transform: uppercase;
|
|
507
|
+
letter-spacing: 0.5px;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.changes-section {
|
|
511
|
+
background: white;
|
|
512
|
+
border-radius: 12px;
|
|
513
|
+
padding: 20px;
|
|
514
|
+
margin-bottom: 24px;
|
|
515
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.changes-section h2 {
|
|
519
|
+
font-size: 18px;
|
|
520
|
+
font-weight: 600;
|
|
521
|
+
margin-bottom: 16px;
|
|
522
|
+
color: #24292f;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.change-item {
|
|
526
|
+
background: #f6f8fa;
|
|
527
|
+
border-radius: 8px;
|
|
528
|
+
padding: 16px;
|
|
529
|
+
margin-bottom: 12px;
|
|
530
|
+
border: 1px solid #d1d9e0;
|
|
531
|
+
transition: all 0.2s;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.change-item:hover {
|
|
535
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
536
|
+
transform: translateY(-1px);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.change-location {
|
|
540
|
+
font-size: 12px;
|
|
541
|
+
color: #656d76;
|
|
542
|
+
margin-bottom: 8px;
|
|
543
|
+
font-weight: 500;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.change-content {
|
|
547
|
+
display: grid;
|
|
548
|
+
gap: 8px;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.before, .after {
|
|
552
|
+
padding: 8px 12px;
|
|
553
|
+
border-radius: 6px;
|
|
554
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
555
|
+
font-size: 13px;
|
|
556
|
+
word-break: break-all;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.before {
|
|
560
|
+
background: #ffebe9;
|
|
561
|
+
color: #cf222e;
|
|
562
|
+
border: 1px solid #ff8182;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.after {
|
|
566
|
+
background: #dafbe1;
|
|
567
|
+
color: #1a7f37;
|
|
568
|
+
border: 1px solid #7ee787;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.change-reason {
|
|
572
|
+
font-size: 12px;
|
|
573
|
+
color: #0969da;
|
|
574
|
+
margin-top: 8px;
|
|
575
|
+
font-style: italic;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.empty-state {
|
|
579
|
+
text-align: center;
|
|
580
|
+
padding: 40px;
|
|
581
|
+
color: #656d76;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.processing-time {
|
|
585
|
+
position: fixed;
|
|
586
|
+
bottom: 20px;
|
|
587
|
+
right: 20px;
|
|
588
|
+
background: #0969da;
|
|
589
|
+
color: white;
|
|
590
|
+
padding: 8px 16px;
|
|
591
|
+
border-radius: 20px;
|
|
592
|
+
font-size: 12px;
|
|
593
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
594
|
+
}
|
|
595
|
+
</style>
|
|
596
|
+
</head>
|
|
597
|
+
<body>
|
|
598
|
+
<div class="container">
|
|
599
|
+
<div class="header">
|
|
600
|
+
<h1>📄 Document Processing Changes Report</h1>
|
|
601
|
+
<div class="meta-info">
|
|
602
|
+
<div>📁 <strong>File:</strong> ${comparison.documentPath}</div>
|
|
603
|
+
<div>🕐 <strong>Start:</strong> ${comparison.processingStartTime.toLocaleString()}</div>
|
|
604
|
+
<div>🕑 <strong>End:</strong> ${comparison.processingEndTime?.toLocaleString() || 'In Progress'}</div>
|
|
605
|
+
<div>⚡ <strong>Duration:</strong> ${comparison.statistics.processingDurationMs}ms</div>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<div class="statistics">
|
|
610
|
+
<h2>Processing Statistics</h2>
|
|
611
|
+
<div class="stat-grid">
|
|
612
|
+
<div class="stat-card">
|
|
613
|
+
<div class="value">${comparison.statistics.totalChanges}</div>
|
|
614
|
+
<div class="label">Total Changes</div>
|
|
615
|
+
</div>
|
|
616
|
+
<div class="stat-card">
|
|
617
|
+
<div class="value">${comparison.statistics.hyperlinksModified}</div>
|
|
618
|
+
<div class="label">Hyperlinks Modified</div>
|
|
619
|
+
</div>
|
|
620
|
+
<div class="stat-card">
|
|
621
|
+
<div class="value">${comparison.statistics.urlsChanged}</div>
|
|
622
|
+
<div class="label">URLs Changed</div>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="stat-card">
|
|
625
|
+
<div class="value">${comparison.statistics.displayTextsChanged}</div>
|
|
626
|
+
<div class="label">Texts Updated</div>
|
|
627
|
+
</div>
|
|
628
|
+
<div class="stat-card">
|
|
629
|
+
<div class="value">${comparison.statistics.contentIdsAppended}</div>
|
|
630
|
+
<div class="label">Content IDs Added</div>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="stat-card">
|
|
633
|
+
<div class="value">${comparison.statistics.stylesApplied}</div>
|
|
634
|
+
<div class="label">Styles Applied</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
${this.renderHyperlinkChanges(comparison)}
|
|
640
|
+
${this.renderStyleChanges(comparison)}
|
|
641
|
+
${this.renderContentChanges(comparison)}
|
|
642
|
+
</div>
|
|
643
|
+
|
|
644
|
+
<div class="processing-time">
|
|
645
|
+
Processing took ${comparison.statistics.processingDurationMs}ms
|
|
646
|
+
</div>
|
|
647
|
+
</body>
|
|
648
|
+
</html>
|
|
649
|
+
`;
|
|
650
|
+
|
|
651
|
+
return html;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Render hyperlink changes section
|
|
656
|
+
*/
|
|
657
|
+
private renderHyperlinkChanges(comparison: ProcessingComparison): string {
|
|
658
|
+
if (comparison.hyperlinkChanges.length === 0) {
|
|
659
|
+
return `
|
|
660
|
+
<div class="changes-section">
|
|
661
|
+
<h2>🔗 Hyperlink Changes</h2>
|
|
662
|
+
<div class="empty-state">No hyperlink changes detected</div>
|
|
663
|
+
</div>
|
|
664
|
+
`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const changes = comparison.hyperlinkChanges
|
|
668
|
+
.map(
|
|
669
|
+
(change) => `
|
|
670
|
+
<div class="change-item">
|
|
671
|
+
<div class="change-location">📍 Paragraph ${change.paragraphIndex + 1}, Hyperlink ${change.hyperlinkIndex + 1}</div>
|
|
672
|
+
<div class="change-content">
|
|
673
|
+
${
|
|
674
|
+
change.originalUrl !== change.modifiedUrl
|
|
675
|
+
? `
|
|
676
|
+
<div><strong>URL:</strong></div>
|
|
677
|
+
<div class="before">🔴 ${this.escapeHtml(change.originalUrl)}</div>
|
|
678
|
+
<div class="after">🟢 ${this.escapeHtml(change.modifiedUrl)}</div>
|
|
679
|
+
`
|
|
680
|
+
: ''
|
|
681
|
+
}
|
|
682
|
+
${
|
|
683
|
+
change.originalText !== change.modifiedText
|
|
684
|
+
? `
|
|
685
|
+
<div><strong>Display Text:</strong></div>
|
|
686
|
+
<div class="before">🔴 ${this.escapeHtml(change.originalText)}</div>
|
|
687
|
+
<div class="after">🟢 ${this.escapeHtml(change.modifiedText)}</div>
|
|
688
|
+
`
|
|
689
|
+
: ''
|
|
690
|
+
}
|
|
691
|
+
</div>
|
|
692
|
+
<div class="change-reason">💡 ${change.changeReason}</div>
|
|
693
|
+
</div>
|
|
694
|
+
`
|
|
695
|
+
)
|
|
696
|
+
.join('');
|
|
697
|
+
|
|
698
|
+
return `
|
|
699
|
+
<div class="changes-section">
|
|
700
|
+
<h2>🔗 Hyperlink Changes (${comparison.hyperlinkChanges.length})</h2>
|
|
701
|
+
${changes}
|
|
702
|
+
</div>
|
|
703
|
+
`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Render style changes section
|
|
708
|
+
*/
|
|
709
|
+
private renderStyleChanges(comparison: ProcessingComparison): string {
|
|
710
|
+
if (comparison.styleChanges.length === 0) {
|
|
711
|
+
return `
|
|
712
|
+
<div class="changes-section">
|
|
713
|
+
<h2>🎨 Style Changes</h2>
|
|
714
|
+
<div class="empty-state">No style changes detected</div>
|
|
715
|
+
</div>
|
|
716
|
+
`;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const changes = comparison.styleChanges
|
|
720
|
+
.map(
|
|
721
|
+
(change) => `
|
|
722
|
+
<div class="change-item">
|
|
723
|
+
<div class="change-location">📍 Paragraph ${change.paragraphIndex + 1}</div>
|
|
724
|
+
<div class="change-content">
|
|
725
|
+
<div><strong>Applied Style:</strong> ${change.styleName} (${change.styleId})</div>
|
|
726
|
+
${Object.entries(change.properties)
|
|
727
|
+
.map(([key, value]) => `<div style="margin-left: 20px;">• ${key}: ${value}</div>`)
|
|
728
|
+
.join('')}
|
|
729
|
+
</div>
|
|
730
|
+
</div>
|
|
731
|
+
`
|
|
732
|
+
)
|
|
733
|
+
.join('');
|
|
734
|
+
|
|
735
|
+
return `
|
|
736
|
+
<div class="changes-section">
|
|
737
|
+
<h2>🎨 Style Changes (${comparison.styleChanges.length})</h2>
|
|
738
|
+
${changes}
|
|
739
|
+
</div>
|
|
740
|
+
`;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Render content changes section
|
|
745
|
+
*/
|
|
746
|
+
private renderContentChanges(comparison: ProcessingComparison): string {
|
|
747
|
+
if (comparison.contentChanges.length === 0) {
|
|
748
|
+
return `
|
|
749
|
+
<div class="changes-section">
|
|
750
|
+
<h2>📝 Content Changes</h2>
|
|
751
|
+
<div class="empty-state">No content changes detected</div>
|
|
752
|
+
</div>
|
|
753
|
+
`;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const changes = comparison.contentChanges
|
|
757
|
+
.map(
|
|
758
|
+
(change) => `
|
|
759
|
+
<div class="change-item">
|
|
760
|
+
<div class="change-location">📍 Paragraph ${change.paragraphIndex + 1}</div>
|
|
761
|
+
<div class="change-content">
|
|
762
|
+
${
|
|
763
|
+
change.type === 'added'
|
|
764
|
+
? `
|
|
765
|
+
<div class="after">🟢 Added: ${this.escapeHtml(change.modifiedContent || '')}</div>
|
|
766
|
+
`
|
|
767
|
+
: ''
|
|
768
|
+
}
|
|
769
|
+
${
|
|
770
|
+
change.type === 'removed'
|
|
771
|
+
? `
|
|
772
|
+
<div class="before">🔴 Removed: ${this.escapeHtml(change.originalContent || '')}</div>
|
|
773
|
+
`
|
|
774
|
+
: ''
|
|
775
|
+
}
|
|
776
|
+
${change.type === 'modified' && change.diff ? this.renderDiff(change.diff) : ''}
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
`
|
|
780
|
+
)
|
|
781
|
+
.join('');
|
|
782
|
+
|
|
783
|
+
return `
|
|
784
|
+
<div class="changes-section">
|
|
785
|
+
<h2>📝 Content Changes (${comparison.contentChanges.length})</h2>
|
|
786
|
+
${changes}
|
|
787
|
+
</div>
|
|
788
|
+
`;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Render diff with inline changes
|
|
793
|
+
*/
|
|
794
|
+
private renderDiff(changes: Change[]): string {
|
|
795
|
+
let html = '<div style="line-height: 1.6;">';
|
|
796
|
+
|
|
797
|
+
for (const change of changes) {
|
|
798
|
+
if (change.added) {
|
|
799
|
+
html += `<span style="background: #dafbe1; color: #1a7f37; padding: 2px 4px; border-radius: 3px;">${this.escapeHtml(change.value)}</span>`;
|
|
800
|
+
} else if (change.removed) {
|
|
801
|
+
html += `<span style="background: #ffebe9; color: #cf222e; padding: 2px 4px; text-decoration: line-through; border-radius: 3px;">${this.escapeHtml(change.value)}</span>`;
|
|
802
|
+
} else {
|
|
803
|
+
html += this.escapeHtml(change.value);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
html += '</div>';
|
|
808
|
+
return html;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Escape HTML for safe rendering
|
|
813
|
+
*/
|
|
814
|
+
private escapeHtml(text: string): string {
|
|
815
|
+
const div = document?.createElement ? document.createElement('div') : null;
|
|
816
|
+
if (div) {
|
|
817
|
+
div.textContent = text;
|
|
818
|
+
return div.innerHTML;
|
|
819
|
+
}
|
|
820
|
+
// Fallback for Node.js environment
|
|
821
|
+
return text
|
|
822
|
+
.replace(/&/g, '&')
|
|
823
|
+
.replace(/</g, '<')
|
|
824
|
+
.replace(/>/g, '>')
|
|
825
|
+
.replace(/"/g, '"')
|
|
826
|
+
.replace(/'/g, ''');
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Reset tracking for new document
|
|
831
|
+
*/
|
|
832
|
+
reset(): void {
|
|
833
|
+
this.comparison = null;
|
|
834
|
+
this.originalHyperlinks.clear();
|
|
835
|
+
this.originalStyles.clear();
|
|
836
|
+
this.changeIdCounter = 0;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Get current comparison
|
|
841
|
+
*/
|
|
842
|
+
getCurrentComparison(): ProcessingComparison | null {
|
|
843
|
+
return this.comparison;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Export singleton instance
|
|
848
|
+
export const documentProcessingComparison = new DocumentProcessingComparison();
|
|
849
|
+
|
|
850
|
+
// Type for hyperlink snapshot
|
|
851
|
+
interface HyperlinkSnapshot {
|
|
852
|
+
paragraphIndex: number;
|
|
853
|
+
hyperlinkIndex: number;
|
|
854
|
+
url: string;
|
|
855
|
+
text: string;
|
|
856
|
+
}
|