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,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyperlinkProcessor - Hyperlink manipulation and API integration
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Hyperlink formatting standardization
|
|
6
|
+
* - PowerAutomate API integration for URL/text updates
|
|
7
|
+
* - Custom replacement rules
|
|
8
|
+
* - Internal hyperlink repair
|
|
9
|
+
* - URL update batch processing
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
Document,
|
|
14
|
+
Hyperlink,
|
|
15
|
+
Revision,
|
|
16
|
+
} from "docxmlater";
|
|
17
|
+
import type { DetailedHyperlinkInfo, HyperlinkType } from "@/types/hyperlink";
|
|
18
|
+
import type { DocumentChange } from "@/types/session";
|
|
19
|
+
import { logger } from "@/utils/logger";
|
|
20
|
+
import { sanitizeHyperlinkText } from "@/utils/textSanitizer";
|
|
21
|
+
import { extractLookupIds } from "@/utils/urlPatterns";
|
|
22
|
+
import { hyperlinkService } from "../../HyperlinkService";
|
|
23
|
+
import { DocXMLaterProcessor } from "../DocXMLaterProcessor";
|
|
24
|
+
import { documentProcessingComparison } from "../DocumentProcessingComparison";
|
|
25
|
+
|
|
26
|
+
const log = logger.namespace("HyperlinkProcessor");
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result of URL update batch operation
|
|
30
|
+
*/
|
|
31
|
+
export interface UrlUpdateResult {
|
|
32
|
+
updated: number;
|
|
33
|
+
failed: Array<{
|
|
34
|
+
oldUrl: string;
|
|
35
|
+
newUrl: string;
|
|
36
|
+
error: unknown;
|
|
37
|
+
paragraphIndex?: number;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Result of hyperlink processing
|
|
43
|
+
*/
|
|
44
|
+
export interface HyperlinkProcessingResult {
|
|
45
|
+
updatedUrls: number;
|
|
46
|
+
updatedDisplayTexts: number;
|
|
47
|
+
standardizedCount: number;
|
|
48
|
+
changes: DocumentChange[];
|
|
49
|
+
processedLinks: Array<{
|
|
50
|
+
id: string;
|
|
51
|
+
url: string;
|
|
52
|
+
displayText: string;
|
|
53
|
+
type: HyperlinkType;
|
|
54
|
+
location: string;
|
|
55
|
+
status: "processed" | "skipped" | "error";
|
|
56
|
+
before: string;
|
|
57
|
+
after: string;
|
|
58
|
+
modifications: string[];
|
|
59
|
+
}>;
|
|
60
|
+
errorMessages: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Options for hyperlink processing
|
|
65
|
+
*/
|
|
66
|
+
export interface HyperlinkProcessingOptions {
|
|
67
|
+
apiEndpoint?: string;
|
|
68
|
+
operations?: {
|
|
69
|
+
fixContentIds?: boolean;
|
|
70
|
+
updateTitles?: boolean;
|
|
71
|
+
processHyperlinks?: boolean;
|
|
72
|
+
standardizeHyperlinkColor?: boolean;
|
|
73
|
+
fixInternalHyperlinks?: boolean;
|
|
74
|
+
};
|
|
75
|
+
trackChanges?: boolean;
|
|
76
|
+
userProfile?: {
|
|
77
|
+
firstName: string;
|
|
78
|
+
lastName: string;
|
|
79
|
+
email: string;
|
|
80
|
+
};
|
|
81
|
+
customReplacements?: Array<{
|
|
82
|
+
find: string;
|
|
83
|
+
replace: string;
|
|
84
|
+
matchType: "contains" | "exact" | "startsWith";
|
|
85
|
+
applyTo: "url" | "text" | "both";
|
|
86
|
+
}>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Hyperlink processing service
|
|
91
|
+
*/
|
|
92
|
+
export class HyperlinkProcessor {
|
|
93
|
+
private docXMLater: DocXMLaterProcessor;
|
|
94
|
+
private readonly DEBUG = process.env.NODE_ENV !== "production";
|
|
95
|
+
|
|
96
|
+
constructor() {
|
|
97
|
+
this.docXMLater = new DocXMLaterProcessor();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Standardize hyperlink formatting to Verdana 12pt blue underlined
|
|
102
|
+
*/
|
|
103
|
+
async standardizeFormatting(doc: Document): Promise<number> {
|
|
104
|
+
let standardizedCount = 0;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const hyperlinks = await this.docXMLater.extractHyperlinks(doc);
|
|
108
|
+
log.debug(`Found ${hyperlinks.length} hyperlinks to standardize`);
|
|
109
|
+
|
|
110
|
+
for (const { hyperlink, url, text } of hyperlinks) {
|
|
111
|
+
try {
|
|
112
|
+
hyperlink.setFormatting({
|
|
113
|
+
font: "Verdana",
|
|
114
|
+
size: 12,
|
|
115
|
+
color: "0000FF",
|
|
116
|
+
underline: "single",
|
|
117
|
+
bold: false,
|
|
118
|
+
italic: false,
|
|
119
|
+
});
|
|
120
|
+
standardizedCount++;
|
|
121
|
+
log.debug(`Standardized hyperlink: "${text}" (${url})`);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
log.warn(`Failed to standardize hyperlink "${text}": ${error}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
log.info(`Standardized ${standardizedCount} of ${hyperlinks.length} hyperlinks`);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
log.error(`Error standardizing hyperlink formatting: ${error}`);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return standardizedCount;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Apply URL updates to hyperlinks with track changes support
|
|
138
|
+
*/
|
|
139
|
+
async applyUrlUpdates(
|
|
140
|
+
doc: Document,
|
|
141
|
+
urlMap: Map<string, string>,
|
|
142
|
+
author: string = "DocHub"
|
|
143
|
+
): Promise<UrlUpdateResult> {
|
|
144
|
+
if (urlMap.size === 0) {
|
|
145
|
+
return { updated: 0, failed: [] };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const failedUrls: UrlUpdateResult["failed"] = [];
|
|
149
|
+
let updatedCount = 0;
|
|
150
|
+
const paragraphs = doc.getAllParagraphs();
|
|
151
|
+
const trackChangesEnabled = doc.isTrackChangesEnabled();
|
|
152
|
+
|
|
153
|
+
log.debug(`Processing ${paragraphs.length} paragraphs for URL updates`);
|
|
154
|
+
|
|
155
|
+
for (let paraIndex = 0; paraIndex < paragraphs.length; paraIndex++) {
|
|
156
|
+
const para = paragraphs[paraIndex];
|
|
157
|
+
const content = para.getContent();
|
|
158
|
+
|
|
159
|
+
for (const item of [...content]) {
|
|
160
|
+
if (item instanceof Hyperlink) {
|
|
161
|
+
const oldUrl = item.getUrl();
|
|
162
|
+
|
|
163
|
+
if (oldUrl && urlMap.has(oldUrl)) {
|
|
164
|
+
const newUrl = urlMap.get(oldUrl)!;
|
|
165
|
+
|
|
166
|
+
if (oldUrl === newUrl) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
if (trackChangesEnabled) {
|
|
172
|
+
const oldHyperlink = item.clone();
|
|
173
|
+
item.setUrl(newUrl);
|
|
174
|
+
|
|
175
|
+
const deletion = Revision.createDeletion(author, [oldHyperlink]);
|
|
176
|
+
const insertion = Revision.createInsertion(author, [item]);
|
|
177
|
+
|
|
178
|
+
const replaced = para.replaceContent(item, [deletion, insertion]);
|
|
179
|
+
|
|
180
|
+
if (replaced) {
|
|
181
|
+
const revisionManager = doc.getRevisionManager();
|
|
182
|
+
revisionManager.register(deletion);
|
|
183
|
+
revisionManager.register(insertion);
|
|
184
|
+
log.debug(`Created tracked change for hyperlink URL: ${oldUrl} -> ${newUrl}`);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
item.setUrl(newUrl);
|
|
188
|
+
log.debug(`Updated hyperlink URL: ${oldUrl} -> ${newUrl}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
updatedCount++;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
log.error(`Failed to update URL at paragraph ${paraIndex}: ${oldUrl} -> ${newUrl}`, error);
|
|
194
|
+
failedUrls.push({
|
|
195
|
+
oldUrl,
|
|
196
|
+
newUrl,
|
|
197
|
+
error,
|
|
198
|
+
paragraphIndex: paraIndex,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (failedUrls.length > 0) {
|
|
207
|
+
log.warn(`URL update completed with ${failedUrls.length} failures`);
|
|
208
|
+
} else {
|
|
209
|
+
log.info(`Successfully updated ${updatedCount} hyperlink URLs`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { updated: updatedCount, failed: failedUrls };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Process custom URL and text replacements
|
|
217
|
+
*/
|
|
218
|
+
async processCustomReplacements(
|
|
219
|
+
doc: Document,
|
|
220
|
+
replacements: NonNullable<HyperlinkProcessingOptions["customReplacements"]>
|
|
221
|
+
): Promise<{ updatedUrls: number; updatedTexts: number }> {
|
|
222
|
+
const hyperlinks = await this.docXMLater.extractHyperlinks(doc);
|
|
223
|
+
let updatedUrls = 0;
|
|
224
|
+
let updatedTexts = 0;
|
|
225
|
+
|
|
226
|
+
for (const { hyperlink, url, text } of hyperlinks) {
|
|
227
|
+
for (const rule of replacements) {
|
|
228
|
+
if (rule.applyTo === "url" || rule.applyTo === "both") {
|
|
229
|
+
if (url && this.matchesPattern(url, rule.find, rule.matchType)) {
|
|
230
|
+
const newUrl = url.replace(rule.find, rule.replace);
|
|
231
|
+
hyperlink.setUrl(newUrl);
|
|
232
|
+
updatedUrls++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (rule.applyTo === "text" || rule.applyTo === "both") {
|
|
237
|
+
if (this.matchesPattern(text, rule.find, rule.matchType)) {
|
|
238
|
+
const newText = text.replace(rule.find, rule.replace);
|
|
239
|
+
hyperlink.setText(newText);
|
|
240
|
+
updatedTexts++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { updatedUrls, updatedTexts };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Fix internal hyperlinks - repair broken bookmarks
|
|
251
|
+
*/
|
|
252
|
+
async fixInternalHyperlinks(doc: Document): Promise<number> {
|
|
253
|
+
const hyperlinks = await this.docXMLater.extractHyperlinks(doc);
|
|
254
|
+
let fixedCount = 0;
|
|
255
|
+
|
|
256
|
+
for (const { hyperlink, text } of hyperlinks) {
|
|
257
|
+
const anchor = hyperlink.getAnchor();
|
|
258
|
+
if (!anchor) continue;
|
|
259
|
+
|
|
260
|
+
const bookmarkExists = doc.hasBookmark(anchor);
|
|
261
|
+
|
|
262
|
+
if (!bookmarkExists) {
|
|
263
|
+
if (text) {
|
|
264
|
+
const matchingHeading = this.findHeadingByText(doc, text);
|
|
265
|
+
if (matchingHeading) {
|
|
266
|
+
const newBookmark = this.createBookmarkForHeading(doc, matchingHeading, anchor);
|
|
267
|
+
if (newBookmark) {
|
|
268
|
+
fixedCount++;
|
|
269
|
+
log.info(`Created bookmark "${anchor}" for heading "${text}"`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return fixedCount;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Find matching API result for a URL
|
|
281
|
+
*/
|
|
282
|
+
findMatchingApiResult(url: string, apiResultsMap: Map<string, unknown>): unknown {
|
|
283
|
+
if (!url || !apiResultsMap || apiResultsMap.size === 0) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const lookupIds = extractLookupIds(url);
|
|
288
|
+
if (!lookupIds) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (lookupIds.contentId) {
|
|
293
|
+
const result = apiResultsMap.get(lookupIds.contentId);
|
|
294
|
+
if (result) {
|
|
295
|
+
log.debug(`Matched by Content_ID: ${lookupIds.contentId}`);
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (lookupIds.documentId) {
|
|
301
|
+
const result = apiResultsMap.get(lookupIds.documentId);
|
|
302
|
+
if (result) {
|
|
303
|
+
log.debug(`Matched by Document_ID: ${lookupIds.documentId}`);
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Pattern matching helper
|
|
313
|
+
*/
|
|
314
|
+
private matchesPattern(
|
|
315
|
+
text: string,
|
|
316
|
+
pattern: string,
|
|
317
|
+
matchType: "contains" | "exact" | "startsWith"
|
|
318
|
+
): boolean {
|
|
319
|
+
switch (matchType) {
|
|
320
|
+
case "exact":
|
|
321
|
+
return text === pattern;
|
|
322
|
+
case "startsWith":
|
|
323
|
+
return text.startsWith(pattern);
|
|
324
|
+
case "contains":
|
|
325
|
+
default:
|
|
326
|
+
return text.includes(pattern);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Find heading by text content
|
|
332
|
+
*/
|
|
333
|
+
private findHeadingByText(doc: Document, searchText: string): unknown | null {
|
|
334
|
+
const normalizedSearch = searchText.trim().toLowerCase();
|
|
335
|
+
const paragraphs = doc.getAllParagraphs();
|
|
336
|
+
|
|
337
|
+
for (const para of paragraphs) {
|
|
338
|
+
const style = para.getStyle();
|
|
339
|
+
|
|
340
|
+
if (style && (style.startsWith("Heading") || style.includes("Heading"))) {
|
|
341
|
+
const paraText = (para.getText() || "").trim().toLowerCase();
|
|
342
|
+
if (paraText === normalizedSearch) {
|
|
343
|
+
return para;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Create bookmark for heading
|
|
353
|
+
*/
|
|
354
|
+
private createBookmarkForHeading(
|
|
355
|
+
doc: Document,
|
|
356
|
+
heading: unknown,
|
|
357
|
+
bookmarkName: string
|
|
358
|
+
): boolean {
|
|
359
|
+
try {
|
|
360
|
+
// Implementation depends on docxmlater API
|
|
361
|
+
// This is a placeholder for the actual implementation
|
|
362
|
+
return false;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
log.warn(`Failed to create bookmark: ${error}`);
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export const hyperlinkProcessor = new HyperlinkProcessor();
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListProcessor - List formatting and bullet/numbering operations
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - List bullet settings and uniformity
|
|
6
|
+
* - Numbered list formatting
|
|
7
|
+
* - List prefix standardization (font, size, color)
|
|
8
|
+
* - Indentation configuration
|
|
9
|
+
* - Spacing between list items
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Document, inchesToTwips } from "docxmlater";
|
|
13
|
+
import { logger } from "@/utils/logger";
|
|
14
|
+
|
|
15
|
+
const log = logger.namespace("ListProcessor");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for list indentation levels
|
|
19
|
+
*/
|
|
20
|
+
export interface ListIndentationLevel {
|
|
21
|
+
level: number;
|
|
22
|
+
symbolIndent: number; // Symbol/bullet position in inches
|
|
23
|
+
textIndent: number; // Text position in inches
|
|
24
|
+
bulletChar?: string;
|
|
25
|
+
numberedFormat?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* List bullet settings
|
|
30
|
+
*/
|
|
31
|
+
export interface ListBulletSettings {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
indentationLevels: ListIndentationLevel[];
|
|
34
|
+
spacingBetweenItems: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Result of list processing
|
|
39
|
+
*/
|
|
40
|
+
export interface ListProcessingResult {
|
|
41
|
+
listsUpdated: number;
|
|
42
|
+
levelsProcessed: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* List processing service
|
|
47
|
+
*/
|
|
48
|
+
export class ListProcessor {
|
|
49
|
+
private readonly DEBUG = process.env.NODE_ENV !== "production";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Apply list indentation settings to all lists in the document
|
|
53
|
+
*/
|
|
54
|
+
async applyListIndentation(
|
|
55
|
+
doc: Document,
|
|
56
|
+
settings: ListBulletSettings
|
|
57
|
+
): Promise<ListProcessingResult> {
|
|
58
|
+
if (!settings.enabled || !settings.indentationLevels.length) {
|
|
59
|
+
return { listsUpdated: 0, levelsProcessed: 0 };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let listsUpdated = 0;
|
|
63
|
+
let levelsProcessed = 0;
|
|
64
|
+
const paragraphs = doc.getAllParagraphs();
|
|
65
|
+
|
|
66
|
+
for (const para of paragraphs) {
|
|
67
|
+
const numbering = para.getNumbering();
|
|
68
|
+
if (!numbering) continue;
|
|
69
|
+
|
|
70
|
+
const level = numbering.level || 0;
|
|
71
|
+
const indentSetting = settings.indentationLevels.find((l) => l.level === level);
|
|
72
|
+
|
|
73
|
+
if (indentSetting) {
|
|
74
|
+
// Validate indentation - symbolIndent must be less than textIndent
|
|
75
|
+
if (indentSetting.symbolIndent >= indentSetting.textIndent) {
|
|
76
|
+
log.warn(
|
|
77
|
+
`Invalid indentation for level ${level}: symbolIndent (${indentSetting.symbolIndent}) ` +
|
|
78
|
+
`must be less than textIndent (${indentSetting.textIndent}). Skipping.`
|
|
79
|
+
);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Apply indentation using individual methods
|
|
85
|
+
para.setLeftIndent(inchesToTwips(indentSetting.textIndent));
|
|
86
|
+
// Hanging indent is implemented as a negative first-line indent
|
|
87
|
+
const hangingTwips = inchesToTwips(indentSetting.textIndent - indentSetting.symbolIndent);
|
|
88
|
+
para.setFirstLineIndent(-hangingTwips);
|
|
89
|
+
|
|
90
|
+
// Apply spacing if configured
|
|
91
|
+
if (settings.spacingBetweenItems > 0) {
|
|
92
|
+
para.setSpaceAfter(settings.spacingBetweenItems * 20); // Convert to twips
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
listsUpdated++;
|
|
96
|
+
levelsProcessed++;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
log.warn(`Failed to apply indentation to list item: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log.info(`Applied indentation to ${listsUpdated} list items`);
|
|
104
|
+
return { listsUpdated, levelsProcessed };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Standardize list prefix formatting (bullet/number font, size, color)
|
|
109
|
+
* Uses NumberingLevel API setters instead of raw XML manipulation.
|
|
110
|
+
*/
|
|
111
|
+
async standardizeListPrefixFormatting(doc: Document): Promise<number> {
|
|
112
|
+
let standardizedCount = 0;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const numberingManager = doc.getNumberingManager();
|
|
116
|
+
if (!numberingManager) {
|
|
117
|
+
log.warn("No numbering manager available for list prefix standardization");
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const abstractNums = numberingManager.getAllAbstractNumberings();
|
|
122
|
+
log.debug(`Found ${abstractNums.length} abstract numberings to process`);
|
|
123
|
+
|
|
124
|
+
// Special bullet fonts that should be preserved (used for special characters like open/closed circles)
|
|
125
|
+
const specialBulletFonts = ["Webdings", "Wingdings", "Symbol", "Wingdings 2", "Wingdings 3", "Courier New"];
|
|
126
|
+
|
|
127
|
+
for (const abstractNum of abstractNums) {
|
|
128
|
+
for (let levelIndex = 0; levelIndex <= 8; levelIndex++) {
|
|
129
|
+
const level = abstractNum.getLevel(levelIndex);
|
|
130
|
+
if (!level) continue;
|
|
131
|
+
|
|
132
|
+
// Check if this level uses a special bullet font that should be preserved
|
|
133
|
+
const currentFont = level.getProperties().font;
|
|
134
|
+
const preserveFont = currentFont && specialBulletFonts.includes(currentFont);
|
|
135
|
+
|
|
136
|
+
// Preserve special bullet fonts, otherwise use Verdana
|
|
137
|
+
const fontToUse = preserveFont ? currentFont : "Verdana";
|
|
138
|
+
level.setFont(fontToUse);
|
|
139
|
+
level.setColor("000000");
|
|
140
|
+
level.setFontSize(24); // 24 half-points = 12pt
|
|
141
|
+
level.setBold(false);
|
|
142
|
+
|
|
143
|
+
standardizedCount++;
|
|
144
|
+
|
|
145
|
+
if (preserveFont) {
|
|
146
|
+
log.debug(`Standardized list level ${levelIndex}: preserved ${currentFont} font, 12pt black`);
|
|
147
|
+
} else {
|
|
148
|
+
log.debug(`Standardized list level ${levelIndex}: Verdana 12pt black`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (standardizedCount > 0) {
|
|
154
|
+
log.info(`Standardized ${standardizedCount} list prefix levels`);
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
log.error(`Error standardizing list prefix formatting: ${error}`);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return standardizedCount;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Apply bullet uniformity - standardize bullet characters
|
|
166
|
+
* Uses NumberingLevel API setters instead of raw XML manipulation.
|
|
167
|
+
*/
|
|
168
|
+
async applyBulletUniformity(
|
|
169
|
+
doc: Document,
|
|
170
|
+
settings: ListBulletSettings
|
|
171
|
+
): Promise<ListProcessingResult> {
|
|
172
|
+
let listsUpdated = 0;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const numberingManager = doc.getNumberingManager();
|
|
176
|
+
if (!numberingManager) {
|
|
177
|
+
return { listsUpdated: 0, levelsProcessed: 0 };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const abstractNums = numberingManager.getAllAbstractNumberings();
|
|
181
|
+
|
|
182
|
+
// Update bullet characters based on settings
|
|
183
|
+
for (const levelConfig of settings.indentationLevels) {
|
|
184
|
+
if (!levelConfig.bulletChar) continue;
|
|
185
|
+
|
|
186
|
+
for (const abstractNum of abstractNums) {
|
|
187
|
+
const level = abstractNum.getLevel(levelConfig.level);
|
|
188
|
+
if (!level) continue;
|
|
189
|
+
|
|
190
|
+
// Only update bullet lists, not numbered lists
|
|
191
|
+
if (level.getFormat() !== "bullet") continue;
|
|
192
|
+
|
|
193
|
+
level.setText(levelConfig.bulletChar);
|
|
194
|
+
listsUpdated++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (listsUpdated > 0) {
|
|
199
|
+
log.info(`Updated bullet characters in ${listsUpdated} list levels`);
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
log.error(`Error applying bullet uniformity: ${error}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { listsUpdated, levelsProcessed: listsUpdated };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if a paragraph is a bullet list item
|
|
210
|
+
*/
|
|
211
|
+
isBulletList(doc: Document, numId: number): boolean {
|
|
212
|
+
try {
|
|
213
|
+
const numberingManager = doc.getNumberingManager();
|
|
214
|
+
const instance = numberingManager?.getNumberingInstance(numId);
|
|
215
|
+
if (!instance) return false;
|
|
216
|
+
|
|
217
|
+
const abstractNumId = instance.getAbstractNumId();
|
|
218
|
+
const abstractNum = numberingManager?.getAbstractNumbering(abstractNumId);
|
|
219
|
+
if (!abstractNum) return false;
|
|
220
|
+
|
|
221
|
+
const level0 = abstractNum.getLevel(0);
|
|
222
|
+
return level0?.getFormat() === "bullet";
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if a paragraph is a numbered list item
|
|
230
|
+
*/
|
|
231
|
+
isNumberedList(doc: Document, numId: number): boolean {
|
|
232
|
+
try {
|
|
233
|
+
const numberingManager = doc.getNumberingManager();
|
|
234
|
+
const instance = numberingManager?.getNumberingInstance(numId);
|
|
235
|
+
if (!instance) return false;
|
|
236
|
+
|
|
237
|
+
const abstractNumId = instance.getAbstractNumId();
|
|
238
|
+
const abstractNum = numberingManager?.getAbstractNumbering(abstractNumId);
|
|
239
|
+
if (!abstractNum) return false;
|
|
240
|
+
|
|
241
|
+
const level0 = abstractNum.getLevel(0);
|
|
242
|
+
const format = level0?.getFormat();
|
|
243
|
+
return (
|
|
244
|
+
format === "decimal" ||
|
|
245
|
+
format === "lowerLetter" ||
|
|
246
|
+
format === "upperLetter" ||
|
|
247
|
+
format === "lowerRoman" ||
|
|
248
|
+
format === "upperRoman"
|
|
249
|
+
);
|
|
250
|
+
} catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const listProcessor = new ListProcessor();
|