diff --git a/apps/mobile/app/components/auth/change-password.tsx b/apps/mobile/app/components/auth/change-password.tsx
index 80f24a8c7..862529fc9 100644
--- a/apps/mobile/app/components/auth/change-password.tsx
+++ b/apps/mobile/app/components/auth/change-password.tsx
@@ -21,7 +21,10 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
import { View } from "react-native";
+import { TextInput } from "react-native-gesture-handler";
+import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { db } from "../../common/database";
+import { Spacing } from "../../common/design/spacing";
import BackupService from "../../services/backup";
import { ToastManager } from "../../services/event-manager";
import Navigation from "../../services/navigation";
@@ -29,14 +32,12 @@ import { useUserStore } from "../../stores/use-user-store";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
+import RecoveryKeySheet from "../sheets/recovery-key";
import AppIcon from "../ui/AppIcon";
import { Button } from "../ui/button";
import FormInput, { createFormRef, validators } from "../ui/input/form-input";
import { Notice } from "../ui/notice";
import Paragraph from "../ui/typography/paragraph";
-import { TextInput } from "react-native-gesture-handler";
-import { Spacing } from "../../common/design/spacing";
-import RecoveryKeySheet from "../sheets/recovery-key";
export const ChangePassword = () => {
const { colors } = useThemeColors();
@@ -107,13 +108,17 @@ export const ChangePassword = () => {
};
return (
-
{
/>
{strings.yourSecurityIsPriority()}
-
+
);
};
diff --git a/apps/mobile/app/components/dialog/functions.ts b/apps/mobile/app/components/dialog/functions.ts
index 813761764..f2644a5e0 100644
--- a/apps/mobile/app/components/dialog/functions.ts
+++ b/apps/mobile/app/components/dialog/functions.ts
@@ -60,7 +60,7 @@ export type DialogInfo = {
ref: RefObject;
inputProps?: TextInputProps;
}[];
- onFormSubmit?: (form: FormRef) => Promise;
+ onFormSubmit?: (form: FormRef, checked?: boolean) => Promise;
};
input: boolean;
inputLabel?: string;
diff --git a/apps/mobile/app/components/dialog/index.tsx b/apps/mobile/app/components/dialog/index.tsx
index fbeedd057..dfb9d3ec1 100644
--- a/apps/mobile/app/components/dialog/index.tsx
+++ b/apps/mobile/app/components/dialog/index.tsx
@@ -74,7 +74,10 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
}
if (dialogInfo.form.onFormSubmit) {
setLoading(true);
- const result = await dialogInfo.form.onFormSubmit(formRef.current);
+ const result = await dialogInfo.form.onFormSubmit(
+ formRef.current,
+ checked
+ );
if (result === false) {
setLoading(false);
return;
diff --git a/apps/mobile/app/components/dialogs/vault/index.tsx b/apps/mobile/app/components/dialogs/vault/index.tsx
index 93fb4bba4..b49f7c68e 100644
--- a/apps/mobile/app/components/dialogs/vault/index.tsx
+++ b/apps/mobile/app/components/dialogs/vault/index.tsx
@@ -188,7 +188,6 @@ export const VaultDialog: React.FC = () => {
}
await db.vault.delete(deleteAll);
await BiometricService.resetCredentials();
-
if (deleteAll) {
noteIds.forEach((id) => {
eSendEvent(
@@ -753,9 +752,13 @@ export const VaultDialog: React.FC = () => {
name="password"
formRef={formRef}
label={
- isChangePassword
- ? strings.currentPassword()
- : strings.password()
+ isDeleteVault
+ ? strings.enterAccountPassword()
+ : isClearVault
+ ? strings.enterVaultPassword()
+ : isChangePassword
+ ? strings.currentPassword()
+ : strings.password()
}
fwdRef={passInputRef}
editable={!loading}
@@ -809,11 +812,17 @@ export const VaultDialog: React.FC = () => {
width="100%"
style={{
justifyContent: "flex-start",
- paddingHorizontal: 0
+ paddingHorizontal: 0,
+ paddingVertical: 0,
+ marginTop: Spacing.LEVEL_1
}}
title={strings.deleteAllNotes()}
type="transparent"
- iconColor={colors.error.accent}
+ iconColor={
+ deleteAll
+ ? [colors.error.accent, colors.error.accentForeground]
+ : colors.error.accent
+ }
textStyle={{
color: colors.error.accent
}}
diff --git a/apps/mobile/app/components/sheets/export-notes/index.tsx b/apps/mobile/app/components/sheets/export-notes/index.tsx
index 391da494f..2a505cccd 100644
--- a/apps/mobile/app/components/sheets/export-notes/index.tsx
+++ b/apps/mobile/app/components/sheets/export-notes/index.tsx
@@ -19,7 +19,7 @@ along with this program. If not, see .
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
-import React, { useState } from "react";
+import React, { useRef, useState } from "react";
import { ActivityIndicator, Linking, Platform, View } from "react-native";
import FileViewer from "react-native-file-viewer";
import Share from "react-native-share";
@@ -65,11 +65,14 @@ const ExportNotesSheet = ({
| undefined
>();
const [status, setStatus] = useState();
+ const abortController = useRef(undefined);
const exportNoteAs = async (
type: "pdf" | "txt" | "md" | "html" | "md-frontmatter"
) => {
if (exporting) return;
+ const controller = new AbortController();
+ abortController.current = controller;
setExporting(true);
update?.({ disableClosing: true } as PresentSheetOptions);
setComplete(false);
@@ -78,7 +81,8 @@ const ExportNotesSheet = ({
result = await Exporter.bulkExport(
db.notes.all.where((eb) => eb("id", "in", ids)),
type,
- setStatus
+ setStatus,
+ controller.signal
);
} else {
const note = await db.notes.note(ids[0]);
@@ -86,10 +90,15 @@ const ExportNotesSheet = ({
setExporting(false);
return;
}
- result = await Exporter.exportNote(note, type, setStatus);
+ result = await Exporter.exportNote(
+ note,
+ type,
+ setStatus,
+ controller.signal
+ );
await sleep(1000);
}
- if (!result) {
+ if (!result || controller.signal.aborted) {
update?.({ disableClosing: false } as PresentSheetOptions);
return setExporting(false);
}
@@ -100,6 +109,13 @@ const ExportNotesSheet = ({
requestInAppReview();
};
+ const cancelExport = () => {
+ abortController.current?.abort();
+ update?.({ disableClosing: false } as PresentSheetOptions);
+ setExporting(false);
+ setStatus(undefined);
+ };
+
const actions = [
{
title: "PDF",
@@ -276,6 +292,12 @@ const ExportNotesSheet = ({
" " +
strings.pleaseWait()}
+
>
) : (
<>
diff --git a/apps/mobile/app/components/sheets/force-sync/index.tsx b/apps/mobile/app/components/sheets/force-sync/index.tsx
index 6b807d5e8..4210691e4 100644
--- a/apps/mobile/app/components/sheets/force-sync/index.tsx
+++ b/apps/mobile/app/components/sheets/force-sync/index.tsx
@@ -20,7 +20,7 @@ along with this program. If not, see .
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
-import { View } from "react-native";
+import { Linking, View } from "react-native";
import { Radius, Spacing } from "../../../common/design/spacing";
import { Button } from "../../ui/button";
import AppIcon from "../../ui/AppIcon";
@@ -142,6 +142,9 @@ function ForceSync({ mode, close }: ForceSyncProps) {
size={AppFontSize.sm}
fontFamily="SEMI_BOLD"
color={colors.primary.accent}
+ onPress={() => {
+ Linking.openURL("mailto:support@streetwriters.co");
+ }}
>
support@streetwriters.co
@@ -164,7 +167,7 @@ function ForceSync({ mode, close }: ForceSyncProps) {
>
{
style={{
maxWidth: "100%",
flexDirection: "row",
- alignItems: "flex-start",
+ alignItems:
+ !description && !toastOptions.func ? "center" : "flex-start",
gap: Spacing.LEVEL_1,
padding: Spacing.LEVEL_1,
borderRadius: Radius.S,
@@ -212,9 +213,6 @@ export const Toast = ({ context = "global" }) => {
iconFamily={toastOptions.icon ? "material" : variant.icon.family}
size={16}
color={variant.accent}
- style={{
- marginTop: description ? 1 : 0
- }}
/>
{
{title ? (
{title}
diff --git a/apps/mobile/app/components/ui/checkbox.tsx b/apps/mobile/app/components/ui/checkbox.tsx
index 1b41804af..0fdb6c7bd 100644
--- a/apps/mobile/app/components/ui/checkbox.tsx
+++ b/apps/mobile/app/components/ui/checkbox.tsx
@@ -92,9 +92,6 @@ export const Checkbox = ({
name={checked ? "checkbox" : "box-empty"}
iconFamily="notesnook"
size={iconSize}
- style={{
- marginTop: 1.5
- }}
color={
iconColor ||
(checked
diff --git a/apps/mobile/app/components/ui/input/form-input.tsx b/apps/mobile/app/components/ui/input/form-input.tsx
index e768b1a73..0b1b34f7a 100644
--- a/apps/mobile/app/components/ui/input/form-input.tsx
+++ b/apps/mobile/app/components/ui/input/form-input.tsx
@@ -198,6 +198,11 @@ export const validators = {
? undefined
: message || `Must be at least ${length} characters`,
+ number:
+ (message = "Please enter a valid number") =>
+ (value: string) =>
+ !value?.trim() || !Number.isNaN(Number(value)) ? undefined : message,
+
url:
(message = "Please enter a valid URL") =>
(value: string) =>
diff --git a/apps/mobile/app/hooks/use-vault-status.ts b/apps/mobile/app/hooks/use-vault-status.ts
index 2a3ff0267..3c3cb1be0 100644
--- a/apps/mobile/app/hooks/use-vault-status.ts
+++ b/apps/mobile/app/hooks/use-vault-status.ts
@@ -43,6 +43,7 @@ export const useVaultStatus = () => {
db.vault?.exists().then(async (exists) => {
const available = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
+ console.log("VAULT EXISTS?", exists);
setVaultStatus({
exists: exists,
biometryEnrolled: fingerprint,
diff --git a/apps/mobile/app/screens/settings/components/notesnook-circle.tsx b/apps/mobile/app/screens/settings/components/notesnook-circle.tsx
index 91ca01104..77566ad42 100644
--- a/apps/mobile/app/screens/settings/components/notesnook-circle.tsx
+++ b/apps/mobile/app/screens/settings/components/notesnook-circle.tsx
@@ -275,6 +275,10 @@ const Partner = ({
}}
onPress={() => {
Clipboard.setString(code);
+ ToastManager.show({
+ message: strings.codeCopied(),
+ type: "success"
+ });
}}
/>
diff --git a/apps/mobile/app/screens/settings/components/picker/index.tsx b/apps/mobile/app/screens/settings/components/picker/index.tsx
index 4fe39a1f9..7289496b7 100644
--- a/apps/mobile/app/screens/settings/components/picker/index.tsx
+++ b/apps/mobile/app/screens/settings/components/picker/index.tsx
@@ -106,15 +106,24 @@ export function SettingsPicker({
type={
compareValue(currentValue, item)
? "shade-plain"
- : "secondary-outline"
+ : "plain-outline"
}
onPress={() => {
onChange(item);
}}
+ textStyle={{
+ color: compareValue(currentValue, item)
+ ? colors.primary.heading
+ : colors.primary.paragraph
+ }}
style={{
paddingVertical: Spacing.LEVEL_1,
paddingHorizontal: Spacing.LEVEL_1,
- borderRadius: Radius.XS
+ borderRadius: Radius.XS,
+ borderWidth: 1,
+ borderColor: compareValue(currentValue, item)
+ ? "transparent"
+ : colors.primary.border
}}
/>
))}
diff --git a/apps/mobile/app/screens/settings/components/picker/pickers.tsx b/apps/mobile/app/screens/settings/components/picker/pickers.tsx
index ad32d986a..73a52f98e 100644
--- a/apps/mobile/app/screens/settings/components/picker/pickers.tsx
+++ b/apps/mobile/app/screens/settings/components/picker/pickers.tsx
@@ -66,7 +66,7 @@ export const FontPicker = createSettingsPicker<
},
getItemKey: (item) => item.id,
options: getFonts(),
- compareValue: (current, item) => current === item.id,
+ compareValue: (current: any, item) => current.id === item.id,
isFeatureAvailable: async () => true,
isOptionAvailable: async () => true
});
diff --git a/apps/mobile/app/screens/settings/components/theme-selector.tsx b/apps/mobile/app/screens/settings/components/theme-selector.tsx
index 10b282f96..913ba2fba 100644
--- a/apps/mobile/app/screens/settings/components/theme-selector.tsx
+++ b/apps/mobile/app/screens/settings/components/theme-selector.tsx
@@ -514,8 +514,8 @@ function ThemeSelector() {
textStyle={{
color:
colorScheme === "all"
- ? colors.secondary.paragraph
- : colors.primary.paragraph
+ ? colors.primary.heading
+ : colors.secondary.paragraph
}}
title={strings.all()}
fontSize={AppFontSize.sm}
@@ -533,8 +533,8 @@ function ThemeSelector() {
textStyle={{
color:
colorScheme === "dark"
- ? colors.secondary.paragraph
- : colors.primary.paragraph
+ ? colors.primary.heading
+ : colors.secondary.paragraph
}}
title={strings.dark()}
fontSize={AppFontSize.sm}
@@ -553,8 +553,8 @@ function ThemeSelector() {
textStyle={{
color:
colorScheme === "light"
- ? colors.secondary.paragraph
- : colors.primary.paragraph
+ ? colors.primary.heading
+ : colors.secondary.paragraph
}}
title={strings.light()}
onPress={() => {
diff --git a/apps/mobile/app/screens/settings/groups/about.tsx b/apps/mobile/app/screens/settings/groups/about.tsx
index f160b5946..b49501350 100644
--- a/apps/mobile/app/screens/settings/groups/about.tsx
+++ b/apps/mobile/app/screens/settings/groups/about.tsx
@@ -63,6 +63,7 @@ export const aboutGroup: SettingSection = {
icon: "device-mobile-camera",
iconFamily: "notesnook",
description: strings.checkForUpdatesDesc(),
+ isModal: true,
modifer: async () => {
presentSheet({
//@ts-ignore // Migrate to ts
diff --git a/apps/mobile/app/screens/settings/groups/account-local.ts b/apps/mobile/app/screens/settings/groups/account-local.ts
index add821265..df4a6723d 100644
--- a/apps/mobile/app/screens/settings/groups/account-local.ts
+++ b/apps/mobile/app/screens/settings/groups/account-local.ts
@@ -45,6 +45,7 @@ export const accountLocalGroup: SettingSection = {
icon: "trash",
iconFamily: "notesnook",
description: strings.deleteAccountDesc(),
+ isModal: true,
modifer: () => {
presentDialog({
title: strings.deleteData(),
diff --git a/apps/mobile/app/screens/settings/groups/account.ts b/apps/mobile/app/screens/settings/groups/account.ts
index a0d9724bb..f16d92825 100644
--- a/apps/mobile/app/screens/settings/groups/account.ts
+++ b/apps/mobile/app/screens/settings/groups/account.ts
@@ -168,6 +168,7 @@ export const accountGroup: SettingSection = {
),
icon: "gift",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
presentDialog({
title: strings.redeemGiftCode(),
@@ -232,6 +233,7 @@ export const accountGroup: SettingSection = {
useUserStore((state) => state.profile?.profilePicture),
hidden: () =>
!useUserStore.getState().profile?.profilePicture,
+ isModal: true,
modifer: () => {
presentDialog({
title: strings.removeProfilePicture(),
@@ -260,6 +262,7 @@ export const accountGroup: SettingSection = {
useHook: () =>
useUserStore((state) => state.profile?.fullName),
hidden: () => !useUserStore.getState().profile?.fullName,
+ isModal: true,
modifer: () => {
presentDialog({
title: strings.removeFullName(),
@@ -296,6 +299,7 @@ export const accountGroup: SettingSection = {
icon: "warning-circle",
iconFamily: "notesnook",
hidden: () => Platform.OS !== "ios",
+ isModal: true,
modifer: async () => {
if (Platform.OS === "android") return;
presentSheet({
@@ -366,6 +370,7 @@ export const accountGroup: SettingSection = {
name: strings.change2faMethod(),
icon: "shield-check",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
verifyUser("global", async () => {
MFASheet.present();
@@ -386,6 +391,7 @@ export const accountGroup: SettingSection = {
!(user as User)?.mfa?.isEnabled
);
},
+ isModal: true,
modifer: () => {
verifyUser("global", async () => {
MFASheet.present(true);
@@ -404,6 +410,7 @@ export const accountGroup: SettingSection = {
!(user as User)?.mfa?.isEnabled
);
},
+ isModal: true,
modifer: () => {
verifyUser("global", async () => {
MFASheet.present(true);
@@ -414,6 +421,7 @@ export const accountGroup: SettingSection = {
{
id: "view-2fa-codes",
name: strings.viewRecoveryCodes(),
+ isModal: true,
modifer: () => {
verifyUser("global", async () => {
MFARecoveryCodes.present("sms");
@@ -435,11 +443,11 @@ export const accountGroup: SettingSection = {
id: "recovery-key",
name: strings.saveDataRecoveryKey(),
iconFamily: "notesnook",
+ isModal: true,
modifer: async () => {
- // if (await verifyUser()) {
-
- // }
- RecoveryKeySheet.present();
+ if (await verifyUser()) {
+ RecoveryKeySheet.present();
+ }
},
description: strings.saveDataRecoveryKeyDesc(),
icon: "key"
@@ -468,6 +476,7 @@ export const accountGroup: SettingSection = {
name: strings.clearCache(),
icon: "trash",
iconFamily: "notesnook",
+ isModal: true,
modifer: async () => {
presentDialog({
title: strings.clearCacheConfirm(),
@@ -521,6 +530,7 @@ export const accountGroup: SettingSection = {
description: strings.logoutWarnin(),
icon: "user-sheet-logout",
iconFamily: "notesnook",
+ isModal: true,
modifer: logoutUser
},
{
@@ -530,6 +540,7 @@ export const accountGroup: SettingSection = {
icon: "user-circle-minus",
iconFamily: "notesnook",
description: strings.deleteAccountDesc(),
+ isModal: true,
modifer: () => {
presentDialog({
title: strings.deleteAccount(),
@@ -700,6 +711,7 @@ export const accountGroup: SettingSection = {
description: strings.forcePullChangesDesc(),
icon: "git-pull-request",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
ForceSyncSheet.present("fetch");
}
@@ -710,6 +722,7 @@ export const accountGroup: SettingSection = {
description: strings.forcePushChangesDesc(),
icon: "arrow-fat-up",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
ForceSyncSheet.present("send");
}
diff --git a/apps/mobile/app/screens/settings/groups/back-restore.ts b/apps/mobile/app/screens/settings/groups/back-restore.ts
index 38408e31b..1278bc82d 100644
--- a/apps/mobile/app/screens/settings/groups/back-restore.ts
+++ b/apps/mobile/app/screens/settings/groups/back-restore.ts
@@ -129,6 +129,7 @@ export const backRestoreGroup: SettingSection = {
},
icon: "folder",
iconFamily: "notesnook",
+ isModal: true,
hidden: () =>
!!SettingsService.get().backupDirectoryAndroid ||
Platform.OS !== "android",
@@ -152,6 +153,7 @@ export const backRestoreGroup: SettingSection = {
{
id: "change-backup-dir",
name: strings.changeBackupDir(),
+ isModal: true,
description: () =>
SettingsService.get().backupDirectoryAndroid?.name || "",
icon: "folder",
@@ -187,7 +189,8 @@ export const backRestoreGroup: SettingSection = {
type: "switch",
name: strings.backupEncryption(),
description: strings.backupEncryptionDesc(),
- icon: "lock",
+ icon: "folder-lock",
+ iconFamily: "notesnook",
property: "encryptedBackup",
modifer: async () => {
const user = useUserStore.getState().user;
@@ -233,6 +236,7 @@ export const backRestoreGroup: SettingSection = {
id: "restore-from-files",
name: strings.restoreFromFiles(),
icon: "folder",
+ isModal: true,
modifer: async () => {
useUserStore.setState({
disableAppLockRequests: true
@@ -270,6 +274,7 @@ export const backRestoreGroup: SettingSection = {
id: "select-backup-folder",
name: strings.selectBackupFolder(),
icon: "folder",
+ isModal: true,
hidden: () => Platform.OS !== "android",
modifer: async () => {
const folder = await ScopedStorage.openDocumentTree(true);
@@ -303,6 +308,7 @@ export const backRestoreGroup: SettingSection = {
icon: "export",
iconFamily: "notesnook",
description: strings.exportAllNotesDesc(),
+ isModal: true,
modifer: () => {
verifyUser(undefined, () => {
ExportNotesSheet.present(undefined, true);
diff --git a/apps/mobile/app/screens/settings/groups/customize.ts b/apps/mobile/app/screens/settings/groups/customize.ts
index b16c47bc5..d4f752c95 100644
--- a/apps/mobile/app/screens/settings/groups/customize.ts
+++ b/apps/mobile/app/screens/settings/groups/customize.ts
@@ -130,6 +130,7 @@ export const customizeGroup: SettingSection = {
description: strings.dateFormatDesc(),
icon: "calendar",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
DateFormat.present();
}
@@ -174,6 +175,7 @@ export const customizeGroup: SettingSection = {
description: strings.clearTrashIntervalDesc(),
icon: "trash",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
TrashInterval.present();
}
@@ -273,8 +275,9 @@ export const customizeGroup: SettingSection = {
name: strings.defaultFontSize(),
description: strings.defaultFontSizeDesc(),
type: "input-selector",
- minInputValue: 8,
- maxInputValue: 120,
+ minInputValue: 1,
+ maxInputValue: 400,
+ step: 1,
inputBadgeValue: "px",
icon: "format-size",
property: "defaultFontSize"
@@ -287,7 +290,8 @@ export const customizeGroup: SettingSection = {
property: "defaultLineHeight",
icon: "format-line-spacing",
minInputValue: EDITOR_LINE_HEIGHT.MIN,
- maxInputValue: EDITOR_LINE_HEIGHT.MAX
+ maxInputValue: EDITOR_LINE_HEIGHT.MAX,
+ step: 0.1
}
]
},
@@ -310,11 +314,12 @@ export const customizeGroup: SettingSection = {
});
}
},
-
{
id: "toggle-markdown",
name: strings.mardownShortcuts(),
property: "markdownShortcuts",
+ icon: "markdown",
+ iconFamily: "notesnook",
description: strings.mardownShortcutsDesc(),
type: "switch",
featureId: "markdownShortcuts"
@@ -330,6 +335,8 @@ export const customizeGroup: SettingSection = {
id: "title-format",
name: strings.titleFormat(),
component: "title-format",
+ icon: "pencil-ruler",
+ iconFamily: "notesnook",
description: strings.titleFormatDesc(),
type: "component"
}
diff --git a/apps/mobile/app/screens/settings/groups/help-support.tsx b/apps/mobile/app/screens/settings/groups/help-support.tsx
index 8048566a0..3b639e7d2 100644
--- a/apps/mobile/app/screens/settings/groups/help-support.tsx
+++ b/apps/mobile/app/screens/settings/groups/help-support.tsx
@@ -34,9 +34,9 @@ export const helpSupportGroup: SettingSection = {
name: strings.reportAnIssue(),
icon: "warning-circle",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
presentSheet({
- //@ts-ignore Migrate to TS
component:
});
},
@@ -76,6 +76,7 @@ export const helpSupportGroup: SettingSection = {
description: strings.downloadDebugLogsDesc(),
icon: "bug-droid",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
DownloadLogs.present();
}
diff --git a/apps/mobile/app/screens/settings/groups/privacy-security.ts b/apps/mobile/app/screens/settings/groups/privacy-security.ts
index bff946c50..598313302 100644
--- a/apps/mobile/app/screens/settings/groups/privacy-security.ts
+++ b/apps/mobile/app/screens/settings/groups/privacy-security.ts
@@ -18,6 +18,7 @@ along with this program. If not, see .
*/
import { strings } from "@notesnook/intl";
+import { validators } from "../../../components/ui/input/form-input";
import { db } from "../../../common/database";
import { AppLockPassword } from "../../../components/dialogs/applock-password";
import AppLockTimeout from "../../../components/sheets/app-lock-timeout";
@@ -67,6 +68,7 @@ export const privacySecurityGroup: SettingSection = {
type: "input",
name: strings.corsBypass(),
description: strings.corsBypassDesc(),
+ validators: [validators.url()],
inputProperties: {
defaultValue: "https://cors.notesnook.com",
autoCorrect: false,
@@ -97,6 +99,7 @@ export const privacySecurityGroup: SettingSection = {
iconFamily: "notesnook",
useHook: useVaultStatus,
hidden: (current) => (current as VaultStatusType)?.exists,
+ isModal: true,
modifer: () => {
openVault({
requestType: VaultRequestType.CreateVault,
@@ -143,6 +146,7 @@ export const privacySecurityGroup: SettingSection = {
iconFamily: "notesnook",
description: strings.changeVaultPasswordDesc(),
hidden: (current) => !(current as VaultStatusType)?.exists,
+ isModal: true,
modifer: () =>
openVault({
requestType: VaultRequestType.ChangePassword,
@@ -159,6 +163,7 @@ export const privacySecurityGroup: SettingSection = {
hidden: (current) => !(current as VaultStatusType)?.exists,
icon: "clock",
iconFamily: "notesnook",
+ isModal: true,
modifer: () => {
LockVaultTimer.present();
}
@@ -171,6 +176,7 @@ export const privacySecurityGroup: SettingSection = {
icon: "paint-brush-household",
iconFamily: "notesnook",
hidden: (current) => !(current as VaultStatusType)?.exists,
+ isModal: true,
modifer: () => {
openVault({
requestType: VaultRequestType.ClearVault,
@@ -190,6 +196,7 @@ export const privacySecurityGroup: SettingSection = {
type: "danger",
useHook: useVaultStatus,
hidden: (current) => !(current as VaultStatusType)?.exists,
+ isModal: true,
modifer: () => {
openVault({
requestType: VaultRequestType.DeleteVault,
@@ -281,6 +288,7 @@ export const privacySecurityGroup: SettingSection = {
description: strings.appLockTimeoutDesc(),
icon: "clock",
iconFamily: "notesnook",
+ isModal: true,
modifer: async () => {
if (await verifyUserWithApplock()) {
AppLockTimeout.present();
@@ -302,6 +310,7 @@ export const privacySecurityGroup: SettingSection = {
return verifyUserWithApplock();
},
property: "appLockHasPasswordSecurity",
+ isModal: true,
modifer: () => {
AppLockPassword.present("create");
}
@@ -318,6 +327,7 @@ export const privacySecurityGroup: SettingSection = {
);
},
property: "appLockHasPasswordSecurity",
+ isModal: true,
modifer: () => {
AppLockPassword.present("change");
}
@@ -334,6 +344,7 @@ export const privacySecurityGroup: SettingSection = {
icon: "backspace",
iconFamily: "notesnook",
property: "appLockHasPasswordSecurity",
+ isModal: true,
modifer: () => {
AppLockPassword.present("remove");
}
diff --git a/apps/mobile/app/screens/settings/groups/productivity.ts b/apps/mobile/app/screens/settings/groups/productivity.ts
index 7ce998156..23d4f0c1d 100644
--- a/apps/mobile/app/screens/settings/groups/productivity.ts
+++ b/apps/mobile/app/screens/settings/groups/productivity.ts
@@ -20,6 +20,7 @@ along with this program. If not, see .
import { strings } from "@notesnook/intl";
import notifee from "@notifee/react-native";
import { Platform } from "react-native";
+import { validators } from "../../../components/ui/input/form-input";
import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { SettingSection } from "../types";
@@ -86,6 +87,7 @@ export const productivityGroup: SettingSection = {
iconFamily: "notesnook",
name: strings.defaultSnoozeTime(),
description: strings.defaultSnoozeTimeDesc(),
+ validators: [validators.number()],
inputProperties: {
keyboardType: "decimal-pad",
defaultValue: 5 + "",
diff --git a/apps/mobile/app/screens/settings/restore-backup/index.tsx b/apps/mobile/app/screens/settings/restore-backup/index.tsx
index ddc42b837..64592b44d 100644
--- a/apps/mobile/app/screens/settings/restore-backup/index.tsx
+++ b/apps/mobile/app/screens/settings/restore-backup/index.tsx
@@ -38,6 +38,10 @@ import {
updateProgress
} from "../../../components/dialogs/progress";
import { Button } from "../../../components/ui/button";
+import {
+ createFormRef,
+ validators
+} from "../../../components/ui/input/form-input";
import Paragraph from "../../../components/ui/typography/paragraph";
import { ToastManager } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
@@ -50,30 +54,79 @@ import { Spacing } from "../../../common/design/spacing";
type PasswordOrKey = { password?: string; encryptionKey?: string };
-const withPassword = () => {
- return new Promise((resolve) => {
- let resolved = false;
+const RESTORE_CANCELLED = new Error("restore-cancelled");
+
+const getPasswordError = (e: unknown): string | undefined => {
+ const message = e instanceof Error ? e.message : "";
+ if (message === "Incorrect password.") return strings.passwordIncorrect();
+ if (message === "Invalid encryption key.")
+ return strings.invalid(strings.encryptionKey());
+ return undefined;
+};
+
+const verifyWithImport =
+ (importBackup: (passwordOrKey: PasswordOrKey) => Promise) =>
+ async (passwordOrKey: PasswordOrKey): Promise => {
+ try {
+ await importBackup(passwordOrKey);
+ return undefined;
+ } catch (e) {
+ const error = getPasswordError(e);
+ if (error) return error;
+ throw e;
+ }
+ };
+
+const withPassword = (
+ verify: (passwordOrKey: PasswordOrKey) => Promise
+) => {
+ return new Promise((resolve, reject) => {
+ const formRef = createFormRef({ password: "" });
+ let done = false;
presentDialog({
context: "local",
title: strings.backupEncrypted(),
- input: true,
- inputPlaceholder: strings.password(),
paragraph: strings.backupEnterPassword(),
positiveText: strings.restore(),
secureTextEntry: true,
+ form: {
+ formRef,
+ items: [
+ {
+ name: "password",
+ placeholder: strings.password(),
+ ref: React.createRef(),
+ validators: [validators.required(strings.passwordNotEntered())]
+ }
+ ],
+ onFormSubmit: async (form, isEncryptionKey) => {
+ if (!form.validate()) return false;
+ const value = form.getValue("password");
+ const passwordOrKey: PasswordOrKey = {
+ encryptionKey: isEncryptionKey ? value : undefined,
+ password: isEncryptionKey ? undefined : value
+ };
+ try {
+ const error = await verify(passwordOrKey);
+ if (error) {
+ form.setError("password", error);
+ return false;
+ }
+ } catch (e) {
+ done = true;
+ reject(e);
+ return true;
+ }
+ done = true;
+ resolve(passwordOrKey);
+ return true;
+ }
+ },
onClose: () => {
- if (resolved) return;
+ if (done) return;
resolve({});
},
negativeText: strings.cancel(),
- positivePress: async (password, isEncryptionKey) => {
- resolve({
- encryptionKey: isEncryptionKey ? password : undefined,
- password: isEncryptionKey ? undefined : password
- });
- resolved = true;
- return true;
- },
check: {
info: strings.useEncryptionKey(),
type: "transparent"
@@ -174,23 +227,26 @@ export const restoreBackup = async (options: {
const isEncryptedBackup = backup.encrypted;
- passwordOrKey = !isEncryptedBackup
- ? ({} as PasswordOrKey)
- : passwordOrKey || (await withPassword());
+ const importBackup = (passwordOrKey: PasswordOrKey) =>
+ db.backup.import(backup, {
+ ...passwordOrKey,
+ attachmentsKey: attachmentsKey
+ });
- if (
- isEncryptedBackup &&
- !passwordOrKey?.encryptionKey &&
- !passwordOrKey?.password
- ) {
- endProgress();
- throw new Error(strings.failedToDecryptBackup());
+ if (!isEncryptedBackup) {
+ await importBackup({});
+ continue;
}
- await db.backup.import(backup, {
- ...passwordOrKey,
- attachmentsKey: attachmentsKey
- });
+ if (!passwordOrKey) {
+ passwordOrKey = await withPassword(verifyWithImport(importBackup));
+ if (!passwordOrKey.encryptionKey && !passwordOrKey.password) {
+ throw RESTORE_CANCELLED;
+ }
+ continue;
+ }
+
+ await importBackup(passwordOrKey);
}
});
@@ -238,29 +294,29 @@ export const restoreBackup = async (options: {
const isEncryptedBackup =
typeof backup.data !== "string" && backup.data.cipher;
- updateProgress({
- progress: isEncryptedBackup
- ? strings.decryptingBackup()
- : strings.preparingBackupRestore()
- });
-
- const { encryptionKey, password } = isEncryptedBackup
- ? ({} as PasswordOrKey)
- : await withPassword();
-
- if (isEncryptedBackup && !encryptionKey && !password) {
- endProgress();
- throw new Error(strings.failedToDecryptBackup());
- }
-
await db.transaction(async () => {
+ const importBackup = (passwordOrKey: PasswordOrKey) =>
+ db.backup.import(backup, passwordOrKey);
+
+ if (!isEncryptedBackup) {
+ updateProgress({
+ progress: strings.restoringBackup()
+ });
+ await importBackup({});
+ return;
+ }
+
updateProgress({
- progress: strings.restoringBackup()
- });
- await db.backup.import(backup, {
- encryptionKey,
- password
+ progress: strings.decryptingBackup()
});
+ // Prompt for the password and validate it by importing from inside the
+ // dialog so an incorrect password keeps the dialog open.
+ const passwordOrKey = await withPassword(
+ verifyWithImport(importBackup)
+ );
+ if (!passwordOrKey.encryptionKey && !passwordOrKey.password) {
+ throw RESTORE_CANCELLED;
+ }
});
endProgress();
}
@@ -276,6 +332,8 @@ export const restoreBackup = async (options: {
endProgress();
} catch (e) {
endProgress();
+ // User dismissed the password prompt: abort silently without an error toast.
+ if (e === RESTORE_CANCELLED) return;
DatabaseLogger.error(e as Error);
ToastManager.error(e as Error, strings.restoreFailed());
}
diff --git a/apps/mobile/app/screens/settings/section-item.tsx b/apps/mobile/app/screens/settings/section-item.tsx
index e0c0a26f6..ad054cfa3 100644
--- a/apps/mobile/app/screens/settings/section-item.tsx
+++ b/apps/mobile/app/screens/settings/section-item.tsx
@@ -18,6 +18,7 @@ along with this program. If not, see .
*/
import { FeatureResult, useIsFeatureAvailable } from "@notesnook/common";
+import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import {
NavigationProp,
@@ -32,6 +33,10 @@ import PaywallSheet from "../../components/sheets/paywall";
import AppIcon from "../../components/ui/AppIcon";
import { IconButton } from "../../components/ui/icon-button";
import Input from "../../components/ui/input";
+import FormInput, {
+ createFormRef,
+ FormRef
+} from "../../components/ui/input/form-input";
import { Pressable } from "../../components/ui/pressable";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
@@ -41,7 +46,6 @@ import { SettingStore, useSettingStore } from "../../stores/use-setting-store";
import { AppFontSize } from "../../utils/size";
import { components } from "./components/components";
import { RouteParams, SettingSection } from "./types";
-import { planToDisplayNameShort } from "../../utils/constants";
const _SectionItem = ({ item }: { item: SettingSection }) => {
const { colors } = useThemeColors();
@@ -66,7 +70,23 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
);
const inputRef = useRef(null);
const [loading, setLoading] = useState(false);
- const [inputSelectorValue, setInputSelectorValue] = useState(() =>
+ const fieldName = (item.property as string) || item.id;
+ const formRef = useRef(
+ createFormRef({
+ [fieldName]: item.property
+ ? `${
+ SettingsService.get()[
+ item.property as keyof SettingStore["settings"]
+ ] ??
+ item.inputProperties?.defaultValue ??
+ ""
+ }`
+ : `${item.inputProperties?.defaultValue ?? ""}`
+ })
+ );
+
+ const [selectorError, setSelectorError] = useState();
+ const [selectorValue, setSelectorValue] = useState(() =>
item.property
? `${
SettingsService.get()[
@@ -76,6 +96,66 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
: ""
);
+ const step = item.step || 1;
+ const stepDecimals = `${step}`.split(".")[1]?.length || 0;
+ const roundToStep = (value: number) => Number(value.toFixed(stepDecimals));
+
+ const commitInputValue = (text: string) => {
+ if (!item.property) return;
+ const error = formRef.current?.validateField(fieldName);
+ if (error) return;
+ SettingsService.set({
+ [item.property as string]: text
+ });
+ };
+
+ const validateSelectorValue = (text: string): string | undefined => {
+ for (const validator of item.validators || []) {
+ const error = validator(text, {});
+ if (error) return error;
+ }
+ const min = item.minInputValue ?? 0;
+ const max = item.maxInputValue ?? Number.MAX_SAFE_INTEGER;
+ const num = Number(text);
+
+ if (!text?.trim() || Number.isNaN(num) || num < min || num > max) {
+ return strings.valueMustBeBetween(min, max);
+ }
+ return undefined;
+ };
+
+ const onChangeSelectorValue = (text: string) => {
+ setSelectorValue(text);
+ const error = validateSelectorValue(text);
+ setSelectorError(error);
+ if (error || !text.trim() || !item.property) return;
+ SettingsService.set({
+ [item.property as string]: text
+ });
+ };
+
+ const stepInputValue = (direction: 1 | -1) => {
+ if (!checkIsFeatureAvailable()) return;
+ if (isDisabled || !item.property) return;
+ const min = item.minInputValue ?? 0;
+ const max = item.maxInputValue ?? Number.MAX_SAFE_INTEGER;
+ const raw = `${
+ SettingsService.get()[item.property as keyof SettingStore["settings"]] ??
+ ""
+ }`;
+ const parsed = parseFloat(raw);
+ const base = Number.isNaN(parsed) ? (direction === 1 ? min : max) : parsed;
+ let next = roundToStep(base + direction * step);
+ if (next < min) next = min;
+ if (next > max) next = max;
+ if (next === base) return;
+ setSelectorError(undefined);
+ setSelectorValue(`${next}`);
+ SettingsService.set({
+ [item.property as string]: `${next}`
+ });
+ };
+
const onChangeSettings = async () => {
if (isDisabled) return;
if (!checkIsFeatureAvailable()) return;
@@ -101,39 +181,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
});
};
- const updateInput = (value: any) => {
- setInputSelectorValue(`${value}`);
- };
-
- const onChangeInputSelectorValue = (text: any, commit?: boolean) => {
- // While typing (commit === false) an empty field is allowed so the user can
- // clear and retype; on commit (submit/blur) an empty field falls back to min.
- if (!text && !commit) {
- setInputSelectorValue("");
- return;
- }
-
- const min = item.minInputValue || 0;
- const max = item.maxInputValue || 0;
- const value = parseInt(text);
-
- // Always cap the upper bound. Only enforce the lower bound on commit so that
- // partial entries (e.g. typing "5" on the way to "50" when min is 8) aren't
- // snapped up prematurely.
- let clamped: number;
- if (Number.isNaN(value)) clamped = min;
- else if (value > max) clamped = max;
- else if (commit && value < min) clamped = min;
- else clamped = value;
-
- // The input is controlled via `value`, so the clamped result is reflected in
- // the field directly and can never display a value outside the range.
- setInputSelectorValue(`${clamped}`);
- SettingsService.set({
- [item.property as string]: `${clamped}`
- });
- };
-
useEffect(() => {
setIsHidden(item.hidden && item.hidden(item.property || current));
setIsDisabled(
@@ -337,7 +384,7 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
) : null}
- {item.type === "screen" ? (
+ {item.type === "screen" || item.isModal ? (
{
)}
{item.type === "input" && (
- {
- SettingsService.set({
- [item.property as string]: e.nativeEvent.text
- });
- item.inputProperties?.onSubmitEditing?.(e);
- }}
+ name={fieldName}
+ formRef={formRef}
+ validators={item.validators || []}
+ label={item.inputLabel}
editable={!isDisabled}
- onChangeText={(text) => {
- SettingsService.set({
- [item.property as string]: text
- });
- item.inputProperties?.onSubmitEditing?.(text as any);
- }}
+ fwdRef={inputRef}
+ fontSize={AppFontSize.sm}
+ marginBottom={0}
containerStyle={{
marginTop: Spacing.LEVEL_2,
backgroundColor: colors.secondary.background,
- borderWidth: 0
+ borderRadius: Radius.XS
}}
inputStyle={{
color: colors.primary.heading
}}
- fontSize={AppFontSize.sm}
- fwdRef={inputRef}
- onLayout={() => {
- inputRef?.current?.setNativeProps({
- text:
- SettingsService.get()[
- item.property as keyof SettingStore["settings"]
- ] + ""
- });
+ onChangeText={(text) => {
+ commitInputValue(text);
+ }}
+ onSubmitEditing={(e) => {
+ commitInputValue(e.nativeEvent.text);
+ item.inputProperties?.onSubmitEditing?.(e);
}}
- defaultValue={item.inputProperties?.defaultValue}
/>
)}
{item.type === "input-selector" && (
- {
- if (!checkIsFeatureAvailable()) return;
- if (isDisabled) return;
- const rawValue = SettingsService.get()[
- item.property as keyof SettingStore["settings"]
- ] as string;
- if (rawValue) {
- const currentValue = parseInt(rawValue);
- const max = item.maxInputValue || 0;
- if (currentValue >= max) return;
- const nextValue = currentValue + 1;
- SettingsService.set({
- [item.property as string]: nextValue
- });
- updateInput(nextValue);
- }
- }}
- size={16}
- type="tertiary"
- style={{
- borderRadius: Radius.XXS,
- padding: Spacing.LEVEL_0,
- width: undefined,
- height: undefined
- }}
- />
- {
- onChangeInputSelectorValue(e.nativeEvent.text, true);
- item.inputProperties?.onSubmitEditing?.(e);
+ stepInputValue(-1)}
+ size={16}
+ type="tertiary"
+ style={{
+ borderRadius: Radius.XXS,
+ padding: Spacing.LEVEL_0,
+ width: undefined,
+ height: undefined
}}
- editable={!isDisabled}
- value={inputSelectorValue}
- onChangeText={(text) => {
- onChangeInputSelectorValue(text);
- item.inputProperties?.onSubmitEditing?.(text as any);
+ />
+
+ {
+ onChangeSelectorValue(e.nativeEvent.text);
+ item.inputProperties?.onSubmitEditing?.(e);
+ }}
+ editable={!isDisabled}
+ value={selectorValue}
+ onChangeText={onChangeSelectorValue}
+ keyboardType="decimal-pad"
+ containerStyle={{
+ borderWidth: 0,
+ paddingLeft: 0,
+ paddingRight: 0,
+ height: 25,
+ borderRadius: 0,
+ minWidth: 25
+ }}
+ fontSize={AppFontSize.sm}
+ inputStyle={{
+ textAlign: "center",
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingLeft: 0,
+ paddingRight: 0,
+ fontFamily: FontFamily.SEMI_BOLD,
+ color: colors.primary.heading
+ }}
+ wrapperStyle={{
+ flexGrow: 0,
+ marginBottom: 0,
+ paddingLeft: 0,
+ paddingRight: 0
+ }}
+ buttons={
+ <>
+ {item.inputBadgeValue ? (
+
+ {item.inputBadgeValue}
+
+ ) : null}
+ >
+ }
+ />
+
+
+ stepInputValue(1)}
+ size={16}
+ type="tertiary"
+ style={{
+ borderRadius: Radius.XXS,
+ padding: Spacing.LEVEL_0,
+ width: undefined,
+ height: undefined
}}
- fontSize={AppFontSize.sm}
- inputStyle={{
- textAlign: "center",
- paddingTop: 0,
- paddingBottom: 0,
- paddingLeft: 0,
- paddingRight: 0,
- fontFamily: FontFamily.SEMI_BOLD,
- color: colors.primary.heading
- }}
- wrapperStyle={{
- flexGrow: 0,
- marginBottom: 0,
- paddingLeft: 0,
- paddingRight: 0
- }}
- buttons={
- <>
- {item.inputBadgeValue ? (
-
- px
-
- ) : null}
- >
- }
/>
- {
- if (!checkIsFeatureAvailable()) return;
- if (isDisabled) return;
- const rawValue = SettingsService.get()[
- item.property as keyof SettingStore["settings"]
- ] as string;
- if (rawValue) {
- const currentValue = parseInt(rawValue);
- const minValue = item.minInputValue || 0;
- if (currentValue <= minValue) return;
- const nextValue = currentValue - 1;
- SettingsService.set({
- [item.property as string]: nextValue
- });
- updateInput(nextValue);
- }
- }}
- size={16}
- type="tertiary"
- style={{
- borderRadius: Radius.XXS,
- padding: Spacing.LEVEL_0,
- width: undefined,
- height: undefined
- }}
- />
+ {selectorError ? (
+
+ {" "}
+ {selectorError}
+
+ ) : null}
)}
diff --git a/apps/mobile/app/screens/settings/types.ts b/apps/mobile/app/screens/settings/types.ts
index 2b1ad90a2..3c6a1fa26 100644
--- a/apps/mobile/app/screens/settings/types.ts
+++ b/apps/mobile/app/screens/settings/types.ts
@@ -21,6 +21,7 @@ import { TextInput } from "react-native";
import { Settings } from "../../stores/use-setting-store";
import { FeatureId } from "@notesnook/common";
import { IconProps } from "../../components/ui/AppIcon";
+import { FieldValidator } from "../../components/ui/input/form-input";
export type SettingSection = {
id: string;
@@ -51,11 +52,25 @@ export type SettingSection = {
options?: any[];
minInputValue?: number;
maxInputValue?: number;
+ /**
+ * Amount to increment/decrement an `input-selector` by when the plus/minus
+ * buttons are pressed. Defaults to `1` (e.g. font size). Use a fractional
+ * value for point-based inputs like line height (e.g. `0.1`).
+ */
+ step?: number;
+ /**
+ * Validators applied to `input` and `input-selector` fields. Invalid values
+ * surface an error (styled like `FormInput`) and are not saved.
+ */
+ validators?: FieldValidator[];
+ /** Optional label rendered above an `input` field. */
+ inputLabel?: string;
inputBadgeValue?: string;
onVerify?: () => Promise;
hideHeader?: boolean;
disabled?: (current: unknown) => boolean;
featureId?: FeatureId;
+ isModal?: boolean;
};
export type SettingsGroup = {
diff --git a/apps/mobile/app/services/exporter.ts b/apps/mobile/app/services/exporter.ts
index 724f0aaf0..64e8a34c0 100644
--- a/apps/mobile/app/services/exporter.ts
+++ b/apps/mobile/app/services/exporter.ts
@@ -176,7 +176,8 @@ async function exportAttachmentToFile(
async function bulkExport(
notes: FilteredSelector,
type: "txt" | "pdf" | "md" | "html" | "md-frontmatter",
- callback: (progress?: string) => void
+ callback: (progress?: string) => void,
+ signal?: AbortSignal
) {
const totalNotes = await notes.count();
@@ -193,6 +194,11 @@ async function bulkExport(
format: type,
unlockVault: unlockVaultForNoteExport as () => Promise
})) {
+ if (signal?.aborted) {
+ RNFetchBlob.fs.unlink(cacheFolder).catch(() => {});
+ return;
+ }
+
if (item instanceof Error) {
DatabaseLogger.error(item);
continue;
@@ -223,7 +229,8 @@ async function bulkExport(
async function exportNote(
note: Note,
type: "txt" | "pdf" | "md" | "html" | "md-frontmatter",
- callback: (progress?: string) => void
+ callback: (progress?: string) => void,
+ signal?: AbortSignal
) {
const fileFuncions = await resolveFileFunctions(type);
@@ -239,6 +246,11 @@ async function exportNote(
format: type,
unlockVault: unlockVaultForNoteExport as () => Promise
})) {
+ if (signal?.aborted) {
+ RNFetchBlob.fs.unlink(cacheFolder).catch(() => {});
+ return;
+ }
+
if (item instanceof Error) {
DatabaseLogger.error(item);
continue;
diff --git a/apps/mobile/fonts/notesnook-icons.glyphmap.json b/apps/mobile/fonts/notesnook-icons.glyphmap.json
index 6f96baac4..a68515441 100644
--- a/apps/mobile/fonts/notesnook-icons.glyphmap.json
+++ b/apps/mobile/fonts/notesnook-icons.glyphmap.json
@@ -1 +1 @@
-{"m":{"f":"notesnook-icons","u":1024,"z":1020,"s":59648,"h":"3001559bcdc9b472bb2eb024785dc5615ecc63311dd8433059e4969819ec754b"},"i":{"archive":[1024,[[59648,"#666666"]]],"arrow-back":[1024,[[59649,"rgba(255,255,255,0.7)"]]],"arrow-clockwise":[1024,[[59650,"#181818"]]],"arrow-counter-clockwise":[1024,[[59651,"#181818"]]],"arrow-fat-up":[1024,[[59652,"var(--fill-0, #181818)"]]],"arrow-right":[1024,[[59653,"#008836"]]],"arrow-square-out":[1024,[[59654,"#666666"]]],"arrow-u-up-left":[1024,[[59655,"var(--fill-0, #181818)"]]],"arrows-clockwise":[1024,[[59656,"var(--fill-0, #181818)"]]],"backspace":[1024,[[59657,"#181818"]]],"bag-simple":[1024,[[59658,"var(--fill-0, #181818)"]]],"bell-z":[1024,[[59659,"#181818"]]],"bell":[1024,[[59660,"#666666"]]],"bomb-off":[1024,[[59661,"currentColor"]]],"bomb":[1024,[[59662,"#666666"]]],"book-open":[1024,[[59663,"#666666"]]],"bookmark":[1024,[[59664,"#666666"]]],"box-empty":[1024,[[59665,"#B0B0B1"]]],"bug-droid":[1024,[[59666,"var(--fill-0, #181818)"]]],"calendar-check":[946,[[59667,"var(--fill-0, #181818)"]]],"calendar-day":[1024,[[59668,"#181818"]]],"calendar-dots":[946,[[59669,"var(--fill-0, #181818)"]]],"calendar":[1024,[[59670,"rgba(102,102,102,0.8)"]]],"chart-donut":[1024,[[59671,"#181818"]]],"chart-line-up":[1024,[[59672,"var(--fill-0, #181818)"]]],"chat":[1024,[[59673,"white"]]],"check-circle":[1024,[[59674,"#008836"]]],"check-small":[951,[[59675,"#008836"]]],"check-square":[1024,[[59676,"#181818"]]],"check":[1024,[[59677,"#008836"]]],"checkbox-intermediate":[1024,[[59678,"currentColor"]]],"checkbox":[1024,[[59679,"#008836"],[59680,"white"]]],"checks":[1024,[[59681,"#181818"]]],"chevron-down":[1024,[[59682,"#181818"]]],"chevron-left":[1024,[[59683,"currentColor"]]],"chevron-right":[1024,[[59684,"#181818"]]],"chevron-up":[1024,[[59685,"#181818"]]],"clock-counter-clockwise":[1024,[[59686,"var(--fill-0, #181818)"]]],"clock":[1024,[[59687,"#181818"]]],"close":[1024,[[59688,"#666666"]]],"cloud-check":[1024,[[59689,"var(--fill-0, #181818)"]]],"cloud-upload":[1024,[[59690,"#858585"]]],"cloud":[1024,[[59691,"white"]]],"copy":[1024,[[59692,"#008836"]]],"crown-simple":[1024,[[59693,"#E0B637"]]],"dark-mode-outline":[1024,[[59694,"currentColor"]]],"delete-restore":[1024,[[59695,"currentColor"]]],"device-mobile-camera":[1024,[[59696,"var(--fill-0, #181818)"]]],"discord-logo":[1024,[[59697,"var(--fill-0, #181818)"]]],"dots-three":[1024,[[59698,"#202020"]]],"download-simple":[1024,[[59699,"var(--fill-0, #181818)"]]],"drive-file-move":[1088,[[59700,"#666666"]]],"duplicate":[1024,[[59701,"currentColor"]]],"edit-pencil":[1024,[[59702,"#181818"]]],"ellipse":[1024,[[59703,"#A6A6A6"]]],"envelope-simple":[1024,[[59704,"white"]]],"export":[1024,[[59705,"var(--fill-0, #181818)"]]],"eye-closed":[1024,[[59706,"white"]]],"eye-filled":[1024,[[59707,"#666666"]]],"eye-open":[1024,[[59708,"#666666"]]],"eye-slash":[1024,[[59709,"var(--fill-0, #181818)"]]],"file-cloud":[1024,[[59710,"#181818"]]],"file-dashed":[1024,[[59711,"var(--fill-0, #181818)"]]],"file-html":[1024,[[59712,"#181818"]]],"file-pdf":[1024,[[59713,"#181818"]]],"file-text":[1024,[[59714,"var(--fill-0, #181818)"]]],"file":[1024,[[59715,"white"]]],"fingerprint-simple":[1024,[[59716,"#181818"]]],"folder":[1024,[[59717,"#181818"]]],"funnel":[1024,[[59718,"#666666"]]],"gift":[1024,[[59719,"#181818"]]],"git-pull-request":[1024,[[59720,"var(--fill-0, #181818)"]]],"github-logo":[1024,[[59721,"#181818"]]],"hard-drives":[1024,[[59722,"var(--fill-0, #181818)"]]],"home":[1024,[[59723,"#181818"]]],"house":[1024,[[59724,"#6F6F6F"]]],"identifier":[1024,[[59725,"currentColor"]]],"image-outline":[1024,[[59726,"white"]]],"image":[1024,[[59727,"#181818"]]],"key":[1024,[[59728,"var(--fill-0, #181818)"]]],"link-alt":[1024,[[59729,"#666666"]]],"link-simple":[1024,[[59730,"white"]]],"link":[560,[[59731,"#666666"]]],"list":[2190,[[59732,"#181818"]]],"lock-simple":[1024,[[59733,"white"]]],"lock":[1024,[[59734,"#181818"]]],"markdown":[1024,[[59735,"#181818"]]],"mastodon-logo":[1024,[[59736,"var(--fill-0, #181818)"]]],"menu":[1024,[[59737,"#181818"]]],"message-badge-outline":[1024,[[59738,"currentColor"]]],"minus":[1024,[[59739,"#181818"]]],"mode-edit":[1024,[[59740,"#181818"]]],"moon":[1024,[[59741,"var(--fill-0, #181818)"]]],"music-notes":[1024,[[59742,"#181818"]]],"network":[1024,[[59743,"var(--fill-0, #181818)"]]],"note":[1024,[[59744,"#181818"]]],"notification":[1024,[[59745,"var(--fill-0, #181818)"]]],"numpad":[1024,[[59746,"#181818"]]],"nut":[1024,[[59747,"#181818"]]],"paint-brush-household":[1024,[[59748,"#181818"]]],"paint-roller":[1024,[[59749,"var(--fill-0, #181818)"]]],"palette":[1024,[[59750,"#666666"]]],"paperclip":[1024,[[59751,"#666666"]]],"pause":[1024,[[59752,"var(--fill-0, #181818)"]]],"pencil-ruler":[1024,[[59753,"#181818"]]],"pencil-simple-line":[1024,[[59754,"var(--fill-0, #181818)"]]],"pencil-simple-slash":[1024,[[59755,"#858585"]]],"pencil-simple":[1024,[[59756,"#181818"]]],"pin":[939,[[59757,"#666666"]]],"plus":[1024,[[59758,"#181818"]]],"radio-button":[1024,[[59759,"#008836"]]],"recovery-key-cloud-arrow-down":[1024,[[59760,"var(--fill-0, #181818)"]]],"recovery-key-copy":[1024,[[59761,"var(--stroke-0, #008836)"]]],"recovery-key-file":[1024,[[59762,"var(--fill-0, #181818)"]]],"recovery-key-key":[1024,[[59763,"var(--fill-0, #181818)"]]],"recovery-key-qr-code":[1024,[[59764,"var(--fill-0, #181818)"]]],"recovery-key-shield-check":[1024,[[59765,"var(--fill-0, #008836)"]]],"search":[1024,[[59766,"#181818"]]],"share":[1024,[[59767,"#666666"]]],"shield-check":[1024,[[59768,"#181818"]]],"shield-plus":[1024,[[59769,"#181818"]]],"shield":[1024,[[59770,"var(--fill-0, #181818)"]]],"shopping-mode":[1024,[[59771,"#666666"]]],"sliders-horizontal":[1024,[[59772,"var(--fill-0, #181818)"]]],"sliders":[1024,[[59773,"#181818"]]],"sort-ascending":[1024,[[59774,"#666666"]]],"sort-descending":[1024,[[59775,"currentColor"]]],"speaker-high":[1024,[[59776,"#181818"]]],"spellcheck":[1067,[[59777,"currentColor"]]],"square-out":[1024,[[59778,"#181818"]]],"squares-four":[1024,[[59779,"var(--fill-0, #181818)"]]],"star-filled":[1024,[[59780,"#E5C131"]]],"star":[1024,[[59781,"#666666"]]],"sun":[1024,[[59782,"#666666"]]],"swatches":[1024,[[59783,"var(--fill-0, #181818)"]]],"sync-disabled":[911,[[59784,"#858585"]]],"table":[1024,[[59785,"#181818"]]],"telegram-logo":[1024,[[59786,"var(--fill-0, #181818)"]]],"text-aa":[1024,[[59787,"#181818"]]],"toggle-off":[1725,[[59788,"#DADADA"]]],"toggle-on":[1725,[[59789,"#008836"]]],"trash-alt":[1024,[[59790,"#FB2C36"]]],"trash":[1024,[[59791,"#666666"]]],"tray-arrow-down":[1024,[[59792,"#181818"]]],"upload":[1024,[[59793,"#181818"]]],"user-circle-minus":[1024,[[59794,"var(--fill-0, #FF242E)"]]],"user-sheet-docs":[1024,[[59795,"var(--fill-0, #181818)"]]],"user-sheet-logout":[1024,[[59796,"var(--fill-0, #FB2C36)"]]],"user-sheet-settings":[1024,[[59797,"var(--fill-0, #181818)"]]],"user-sheet-support":[1024,[[59798,"var(--fill-0, #181818)"]]],"user-sheet-sync":[1024,[[59799,"var(--fill-0, #181818)"]]],"user":[1024,[[59800,"var(--fill-0, #181818)"]]],"users-three":[1024,[[59801,"var(--fill-0, #181818)"]]],"video-camera":[1024,[[59802,"#181818"]]],"view-list":[1024,[[59803,"#181818"]]],"warning-circle":[1024,[[59804,"#BB3431"]]],"warning":[1024,[[59805,"#FF242E"]]],"wifi-slash":[1024,[[59806,"var(--fill-0, #181818)"]]],"wrench":[1024,[[59807,"#181818"]]],"x-logo":[1024,[[59808,"var(--fill-0, #181818)"]]]}}
\ No newline at end of file
+{"m":{"f":"notesnook-icons","u":1024,"z":1020,"s":59648,"h":"c2a0f21ba20eefa38ead07a186c10071673e127c90aee008cc0b2c02490579b3"},"i":{"archive":[1024,[[59648,"#666666"]]],"arrow-back":[1024,[[59649,"rgba(255,255,255,0.7)"]]],"arrow-clockwise":[1024,[[59650,"#181818"]]],"arrow-counter-clockwise":[1024,[[59651,"#181818"]]],"arrow-fat-up":[1024,[[59652,"var(--fill-0, #181818)"]]],"arrow-right":[1024,[[59653,"#008836"]]],"arrow-square-out":[1024,[[59654,"#666666"]]],"arrow-u-up-left":[1024,[[59655,"var(--fill-0, #181818)"]]],"arrows-clockwise":[1024,[[59656,"var(--fill-0, #181818)"]]],"backspace":[1024,[[59657,"#181818"]]],"bag-simple":[1024,[[59658,"var(--fill-0, #181818)"]]],"bell-z":[1024,[[59659,"#181818"]]],"bell":[1024,[[59660,"#666666"]]],"bomb-off":[1024,[[59661,"currentColor"]]],"bomb":[1024,[[59662,"#666666"]]],"book-open":[1024,[[59663,"#666666"]]],"bookmark":[1024,[[59664,"#666666"]]],"box-empty":[1024,[[59665,"#B0B0B1"]]],"bug-droid":[1024,[[59666,"var(--fill-0, #181818)"]]],"calendar-check":[946,[[59667,"var(--fill-0, #181818)"]]],"calendar-day":[1024,[[59668,"#181818"]]],"calendar-dots":[946,[[59669,"var(--fill-0, #181818)"]]],"calendar":[1024,[[59670,"rgba(102,102,102,0.8)"]]],"chart-donut":[1024,[[59671,"#181818"]]],"chart-line-up":[1024,[[59672,"var(--fill-0, #181818)"]]],"chat":[1024,[[59673,"white"]]],"check-circle":[1024,[[59674,"#008836"]]],"check-small":[951,[[59675,"#008836"]]],"check-square":[1024,[[59676,"#181818"]]],"check":[1024,[[59677,"#008836"]]],"checkbox-intermediate":[1024,[[59678,"currentColor"]]],"checkbox":[1024,[[59679,"#008836"],[59680,"white"]]],"checks":[1024,[[59681,"#181818"]]],"chevron-down":[1024,[[59682,"#181818"]]],"chevron-left":[1024,[[59683,"currentColor"]]],"chevron-right":[1024,[[59684,"#181818"]]],"chevron-up":[1024,[[59685,"#181818"]]],"clock-counter-clockwise":[1024,[[59686,"var(--fill-0, #181818)"]]],"clock":[1024,[[59687,"#181818"]]],"close":[1024,[[59688,"#666666"]]],"cloud-check":[1024,[[59689,"var(--fill-0, #181818)"]]],"cloud-upload":[1024,[[59690,"#858585"]]],"cloud":[1024,[[59691,"white"]]],"copy":[1024,[[59692,"#008836"]]],"crown-simple":[1024,[[59693,"#E0B637"]]],"dark-mode-outline":[1024,[[59694,"currentColor"]]],"delete-restore":[1024,[[59695,"currentColor"]]],"device-mobile-camera":[1024,[[59696,"var(--fill-0, #181818)"]]],"discord-logo":[1024,[[59697,"var(--fill-0, #181818)"]]],"dots-three":[1024,[[59698,"#202020"]]],"download-simple":[1024,[[59699,"var(--fill-0, #181818)"]]],"drive-file-move":[1088,[[59700,"#666666"]]],"duplicate":[1024,[[59701,"currentColor"]]],"edit-pencil":[1024,[[59702,"#181818"]]],"ellipse":[1024,[[59703,"#A6A6A6"]]],"envelope-simple":[1024,[[59704,"white"]]],"export":[1024,[[59705,"var(--fill-0, #181818)"]]],"eye-closed":[1024,[[59706,"white"]]],"eye-filled":[1024,[[59707,"#666666"]]],"eye-open":[1024,[[59708,"#666666"]]],"eye-slash":[1024,[[59709,"var(--fill-0, #181818)"]]],"file-cloud":[1024,[[59710,"#181818"]]],"file-dashed":[1024,[[59711,"var(--fill-0, #181818)"]]],"file-html":[1024,[[59712,"#181818"]]],"file-pdf":[1024,[[59713,"#181818"]]],"file-text":[1024,[[59714,"var(--fill-0, #181818)"]]],"file":[1024,[[59715,"white"]]],"fingerprint-simple":[1024,[[59716,"#181818"]]],"folder-lock":[1024,[[59717,"#181818"]]],"folder":[1024,[[59718,"#181818"]]],"funnel":[1024,[[59719,"#666666"]]],"gift":[1024,[[59720,"#181818"]]],"git-pull-request":[1024,[[59721,"var(--fill-0, #181818)"]]],"github-logo":[1024,[[59722,"#181818"]]],"hard-drives":[1024,[[59723,"var(--fill-0, #181818)"]]],"home":[1024,[[59724,"#181818"]]],"house":[1024,[[59725,"#6F6F6F"]]],"identifier":[1024,[[59726,"currentColor"]]],"image-outline":[1024,[[59727,"white"]]],"image":[1024,[[59728,"#181818"]]],"key":[1024,[[59729,"var(--fill-0, #181818)"]]],"link-alt":[1024,[[59730,"#666666"]]],"link-simple":[1024,[[59731,"white"]]],"link":[560,[[59732,"#666666"]]],"list":[2190,[[59733,"#181818"]]],"lock-simple":[1024,[[59734,"white"]]],"lock":[1024,[[59735,"#181818"]]],"markdown":[1024,[[59736,"#181818"]]],"mastodon-logo":[1024,[[59737,"var(--fill-0, #181818)"]]],"menu":[1024,[[59738,"#181818"]]],"message-badge-outline":[1024,[[59739,"currentColor"]]],"minus":[1024,[[59740,"#181818"]]],"mode-edit":[1024,[[59741,"#181818"]]],"moon":[1024,[[59742,"var(--fill-0, #181818)"]]],"music-notes":[1024,[[59743,"#181818"]]],"network":[1024,[[59744,"var(--fill-0, #181818)"]]],"note":[1024,[[59745,"#181818"]]],"notification":[1024,[[59746,"var(--fill-0, #181818)"]]],"numpad":[1024,[[59747,"#181818"]]],"nut":[1024,[[59748,"#181818"]]],"paint-brush-household":[1024,[[59749,"#181818"]]],"paint-roller":[1024,[[59750,"var(--fill-0, #181818)"]]],"palette":[1024,[[59751,"#666666"]]],"paperclip":[1024,[[59752,"#666666"]]],"pause":[1024,[[59753,"var(--fill-0, #181818)"]]],"pencil-ruler":[1024,[[59754,"#181818"]]],"pencil-simple-line":[1024,[[59755,"var(--fill-0, #181818)"]]],"pencil-simple-slash":[1024,[[59756,"#858585"]]],"pencil-simple":[1024,[[59757,"#181818"]]],"pin":[939,[[59758,"#666666"]]],"plus":[1024,[[59759,"#181818"]]],"radio-button":[1024,[[59760,"#008836"]]],"recovery-key-cloud-arrow-down":[1024,[[59761,"var(--fill-0, #181818)"]]],"recovery-key-copy":[1024,[[59762,"var(--stroke-0, #008836)"]]],"recovery-key-file":[1024,[[59763,"var(--fill-0, #181818)"]]],"recovery-key-key":[1024,[[59764,"var(--fill-0, #181818)"]]],"recovery-key-qr-code":[1024,[[59765,"var(--fill-0, #181818)"]]],"recovery-key-shield-check":[1024,[[59766,"var(--fill-0, #008836)"]]],"search":[1024,[[59767,"#181818"]]],"share":[1024,[[59768,"#666666"]]],"shield-check":[1024,[[59769,"#181818"]]],"shield-plus":[1024,[[59770,"#181818"]]],"shield":[1024,[[59771,"var(--fill-0, #181818)"]]],"shopping-mode":[1024,[[59772,"#666666"]]],"sliders-horizontal":[1024,[[59773,"var(--fill-0, #181818)"]]],"sliders":[1024,[[59774,"#181818"]]],"sort-ascending":[1024,[[59775,"#666666"]]],"sort-descending":[1024,[[59776,"currentColor"]]],"speaker-high":[1024,[[59777,"#181818"]]],"spellcheck":[1067,[[59778,"currentColor"]]],"square-out":[1024,[[59779,"#181818"]]],"squares-four":[1024,[[59780,"var(--fill-0, #181818)"]]],"star-filled":[1024,[[59781,"#E5C131"]]],"star":[1024,[[59782,"#666666"]]],"sun":[1024,[[59783,"#666666"]]],"swatches":[1024,[[59784,"var(--fill-0, #181818)"]]],"sync-disabled":[911,[[59785,"#858585"]]],"table":[1024,[[59786,"#181818"]]],"telegram-logo":[1024,[[59787,"var(--fill-0, #181818)"]]],"text-aa":[1024,[[59788,"#181818"]]],"toggle-off":[1725,[[59789,"#DADADA"]]],"toggle-on":[1725,[[59790,"#008836"]]],"trash-alt":[1024,[[59791,"#FB2C36"]]],"trash":[1024,[[59792,"#666666"]]],"tray-arrow-down":[1024,[[59793,"#181818"]]],"upload":[1024,[[59794,"#181818"]]],"user-circle-minus":[1024,[[59795,"var(--fill-0, #FF242E)"]]],"user-sheet-docs":[1024,[[59796,"var(--fill-0, #181818)"]]],"user-sheet-logout":[1024,[[59797,"var(--fill-0, #FB2C36)"]]],"user-sheet-settings":[1024,[[59798,"var(--fill-0, #181818)"]]],"user-sheet-support":[1024,[[59799,"var(--fill-0, #181818)"]]],"user-sheet-sync":[1024,[[59800,"var(--fill-0, #181818)"]]],"user":[1024,[[59801,"var(--fill-0, #181818)"]]],"users-three":[1024,[[59802,"var(--fill-0, #181818)"]]],"video-camera":[1024,[[59803,"#181818"]]],"view-list":[1024,[[59804,"#181818"]]],"warning-circle":[1024,[[59805,"#BB3431"]]],"warning":[1024,[[59806,"#FF242E"]]],"wifi-slash":[1024,[[59807,"var(--fill-0, #181818)"]]],"wrench":[1024,[[59808,"#181818"]]],"x-logo":[1024,[[59809,"var(--fill-0, #181818)"]]]}}
\ No newline at end of file
diff --git a/apps/mobile/fonts/notesnook-icons.ttf b/apps/mobile/fonts/notesnook-icons.ttf
index 909225b30..2f6592d1f 100644
Binary files a/apps/mobile/fonts/notesnook-icons.ttf and b/apps/mobile/fonts/notesnook-icons.ttf differ
diff --git a/apps/mobile/ios/nanoicons-fonts/notesnook-icons.ttf b/apps/mobile/ios/nanoicons-fonts/notesnook-icons.ttf
index 909225b30..2f6592d1f 100644
Binary files a/apps/mobile/ios/nanoicons-fonts/notesnook-icons.ttf and b/apps/mobile/ios/nanoicons-fonts/notesnook-icons.ttf differ
diff --git a/apps/mobile/react-native.config.js b/apps/mobile/react-native.config.js
index dd7538c12..ef1ec54f0 100644
--- a/apps/mobile/react-native.config.js
+++ b/apps/mobile/react-native.config.js
@@ -18,7 +18,7 @@ along with this program. If not, see .
*/
const isGithubRelease = false;
const config = {
- // commands: require("@callstack/repack/commands/rspack")
+ commands: require("@callstack/repack/commands/rspack")
};
if (!config.dependencies) config.dependencies = {};
diff --git a/packages/icons/svgs/folder-lock.svg b/packages/icons/svgs/folder-lock.svg
new file mode 100644
index 000000000..f2842893e
--- /dev/null
+++ b/packages/icons/svgs/folder-lock.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po
index cb99471cc..93cbaa245 100644
--- a/packages/intl/locale/en.po
+++ b/packages/intl/locale/en.po
@@ -1896,6 +1896,10 @@ msgstr "Code"
msgid "Code block"
msgstr "Code block"
+#: src/strings.ts:3017
+msgid "Code copied"
+msgstr "Code copied"
+
#: src/strings.ts:2330
msgid "Code remove"
msgstr "Code remove"
@@ -3017,6 +3021,10 @@ msgstr "Enter the gift code to redeem your subscription."
msgid "Enter title"
msgstr "Enter title"
+#: src/strings.ts:3018
+msgid "Enter vault password"
+msgstr "Enter vault password"
+
#: src/strings.ts:1541
msgid "Enter verification code sent to your new email"
msgstr "Enter verification code sent to your new email"
diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po
index a30b06ba5..b0f654a26 100644
--- a/packages/intl/locale/pseudo-LOCALE.po
+++ b/packages/intl/locale/pseudo-LOCALE.po
@@ -1885,6 +1885,10 @@ msgstr ""
msgid "Code block"
msgstr ""
+#: src/strings.ts:3017
+msgid "Code copied"
+msgstr ""
+
#: src/strings.ts:2330
msgid "Code remove"
msgstr ""
@@ -3006,6 +3010,10 @@ msgstr ""
msgid "Enter title"
msgstr ""
+#: src/strings.ts:3018
+msgid "Enter vault password"
+msgstr ""
+
#: src/strings.ts:1541
msgid "Enter verification code sent to your new email"
msgstr ""
diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts
index 88d1ce537..c536e4e72 100644
--- a/packages/intl/src/strings.ts
+++ b/packages/intl/src/strings.ts
@@ -3017,5 +3017,7 @@ Continue without attachments?`,
setExpiryDesc: () => t`Note will get deleted on the set date.`,
changeCreatedDate: () => t`Change created date`,
changeCreatedDateDesc: () =>
- t`Select date and time to change the created date`
+ t`Select date and time to change the created date`,
+ codeCopied: () => t`Code copied`,
+ enterVaultPassword: () => t`Enter vault password`
};