feat(redaction): improve manual redaction with color selection and updated UI elements (#5679)

# Description of Changes

<img width="1920" height="977" alt="image"
src="https://github.com/user-attachments/assets/17e451b7-df2b-4097-b8aa-66954d89b935"
/>


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: Balázs Szücs <[email protected]>
This commit is contained in:
Balázs Szücs
2026-02-12 19:26:26 +00:00
committed by GitHub
parent 4b14ddfb37
commit f3a4dbc903
6 changed files with 113 additions and 105 deletions
@@ -4879,6 +4879,9 @@ applyRedactions = "Apply Redactions"
applyWarning = "⚠️ Permanent application, cannot be undone and the data underneath will be deleted" applyWarning = "⚠️ Permanent application, cannot be undone and the data underneath will be deleted"
boxRedaction = "Box draw redaction" boxRedaction = "Box draw redaction"
colourPicker = "Colour Picker" colourPicker = "Colour Picker"
colorLabel = "Redaction Colour"
active = "Redaction Mode Active"
activate = "Activate Redaction Tool"
controlsTitle = "Manual Redaction Controls" controlsTitle = "Manual Redaction Controls"
convertPDFToImageLabel = "Convert PDF to PDF-Image (Used to remove text behind the box)" convertPDFToImageLabel = "Convert PDF to PDF-Image (Used to remove text behind the box)"
export = "Export" export = "Export"
@@ -1,8 +1,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useEffect, useRef, useCallback } from 'react'; import { useEffect, useRef, useCallback } from 'react';
import { Button, Stack, Text, Group, Divider } from '@mantine/core'; import { Button, Stack, Text, Divider, ColorInput } from '@mantine/core';
import HighlightAltIcon from '@mui/icons-material/HighlightAlt'; import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import CropFreeIcon from '@mui/icons-material/CropFree';
import { useRedaction, useRedactionMode } from '@app/contexts/RedactionContext'; import { useRedaction, useRedactionMode } from '@app/contexts/RedactionContext';
import { useViewer } from '@app/contexts/ViewerContext'; import { useViewer } from '@app/contexts/ViewerContext';
import { useSignature } from '@app/contexts/SignatureContext'; import { useSignature } from '@app/contexts/SignatureContext';
@@ -19,8 +18,8 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
const { t } = useTranslation(); const { t } = useTranslation();
// Use our RedactionContext which bridges to EmbedPDF // Use our RedactionContext which bridges to EmbedPDF
const { activateTextSelection, activateMarquee, redactionsApplied, setActiveType } = useRedaction(); const { activateManualRedact, redactionsApplied, setActiveType, setManualRedactColor } = useRedaction();
const { pendingCount, activeType, isBridgeReady } = useRedactionMode(); const { pendingCount, activeType, isBridgeReady, isRedacting, manualRedactColor } = useRedactionMode();
// Get viewer context to manage annotation mode and save changes // Get viewer context to manage annotation mode and save changes
const { isAnnotationMode, setAnnotationMode, applyChanges, activeFileIndex } = useViewer(); const { isAnnotationMode, setAnnotationMode, applyChanges, activeFileIndex } = useViewer();
@@ -28,9 +27,8 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
// Get signature context to deactivate annotation tools when switching to redaction // Get signature context to deactivate annotation tools when switching to redaction
const { signatureApiRef } = useSignature(); const { signatureApiRef } = useSignature();
// Check which tool is active based on activeType // Check if redaction mode is active
const isSelectionActive = activeType === 'redactSelection'; const isRedactActive = isRedacting;
const isMarqueeActive = activeType === 'marqueeRedact';
// Track if we've auto-activated for the current bridge session // Track if we've auto-activated for the current bridge session
const hasAutoActivated = useRef(false); const hasAutoActivated = useRef(false);
@@ -47,12 +45,12 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
const timer = setTimeout(() => { const timer = setTimeout(() => {
// Deactivate annotation mode to show redaction layer // Deactivate annotation mode to show redaction layer
setAnnotationMode(false); setAnnotationMode(false);
// Pre-select the Mark Text tool // Pre-select the Redaction tool
activateTextSelection(); activateManualRedact();
}, 150); }, 150);
return () => clearTimeout(timer); return () => clearTimeout(timer);
} }
}, [isBridgeReady, disabled, activateTextSelection, setAnnotationMode]); }, [isBridgeReady, disabled, activateManualRedact, setAnnotationMode]);
// Reset auto-activation flag when disabled changes or bridge becomes not ready // Reset auto-activation flag when disabled changes or bridge becomes not ready
useEffect(() => { useEffect(() => {
@@ -68,18 +66,16 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
prevFileIndexRef.current = activeFileIndex; prevFileIndexRef.current = activeFileIndex;
// Reset active type to null when switching files // Reset active type to null when switching files
// This makes both buttons appear unselected, requiring the user to re-click if (activeType) {
// which ensures proper activation on the new PDF
if (isSelectionActive || isMarqueeActive) {
setActiveType(null); setActiveType(null);
} }
// Reset auto-activation flag so new file can auto-activate // Reset auto-activation flag so new file can auto-activate
hasAutoActivated.current = false; hasAutoActivated.current = false;
} }
}, [activeFileIndex, isSelectionActive, isMarqueeActive, setActiveType]); }, [activeFileIndex, activeType, setActiveType]);
const handleSelectionClick = () => { const handleRedactClick = () => {
// Deactivate annotation mode and tools to switch to redaction layer // Deactivate annotation mode and tools to switch to redaction layer
if (isAnnotationMode) { if (isAnnotationMode) {
setAnnotationMode(false); setAnnotationMode(false);
@@ -93,34 +89,7 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
} }
} }
if (isSelectionActive && !isAnnotationMode) { activateManualRedact();
// If already active and not coming from annotation mode, switch to marquee
activateMarquee();
} else {
activateTextSelection();
}
};
const handleMarqueeClick = () => {
// Deactivate annotation mode and tools to switch to redaction layer
if (isAnnotationMode) {
setAnnotationMode(false);
// Deactivate any active annotation tools (like draw)
if (signatureApiRef?.current) {
try {
signatureApiRef.current.deactivateTools();
} catch (error) {
console.log('Unable to deactivate annotation tools:', error);
}
}
}
if (isMarqueeActive && !isAnnotationMode) {
// If already active and not coming from annotation mode, switch to selection
activateTextSelection();
} else {
activateMarquee();
}
}; };
// Handle saving changes - this will apply pending redactions and save to file // Handle saving changes - this will apply pending redactions and save to file
@@ -149,43 +118,27 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
{t('redact.manual.instructions', 'Select text or draw areas on the PDF to mark content for redaction.')} {t('redact.manual.instructions', 'Select text or draw areas on the PDF to mark content for redaction.')}
</Text> </Text>
<Group gap="sm" grow wrap="nowrap"> <ColorInput
{/* Mark Text Selection Tool */} label={t('redact.manual.colorLabel', 'Redaction Colour')}
<Button value={manualRedactColor}
variant={isSelectionActive && !isAnnotationMode ? 'filled' : 'outline'} onChange={setManualRedactColor}
color={isSelectionActive && !isAnnotationMode ? 'blue' : 'gray'} disabled={disabled || !isApiReady}
leftSection={<HighlightAltIcon style={{ fontSize: 18, flexShrink: 0 }} />} size="sm"
onClick={handleSelectionClick} format="hex"
disabled={disabled || !isApiReady} popoverProps={{ withinPortal: true }}
size="sm" />
styles={{
root: {
minWidth: 0,
},
label: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
}}
>
{t('redact.manual.markText', 'Mark Text')}
</Button>
{/* Mark Area (Marquee) Tool */} <Button
<Button variant={isRedactActive && !isAnnotationMode ? 'filled' : 'outline'}
variant={isMarqueeActive && !isAnnotationMode ? 'filled' : 'outline'} color={isRedactActive && !isAnnotationMode ? 'blue' : 'gray'}
color={isMarqueeActive && !isAnnotationMode ? 'blue' : 'gray'} leftSection={<AutoFixHighIcon style={{ fontSize: 18, flexShrink: 0 }} />}
leftSection={<CropFreeIcon style={{ fontSize: 18, flexShrink: 0 }} />} onClick={handleRedactClick}
onClick={handleMarqueeClick} disabled={disabled || !isApiReady}
disabled={disabled || !isApiReady} fullWidth
size="sm" size="sm"
styles={{ >
root: { {isRedactActive && !isAnnotationMode ? t('redact.manual.active', 'Redaction Mode Active') : t('redact.manual.activate', 'Activate Redaction Tool')}
minWidth: 0, </Button>
},
label: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
}}
>
{t('redact.manual.markArea', 'Mark Area')}
</Button>
</Group>
{/* Save Changes Button - applies pending redactions and saves to file */} {/* Save Changes Button - applies pending redactions and saves to file */}
<Button <Button
@@ -131,7 +131,10 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
// Register redaction plugin (depends on InteractionManager, Selection, History) // Register redaction plugin (depends on InteractionManager, Selection, History)
// Always register for redaction functionality // Always register for redaction functionality
createPluginRegistration(RedactionPluginPackage), createPluginRegistration(RedactionPluginPackage, {
useAnnotationMode: true,
drawBlackBoxes: false,
}),
// Register pan plugin (depends on Viewport, InteractionManager) // Register pan plugin (depends on Viewport, InteractionManager)
createPluginRegistration(PanPluginPackage, { createPluginRegistration(PanPluginPackage, {
@@ -645,7 +648,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
{(enableAnnotations || enableRedaction || isManualRedactionMode) && <SignatureAPIBridge ref={signatureApiRef} />} {(enableAnnotations || enableRedaction || isManualRedactionMode) && <SignatureAPIBridge ref={signatureApiRef} />}
{(enableRedaction || isManualRedactionMode) && <RedactionPendingTracker ref={redactionTrackerRef} />} {(enableRedaction || isManualRedactionMode) && <RedactionPendingTracker ref={redactionTrackerRef} />}
{enableAnnotations && <AnnotationAPIBridge ref={annotationApiRef} />} {enableAnnotations && <AnnotationAPIBridge ref={annotationApiRef} />}
<ExportAPIBridge /> <ExportAPIBridge />
<BookmarkAPIBridge /> <BookmarkAPIBridge />
<PrintAPIBridge /> <PrintAPIBridge />
@@ -706,12 +709,13 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
<SelectionLayer documentId={documentId} pageIndex={pageIndex} /> <SelectionLayer documentId={documentId} pageIndex={pageIndex} />
{/* AnnotationLayer for annotation editing (only when enabled) */} {/* AnnotationLayer for annotation editing and annotation-based redactions */}
{enableAnnotations && ( {(enableAnnotations || enableRedaction) && (
<AnnotationLayer <AnnotationLayer
documentId={documentId} documentId={documentId}
pageIndex={pageIndex} pageIndex={pageIndex}
selectionOutlineColor="#007ACC" selectionOutlineColor="#007ACC"
selectionMenu={(props) => <RedactionSelectionMenu {...props} />}
/> />
)} )}
@@ -1,7 +1,9 @@
import { useEffect, useImperativeHandle } from 'react'; import { useEffect, useImperativeHandle } from 'react';
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react'; import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
import { PdfAnnotationSubtype } from '@embedpdf/models';
import { useRedaction } from '@app/contexts/RedactionContext'; import { useRedaction } from '@app/contexts/RedactionContext';
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId'; import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
/** /**
* RedactionAPIBridge - Uses embedPDF v2.5.0 * RedactionAPIBridge - Uses embedPDF v2.5.0
@@ -10,24 +12,25 @@ import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId'
*/ */
export function RedactionAPIBridge() { export function RedactionAPIBridge() {
const activeDocumentId = useActiveDocumentId(); const activeDocumentId = useActiveDocumentId();
// Don't render the inner component until we have a valid document ID // Don't render the inner component until we have a valid document ID
if (!activeDocumentId) { if (!activeDocumentId) {
return null; return null;
} }
return <RedactionAPIBridgeInner documentId={activeDocumentId} />; return <RedactionAPIBridgeInner documentId={activeDocumentId} />;
} }
function RedactionAPIBridgeInner({ documentId }: { documentId: string }) { function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
const { state, provides } = useEmbedPdfRedaction(documentId); const { state, provides: redactionProvides } = useEmbedPdfRedaction(documentId);
const { const { provides: annotationProvides } = useAnnotationCapability();
redactionApiRef, const {
setPendingCount, redactionApiRef,
setActiveType, setPendingCount,
setActiveType,
setIsRedacting, setIsRedacting,
setRedactionsApplied, setBridgeReady,
setBridgeReady manualRedactColor
} = useRedaction(); } = useRedaction();
// Mark bridge as ready on mount, not ready on unmount // Mark bridge as ready on mount, not ready on unmount
@@ -45,34 +48,51 @@ function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
setActiveType(state.activeType ?? null); setActiveType(state.activeType ?? null);
setIsRedacting(state.isRedacting ?? false); 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 // Expose the EmbedPDF API through our context's ref
// Uses v2.5.0 unified redaction mode // Uses v2.5.0 unified redaction mode
useImperativeHandle(redactionApiRef, () => ({ useImperativeHandle(redactionApiRef, () => ({
// Unified redaction methods (v2.5.0) // Unified redaction methods (v2.5.0)
toggleRedact: () => { toggleRedact: () => {
provides?.toggleRedact(); redactionProvides?.toggleRedact();
}, },
enableRedact: () => { enableRedact: () => {
provides?.enableRedact(); redactionProvides?.enableRedact();
}, },
isRedactActive: () => { isRedactActive: () => {
return provides?.isRedactActive() ?? false; return redactionProvides?.isRedactActive() ?? false;
}, },
endRedact: () => { endRedact: () => {
provides?.endRedact(); redactionProvides?.endRedact();
}, },
// Common methods // Common methods
commitAllPending: () => { commitAllPending: () => {
provides?.commitAllPending(); redactionProvides?.commitAllPending();
// Don't set redactionsApplied here - it should only be set after the file is saved // 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 // The save operation in applyChanges will handle setting/clearing this flag
}, },
getActiveType: () => state?.activeType ?? null, getActiveType: () => state?.activeType ?? null,
getPendingCount: () => state?.pendingCount ?? 0, getPendingCount: () => state?.pendingCount ?? 0,
}), [provides, state, setRedactionsApplied]); }), [redactionProvides, state]);
return null; return null;
} }
@@ -1,4 +1,5 @@
import { useRedaction as useEmbedPdfRedaction, RedactionSelectionMenuProps } from '@embedpdf/plugin-redaction/react'; import { useRedaction as useEmbedPdfRedaction, RedactionSelectionMenuProps } from '@embedpdf/plugin-redaction/react';
import { PdfAnnotationSubtype } from '@embedpdf/models';
import { ActionIcon, Tooltip, Button, Group } from '@mantine/core'; import { ActionIcon, Tooltip, Button, Group } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
@@ -10,7 +11,7 @@ import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId'
export type { RedactionSelectionMenuProps }; export type { RedactionSelectionMenuProps };
export function RedactionSelectionMenu(props: RedactionSelectionMenuProps) { export function RedactionSelectionMenu(props: any) {
const activeDocumentId = useActiveDocumentId(); const activeDocumentId = useActiveDocumentId();
// Don't render until we have a valid document ID // Don't render until we have a valid document ID
@@ -32,7 +33,12 @@ function RedactionSelectionMenuInner({
selected, selected,
menuWrapperProps, menuWrapperProps,
}: RedactionSelectionMenuProps & { documentId: string }) { }: 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 pageIndex = context?.pageIndex;
const { t } = useTranslation(); const { t } = useTranslation();
const { provides } = useEmbedPdfRedaction(documentId); const { provides } = useEmbedPdfRedaction(documentId);
@@ -64,7 +70,7 @@ function RedactionSelectionMenuInner({
// Calculate position for portal based on wrapper element // Calculate position for portal based on wrapper element
useEffect(() => { useEffect(() => {
if (!selected || !item || !wrapperRef.current) { if (!selected || !isRedaction || !item || !wrapperRef.current) {
setMenuPosition(null); setMenuPosition(null);
return; return;
} }
@@ -99,7 +105,7 @@ function RedactionSelectionMenuInner({
}, [selected, item]); }, [selected, item]);
// Early return AFTER all hooks have been called // Early return AFTER all hooks have been called
if (!selected || !item) return null; if (!selected || !isRedaction || !item) return null;
const menuContent = menuPosition ? ( const menuContent = menuPosition ? (
<div <div
@@ -35,6 +35,8 @@ interface RedactionState {
isRedacting: boolean; isRedacting: boolean;
// Whether the redaction API bridge is ready (API ref is populated) // Whether the redaction API bridge is ready (API ref is populated)
isBridgeReady: boolean; isBridgeReady: boolean;
// Color for manual redaction
manualRedactColor: string;
} }
/** /**
@@ -50,10 +52,13 @@ interface RedactionActions {
setActiveType: (type: RedactionMode | null) => void; setActiveType: (type: RedactionMode | null) => void;
setIsRedacting: (isRedacting: boolean) => void; setIsRedacting: (isRedacting: boolean) => void;
setBridgeReady: (ready: boolean) => void; setBridgeReady: (ready: boolean) => void;
setManualRedactColor: (color: string) => void;
// Unified redaction actions (v2.5.0) // Unified redaction actions (v2.5.0)
activateRedact: () => void; activateRedact: () => void;
deactivateRedact: () => void; deactivateRedact: () => void;
commitAllPending: () => void; commitAllPending: () => void;
// Unified manual redaction action
activateManualRedact: () => void;
// Legacy UI actions (for backwards compatibility with UI) // Legacy UI actions (for backwards compatibility with UI)
activateTextSelection: () => void; activateTextSelection: () => void;
activateMarquee: () => void; activateMarquee: () => void;
@@ -79,6 +84,7 @@ const initialState: RedactionState = {
activeType: null, activeType: null,
isRedacting: false, isRedacting: false,
isBridgeReady: 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 // Keep navigation guard aware of pending or applied redactions so we block navigation
// Also clear the flag when all redactions have been saved // Also clear the flag when all redactions have been saved
useEffect(() => { useEffect(() => {
@@ -175,6 +188,12 @@ export const RedactionProvider: React.FC<{ children: ReactNode }> = ({ children
} }
}, [setRedactionsApplied]); }, [setRedactionsApplied]);
const activateManualRedact = useCallback(() => {
if (redactionApiRef.current) {
redactionApiRef.current.enableRedact();
}
}, []);
// Legacy UI actions for backwards compatibility // Legacy UI actions for backwards compatibility
// In v2.5.0, both text selection and marquee use the same unified mode // 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 // 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, setActiveType,
setIsRedacting, setIsRedacting,
setBridgeReady, setBridgeReady,
setManualRedactColor,
activateRedact, activateRedact,
deactivateRedact, deactivateRedact,
commitAllPending, commitAllPending,
activateManualRedact,
activateTextSelection, activateTextSelection,
activateMarquee, activateMarquee,
}; };
@@ -239,6 +260,7 @@ export const useRedactionMode = () => {
activeType: context?.activeType || null, activeType: context?.activeType || null,
isRedacting: context?.isRedacting || false, isRedacting: context?.isRedacting || false,
isBridgeReady: context?.isBridgeReady || false, isBridgeReady: context?.isBridgeReady || false,
manualRedactColor: context?.manualRedactColor || '#000000',
}; };
}; };