一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
96 行
3.3 KiB
TypeScript
96 行
3.3 KiB
TypeScript
import * as React from 'react';
|
|
import { useCallback, useState } from 'react';
|
|
import { Dispatch } from 'redux';
|
|
import { _ } from '@joplin/lib/locale';
|
|
import NoteLockSession from '@joplin/lib/services/noteLock/NoteLockSession';
|
|
import Button, { ButtonLevel } from '../../Button/Button';
|
|
import LabelledPasswordInput from '../../PasswordInput/LabelledPasswordInput';
|
|
|
|
interface Props {
|
|
noteTitle: string;
|
|
hasNoteLockKey: boolean;
|
|
dispatch: Dispatch;
|
|
undecryptable?: boolean;
|
|
migrationRunning?: boolean;
|
|
lockedInShare?: boolean;
|
|
}
|
|
|
|
export default function NoteLockPanel(props: Props) {
|
|
const [password, setPassword] = useState('');
|
|
const [unlocking, setUnlocking] = useState(false);
|
|
const [errorMessage, setErrorMessage] = useState('');
|
|
|
|
const onPasswordChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
setPassword(event.target.value);
|
|
}, []);
|
|
|
|
const unlock = useCallback(async () => {
|
|
if (!password || unlocking) return;
|
|
setUnlocking(true);
|
|
try {
|
|
await NoteLockSession.instance().unlock(password);
|
|
// No success handling: the unlock event swaps this panel out for the note.
|
|
} catch (error) {
|
|
// WebCrypto reports a wrong password as a generic OperationError.
|
|
setErrorMessage(error.name === 'OperationError' ? _('Invalid password') : error.message);
|
|
setUnlocking(false);
|
|
}
|
|
}, [password, unlocking]);
|
|
|
|
const onSubmit = useCallback((event: React.FormEvent) => {
|
|
event.preventDefault();
|
|
void unlock();
|
|
}, [unlock]);
|
|
|
|
const onSetUpClick = useCallback(() => {
|
|
props.dispatch({
|
|
type: 'NAV_GO',
|
|
routeName: 'Config',
|
|
props: { defaultSection: 'noteLock' },
|
|
});
|
|
}, [props.dispatch]);
|
|
|
|
const renderAction = () => {
|
|
if (props.migrationRunning) {
|
|
return <p className="message">{_('This note cannot be read while a note lock key migration is in progress.')}</p>;
|
|
}
|
|
|
|
if (props.undecryptable) {
|
|
return <p className="message">{_('This note could not be unlocked. If it was locked prior to a password reset, the content is no longer recoverable.')}</p>;
|
|
}
|
|
|
|
if (!props.hasNoteLockKey) {
|
|
return (
|
|
<>
|
|
<p className="message">{_('Reading this note requires the note lock password, which has not been set up on this device yet.')}</p>
|
|
<Button level={ButtonLevel.Primary} title={_('Set up note lock')} onClick={onSetUpClick} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<form className="form unlock-form" onSubmit={onSubmit}>
|
|
<p className="message">{_('This note is locked. Enter your password to unlock your notes for this session.')}</p>
|
|
<div className="field">
|
|
<LabelledPasswordInput
|
|
labelText={_('Note lock password')}
|
|
value={password}
|
|
onChange={onPasswordChange}
|
|
/>
|
|
</div>
|
|
{!!errorMessage && <p className="error" role="alert">{errorMessage}</p>}
|
|
<Button type='button' level={ButtonLevel.Primary} title={_('Unlock')} disabled={!password || unlocking} onClick={unlock} />
|
|
</form>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className={`note-lock-panel ${props.lockedInShare ? '-in-share' : ''}`}>
|
|
<i className="icon fas fa-lock" role="img" aria-label={_('Locked note')}></i>
|
|
<h2 className="title">{props.noteTitle}</h2>
|
|
{renderAction()}
|
|
{props.lockedInShare ? <div className="warning-banner banner">{_('This note may not be readable because it is contained within a share.')}</div> : null}
|
|
</div>
|
|
);
|
|
}
|