}
/>
)}
diff --git a/frontend/src/core/components/viewer/RedactionAPIBridge.tsx b/frontend/src/core/components/viewer/RedactionAPIBridge.tsx
index 6ac8596f2..4824d3434 100644
--- a/frontend/src/core/components/viewer/RedactionAPIBridge.tsx
+++ b/frontend/src/core/components/viewer/RedactionAPIBridge.tsx
@@ -1,7 +1,9 @@
import { useEffect, useImperativeHandle } from 'react';
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
+import { PdfAnnotationSubtype } from '@embedpdf/models';
import { useRedaction } from '@app/contexts/RedactionContext';
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
+import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
/**
* RedactionAPIBridge - Uses embedPDF v2.5.0
@@ -10,24 +12,25 @@ import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId'
*/
export function RedactionAPIBridge() {
const activeDocumentId = useActiveDocumentId();
-
+
// Don't render the inner component until we have a valid document ID
if (!activeDocumentId) {
return null;
}
-
+
return ;
}
function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
- const { state, provides } = useEmbedPdfRedaction(documentId);
- const {
- redactionApiRef,
- setPendingCount,
- setActiveType,
+ const { state, provides: redactionProvides } = useEmbedPdfRedaction(documentId);
+ const { provides: annotationProvides } = useAnnotationCapability();
+ const {
+ redactionApiRef,
+ setPendingCount,
+ setActiveType,
setIsRedacting,
- setRedactionsApplied,
- setBridgeReady
+ setBridgeReady,
+ manualRedactColor
} = useRedaction();
// Mark bridge as ready on mount, not ready on unmount
@@ -45,34 +48,51 @@ function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
setActiveType(state.activeType ?? null);
setIsRedacting(state.isRedacting ?? false);
}
- }, [state?.pendingCount, state?.activeType, state?.isRedacting, setPendingCount, setActiveType, setIsRedacting]);
+ }, [state, setPendingCount, setActiveType, setIsRedacting]);
+
+ // Synchronize manual redaction color with EmbedPDF
+ // Manual redaction uses the 'redact' annotation tool internally
+ useEffect(() => {
+ const annotationApi = annotationProvides as any;
+ if (annotationApi?.setToolDefaults) {
+ annotationApi.setToolDefaults('redact', {
+ type: PdfAnnotationSubtype.REDACT,
+ strokeColor: manualRedactColor,
+ color: manualRedactColor,
+ overlayColor: manualRedactColor,
+ fillColor: manualRedactColor,
+ interiorColor: manualRedactColor,
+ backgroundColor: manualRedactColor,
+ opacity: 1
+ });
+ }
+ }, [annotationProvides, manualRedactColor]);
// Expose the EmbedPDF API through our context's ref
// Uses v2.5.0 unified redaction mode
useImperativeHandle(redactionApiRef, () => ({
// Unified redaction methods (v2.5.0)
toggleRedact: () => {
- provides?.toggleRedact();
+ redactionProvides?.toggleRedact();
},
enableRedact: () => {
- provides?.enableRedact();
+ redactionProvides?.enableRedact();
},
isRedactActive: () => {
- return provides?.isRedactActive() ?? false;
+ return redactionProvides?.isRedactActive() ?? false;
},
endRedact: () => {
- provides?.endRedact();
+ redactionProvides?.endRedact();
},
// Common methods
commitAllPending: () => {
- provides?.commitAllPending();
+ redactionProvides?.commitAllPending();
// Don't set redactionsApplied here - it should only be set after the file is saved
// The save operation in applyChanges will handle setting/clearing this flag
},
getActiveType: () => state?.activeType ?? null,
getPendingCount: () => state?.pendingCount ?? 0,
- }), [provides, state, setRedactionsApplied]);
+ }), [redactionProvides, state]);
return null;
}
-
diff --git a/frontend/src/core/components/viewer/RedactionSelectionMenu.tsx b/frontend/src/core/components/viewer/RedactionSelectionMenu.tsx
index 949492a55..995a2ddda 100644
--- a/frontend/src/core/components/viewer/RedactionSelectionMenu.tsx
+++ b/frontend/src/core/components/viewer/RedactionSelectionMenu.tsx
@@ -1,4 +1,5 @@
import { useRedaction as useEmbedPdfRedaction, RedactionSelectionMenuProps } from '@embedpdf/plugin-redaction/react';
+import { PdfAnnotationSubtype } from '@embedpdf/models';
import { ActionIcon, Tooltip, Button, Group } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { createPortal } from 'react-dom';
@@ -10,7 +11,7 @@ import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId'
export type { RedactionSelectionMenuProps };
-export function RedactionSelectionMenu(props: RedactionSelectionMenuProps) {
+export function RedactionSelectionMenu(props: any) {
const activeDocumentId = useActiveDocumentId();
// Don't render until we have a valid document ID
@@ -32,7 +33,12 @@ function RedactionSelectionMenuInner({
selected,
menuWrapperProps,
}: RedactionSelectionMenuProps & { documentId: string }) {
- const item = context?.item;
+ const item = context?.type === 'redaction'
+ ? context.item
+ : (context?.type === 'annotation' ? (context as any).annotation?.object : null);
+
+ const isRedaction = context?.type === 'redaction' || (context?.type === 'annotation' && item?.type === PdfAnnotationSubtype.REDACT);
+
const pageIndex = context?.pageIndex;
const { t } = useTranslation();
const { provides } = useEmbedPdfRedaction(documentId);
@@ -64,7 +70,7 @@ function RedactionSelectionMenuInner({
// Calculate position for portal based on wrapper element
useEffect(() => {
- if (!selected || !item || !wrapperRef.current) {
+ if (!selected || !isRedaction || !item || !wrapperRef.current) {
setMenuPosition(null);
return;
}
@@ -99,7 +105,7 @@ function RedactionSelectionMenuInner({
}, [selected, item]);
// Early return AFTER all hooks have been called
- if (!selected || !item) return null;
+ if (!selected || !isRedaction || !item) return null;
const menuContent = menuPosition ? (
void;
setIsRedacting: (isRedacting: boolean) => void;
setBridgeReady: (ready: boolean) => void;
+ setManualRedactColor: (color: string) => void;
// Unified redaction actions (v2.5.0)
activateRedact: () => void;
deactivateRedact: () => void;
commitAllPending: () => void;
+ // Unified manual redaction action
+ activateManualRedact: () => void;
// Legacy UI actions (for backwards compatibility with UI)
activateTextSelection: () => void;
activateMarquee: () => void;
@@ -79,6 +84,7 @@ const initialState: RedactionState = {
activeType: null,
isRedacting: false,
isBridgeReady: false,
+ manualRedactColor: '#000000',
};
/**
@@ -141,6 +147,13 @@ export const RedactionProvider: React.FC<{ children: ReactNode }> = ({ children
}));
}, []);
+ const setManualRedactColor = useCallback((color: string) => {
+ setState(prev => ({
+ ...prev,
+ manualRedactColor: color,
+ }));
+ }, []);
+
// Keep navigation guard aware of pending or applied redactions so we block navigation
// Also clear the flag when all redactions have been saved
useEffect(() => {
@@ -175,6 +188,12 @@ export const RedactionProvider: React.FC<{ children: ReactNode }> = ({ children
}
}, [setRedactionsApplied]);
+ const activateManualRedact = useCallback(() => {
+ if (redactionApiRef.current) {
+ redactionApiRef.current.enableRedact();
+ }
+ }, []);
+
// Legacy UI actions for backwards compatibility
// In v2.5.0, both text selection and marquee use the same unified mode
// These just activate the unified redact mode and set the active type for UI state
@@ -202,9 +221,11 @@ export const RedactionProvider: React.FC<{ children: ReactNode }> = ({ children
setActiveType,
setIsRedacting,
setBridgeReady,
+ setManualRedactColor,
activateRedact,
deactivateRedact,
commitAllPending,
+ activateManualRedact,
activateTextSelection,
activateMarquee,
};
@@ -239,6 +260,7 @@ export const useRedactionMode = () => {
activeType: context?.activeType || null,
isRedacting: context?.isRedacting || false,
isBridgeReady: context?.isBridgeReady || false,
+ manualRedactColor: context?.manualRedactColor || '#000000',
};
};