mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Initial Remove Password implementation
This commit is contained in:
parent
4c17c520d7
commit
25cc9ec8d3
@ -811,16 +811,6 @@
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
"title": "Remove password",
|
||||
"header": "Remove password (Decrypt)",
|
||||
"selectText": {
|
||||
"1": "Select PDF to Decrypt",
|
||||
"2": "Password"
|
||||
},
|
||||
"submit": "Remove"
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
},
|
||||
@ -1875,5 +1865,21 @@
|
||||
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
"title": "Remove Password",
|
||||
"password": {
|
||||
"label": "Current Password",
|
||||
"placeholder": "Enter current password",
|
||||
"completed": "Password configured"
|
||||
},
|
||||
"filenamePrefix": "decrypted",
|
||||
"error": {
|
||||
"failed": "An error occurred while removing the password from the PDF."
|
||||
},
|
||||
"results": {
|
||||
"title": "Decrypted PDFs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -752,16 +752,6 @@
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
"title": "Remove password",
|
||||
"header": "Remove password (Decrypt)",
|
||||
"selectText": {
|
||||
"1": "Select PDF to Decrypt",
|
||||
"2": "Password"
|
||||
},
|
||||
"submit": "Remove"
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
},
|
||||
@ -1737,5 +1727,21 @@
|
||||
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
||||
}
|
||||
}
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
"title": "Remove Password",
|
||||
"password": {
|
||||
"label": "Current Password",
|
||||
"placeholder": "Enter current password",
|
||||
"completed": "Password configured"
|
||||
},
|
||||
"filenamePrefix": "decrypted",
|
||||
"error": {
|
||||
"failed": "An error occurred while removing the password from the PDF."
|
||||
},
|
||||
"results": {
|
||||
"title": "Decrypted PDFs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { Stack, Text, PasswordInput } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RemovePasswordParameters } from "../../../hooks/tools/removePassword/useRemovePasswordParameters";
|
||||
|
||||
interface RemovePasswordSettingsProps {
|
||||
parameters: RemovePasswordParameters;
|
||||
onParameterChange: (key: keyof RemovePasswordParameters, value: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const RemovePasswordSettings = ({ parameters, onParameterChange, disabled = false }: RemovePasswordSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Stack gap="sm">
|
||||
<PasswordInput
|
||||
label={t('removePassword.password.label', 'Current Password')}
|
||||
placeholder={t('removePassword.password.placeholder', 'Enter current password')}
|
||||
value={parameters.password}
|
||||
onChange={(e) => onParameterChange('password', e.target.value)}
|
||||
disabled={disabled}
|
||||
required
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default RemovePasswordSettings;
|
@ -0,0 +1,24 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import { RemovePasswordParameters } from './useRemovePasswordParameters';
|
||||
|
||||
export const useRemovePasswordOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const buildFormData = (parameters: RemovePasswordParameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
formData.append("password", parameters.password);
|
||||
return formData;
|
||||
};
|
||||
|
||||
return useToolOperation<RemovePasswordParameters>({
|
||||
operationType: 'removePassword',
|
||||
endpoint: '/api/v1/security/remove-password',
|
||||
buildFormData,
|
||||
filePrefix: t('removePassword.filenamePrefix', 'decrypted') + '_',
|
||||
multiFileEndpoint: false,
|
||||
getErrorMessage: createStandardErrorHandler(t('removePassword.error.failed', 'An error occurred while removing the password from the PDF.'))
|
||||
});
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface RemovePasswordParameters {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RemovePasswordParametersHook {
|
||||
parameters: RemovePasswordParameters;
|
||||
updateParameter: <K extends keyof RemovePasswordParameters>(parameter: K, value: RemovePasswordParameters[K]) => void;
|
||||
resetParameters: () => void;
|
||||
validateParameters: () => boolean;
|
||||
getEndpointName: () => string;
|
||||
}
|
||||
|
||||
export const defaultParameters: RemovePasswordParameters = {
|
||||
password: '',
|
||||
};
|
||||
|
||||
export const useRemovePasswordParameters = (): RemovePasswordParametersHook => {
|
||||
const [parameters, setParameters] = useState<RemovePasswordParameters>(defaultParameters);
|
||||
|
||||
const updateParameter = <K extends keyof RemovePasswordParameters>(parameter: K, value: RemovePasswordParameters[K]) => {
|
||||
setParameters(prev => ({
|
||||
...prev,
|
||||
[parameter]: value,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const resetParameters = () => {
|
||||
setParameters(defaultParameters);
|
||||
};
|
||||
|
||||
const validateParameters = () => {
|
||||
return parameters.password !== '';
|
||||
};
|
||||
|
||||
const getEndpointName = () => {
|
||||
return 'remove-password';
|
||||
};
|
||||
|
||||
return {
|
||||
parameters,
|
||||
updateParameter,
|
||||
resetParameters,
|
||||
validateParameters,
|
||||
getEndpointName,
|
||||
};
|
||||
};
|
@ -6,8 +6,9 @@ import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
|
||||
import ApiIcon from "@mui/icons-material/Api";
|
||||
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
||||
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
||||
import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
|
||||
import { Tool, ToolDefinition, ToolRegistry } from "../types/tool";
|
||||
|
||||
|
||||
// Add entry here with maxFiles, endpoints, and lazy component
|
||||
@ -104,6 +105,15 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
||||
description: "Change document restrictions and permissions",
|
||||
endpoints: ["add-password"]
|
||||
},
|
||||
removePassword: {
|
||||
id: "removePassword",
|
||||
icon: <LockOpenIcon />,
|
||||
component: React.lazy(() => import("../tools/RemovePassword")),
|
||||
maxFiles: -1,
|
||||
category: "security",
|
||||
description: "Remove password protection from PDF files",
|
||||
endpoints: ["remove-password"]
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
167
frontend/src/tools/RemovePassword.tsx
Normal file
167
frontend/src/tools/RemovePassword.tsx
Normal file
@ -0,0 +1,167 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Box, Button, Stack, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||
|
||||
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
||||
import OperationButton from "../components/tools/shared/OperationButton";
|
||||
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
||||
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
||||
|
||||
import RemovePasswordSettings from "../components/tools/removePassword/RemovePasswordSettings";
|
||||
|
||||
import { useRemovePasswordParameters } from "../hooks/tools/removePassword/useRemovePasswordParameters";
|
||||
import { useRemovePasswordOperation } from "../hooks/tools/removePassword/useRemovePasswordOperation";
|
||||
import { BaseToolProps } from "../types/tool";
|
||||
|
||||
const RemovePassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setCurrentMode } = useFileContext();
|
||||
const { selectedFiles } = useToolFileSelection();
|
||||
|
||||
const removePasswordParams = useRemovePasswordParameters();
|
||||
const removePasswordOperation = useRemovePasswordOperation();
|
||||
|
||||
// Endpoint validation
|
||||
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(removePasswordParams.getEndpointName());
|
||||
|
||||
useEffect(() => {
|
||||
removePasswordOperation.resetResults();
|
||||
onPreviewFile?.(null);
|
||||
}, [removePasswordParams.parameters, selectedFiles]);
|
||||
|
||||
const handleRemovePassword = async () => {
|
||||
try {
|
||||
await removePasswordOperation.executeOperation(
|
||||
removePasswordParams.parameters,
|
||||
selectedFiles
|
||||
);
|
||||
if (removePasswordOperation.files && onComplete) {
|
||||
onComplete(removePasswordOperation.files);
|
||||
}
|
||||
} catch (error) {
|
||||
if (onError) {
|
||||
onError(error instanceof Error ? error.message : t('removePassword.error.failed', 'Remove password operation failed'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleThumbnailClick = (file: File) => {
|
||||
onPreviewFile?.(file);
|
||||
sessionStorage.setItem('previousMode', 'removePassword');
|
||||
setCurrentMode('viewer');
|
||||
};
|
||||
|
||||
const handleSettingsReset = () => {
|
||||
removePasswordOperation.resetResults();
|
||||
onPreviewFile?.(null);
|
||||
setCurrentMode('removePassword');
|
||||
};
|
||||
|
||||
const hasFiles = selectedFiles.length > 0;
|
||||
const hasResults = removePasswordOperation.files.length > 0 || removePasswordOperation.downloadUrl !== null;
|
||||
const filesCollapsed = hasFiles;
|
||||
const passwordCollapsed = hasResults;
|
||||
|
||||
const previewResults = useMemo(() =>
|
||||
removePasswordOperation.files?.map((file, index) => ({
|
||||
file,
|
||||
thumbnail: removePasswordOperation.thumbnails[index]
|
||||
})) || [],
|
||||
[removePasswordOperation.files, removePasswordOperation.thumbnails]
|
||||
);
|
||||
|
||||
return (
|
||||
<ToolStepContainer>
|
||||
<Stack gap="sm" h="94vh" p="sm" style={{ overflow: 'auto' }}>
|
||||
{/* Files Step */}
|
||||
<ToolStep
|
||||
title={t('files.title', 'Files')}
|
||||
isVisible={true}
|
||||
isCollapsed={filesCollapsed}
|
||||
isCompleted={filesCollapsed}
|
||||
completedMessage={hasFiles ?
|
||||
selectedFiles.length === 1
|
||||
? t('files.selected.single', 'Selected: {{filename}}', { filename: selectedFiles[0].name })
|
||||
: t('files.selected.multiple', 'Selected: {{count}} files', { count: selectedFiles.length })
|
||||
: undefined}
|
||||
>
|
||||
<FileStatusIndicator
|
||||
selectedFiles={selectedFiles}
|
||||
placeholder={t('files.placeholder', 'Select a PDF file in the main view to get started')}
|
||||
/>
|
||||
</ToolStep>
|
||||
|
||||
{/* Password Step */}
|
||||
<ToolStep
|
||||
title={t('removePassword.title', 'Remove Password')}
|
||||
isVisible={hasFiles}
|
||||
isCollapsed={passwordCollapsed}
|
||||
isCompleted={passwordCollapsed}
|
||||
onCollapsedClick={hasResults ? handleSettingsReset : undefined}
|
||||
completedMessage={passwordCollapsed ? t('removePassword.password.completed', 'Password configured') : undefined}
|
||||
>
|
||||
<RemovePasswordSettings
|
||||
parameters={removePasswordParams.parameters}
|
||||
onParameterChange={removePasswordParams.updateParameter}
|
||||
disabled={endpointLoading}
|
||||
/>
|
||||
</ToolStep>
|
||||
|
||||
<Box mt="md">
|
||||
<OperationButton
|
||||
onClick={handleRemovePassword}
|
||||
isLoading={removePasswordOperation.isLoading}
|
||||
disabled={!removePasswordParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||
loadingText={t('loading')}
|
||||
submitText={t('removePassword.submit', 'Remove Password')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Results Step */}
|
||||
<ToolStep
|
||||
title={t('results.title', 'Results')}
|
||||
isVisible={hasResults}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
{removePasswordOperation.status && (
|
||||
<Text size="sm" c="dimmed">{removePasswordOperation.status}</Text>
|
||||
)}
|
||||
|
||||
<ErrorNotification
|
||||
error={removePasswordOperation.errorMessage}
|
||||
onClose={removePasswordOperation.clearError}
|
||||
/>
|
||||
|
||||
{removePasswordOperation.downloadUrl && (
|
||||
<Button
|
||||
component="a"
|
||||
href={removePasswordOperation.downloadUrl}
|
||||
download={removePasswordOperation.downloadFilename}
|
||||
leftSection={<DownloadIcon />}
|
||||
color="green"
|
||||
fullWidth
|
||||
mb="md"
|
||||
>
|
||||
{t("download", "Download")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<ResultsPreview
|
||||
files={previewResults}
|
||||
onFileClick={handleThumbnailClick}
|
||||
isGeneratingThumbnails={removePasswordOperation.isGeneratingThumbnails}
|
||||
title={t('removePassword.results.title', 'Decrypted PDFs')}
|
||||
/>
|
||||
</Stack>
|
||||
</ToolStep>
|
||||
</Stack>
|
||||
</ToolStepContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default RemovePassword;
|
@ -5,18 +5,19 @@
|
||||
import { ProcessedFile } from './processing';
|
||||
import { PDFDocument, PDFPage, PageOperation } from './pageEditor';
|
||||
|
||||
export type ModeType =
|
||||
| 'viewer'
|
||||
| 'pageEditor'
|
||||
| 'fileEditor'
|
||||
| 'merge'
|
||||
| 'split'
|
||||
| 'compress'
|
||||
| 'ocr'
|
||||
| 'convert'
|
||||
export type ModeType =
|
||||
| 'viewer'
|
||||
| 'pageEditor'
|
||||
| 'fileEditor'
|
||||
| 'merge'
|
||||
| 'split'
|
||||
| 'compress'
|
||||
| 'ocr'
|
||||
| 'convert'
|
||||
| 'sanitize'
|
||||
| 'addPassword'
|
||||
| 'changePermissions';
|
||||
| 'changePermissions'
|
||||
| 'removePassword';
|
||||
|
||||
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user