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,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff Utilities - Word-level text comparison for document comparison views
|
|
3
|
+
*
|
|
4
|
+
* Uses the 'diff' library (already installed) for generating word-level
|
|
5
|
+
* differences between pre-processing and post-processing document content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { diffWords, diffLines, Change } from 'diff';
|
|
9
|
+
import type { DiffSegment, ParagraphDiff, DocumentDiff } from '@/types/editor';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate word-level diff between two strings
|
|
13
|
+
*
|
|
14
|
+
* @param original - Original text (before processing)
|
|
15
|
+
* @param modified - Modified text (after processing)
|
|
16
|
+
* @returns Arrays of diff segments for each side
|
|
17
|
+
*/
|
|
18
|
+
export function generateWordDiff(
|
|
19
|
+
original: string,
|
|
20
|
+
modified: string
|
|
21
|
+
): { originalSegments: DiffSegment[]; modifiedSegments: DiffSegment[] } {
|
|
22
|
+
const changes = diffWords(original, modified);
|
|
23
|
+
|
|
24
|
+
const originalSegments: DiffSegment[] = [];
|
|
25
|
+
const modifiedSegments: DiffSegment[] = [];
|
|
26
|
+
|
|
27
|
+
for (const change of changes) {
|
|
28
|
+
if (change.added) {
|
|
29
|
+
// Text was added (only in modified)
|
|
30
|
+
modifiedSegments.push({
|
|
31
|
+
text: change.value,
|
|
32
|
+
type: 'added',
|
|
33
|
+
});
|
|
34
|
+
} else if (change.removed) {
|
|
35
|
+
// Text was removed (only in original)
|
|
36
|
+
originalSegments.push({
|
|
37
|
+
text: change.value,
|
|
38
|
+
type: 'removed',
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
// Text is unchanged (in both)
|
|
42
|
+
originalSegments.push({
|
|
43
|
+
text: change.value,
|
|
44
|
+
type: 'unchanged',
|
|
45
|
+
});
|
|
46
|
+
modifiedSegments.push({
|
|
47
|
+
text: change.value,
|
|
48
|
+
type: 'unchanged',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { originalSegments, modifiedSegments };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a complete document diff from paragraph arrays
|
|
58
|
+
*
|
|
59
|
+
* @param original - Array of original paragraph texts
|
|
60
|
+
* @param modified - Array of modified paragraph texts
|
|
61
|
+
* @returns Complete document diff with per-paragraph analysis
|
|
62
|
+
*/
|
|
63
|
+
export function generateDocumentDiff(
|
|
64
|
+
original: string[],
|
|
65
|
+
modified: string[]
|
|
66
|
+
): DocumentDiff {
|
|
67
|
+
const paragraphDiffs: ParagraphDiff[] = [];
|
|
68
|
+
|
|
69
|
+
let wordsAdded = 0;
|
|
70
|
+
let wordsRemoved = 0;
|
|
71
|
+
let wordsModified = 0;
|
|
72
|
+
let changedParagraphs = 0;
|
|
73
|
+
let addedParagraphs = 0;
|
|
74
|
+
let removedParagraphs = 0;
|
|
75
|
+
|
|
76
|
+
// Use line-level diff first to align paragraphs
|
|
77
|
+
const lineDiffs = diffLines(original.join('\n'), modified.join('\n'));
|
|
78
|
+
|
|
79
|
+
let originalIndex = 0;
|
|
80
|
+
let modifiedIndex = 0;
|
|
81
|
+
let paragraphIndex = 0;
|
|
82
|
+
|
|
83
|
+
for (const lineDiff of lineDiffs) {
|
|
84
|
+
const lines = lineDiff.value.split('\n').filter((line) => line !== '');
|
|
85
|
+
|
|
86
|
+
if (lineDiff.added) {
|
|
87
|
+
// Paragraphs were added
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
paragraphDiffs.push({
|
|
90
|
+
index: paragraphIndex++,
|
|
91
|
+
original: '',
|
|
92
|
+
modified: line,
|
|
93
|
+
originalSegments: [],
|
|
94
|
+
modifiedSegments: [{ text: line, type: 'added' }],
|
|
95
|
+
hasChanges: true,
|
|
96
|
+
});
|
|
97
|
+
addedParagraphs++;
|
|
98
|
+
wordsAdded += countWords(line);
|
|
99
|
+
modifiedIndex++;
|
|
100
|
+
}
|
|
101
|
+
} else if (lineDiff.removed) {
|
|
102
|
+
// Paragraphs were removed
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
paragraphDiffs.push({
|
|
105
|
+
index: paragraphIndex++,
|
|
106
|
+
original: line,
|
|
107
|
+
modified: '',
|
|
108
|
+
originalSegments: [{ text: line, type: 'removed' }],
|
|
109
|
+
modifiedSegments: [],
|
|
110
|
+
hasChanges: true,
|
|
111
|
+
});
|
|
112
|
+
removedParagraphs++;
|
|
113
|
+
wordsRemoved += countWords(line);
|
|
114
|
+
originalIndex++;
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// Paragraphs match - but content might differ
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
const origText = original[originalIndex] || '';
|
|
120
|
+
const modText = modified[modifiedIndex] || '';
|
|
121
|
+
|
|
122
|
+
if (origText === modText) {
|
|
123
|
+
// Exact match
|
|
124
|
+
paragraphDiffs.push({
|
|
125
|
+
index: paragraphIndex++,
|
|
126
|
+
original: origText,
|
|
127
|
+
modified: modText,
|
|
128
|
+
originalSegments: [{ text: origText, type: 'unchanged' }],
|
|
129
|
+
modifiedSegments: [{ text: modText, type: 'unchanged' }],
|
|
130
|
+
hasChanges: false,
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
// Content changed within paragraph
|
|
134
|
+
const { originalSegments, modifiedSegments } = generateWordDiff(
|
|
135
|
+
origText,
|
|
136
|
+
modText
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
paragraphDiffs.push({
|
|
140
|
+
index: paragraphIndex++,
|
|
141
|
+
original: origText,
|
|
142
|
+
modified: modText,
|
|
143
|
+
originalSegments,
|
|
144
|
+
modifiedSegments,
|
|
145
|
+
hasChanges: true,
|
|
146
|
+
});
|
|
147
|
+
changedParagraphs++;
|
|
148
|
+
|
|
149
|
+
// Count word changes
|
|
150
|
+
const origWords = countWords(origText);
|
|
151
|
+
const modWords = countWords(modText);
|
|
152
|
+
wordsModified += Math.abs(modWords - origWords);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
originalIndex++;
|
|
156
|
+
modifiedIndex++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Handle any remaining paragraphs
|
|
162
|
+
while (originalIndex < original.length) {
|
|
163
|
+
const origText = original[originalIndex];
|
|
164
|
+
paragraphDiffs.push({
|
|
165
|
+
index: paragraphIndex++,
|
|
166
|
+
original: origText,
|
|
167
|
+
modified: '',
|
|
168
|
+
originalSegments: [{ text: origText, type: 'removed' }],
|
|
169
|
+
modifiedSegments: [],
|
|
170
|
+
hasChanges: true,
|
|
171
|
+
});
|
|
172
|
+
removedParagraphs++;
|
|
173
|
+
wordsRemoved += countWords(origText);
|
|
174
|
+
originalIndex++;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
while (modifiedIndex < modified.length) {
|
|
178
|
+
const modText = modified[modifiedIndex];
|
|
179
|
+
paragraphDiffs.push({
|
|
180
|
+
index: paragraphIndex++,
|
|
181
|
+
original: '',
|
|
182
|
+
modified: modText,
|
|
183
|
+
originalSegments: [],
|
|
184
|
+
modifiedSegments: [{ text: modText, type: 'added' }],
|
|
185
|
+
hasChanges: true,
|
|
186
|
+
});
|
|
187
|
+
addedParagraphs++;
|
|
188
|
+
wordsAdded += countWords(modText);
|
|
189
|
+
modifiedIndex++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
original,
|
|
194
|
+
modified,
|
|
195
|
+
paragraphDiffs,
|
|
196
|
+
stats: {
|
|
197
|
+
totalParagraphs: paragraphDiffs.length,
|
|
198
|
+
changedParagraphs,
|
|
199
|
+
addedParagraphs,
|
|
200
|
+
removedParagraphs,
|
|
201
|
+
wordsAdded,
|
|
202
|
+
wordsRemoved,
|
|
203
|
+
wordsModified,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Count words in a string
|
|
210
|
+
*/
|
|
211
|
+
function countWords(text: string): number {
|
|
212
|
+
return text
|
|
213
|
+
.trim()
|
|
214
|
+
.split(/\s+/)
|
|
215
|
+
.filter((word) => word.length > 0).length;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Simplify diff result by merging consecutive segments of the same type
|
|
220
|
+
*
|
|
221
|
+
* @param segments - Array of diff segments
|
|
222
|
+
* @returns Merged segments
|
|
223
|
+
*/
|
|
224
|
+
export function mergeConsecutiveSegments(segments: DiffSegment[]): DiffSegment[] {
|
|
225
|
+
if (segments.length === 0) return segments;
|
|
226
|
+
|
|
227
|
+
const merged: DiffSegment[] = [];
|
|
228
|
+
let current = { ...segments[0] };
|
|
229
|
+
|
|
230
|
+
for (let i = 1; i < segments.length; i++) {
|
|
231
|
+
const segment = segments[i];
|
|
232
|
+
if (segment.type === current.type) {
|
|
233
|
+
current.text += segment.text;
|
|
234
|
+
} else {
|
|
235
|
+
merged.push(current);
|
|
236
|
+
current = { ...segment };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
merged.push(current);
|
|
241
|
+
return merged;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Calculate similarity percentage between two texts
|
|
246
|
+
*
|
|
247
|
+
* @param original - Original text
|
|
248
|
+
* @param modified - Modified text
|
|
249
|
+
* @returns Percentage similarity (0-100)
|
|
250
|
+
*/
|
|
251
|
+
export function calculateSimilarity(original: string, modified: string): number {
|
|
252
|
+
if (original === modified) return 100;
|
|
253
|
+
if (original.length === 0 && modified.length === 0) return 100;
|
|
254
|
+
if (original.length === 0 || modified.length === 0) return 0;
|
|
255
|
+
|
|
256
|
+
const changes = diffWords(original, modified);
|
|
257
|
+
let unchangedLength = 0;
|
|
258
|
+
let totalLength = 0;
|
|
259
|
+
|
|
260
|
+
for (const change of changes) {
|
|
261
|
+
totalLength += change.value.length;
|
|
262
|
+
if (!change.added && !change.removed) {
|
|
263
|
+
unchangedLength += change.value.length;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return totalLength > 0 ? Math.round((unchangedLength / totalLength) * 100) : 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get a summary of changes between two paragraph arrays
|
|
272
|
+
*
|
|
273
|
+
* @param original - Original paragraphs
|
|
274
|
+
* @param modified - Modified paragraphs
|
|
275
|
+
* @returns Human-readable summary
|
|
276
|
+
*/
|
|
277
|
+
export function getDiffSummary(original: string[], modified: string[]): string {
|
|
278
|
+
const diff = generateDocumentDiff(original, modified);
|
|
279
|
+
const { stats } = diff;
|
|
280
|
+
|
|
281
|
+
const parts: string[] = [];
|
|
282
|
+
|
|
283
|
+
if (stats.changedParagraphs > 0) {
|
|
284
|
+
parts.push(`${stats.changedParagraphs} modified`);
|
|
285
|
+
}
|
|
286
|
+
if (stats.addedParagraphs > 0) {
|
|
287
|
+
parts.push(`${stats.addedParagraphs} added`);
|
|
288
|
+
}
|
|
289
|
+
if (stats.removedParagraphs > 0) {
|
|
290
|
+
parts.push(`${stats.removedParagraphs} removed`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (parts.length === 0) {
|
|
294
|
+
return 'No changes';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return `${parts.join(', ')} paragraphs`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Filter paragraph diffs to only show changed paragraphs
|
|
302
|
+
*
|
|
303
|
+
* @param diffs - All paragraph diffs
|
|
304
|
+
* @param includeContext - Number of unchanged paragraphs to include around changes
|
|
305
|
+
* @returns Filtered diffs with context markers
|
|
306
|
+
*/
|
|
307
|
+
export function filterChangedParagraphs(
|
|
308
|
+
diffs: ParagraphDiff[],
|
|
309
|
+
includeContext: number = 2
|
|
310
|
+
): ParagraphDiff[] {
|
|
311
|
+
if (includeContext === 0) {
|
|
312
|
+
return diffs.filter((d) => d.hasChanges);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const result: ParagraphDiff[] = [];
|
|
316
|
+
const showIndices = new Set<number>();
|
|
317
|
+
|
|
318
|
+
// Mark all changed paragraphs and their context
|
|
319
|
+
diffs.forEach((diff, index) => {
|
|
320
|
+
if (diff.hasChanges) {
|
|
321
|
+
for (
|
|
322
|
+
let i = Math.max(0, index - includeContext);
|
|
323
|
+
i <= Math.min(diffs.length - 1, index + includeContext);
|
|
324
|
+
i++
|
|
325
|
+
) {
|
|
326
|
+
showIndices.add(i);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Build result with indices in order
|
|
332
|
+
const sortedIndices = Array.from(showIndices).sort((a, b) => a - b);
|
|
333
|
+
for (const index of sortedIndices) {
|
|
334
|
+
result.push(diffs[index]);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export default {
|
|
341
|
+
generateWordDiff,
|
|
342
|
+
generateDocumentDiff,
|
|
343
|
+
mergeConsecutiveSegments,
|
|
344
|
+
calculateSimilarity,
|
|
345
|
+
getDiffSummary,
|
|
346
|
+
filterChangedParagraphs,
|
|
347
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Utility Functions for DocXMLater
|
|
3
|
+
*
|
|
4
|
+
* Provides safe wrappers around DocXMLater operations with guaranteed resource cleanup.
|
|
5
|
+
* These utilities ensure documents are always disposed, preventing memory leaks.
|
|
6
|
+
*
|
|
7
|
+
* @module documentUtils
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Document } from 'docxmlater';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for document loading
|
|
14
|
+
*/
|
|
15
|
+
export interface DocumentLoadOptions {
|
|
16
|
+
/** Enable strict XML parsing (default: false for compatibility) */
|
|
17
|
+
strictParsing?: boolean;
|
|
18
|
+
/** How to handle tracked changes: 'preserve' keeps them, 'accept' accepts them */
|
|
19
|
+
revisionHandling?: 'preserve' | 'accept';
|
|
20
|
+
/** Whether to accept all revisions on load (default: false) */
|
|
21
|
+
acceptRevisions?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute an operation on a document loaded from file with guaranteed disposal.
|
|
26
|
+
*
|
|
27
|
+
* This function ensures that the document is always disposed, even if the operation
|
|
28
|
+
* throws an error. Use this for any document processing that doesn't need to keep
|
|
29
|
+
* the document open after the operation completes.
|
|
30
|
+
*
|
|
31
|
+
* @template T - The return type of the operation
|
|
32
|
+
* @param filePath - Path to the DOCX file
|
|
33
|
+
* @param operation - Async function that receives the loaded document
|
|
34
|
+
* @param options - Optional loading configuration
|
|
35
|
+
* @returns Promise resolving to the operation result
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Extract all text from a document
|
|
40
|
+
* const text = await withDocumentFromFile('report.docx', async (doc) => {
|
|
41
|
+
* const paragraphs = doc.getAllParagraphs();
|
|
42
|
+
* return paragraphs.map(p => p.getText()).join('\n');
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Count hyperlinks in a document
|
|
46
|
+
* const count = await withDocumentFromFile('document.docx', async (doc) => {
|
|
47
|
+
* const paragraphs = doc.getAllParagraphs();
|
|
48
|
+
* let hyperlinkCount = 0;
|
|
49
|
+
* for (const para of paragraphs) {
|
|
50
|
+
* for (const item of para.getContent()) {
|
|
51
|
+
* if (typeof item.getUrl === 'function') hyperlinkCount++;
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* return hyperlinkCount;
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export async function withDocumentFromFile<T>(
|
|
59
|
+
filePath: string,
|
|
60
|
+
operation: (doc: Document) => Promise<T>,
|
|
61
|
+
options?: DocumentLoadOptions
|
|
62
|
+
): Promise<T> {
|
|
63
|
+
const doc = await Document.load(filePath, {
|
|
64
|
+
strictParsing: options?.strictParsing ?? false,
|
|
65
|
+
revisionHandling: options?.revisionHandling,
|
|
66
|
+
acceptRevisions: options?.acceptRevisions,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
return await operation(doc);
|
|
71
|
+
} finally {
|
|
72
|
+
try {
|
|
73
|
+
doc.dispose();
|
|
74
|
+
} catch {
|
|
75
|
+
// Ignore disposal errors - document may already be disposed
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Execute an operation on a document loaded from buffer with guaranteed disposal.
|
|
82
|
+
*
|
|
83
|
+
* Similar to withDocumentFromFile, but loads from a Buffer instead of a file path.
|
|
84
|
+
* Useful for processing documents from HTTP responses, databases, or other in-memory sources.
|
|
85
|
+
*
|
|
86
|
+
* @template T - The return type of the operation
|
|
87
|
+
* @param buffer - Buffer containing the DOCX file data
|
|
88
|
+
* @param operation - Async function that receives the loaded document
|
|
89
|
+
* @returns Promise resolving to the operation result
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* // Process document from HTTP response
|
|
94
|
+
* const response = await fetch('https://example.com/document.docx');
|
|
95
|
+
* const buffer = Buffer.from(await response.arrayBuffer());
|
|
96
|
+
*
|
|
97
|
+
* const result = await withDocumentFromBuffer(buffer, async (doc) => {
|
|
98
|
+
* // Process document...
|
|
99
|
+
* return { pageCount: doc.getParagraphs().length };
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export async function withDocumentFromBuffer<T>(
|
|
104
|
+
buffer: Buffer,
|
|
105
|
+
operation: (doc: Document) => Promise<T>
|
|
106
|
+
): Promise<T> {
|
|
107
|
+
const doc = await Document.loadFromBuffer(buffer);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
return await operation(doc);
|
|
111
|
+
} finally {
|
|
112
|
+
try {
|
|
113
|
+
doc.dispose();
|
|
114
|
+
} catch {
|
|
115
|
+
// Ignore disposal errors - document may already be disposed
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Execute an operation on a document and save it with guaranteed disposal.
|
|
122
|
+
*
|
|
123
|
+
* This is a convenience wrapper for the common pattern of loading a document,
|
|
124
|
+
* modifying it, and saving the result. The document is disposed after saving.
|
|
125
|
+
*
|
|
126
|
+
* @template T - The return type of the operation (in addition to saving)
|
|
127
|
+
* @param inputPath - Path to the input DOCX file
|
|
128
|
+
* @param outputPath - Path where the modified document should be saved
|
|
129
|
+
* @param operation - Async function that modifies the document and optionally returns data
|
|
130
|
+
* @param options - Optional loading configuration
|
|
131
|
+
* @returns Promise resolving to the operation result
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* // Modify document and save
|
|
136
|
+
* await withDocumentModify(
|
|
137
|
+
* 'input.docx',
|
|
138
|
+
* 'output.docx',
|
|
139
|
+
* async (doc) => {
|
|
140
|
+
* const paragraphs = doc.getAllParagraphs();
|
|
141
|
+
* for (const para of paragraphs) {
|
|
142
|
+
* for (const run of para.getRuns()) {
|
|
143
|
+
* run.setFont('Arial');
|
|
144
|
+
* }
|
|
145
|
+
* }
|
|
146
|
+
* }
|
|
147
|
+
* );
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export async function withDocumentModify<T = void>(
|
|
151
|
+
inputPath: string,
|
|
152
|
+
outputPath: string,
|
|
153
|
+
operation: (doc: Document) => Promise<T>,
|
|
154
|
+
options?: DocumentLoadOptions
|
|
155
|
+
): Promise<T> {
|
|
156
|
+
const doc = await Document.load(inputPath, {
|
|
157
|
+
strictParsing: options?.strictParsing ?? false,
|
|
158
|
+
revisionHandling: options?.revisionHandling,
|
|
159
|
+
acceptRevisions: options?.acceptRevisions,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const result = await operation(doc);
|
|
164
|
+
await doc.save(outputPath);
|
|
165
|
+
return result;
|
|
166
|
+
} finally {
|
|
167
|
+
try {
|
|
168
|
+
doc.dispose();
|
|
169
|
+
} catch {
|
|
170
|
+
// Ignore disposal errors
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Safely dispose a document, catching any errors.
|
|
177
|
+
*
|
|
178
|
+
* Use this when you need to manually dispose a document and want to ensure
|
|
179
|
+
* no exceptions are thrown, even if the document is already disposed or invalid.
|
|
180
|
+
*
|
|
181
|
+
* @param doc - The document to dispose, or null/undefined
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* let doc: Document | null = null;
|
|
186
|
+
* try {
|
|
187
|
+
* doc = await Document.load('file.docx');
|
|
188
|
+
* // ... operations ...
|
|
189
|
+
* } finally {
|
|
190
|
+
* safeDispose(doc);
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export function safeDispose(doc: Document | null | undefined): void {
|
|
195
|
+
if (doc) {
|
|
196
|
+
try {
|
|
197
|
+
doc.dispose();
|
|
198
|
+
} catch {
|
|
199
|
+
// Ignore disposal errors
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron API Guard Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides safe access to the Electron API exposed via contextBridge.
|
|
5
|
+
* These utilities handle cases where the API might not be available:
|
|
6
|
+
* - Running in a browser instead of Electron
|
|
7
|
+
* - During initial page load before preload completes
|
|
8
|
+
* - In test environments
|
|
9
|
+
* - During hot module replacement (HMR) cycles
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ElectronAPI } from '@/global.d';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if the code is running in an Electron environment.
|
|
16
|
+
*
|
|
17
|
+
* @returns true if window.electronAPI is available
|
|
18
|
+
*/
|
|
19
|
+
export function isElectronEnvironment(): boolean {
|
|
20
|
+
return typeof window !== 'undefined' && typeof window.electronAPI !== 'undefined';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the Electron API, throwing if unavailable.
|
|
25
|
+
*
|
|
26
|
+
* @throws Error if not running in Electron environment
|
|
27
|
+
* @returns The electronAPI object
|
|
28
|
+
*/
|
|
29
|
+
export function getElectronAPI(): ElectronAPI {
|
|
30
|
+
if (!isElectronEnvironment()) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'Electron API not available. This feature requires running in the Electron desktop application.'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return window.electronAPI;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the Electron API with a feature-specific error message.
|
|
40
|
+
*
|
|
41
|
+
* @param feature - Name of the feature requiring Electron (for error message)
|
|
42
|
+
* @throws Error if not running in Electron environment
|
|
43
|
+
* @returns The electronAPI object
|
|
44
|
+
*/
|
|
45
|
+
export function requireElectronAPI(feature: string): ElectronAPI {
|
|
46
|
+
if (!isElectronEnvironment()) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Electron API not available - ${feature} requires running in the Electron desktop application.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return window.electronAPI;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Safely check if a specific Electron API method is available.
|
|
56
|
+
*
|
|
57
|
+
* @param method - The method name to check (e.g., 'callPowerAutomateApi')
|
|
58
|
+
* @returns true if the method exists and is callable
|
|
59
|
+
*/
|
|
60
|
+
export function hasElectronMethod(method: keyof ElectronAPI): boolean {
|
|
61
|
+
return isElectronEnvironment() && typeof window.electronAPI[method] === 'function';
|
|
62
|
+
}
|