diff --git a/fonts/mono-special/MonoSpecial-Regular.woff2 b/fonts/mono-special/MonoSpecial-Regular.woff2 index b9208c4075..76e9317656 100644 Binary files a/fonts/mono-special/MonoSpecial-Regular.woff2 and b/fonts/mono-special/MonoSpecial-Regular.woff2 differ diff --git a/ts/components/PreferencesBackups.dom.tsx b/ts/components/PreferencesBackups.dom.tsx index 2e2e8d53a1..ca9d3c68d1 100644 --- a/ts/components/PreferencesBackups.dom.tsx +++ b/ts/components/PreferencesBackups.dom.tsx @@ -38,6 +38,8 @@ export const SIGNAL_BACKUPS_LEARN_MORE_URL = const LOCAL_BACKUPS_PAGES = new Set([ SettingsPage.LocalBackups, + SettingsPage.LocalBackupsSetupKey, + SettingsPage.LocalBackupsSetupFolder, SettingsPage.LocalBackupsKeyReference, ]); @@ -291,13 +293,17 @@ export function PreferencesBackups({ disabled={isAuthPending} onClick={async () => { if (isLocalBackupsSetup) { - setSettingsLocation({ page: SettingsPage.LocalBackups }); + setSettingsLocation({ + page: SettingsPage.LocalBackups, + }); } else { try { setIsAuthPending(true); const result = await promptOSAuth('enable-backups'); if (result === 'success' || result === 'unsupported') { - setSettingsLocation({ page: SettingsPage.LocalBackups }); + setSettingsLocation({ + page: SettingsPage.LocalBackupsSetupFolder, + }); } } finally { setIsAuthPending(false); diff --git a/ts/components/PreferencesLocalBackups.dom.tsx b/ts/components/PreferencesLocalBackups.dom.tsx index f2edb5a53e..4f68d0b854 100644 --- a/ts/components/PreferencesLocalBackups.dom.tsx +++ b/ts/components/PreferencesLocalBackups.dom.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ChangeEvent, JSX, MouseEvent } from 'react'; -import { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { useCallback, useMemo, useState, useRef } from 'react'; import lodash from 'lodash'; import classNames from 'classnames'; @@ -85,24 +85,33 @@ export function PreferencesLocalBackups({ const [isShowingBackupKeyChangedModal, setIsShowingBackupKeyChangedModal] = useState(false); - if (!localBackupFolder) { + if (settingsLocation.page === SettingsPage.LocalBackupsSetupFolder) { + if (localBackupFolder) { + setSettingsLocation({ page: SettingsPage.LocalBackups }); + } return ( { + const folder = await pickLocalBackupFolder(); + if (folder) { + setSettingsLocation({ page: SettingsPage.LocalBackupsSetupKey }); + } + }} /> ); } - const isReferencingBackupKey = - settingsLocation.page === SettingsPage.LocalBackupsKeyReference; - if (!previouslyViewedBackupKeyHash || isReferencingBackupKey) { + if ( + settingsLocation.page === SettingsPage.LocalBackupsSetupKey || + settingsLocation.page === SettingsPage.LocalBackupsKeyReference + ) { return ( { @@ -116,6 +125,16 @@ export function PreferencesLocalBackups({ ); } + if (!localBackupFolder && !isDisablePending) { + setSettingsLocation({ page: SettingsPage.LocalBackupsSetupFolder }); + return null; + } + + if (!previouslyViewedBackupKeyHash && !isDisablePending) { + setSettingsLocation({ page: SettingsPage.LocalBackupsSetupKey }); + return null; + } + async function showKeyReferenceWithAuth() { setAuthError(undefined); @@ -218,7 +237,11 @@ export function PreferencesLocalBackups({ openFileInFolder(localBackupFolder)} + onClick={() => + localBackupFolder + ? openFileInFolder(localBackupFolder) + : undefined + } > {showInFolderText} @@ -557,13 +580,10 @@ function LocalBackupsBackupKeyViewer({ ); const isStepViewOrReference = step === 'view' || step === 'reference'; - const backupKeyForDisplay = useMemo(() => { - return backupKey - .replace(/\s/g, '') - .replace(/.{4}(?=.)/g, '$& ') - .toUpperCase(); - }, [backupKey]); - + const backupKeyForDisplay = useMemo( + () => formatBackupKeyForDisplay(backupKey, { convertAmbiguousChars: true }), + [backupKey] + ); const onCopyBackupKey = useCallback( async function handleCopyBackupKey(e: MouseEvent) { e.preventDefault(); @@ -645,8 +665,10 @@ function LocalBackupsBackupKeyViewer({ {step === 'caution' && (
setIsBackupKeyConfirmed(isValid)} isStepViewOrReference={isStepViewOrReference} @@ -715,13 +738,29 @@ function LocalBackupsBackupKeyViewer({ ); } +function formatBackupKeyForDisplay( + backupKey: string, + { convertAmbiguousChars }: { convertAmbiguousChars: boolean } +): string { + const spacedAndUppercase = backupKey + .toUpperCase() + .replace(/\s/g, '') + .replace(/.{4}(?=.)/g, '$& '); + + if (convertAmbiguousChars) { + return spacedAndUppercase.replace(/O/g, '#').replace(/0/g, '='); + } + + return spacedAndUppercase; +} + function LocalBackupsBackupKeyTextarea({ - backupKey, + backupKeyForDisplay, i18n, onValidate, isStepViewOrReference, }: { - backupKey: string; + backupKeyForDisplay: string; i18n: LocalizerType; onValidate: (isValid: boolean) => void; isStepViewOrReference: boolean; @@ -729,30 +768,39 @@ function LocalBackupsBackupKeyTextarea({ const backupKeyTextareaRef = useRef(null); const [backupKeyInput, setBackupKeyInput] = useState(''); - useEffect(() => { - if (backupKeyTextareaRef.current) { - backupKeyTextareaRef.current.focus(); - } - }, [backupKeyTextareaRef, isStepViewOrReference]); - - const backupKeyNoSpaces = useMemo(() => { - return backupKey.replace(/\s/g, ''); - }, [backupKey]); - const handleTextareaChange = useCallback( (ev: ChangeEvent) => { - const { value } = ev.target; - const valueUppercaseNoSpaces = value.replace(/\s/g, '').toUpperCase(); - const valueForUI = valueUppercaseNoSpaces.replace(/.{4}(?=.)/g, '$& '); - setBackupKeyInput(valueForUI); - onValidate(valueUppercaseNoSpaces === backupKeyNoSpaces); + const { selectionStart, value } = ev.target; + + setBackupKeyInput( + formatBackupKeyForDisplay(value, { convertAmbiguousChars: false }) + ); + + const currentCharIndex = value + .slice(0, selectionStart) + .replace(/\s/g, '').length; + const newCaretIndex = + currentCharIndex + Math.max(0, Math.floor((currentCharIndex - 1) / 4)); + + requestAnimationFrame(() => { + backupKeyTextareaRef.current?.setSelectionRange( + newCaretIndex, + newCaretIndex + ); + }); + + onValidate( + formatBackupKeyForDisplay(value, { convertAmbiguousChars: true }) === + backupKeyForDisplay + ); }, - [backupKeyNoSpaces, onValidate] + [backupKeyForDisplay, onValidate] ); return (