feat: Add UI and logic to handle password-protected PDF documents.

This commit is contained in:
2025-11-28 08:37:04 +01:00
parent 268488d93d
commit 0b4a9441d0
2 changed files with 129 additions and 17 deletions
+68 -15
View File
@@ -10,6 +10,7 @@ import React, {
import type { CSSProperties, JSX, MutableRefObject, RefObject } from 'react';
import {
GlobalWorkerOptions,
PasswordResponses,
getDocument,
} from 'pdfjs-dist';
import type {
@@ -106,6 +107,10 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX.
velocity: 0,
});
const scrollElementRef = useRef<HTMLElement | null>(null);
const [isEncrypted, setIsEncrypted] = useState(false);
const [passwordError, setPasswordError] = useState(false);
const [passwordCallback, setPasswordCallback] = useState<((password: string) => void) | null>(null);
const passwordInputRef = useRef<HTMLInputElement>(null);
const requestQueueFlush = useCallback(() => {
const queue = renderQueueRef.current;
@@ -283,6 +288,14 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX.
url: src,
wasmUrl,
});
loadingTask.onPassword = (callback, reason) => {
setIsEncrypted(true);
setPasswordCallback(() => callback);
if (reason === PasswordResponses.INCORRECT_PASSWORD) {
setPasswordError(true);
}
};
const pdf = await loadingTask.promise;
if (cancelled) {
await pdf.destroy();
@@ -342,6 +355,13 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX.
};
}, [ensureWasmUrl, renderWidth, src]);
const handlePasswordSubmit = useCallback((event: React.FormEvent) => {
event.preventDefault();
if (passwordCallback && passwordInputRef.current) {
passwordCallback(passwordInputRef.current.value);
}
}, [passwordCallback]);
const viewerClasses = ['document-viewer__object', 'document-viewer__object--pdf', className]
.filter(Boolean)
.join(' ');
@@ -553,22 +573,55 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX.
return (
<div className={viewerClasses}>
<div
ref={containerRef}
className={`pdf-viewer__canvas-stack pdf-viewer__canvas-stack--${viewMode}`}
role="document"
aria-label={title || 'PDF document'}
style={stackStyle}
>
{pageElements}
</div>
{showStatus ? (
<div className={statusClasses}>
<div className="document-viewer__message">
{statusMessage}
</div>
{isEncrypted && !pdfRef.current ? (
<div
className="pdf-viewer__password-container"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<form onSubmit={handlePasswordSubmit} className="pdf-viewer__password-form">
<div className="pdf-viewer__password-message">
This document is password protected.
</div>
<div className="pdf-viewer__password-input-group">
<input
ref={passwordInputRef}
type="password"
className="pdf-viewer__password-input"
placeholder="Enter password"
autoFocus
/>
<button type="submit" className="button button--primary">
Unlock
</button>
</div>
{passwordError ? (
<div className="pdf-viewer__password-error">
Incorrect password. Please try again.
</div>
) : null}
</form>
</div>
) : null}
) : (
<>
<div
ref={containerRef}
className={`pdf-viewer__canvas-stack pdf-viewer__canvas-stack--${viewMode}`}
role="document"
aria-label={title || 'PDF document'}
style={stackStyle}
>
{pageElements}
</div>
{showStatus ? (
<div className={statusClasses}>
<div className="document-viewer__message">
{statusMessage}
</div>
</div>
) : null}
</>
)}
</div>
);
};