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,430 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import {
|
|
4
|
+
ArrowRight,
|
|
5
|
+
CheckCircle,
|
|
6
|
+
XCircle,
|
|
7
|
+
AlertCircle,
|
|
8
|
+
Eye,
|
|
9
|
+
EyeOff,
|
|
10
|
+
Filter,
|
|
11
|
+
Search,
|
|
12
|
+
ExternalLink,
|
|
13
|
+
Hash,
|
|
14
|
+
} from 'lucide-react';
|
|
15
|
+
import { cn } from '@/utils/cn';
|
|
16
|
+
import { sanitizeUrl } from '@/utils/urlSanitizer';
|
|
17
|
+
import { Input } from '@/components/common/Input';
|
|
18
|
+
import { Button } from '@/components/common/Button';
|
|
19
|
+
|
|
20
|
+
interface HyperlinkChange {
|
|
21
|
+
id: string;
|
|
22
|
+
displayText: string;
|
|
23
|
+
originalUrl: string;
|
|
24
|
+
newUrl?: string;
|
|
25
|
+
type: 'append' | 'update' | 'remove' | 'validate';
|
|
26
|
+
status: 'pending' | 'approved' | 'rejected' | 'applied';
|
|
27
|
+
location: string; // e.g., "Main Document", "Header", "Footer"
|
|
28
|
+
context?: string; // Surrounding text
|
|
29
|
+
willAppendContentId?: boolean;
|
|
30
|
+
contentId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface HyperlinkPreviewProps {
|
|
34
|
+
changes: HyperlinkChange[];
|
|
35
|
+
onApprove?: (changeId: string) => void;
|
|
36
|
+
onReject?: (changeId: string) => void;
|
|
37
|
+
onApproveAll?: () => void;
|
|
38
|
+
onRejectAll?: () => void;
|
|
39
|
+
onApply?: () => void;
|
|
40
|
+
isReadOnly?: boolean;
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function HyperlinkPreview({
|
|
45
|
+
changes,
|
|
46
|
+
onApprove,
|
|
47
|
+
onReject,
|
|
48
|
+
onApproveAll,
|
|
49
|
+
onRejectAll,
|
|
50
|
+
onApply,
|
|
51
|
+
isReadOnly = false,
|
|
52
|
+
className,
|
|
53
|
+
}: HyperlinkPreviewProps) {
|
|
54
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
55
|
+
const [filterType, setFilterType] = useState<'all' | 'append' | 'update' | 'remove'>('all');
|
|
56
|
+
const [showOnlyPending, setShowOnlyPending] = useState(false);
|
|
57
|
+
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
|
58
|
+
|
|
59
|
+
// Memoize filtered changes to prevent recalculation on every render
|
|
60
|
+
const filteredChanges = useMemo(() => {
|
|
61
|
+
return changes.filter((change) => {
|
|
62
|
+
if (filterType !== 'all' && change.type !== filterType) return false;
|
|
63
|
+
if (showOnlyPending && change.status !== 'pending') return false;
|
|
64
|
+
if (searchTerm) {
|
|
65
|
+
const search = searchTerm.toLowerCase();
|
|
66
|
+
return (
|
|
67
|
+
change.displayText.toLowerCase().includes(search) ||
|
|
68
|
+
change.originalUrl.toLowerCase().includes(search) ||
|
|
69
|
+
change.newUrl?.toLowerCase().includes(search)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
}, [changes, filterType, showOnlyPending, searchTerm]);
|
|
75
|
+
|
|
76
|
+
// Memoize status counts to prevent recalculation
|
|
77
|
+
const { pendingCount, approvedCount, rejectedCount } = useMemo(
|
|
78
|
+
() => ({
|
|
79
|
+
pendingCount: changes.filter((c) => c.status === 'pending').length,
|
|
80
|
+
approvedCount: changes.filter((c) => c.status === 'approved').length,
|
|
81
|
+
rejectedCount: changes.filter((c) => c.status === 'rejected').length,
|
|
82
|
+
}),
|
|
83
|
+
[changes]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const toggleExpanded = useCallback((id: string) => {
|
|
87
|
+
setExpandedItems((prev) => {
|
|
88
|
+
const newSet = new Set(prev);
|
|
89
|
+
if (newSet.has(id)) {
|
|
90
|
+
newSet.delete(id);
|
|
91
|
+
} else {
|
|
92
|
+
newSet.add(id);
|
|
93
|
+
}
|
|
94
|
+
return newSet;
|
|
95
|
+
});
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
// Returns icon with accessible text label for change type
|
|
99
|
+
const getChangeIcon = (type: HyperlinkChange['type']) => {
|
|
100
|
+
switch (type) {
|
|
101
|
+
case 'append':
|
|
102
|
+
return (
|
|
103
|
+
<span className="inline-flex items-center gap-1" title="Append Content ID">
|
|
104
|
+
<Hash className="w-4 h-4 text-blue-500" aria-hidden="true" />
|
|
105
|
+
<span className="sr-only">Append</span>
|
|
106
|
+
</span>
|
|
107
|
+
);
|
|
108
|
+
case 'update':
|
|
109
|
+
return (
|
|
110
|
+
<span className="inline-flex items-center gap-1" title="Update URL">
|
|
111
|
+
<ArrowRight className="w-4 h-4 text-yellow-500" aria-hidden="true" />
|
|
112
|
+
<span className="sr-only">Update</span>
|
|
113
|
+
</span>
|
|
114
|
+
);
|
|
115
|
+
case 'remove':
|
|
116
|
+
return (
|
|
117
|
+
<span className="inline-flex items-center gap-1" title="Remove Hyperlink">
|
|
118
|
+
<XCircle className="w-4 h-4 text-red-500" aria-hidden="true" />
|
|
119
|
+
<span className="sr-only">Remove</span>
|
|
120
|
+
</span>
|
|
121
|
+
);
|
|
122
|
+
case 'validate':
|
|
123
|
+
return (
|
|
124
|
+
<span className="inline-flex items-center gap-1" title="Validate URL">
|
|
125
|
+
<CheckCircle className="w-4 h-4 text-green-500" aria-hidden="true" />
|
|
126
|
+
<span className="sr-only">Validate</span>
|
|
127
|
+
</span>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const getStatusBadge = (status: HyperlinkChange['status']) => {
|
|
133
|
+
switch (status) {
|
|
134
|
+
case 'pending':
|
|
135
|
+
return (
|
|
136
|
+
<span className="px-2 py-0.5 text-xs font-medium bg-yellow-100 text-yellow-700 rounded-full">
|
|
137
|
+
Pending
|
|
138
|
+
</span>
|
|
139
|
+
);
|
|
140
|
+
case 'approved':
|
|
141
|
+
return (
|
|
142
|
+
<span className="px-2 py-0.5 text-xs font-medium bg-green-100 text-green-700 rounded-full">
|
|
143
|
+
Approved
|
|
144
|
+
</span>
|
|
145
|
+
);
|
|
146
|
+
case 'rejected':
|
|
147
|
+
return (
|
|
148
|
+
<span className="px-2 py-0.5 text-xs font-medium bg-red-100 text-red-700 rounded-full">
|
|
149
|
+
Rejected
|
|
150
|
+
</span>
|
|
151
|
+
);
|
|
152
|
+
case 'applied':
|
|
153
|
+
return (
|
|
154
|
+
<span className="px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-700 rounded-full">
|
|
155
|
+
Applied
|
|
156
|
+
</span>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const truncateUrl = (url: string, maxLength: number = 50): string => {
|
|
162
|
+
if (url.length <= maxLength) return url;
|
|
163
|
+
return url.substring(0, maxLength - 3) + '...';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div className={cn('space-y-4', className)}>
|
|
168
|
+
{/* Header */}
|
|
169
|
+
<div className="flex items-center justify-between">
|
|
170
|
+
<div>
|
|
171
|
+
<h3 className="font-semibold text-lg">Hyperlink Changes Preview</h3>
|
|
172
|
+
<p className="text-sm text-muted-foreground">
|
|
173
|
+
Review and approve changes before applying
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
{!isReadOnly && pendingCount > 0 && (
|
|
177
|
+
<div className="flex items-center gap-2">
|
|
178
|
+
<Button variant="outline" size="sm" onClick={onRejectAll}>
|
|
179
|
+
Reject All
|
|
180
|
+
</Button>
|
|
181
|
+
<Button variant="outline" size="sm" onClick={onApproveAll}>
|
|
182
|
+
Approve All
|
|
183
|
+
</Button>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Stats */}
|
|
189
|
+
<div className="grid grid-cols-4 gap-3">
|
|
190
|
+
<div className="text-center p-3 bg-muted/30 rounded-lg">
|
|
191
|
+
<p className="text-lg font-bold">{changes.length}</p>
|
|
192
|
+
<p className="text-xs text-muted-foreground">Total Changes</p>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="text-center p-3 bg-yellow-500/10 rounded-lg">
|
|
195
|
+
<p className="text-lg font-bold text-yellow-600">{pendingCount}</p>
|
|
196
|
+
<p className="text-xs text-muted-foreground">Pending</p>
|
|
197
|
+
</div>
|
|
198
|
+
<div className="text-center p-3 bg-green-500/10 rounded-lg">
|
|
199
|
+
<p className="text-lg font-bold text-green-600">{approvedCount}</p>
|
|
200
|
+
<p className="text-xs text-muted-foreground">Approved</p>
|
|
201
|
+
</div>
|
|
202
|
+
<div className="text-center p-3 bg-red-500/10 rounded-lg">
|
|
203
|
+
<p className="text-lg font-bold text-red-600">{rejectedCount}</p>
|
|
204
|
+
<p className="text-xs text-muted-foreground">Rejected</p>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* Filters */}
|
|
209
|
+
<div className="flex flex-col sm:flex-row gap-3">
|
|
210
|
+
<div className="flex-1">
|
|
211
|
+
<Input
|
|
212
|
+
value={searchTerm}
|
|
213
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
214
|
+
placeholder="Search hyperlinks..."
|
|
215
|
+
leftIcon={<Search className="w-4 h-4" />}
|
|
216
|
+
onClear={() => setSearchTerm('')}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
<div className="flex items-center gap-2">
|
|
220
|
+
<button
|
|
221
|
+
onClick={() => setFilterType('all')}
|
|
222
|
+
className={cn(
|
|
223
|
+
'px-3 py-1.5 text-sm rounded-lg transition-colors',
|
|
224
|
+
filterType === 'all'
|
|
225
|
+
? 'bg-primary text-primary-foreground'
|
|
226
|
+
: 'bg-muted hover:bg-muted/70'
|
|
227
|
+
)}
|
|
228
|
+
>
|
|
229
|
+
All
|
|
230
|
+
</button>
|
|
231
|
+
<button
|
|
232
|
+
onClick={() => setFilterType('append')}
|
|
233
|
+
className={cn(
|
|
234
|
+
'px-3 py-1.5 text-sm rounded-lg transition-colors',
|
|
235
|
+
filterType === 'append'
|
|
236
|
+
? 'bg-primary text-primary-foreground'
|
|
237
|
+
: 'bg-muted hover:bg-muted/70'
|
|
238
|
+
)}
|
|
239
|
+
>
|
|
240
|
+
Append
|
|
241
|
+
</button>
|
|
242
|
+
<button
|
|
243
|
+
onClick={() => setFilterType('update')}
|
|
244
|
+
className={cn(
|
|
245
|
+
'px-3 py-1.5 text-sm rounded-lg transition-colors',
|
|
246
|
+
filterType === 'update'
|
|
247
|
+
? 'bg-primary text-primary-foreground'
|
|
248
|
+
: 'bg-muted hover:bg-muted/70'
|
|
249
|
+
)}
|
|
250
|
+
>
|
|
251
|
+
Update
|
|
252
|
+
</button>
|
|
253
|
+
<button
|
|
254
|
+
onClick={() => setShowOnlyPending(!showOnlyPending)}
|
|
255
|
+
className={cn(
|
|
256
|
+
'p-1.5 rounded-lg transition-colors',
|
|
257
|
+
showOnlyPending ? 'bg-primary text-primary-foreground' : 'bg-muted hover:bg-muted/70'
|
|
258
|
+
)}
|
|
259
|
+
>
|
|
260
|
+
<Filter className="w-4 h-4" />
|
|
261
|
+
</button>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
{/* Changes List */}
|
|
266
|
+
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
|
267
|
+
<AnimatePresence>
|
|
268
|
+
{filteredChanges.map((change, index) => {
|
|
269
|
+
const isExpanded = expandedItems.has(change.id);
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<motion.div
|
|
273
|
+
key={change.id}
|
|
274
|
+
initial={{ opacity: 0, y: 10 }}
|
|
275
|
+
animate={{ opacity: 1, y: 0 }}
|
|
276
|
+
exit={{ opacity: 0, y: -10 }}
|
|
277
|
+
transition={{ delay: index * 0.02 }}
|
|
278
|
+
className={cn(
|
|
279
|
+
'border rounded-lg transition-all',
|
|
280
|
+
change.status === 'approved' && 'bg-green-500/5 border-green-500/20',
|
|
281
|
+
change.status === 'rejected' && 'bg-red-500/5 border-red-500/20',
|
|
282
|
+
change.status === 'pending' && 'hover:bg-muted/50'
|
|
283
|
+
)}
|
|
284
|
+
>
|
|
285
|
+
<div className="p-3">
|
|
286
|
+
<div className="flex items-start gap-3">
|
|
287
|
+
{getChangeIcon(change.type)}
|
|
288
|
+
|
|
289
|
+
<div className="flex-1 min-w-0">
|
|
290
|
+
{/* Display Text and Status */}
|
|
291
|
+
<div className="flex items-center gap-2 mb-1">
|
|
292
|
+
<span className="font-medium truncate">{change.displayText}</span>
|
|
293
|
+
{getStatusBadge(change.status)}
|
|
294
|
+
<span className="text-xs text-muted-foreground">{change.location}</span>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
{/* URL Changes */}
|
|
298
|
+
<div className="space-y-1">
|
|
299
|
+
<div className="flex items-center gap-2 text-sm">
|
|
300
|
+
<span className="text-muted-foreground">From:</span>
|
|
301
|
+
<code className="text-xs bg-muted px-1 py-0.5 rounded">
|
|
302
|
+
{truncateUrl(change.originalUrl)}
|
|
303
|
+
</code>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
{change.newUrl && (
|
|
307
|
+
<div className="flex items-center gap-2 text-sm">
|
|
308
|
+
<span className="text-muted-foreground">To:</span>
|
|
309
|
+
<code className="text-xs bg-primary/10 text-primary px-1 py-0.5 rounded">
|
|
310
|
+
{truncateUrl(change.newUrl)}
|
|
311
|
+
</code>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
|
|
315
|
+
{change.willAppendContentId && change.contentId && (
|
|
316
|
+
<div className="flex items-center gap-2 text-sm">
|
|
317
|
+
<Hash className="w-3 h-3 text-blue-500" />
|
|
318
|
+
<span className="text-blue-600">
|
|
319
|
+
Will append: <code>{change.contentId}</code>
|
|
320
|
+
</span>
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
{/* Expanded Context */}
|
|
326
|
+
<AnimatePresence>
|
|
327
|
+
{isExpanded && change.context && (
|
|
328
|
+
<motion.div
|
|
329
|
+
initial={{ height: 0, opacity: 0 }}
|
|
330
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
331
|
+
exit={{ height: 0, opacity: 0 }}
|
|
332
|
+
transition={{ duration: 0.2 }}
|
|
333
|
+
className="mt-2 pt-2 border-t"
|
|
334
|
+
>
|
|
335
|
+
<p className="text-sm text-muted-foreground">
|
|
336
|
+
<strong>Context:</strong> {change.context}
|
|
337
|
+
</p>
|
|
338
|
+
<div className="mt-2 flex items-center gap-2">
|
|
339
|
+
{/*
|
|
340
|
+
Security: URLs are sanitized via sanitizeUrl() which validates protocols
|
|
341
|
+
and blocks javascript:, data:, vbscript:, file: and other dangerous schemes.
|
|
342
|
+
Only http:, https:, mailto:, tel:, ftp: protocols are allowed.
|
|
343
|
+
See: src/utils/urlSanitizer.ts
|
|
344
|
+
*/}
|
|
345
|
+
{/* deepcode ignore XSS: URL is sanitized via sanitizeUrl() function */}
|
|
346
|
+
<a
|
|
347
|
+
href={sanitizeUrl(change.originalUrl)}
|
|
348
|
+
target="_blank"
|
|
349
|
+
rel="noopener noreferrer"
|
|
350
|
+
className="text-xs text-primary hover:underline flex items-center gap-1"
|
|
351
|
+
>
|
|
352
|
+
View original <ExternalLink className="w-3 h-3" />
|
|
353
|
+
</a>
|
|
354
|
+
{change.newUrl && (
|
|
355
|
+
// deepcode ignore XSS: URL is sanitized via sanitizeUrl() function
|
|
356
|
+
<a
|
|
357
|
+
href={sanitizeUrl(change.newUrl)}
|
|
358
|
+
target="_blank"
|
|
359
|
+
rel="noopener noreferrer"
|
|
360
|
+
className="text-xs text-primary hover:underline flex items-center gap-1"
|
|
361
|
+
>
|
|
362
|
+
View new <ExternalLink className="w-3 h-3" />
|
|
363
|
+
</a>
|
|
364
|
+
)}
|
|
365
|
+
</div>
|
|
366
|
+
</motion.div>
|
|
367
|
+
)}
|
|
368
|
+
</AnimatePresence>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
{/* Actions */}
|
|
372
|
+
<div className="flex items-center gap-1" role="group" aria-label="Change actions">
|
|
373
|
+
<button
|
|
374
|
+
onClick={() => toggleExpanded(change.id)}
|
|
375
|
+
className="p-1 hover:bg-muted rounded-md transition-colors"
|
|
376
|
+
aria-label={isExpanded ? 'Hide details' : 'Show details'}
|
|
377
|
+
aria-expanded={isExpanded}
|
|
378
|
+
>
|
|
379
|
+
{isExpanded ? (
|
|
380
|
+
<EyeOff className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
|
381
|
+
) : (
|
|
382
|
+
<Eye className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
|
383
|
+
)}
|
|
384
|
+
</button>
|
|
385
|
+
|
|
386
|
+
{!isReadOnly && change.status === 'pending' && (
|
|
387
|
+
<>
|
|
388
|
+
<button
|
|
389
|
+
onClick={() => onApprove?.(change.id)}
|
|
390
|
+
className="p-1 hover:bg-green-500/10 rounded-md transition-colors"
|
|
391
|
+
aria-label="Approve change"
|
|
392
|
+
>
|
|
393
|
+
<CheckCircle className="w-4 h-4 text-green-500" aria-hidden="true" />
|
|
394
|
+
</button>
|
|
395
|
+
<button
|
|
396
|
+
onClick={() => onReject?.(change.id)}
|
|
397
|
+
aria-label="Reject change"
|
|
398
|
+
className="p-1 hover:bg-red-500/10 rounded-md transition-colors"
|
|
399
|
+
>
|
|
400
|
+
<XCircle className="w-4 h-4 text-red-500" />
|
|
401
|
+
</button>
|
|
402
|
+
</>
|
|
403
|
+
)}
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
</motion.div>
|
|
408
|
+
);
|
|
409
|
+
})}
|
|
410
|
+
</AnimatePresence>
|
|
411
|
+
|
|
412
|
+
{filteredChanges.length === 0 && (
|
|
413
|
+
<div className="text-center py-8">
|
|
414
|
+
<AlertCircle className="w-12 h-12 text-muted-foreground mx-auto mb-3" />
|
|
415
|
+
<p className="text-muted-foreground">No changes found</p>
|
|
416
|
+
</div>
|
|
417
|
+
)}
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
{/* Apply Button */}
|
|
421
|
+
{!isReadOnly && approvedCount > 0 && (
|
|
422
|
+
<div className="flex justify-end">
|
|
423
|
+
<Button onClick={onApply} className="min-w-[120px]">
|
|
424
|
+
Apply {approvedCount} Change{approvedCount !== 1 ? 's' : ''}
|
|
425
|
+
</Button>
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
}
|