diff --git a/apps/mobile/app/components/dialogs/vault/index.jsx b/apps/mobile/app/components/dialogs/vault/index.jsx
deleted file mode 100644
index 291085eb6..000000000
--- a/apps/mobile/app/components/dialogs/vault/index.jsx
+++ /dev/null
@@ -1,900 +0,0 @@
-/*
-This file is part of the Notesnook project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-import Clipboard from "@react-native-clipboard/clipboard";
-import React, { Component, createRef } from "react";
-import { InteractionManager, View } from "react-native";
-import Share from "react-native-share";
-import { notesnook } from "../../../../e2e/test.ids";
-import { db } from "../../../common/database";
-import BiometricService from "../../../services/biometrics";
-import { DDS } from "../../../services/device-detection";
-import {
- ToastManager,
- eSendEvent,
- eSubscribeEvent,
- eUnSubscribeEvent
-} from "../../../services/event-manager";
-import Navigation from "../../../services/navigation";
-import { getElevationStyle } from "../../../utils/elevation";
-import {
- eCloseActionSheet,
- eCloseVaultDialog,
- eOnLoadNote,
- eOpenVaultDialog,
- eUpdateNoteInEditor
-} from "../../../utils/events";
-import { deleteItems } from "../../../utils/functions";
-import { fluidTabsRef } from "../../../utils/global-refs";
-import { convertNoteToText } from "../../../utils/note-to-text";
-import { sleep } from "../../../utils/time";
-import BaseDialog from "../../dialog/base-dialog";
-import DialogButtons from "../../dialog/dialog-buttons";
-import DialogHeader from "../../dialog/dialog-header";
-import { Toast } from "../../toast";
-import { Button } from "../../ui/button";
-import Input from "../../ui/input";
-import Seperator from "../../ui/seperator";
-import Paragraph from "../../ui/typography/paragraph";
-import { strings } from "@notesnook/intl";
-import { DefaultAppStyles } from "../../../utils/styles";
-
-export class VaultDialog extends Component {
- constructor(props) {
- super(props);
- this.state = {
- visible: false,
- wrongPassword: false,
- loading: false,
- note: {},
- vault: false,
- locked: true,
- permanant: false,
- goToEditor: false,
- share: false,
- passwordsDontMatch: false,
- deleteNote: false,
- focusIndex: null,
- biometricUnlock: false,
- isBiometryEnrolled: false,
- isBiometryAvailable: false,
- fingerprintAccess: false,
- changePassword: false,
- copyNote: false,
- revokeFingerprintAccess: false,
- title: strings.goToEditor(),
- description: null,
- clearVault: false,
- deleteVault: false,
- deleteAll: false,
- noteLocked: false
- };
-
- this.passInputRef = createRef();
- this.confirmPassRef = createRef();
- this.changePassInputRef = createRef();
-
- this.password = null;
- this.confirmPassword = null;
- this.newPassword = null;
- this.title = !this.state.novault
- ? strings.createVault()
- : this.state.fingerprintAccess
- ? strings.vaultFingerprintUnlock()
- : this.state.revokeFingerprintAccess
- ? strings.revokeVaultFingerprintUnlock()
- : this.state.changePassword
- ? strings.changeVaultPassword()
- : this.state.noteLocked
- ? this.state.deleteNote
- ? strings.deleteNote()
- : this.state.share
- ? strings.shareNote()
- : this.state.copyNote
- ? strings.copyNote()
- : this.state.goToEditor
- ? strings.goToEditor()
- : strings.goToEditor()
- : strings.lockNote();
- }
-
- componentDidMount() {
- eSubscribeEvent(eOpenVaultDialog, this.open);
- eSubscribeEvent(eCloseVaultDialog, this.close);
- }
-
- componentWillUnmount() {
- eUnSubscribeEvent(eOpenVaultDialog, this.open);
- eUnSubscribeEvent(eCloseVaultDialog, this.close);
- }
-
- /**
- *
- * @param {import('../../../services/event-manager').vaultType} data
- */
- open = async (data) => {
- let biometry = await BiometricService.isBiometryAvailable();
- let available = false;
- let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
-
- if (biometry) {
- available = true;
- }
-
- this.setState({
- note: data.item,
- novault: data.novault,
- locked: data.locked,
- permanant: data.permanant,
- goToEditor: data.goToEditor,
- share: data.share,
- deleteNote: data.deleteNote,
- copyNote: data.copyNote,
- isBiometryAvailable: available,
- biometricUnlock: fingerprint,
- isBiometryEnrolled: fingerprint,
- fingerprintAccess: data.fingerprintAccess,
- changePassword: data.changePassword,
- revokeFingerprintAccess: data.revokeFingerprintAccess,
- title: data.title,
- description: data.description,
- clearVault: data.clearVault,
- deleteVault: data.deleteVault,
- noteLocked: data.item && (await db.vaults.itemExists(data.item))
- });
-
- if (
- fingerprint &&
- data.novault &&
- !data.fingerprintAccess &&
- !data.revokeFingerprintAccess &&
- !data.changePassword &&
- !data.clearVault &&
- !data.deleteVault
- ) {
- await this._onPressFingerprintAuth(data.title, data.description);
- } else {
- this.setState({
- visible: true
- });
- }
- };
-
- close = () => {
- if (this.state.loading) {
- ToastManager.show({
- heading: this.state.title,
- message: strings.pleaseWait() + "...",
- type: "success",
- context: "local"
- });
- return;
- }
-
- Navigation.queueRoutesForUpdate();
-
- this.password = null;
- this.confirmPassword = null;
- this.setState({
- visible: false,
- note: {},
- locked: false,
- permanant: false,
- goToEditor: false,
- share: false,
- novault: false,
- deleteNote: false,
- passwordsDontMatch: false
- });
- };
-
- onPress = async () => {
- if (this.state.revokeFingerprintAccess) {
- await this._revokeFingerprintAccess();
-
- this.close();
- return;
- }
- if (this.state.loading) return;
-
- if (!this.password) {
- ToastManager.show({
- heading: strings.passwordNotEntered(),
- type: "error",
- context: "local"
- });
- return;
- }
-
- if (!this.state.novault) {
- if (this.password !== this.confirmPassword) {
- ToastManager.show({
- heading: strings.passwordNotMatched(),
- type: "error",
- context: "local"
- });
- this.setState({
- passwordsDontMatch: true
- });
- return;
- }
-
- this._createVault();
- } else if (this.state.changePassword) {
- this.setState({
- loading: true
- });
-
- db.vault
- .changePassword(this.password, this.newPassword)
- .then(() => {
- this.setState({
- loading: false
- });
- if (this.state.biometricUnlock) {
- this._enrollFingerprint(this.newPassword);
- }
- ToastManager.show({
- heading: strings.passwordUpdated(),
- type: "success",
- context: "global"
- });
- this.close();
- })
- .catch((e) => {
- this.setState({
- loading: false
- });
- if (e.message === db.vault.ERRORS.wrongPassword) {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- } else {
- ToastManager.error(e);
- }
- });
- } else if (this.state.locked) {
- if (!this.password || this.password.trim() === 0) {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- this.setState({
- wrongPassword: true
- });
- return;
- }
- if (this.state.noteLocked) {
- await this._unlockNote();
- } else {
- db.vault
- .unlock(this.password)
- .then(async () => {
- this.setState({
- wrongPassword: false
- });
- await this._lockNote();
- })
- .catch((e) => {
- this._takeErrorAction(e);
- });
- }
- } else if (this.state.fingerprintAccess) {
- this._enrollFingerprint(this.password);
- } else if (this.state.clearVault) {
- await this.clearVault();
- } else if (this.state.deleteVault) {
- await this.deleteVault();
- }
- };
-
- deleteVault = async () => {
- this.setState({
- loading: true
- });
- try {
- let verified = true;
- if (await db.user.getUser()) {
- verified = await db.user.verifyPassword(this.password);
- }
- if (verified) {
- let noteIds = [];
- if (this.state.deleteAll) {
- const vault = await db.vaults.default();
- const relations = await db.relations.from(vault, "note").get();
- noteIds = relations.map((item) => item.toId);
- }
- await db.vault.delete(this.state.deleteAll);
-
- if (this.state.deleteAll) {
- noteIds.forEach((id) => {
- eSendEvent(
- eUpdateNoteInEditor,
- {
- id: id,
- deleted: true
- },
- true
- );
- });
- }
- eSendEvent("vaultUpdated");
- this.setState({
- loading: false
- });
- setTimeout(() => {
- this.close();
- }, 100);
- } else {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- }
- } catch (e) {
- console.error(e);
- }
- };
-
- clearVault = async () => {
- this.setState({
- loading: true
- });
- try {
- const vault = await db.vaults.default();
- const relations = await db.relations.from(vault, "note").get();
- const noteIds = relations.map((item) => item.toId);
-
- await db.vault.clear(this.password);
-
- noteIds.forEach((id) => {
- eSendEvent(
- eUpdateNoteInEditor,
- {
- id: id,
- deleted: true
- },
- true
- );
- });
- this.setState({
- loading: false
- });
- this.close();
- eSendEvent("vaultUpdated");
- } catch (e) {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- }
- this.setState({
- loading: false
- });
- };
-
- async _lockNote() {
- if (!this.password || this.password.trim() === 0) {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- return;
- } else {
- await db.vault.add(this.state.note.id);
-
- eSendEvent(eUpdateNoteInEditor, this.state.note, true);
-
- this.close();
- ToastManager.show({
- message: strings.noteLocked(),
- type: "error",
- context: "local"
- });
- this.setState({
- loading: false
- });
- }
- }
-
- async _unlockNote() {
- if (!this.password || this.password.trim() === 0) {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- return;
- }
- if (this.state.permanant) {
- this._permanantUnlock();
- } else {
- await this._openNote();
- }
- }
-
- _openNote = async () => {
- try {
- let note = await db.vault.open(this.state.note.id, this.password);
- if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
- await this._enrollFingerprint(this.password);
- }
-
- if (this.state.goToEditor) {
- this._openInEditor(note);
- } else if (this.state.share) {
- await this._shareNote(note);
- } else if (this.state.deleteNote) {
- await this._deleteNote();
- } else if (this.state.copyNote) {
- await this._copyNote(note);
- }
- } catch (e) {
- this._takeErrorAction(e);
- }
- };
- async _deleteNote() {
- try {
- await db.vault.remove(this.state.note.id, this.password);
- await deleteItems("note", [this.state.note.id]);
- this.close();
- } catch (e) {
- this._takeErrorAction(e);
- }
- }
-
- async _enrollFingerprint(password) {
- this.setState(
- {
- loading: true
- },
- async () => {
- try {
- await db.vault.unlock(password);
- await BiometricService.storeCredentials(password);
- this.setState({
- loading: false
- });
- eSendEvent("vaultUpdated");
- ToastManager.show({
- heading: strings.biometricUnlockEnabled(),
- type: "success",
- context: "global"
- });
- this.close();
- } catch (e) {
- this.close();
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- this.setState({
- loading: false
- });
- }
- }
- );
- }
-
- async _createVault() {
- await db.vault.create(this.password);
-
- if (this.state.biometricUnlock) {
- await this._enrollFingerprint(this.password);
- }
- if (this.state.note?.id) {
- await db.vault.add(this.state.note.id);
- eSendEvent(eUpdateNoteInEditor, this.state.note, true);
- this.setState({
- loading: false
- });
- ToastManager.show({
- heading: strings.noteLocked(),
- type: "success",
- context: "global"
- });
- this.close();
- } else {
- ToastManager.show({
- heading: strings.vaultCreated(),
- type: "success",
- context: "global"
- });
- this.close();
- }
- eSendEvent("vaultUpdated");
- }
-
- _permanantUnlock() {
- db.vault
- .remove(this.state.note.id, this.password)
- .then(() => {
- ToastManager.show({
- heading: strings.noteUnlocked(),
- type: "success",
- context: "global"
- });
- eSendEvent(eUpdateNoteInEditor, this.state.note, true);
- this.close();
- })
- .catch((e) => {
- this._takeErrorAction(e);
- });
- }
-
- _openInEditor(note) {
- this.close();
- InteractionManager.runAfterInteractions(async () => {
- eSendEvent(eOnLoadNote, {
- item: note
- });
- if (!DDS.isTab) {
- fluidTabsRef.current?.goToPage("editor");
- }
- });
- }
-
- async _copyNote(note) {
- Clipboard.setString((await convertNoteToText(note, true)) || "");
- ToastManager.show({
- heading: strings.noteCopied(),
- type: "success",
- context: "global"
- });
- this.close();
- }
-
- async _shareNote(note) {
- this.close();
- try {
- await Share.open({
- heading: note.title,
- failOnCancel: false,
- message: (await convertNoteToText(note)) || ""
- });
- } catch (e) {
- console.error(e);
- }
- }
-
- _takeErrorAction(e) {
- this.setState({
- wrongPassword: true,
- visible: true
- });
- setTimeout(() => {
- ToastManager.show({
- heading: strings.passwordIncorrect(),
- type: "error",
- context: "local"
- });
- }, 500);
- }
-
- _revokeFingerprintAccess = async () => {
- try {
- await BiometricService.resetCredentials();
- eSendEvent("vaultUpdated");
- ToastManager.show({
- heading: strings.biometricUnlockDisabled(),
- type: "success",
- context: "global"
- });
- } catch (e) {
- ToastManager.show({
- heading: e.message,
- type: "success",
- context: "global"
- });
- }
- };
-
- _onPressFingerprintAuth = async (title, description) => {
- try {
- let credentials = await BiometricService.getCredentials(
- title || this.state.title,
- description || this.state.description
- );
-
- if (credentials?.password) {
- this.password = credentials.password;
- this.onPress();
- } else {
- eSendEvent(eCloseActionSheet);
- await sleep(300);
- this.setState({
- visible: true
- });
- }
- } catch (e) {
- console.error(e);
- }
- };
-
- render() {
- const { colors } = this.props;
- const {
- note,
- visible,
- novault,
- deleteNote,
- share,
- goToEditor,
- fingerprintAccess,
- changePassword,
- loading,
- deleteVault,
- clearVault
- } = this.state;
-
- if (!visible) return null;
- return (
- {
- await sleep(100);
-
- this.passInputRef.current?.focus();
- }}
- statusBarTranslucent={false}
- onRequestClose={this.close}
- visible={true}
- >
-
-
-
-
-
- {(novault ||
- changePassword ||
- this.state.clearVault ||
- this.state.deleteVault) &&
- !this.state.revokeFingerprintAccess ? (
- <>
- {
- this.password = value;
- }}
- marginBottom={
- !this.state.biometricUnlock ||
- !this.state.isBiometryEnrolled ||
- !novault ||
- changePassword
- ? 0
- : 10
- }
- onSubmit={() => {
- changePassword
- ? this.confirmPassRef.current?.focus()
- : this.onPress;
- }}
- autoComplete="password"
- returnKeyLabel={
- changePassword ? strings.next() : this.state.title
- }
- returnKeyType={changePassword ? "next" : "done"}
- secureTextEntry
- placeholder={
- changePassword
- ? strings.currentPassword()
- : strings.password()
- }
- />
-
- {!this.state.biometricUnlock ||
- !this.state.isBiometryEnrolled ||
- !novault ||
- changePassword ? null : (
-
-
-
-
-
-
- );
- }
-}
diff --git a/apps/mobile/app/components/dialogs/vault/index.tsx b/apps/mobile/app/components/dialogs/vault/index.tsx
new file mode 100644
index 000000000..e7be54efd
--- /dev/null
+++ b/apps/mobile/app/components/dialogs/vault/index.tsx
@@ -0,0 +1,1001 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import Clipboard from "@react-native-clipboard/clipboard";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { InteractionManager, View, TextInput } from "react-native";
+import Share from "react-native-share";
+import { notesnook } from "../../../../e2e/test.ids";
+import { db } from "../../../common/database";
+import BiometricService from "../../../services/biometrics";
+import { DDS } from "../../../services/device-detection";
+import {
+ ToastManager,
+ Vault,
+ eSendEvent,
+ eSubscribeEvent,
+ eUnSubscribeEvent
+} from "../../../services/event-manager";
+import Navigation from "../../../services/navigation";
+import { getElevationStyle } from "../../../utils/elevation";
+import {
+ eCloseActionSheet,
+ eCloseVaultDialog,
+ eOnLoadNote,
+ eOpenVaultDialog,
+ eUpdateNoteInEditor
+} from "../../../utils/events";
+import { deleteItems } from "../../../utils/functions";
+import { fluidTabsRef } from "../../../utils/global-refs";
+import { convertNoteToText } from "../../../utils/note-to-text";
+import { sleep } from "../../../utils/time";
+import BaseDialog from "../../dialog/base-dialog";
+import DialogButtons from "../../dialog/dialog-buttons";
+import DialogHeader from "../../dialog/dialog-header";
+import { Toast } from "../../toast";
+import { Button } from "../../ui/button";
+import Input from "../../ui/input";
+import Seperator from "../../ui/seperator";
+import Paragraph from "../../ui/typography/paragraph";
+import { strings } from "@notesnook/intl";
+import { DefaultAppStyles } from "../../../utils/styles";
+import { Note, NoteContent, VAULT_ERRORS } from "@notesnook/core";
+import { useThemeColors } from "@notesnook/theme";
+
+type VaultDialogData = {
+ item: Note;
+} & Partial>;
+
+interface VaultDialogState {
+ visible: boolean;
+ wrongPassword: boolean;
+ loading: boolean;
+ note?: Note;
+ vault: boolean;
+ locked: boolean;
+ permanant: boolean;
+ goToEditor: boolean;
+ share: boolean;
+ passwordsDontMatch: boolean;
+ deleteNote: boolean;
+ focusIndex: number | null;
+ biometricUnlock: boolean;
+ isBiometryEnrolled: boolean;
+ isBiometryAvailable: boolean;
+ fingerprintAccess: boolean;
+ changePassword: boolean;
+ copyNote: boolean;
+ revokeFingerprintAccess: boolean;
+ title: string;
+ description: string | null;
+ clearVault: boolean;
+ deleteVault: boolean;
+ deleteAll: boolean;
+ noteLocked: boolean;
+ novault: boolean;
+ customActionTitle: string | null;
+ customActionParagraph: string | null;
+ customAction: boolean;
+ onUnlock?: (
+ item: Note & {
+ content?: NoteContent;
+ },
+ password: string
+ ) => void;
+}
+
+export const VaultDialog: React.FC = () => {
+ const { colors } = useThemeColors();
+
+ const [state, setState] = useState({
+ visible: false,
+ wrongPassword: false,
+ loading: false,
+ note: undefined,
+ vault: false,
+ locked: true,
+ permanant: false,
+ goToEditor: false,
+ share: false,
+ passwordsDontMatch: false,
+ deleteNote: false,
+ focusIndex: null,
+ biometricUnlock: false,
+ isBiometryEnrolled: false,
+ isBiometryAvailable: false,
+ fingerprintAccess: false,
+ changePassword: false,
+ copyNote: false,
+ revokeFingerprintAccess: false,
+ title: strings.goToEditor(),
+ description: null,
+ clearVault: false,
+ deleteVault: false,
+ deleteAll: false,
+ noteLocked: false,
+ novault: false,
+ customActionTitle: null,
+ customActionParagraph: null,
+ customAction: false,
+ onUnlock: undefined
+ });
+
+ const passInputRef = useRef(null);
+ const confirmPassRef = useRef(null);
+ const changePassInputRef = useRef(null);
+
+ const passwordRef = useRef(null);
+ const confirmPasswordRef = useRef(null);
+ const newPasswordRef = useRef(null);
+
+ const open = useCallback(async (data: VaultDialogData) => {
+ const biometry = await BiometricService.isBiometryAvailable();
+ const available = !!biometry;
+ const fingerprint = await BiometricService.hasInternetCredentials();
+
+ const noteLocked = data.item
+ ? await db.vaults.itemExists(data.item)
+ : false;
+
+ setState((prev) => ({
+ ...prev,
+ note: data.item,
+ novault: data.novault || false,
+ locked: data.locked || false,
+ permanant: data.permanant || false,
+ goToEditor: data.goToEditor || false,
+ share: data.share || false,
+ deleteNote: data.deleteNote || false,
+ copyNote: data.copyNote || false,
+ isBiometryAvailable: available,
+ biometricUnlock: fingerprint,
+ isBiometryEnrolled: fingerprint,
+ fingerprintAccess: data.fingerprintAccess || false,
+ changePassword: data.changePassword || false,
+ revokeFingerprintAccess: data.revokeFingerprintAccess || false,
+ title: data.title || strings.goToEditor(),
+ description: data.description || null,
+ clearVault: data.clearVault || false,
+ deleteVault: data.deleteVault || false,
+ noteLocked,
+ customActionTitle: data.customActionTitle || null,
+ customActionParagraph: data.customActionParagraph || null,
+ customAction: !!(data.customActionTitle && data.customActionParagraph),
+ onUnlock: data.onUnlock
+ }));
+
+ if (
+ fingerprint &&
+ data.novault &&
+ !data.fingerprintAccess &&
+ !data.revokeFingerprintAccess &&
+ !data.changePassword &&
+ !data.clearVault &&
+ !data.deleteVault &&
+ !data.customActionTitle
+ ) {
+ await onPressFingerprintAuth(data.title, data.description);
+ } else {
+ setState((prev) => ({ ...prev, visible: true }));
+ }
+ }, []);
+
+ const close = useCallback(() => {
+ if (state.loading) {
+ ToastManager.show({
+ heading: state.title,
+ message: strings.pleaseWait() + "...",
+ type: "success",
+ context: "local"
+ });
+ return;
+ }
+
+ Navigation.queueRoutesForUpdate();
+
+ passwordRef.current = null;
+ confirmPasswordRef.current = null;
+ newPasswordRef.current = null;
+
+ setState({
+ visible: false,
+ wrongPassword: false,
+ loading: false,
+ note: undefined,
+ vault: false,
+ locked: false,
+ permanant: false,
+ goToEditor: false,
+ share: false,
+ passwordsDontMatch: false,
+ deleteNote: false,
+ focusIndex: null,
+ biometricUnlock: false,
+ isBiometryEnrolled: false,
+ isBiometryAvailable: false,
+ fingerprintAccess: false,
+ changePassword: false,
+ copyNote: false,
+ revokeFingerprintAccess: false,
+ title: strings.goToEditor(),
+ description: null,
+ clearVault: false,
+ deleteVault: false,
+ deleteAll: false,
+ noteLocked: false,
+ novault: false,
+ customActionTitle: null,
+ customActionParagraph: null,
+ customAction: false,
+ onUnlock: undefined
+ });
+ }, [state.loading, state.title]);
+
+ const deleteVault = useCallback(async () => {
+ setState((prev) => ({ ...prev, loading: true }));
+ try {
+ let verified = true;
+ if (await db.user.getUser()) {
+ verified = await db.user.verifyPassword(passwordRef.current || "");
+ }
+ if (verified) {
+ let noteIds: string[] = [];
+ if (state.deleteAll) {
+ const vault = await db.vaults.default();
+ const relations = await db.relations
+ .from(
+ {
+ type: "vault",
+ id: vault!.id
+ },
+ "note"
+ )
+ .get();
+ noteIds = relations.map((item) => item.toId);
+ }
+ await db.vault.delete(state.deleteAll);
+
+ if (state.deleteAll) {
+ noteIds.forEach((id) => {
+ eSendEvent(
+ eUpdateNoteInEditor,
+ {
+ id: id,
+ deleted: true
+ },
+ true
+ );
+ });
+ }
+ eSendEvent("vaultUpdated");
+ setState((prev) => ({ ...prev, loading: false }));
+ setTimeout(() => {
+ close();
+ }, 100);
+ } else {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }, [state.deleteAll, close]);
+
+ const clearVault = useCallback(async () => {
+ setState((prev) => ({ ...prev, loading: true }));
+ try {
+ const vault = await db.vaults.default();
+ const relations = await db.relations.from(vault!, "note").get();
+ const noteIds = relations.map((item) => item.toId);
+
+ await db.vault.clear(passwordRef.current || "");
+
+ noteIds.forEach((id) => {
+ eSendEvent(
+ eUpdateNoteInEditor,
+ {
+ id: id,
+ deleted: true
+ },
+ true
+ );
+ });
+ setState((prev) => ({ ...prev, loading: false }));
+ close();
+ eSendEvent("vaultUpdated");
+ } catch (e) {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ }
+ setState((prev) => ({ ...prev, loading: false }));
+ }, [close]);
+
+ const lockNote = useCallback(async () => {
+ if (!passwordRef.current || passwordRef.current.trim() === "") {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ return;
+ } else {
+ await db.vault.add(state.note!.id);
+
+ eSendEvent(eUpdateNoteInEditor, state.note, true);
+
+ close();
+ ToastManager.show({
+ message: strings.noteLocked(),
+ type: "error",
+ context: "local"
+ });
+ setState((prev) => ({ ...prev, loading: false }));
+ }
+ }, [state.note, close]);
+
+ const permanantUnlock = useCallback(() => {
+ db.vault
+ .remove(state.note!.id, passwordRef.current || "")
+ .then(() => {
+ ToastManager.show({
+ heading: strings.noteUnlocked(),
+ type: "success",
+ context: "global"
+ });
+ eSendEvent(eUpdateNoteInEditor, state.note, true);
+ close();
+ })
+ .catch((e) => {
+ takeErrorAction();
+ });
+ }, [state.note, close]);
+
+ const openInEditor = useCallback(
+ (note: Note & { content?: NoteContent }) => {
+ close();
+ InteractionManager.runAfterInteractions(async () => {
+ eSendEvent(eOnLoadNote, {
+ item: note
+ });
+ if (!DDS.isTab) {
+ fluidTabsRef.current?.goToPage("editor");
+ }
+ });
+ },
+ [close]
+ );
+
+ const copyNote = useCallback(
+ async (note: Note & { content?: NoteContent }) => {
+ Clipboard.setString((await convertNoteToText(note, true)) || "");
+ ToastManager.show({
+ heading: strings.noteCopied(),
+ type: "success",
+ context: "global"
+ });
+ close();
+ },
+ [close]
+ );
+
+ const shareNote = useCallback(
+ async (note: Note & { content?: NoteContent }) => {
+ close();
+ try {
+ await Share.open({
+ title: note.title,
+ failOnCancel: false,
+ message: (await convertNoteToText(note)) || ""
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ [close]
+ );
+
+ const deleteNote = useCallback(async () => {
+ try {
+ await db.vault.remove(state.note!.id, passwordRef.current || "");
+ await deleteItems("note", [state.note!.id]);
+ close();
+ } catch (e) {
+ takeErrorAction();
+ }
+ }, [state.note, close]);
+
+ const takeErrorAction = useCallback(() => {
+ setState((prev) => ({
+ ...prev,
+ wrongPassword: true,
+ visible: true
+ }));
+ setTimeout(() => {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ }, 500);
+ }, []);
+
+ const openNote = useCallback(async () => {
+ try {
+ if (!passwordRef.current) throw new Error("Invalid password");
+
+ const note = await db.vault.open(state.note!.id, passwordRef.current);
+ if (!note) throw new Error("Failed to unlock note.");
+ if (state.biometricUnlock && !state.isBiometryEnrolled) {
+ await enrollFingerprint(passwordRef.current || "");
+ }
+
+ if (state.goToEditor) {
+ openInEditor(note);
+ } else if (state.share) {
+ await shareNote(note);
+ } else if (state.deleteNote) {
+ await deleteNote();
+ } else if (state.copyNote) {
+ await copyNote(note);
+ } else if (state.customAction && state.onUnlock) {
+ const password = passwordRef.current;
+ close();
+ await sleep(300);
+ state.onUnlock(note, password);
+ }
+ } catch (e) {
+ takeErrorAction();
+ }
+ }, [
+ state.note,
+ state.biometricUnlock,
+ state.isBiometryEnrolled,
+ state.goToEditor,
+ state.share,
+ state.deleteNote,
+ state.copyNote,
+ state.customAction,
+ state.onUnlock,
+ openInEditor,
+ shareNote,
+ deleteNote,
+ copyNote,
+ close,
+ takeErrorAction
+ ]);
+
+ const unlockNote = useCallback(async () => {
+ if (!passwordRef.current || passwordRef.current.trim() === "") {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ return;
+ }
+ if (state.permanant) {
+ permanantUnlock();
+ } else {
+ await openNote();
+ }
+ }, [state.permanant, permanantUnlock, openNote]);
+
+ const enrollFingerprint = useCallback(
+ async (password: string) => {
+ setState((prev) => ({ ...prev, loading: true }));
+ try {
+ await db.vault.unlock(password);
+ await BiometricService.storeCredentials(password);
+ setState((prev) => ({ ...prev, loading: false }));
+ eSendEvent("vaultUpdated");
+ ToastManager.show({
+ heading: strings.biometricUnlockEnabled(),
+ type: "success",
+ context: "global"
+ });
+ close();
+ } catch (e) {
+ close();
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ setState((prev) => ({ ...prev, loading: false }));
+ }
+ },
+ [close]
+ );
+
+ const createVault = useCallback(async () => {
+ await db.vault.create(passwordRef.current || "");
+
+ if (state.biometricUnlock) {
+ await enrollFingerprint(passwordRef.current || "");
+ }
+ if (state.note?.id) {
+ await db.vault.add(state.note.id);
+ eSendEvent(eUpdateNoteInEditor, state.note, true);
+ setState((prev) => ({ ...prev, loading: false }));
+ ToastManager.show({
+ heading: strings.noteLocked(),
+ type: "success",
+ context: "global"
+ });
+ close();
+ } else {
+ ToastManager.show({
+ heading: strings.vaultCreated(),
+ type: "success",
+ context: "global"
+ });
+ close();
+ }
+ eSendEvent("vaultUpdated");
+ }, [state.biometricUnlock, state.note, enrollFingerprint, close]);
+
+ const revokeFingerprintAccess = useCallback(async () => {
+ try {
+ await BiometricService.resetCredentials();
+ eSendEvent("vaultUpdated");
+ ToastManager.show({
+ heading: strings.biometricUnlockDisabled(),
+ type: "success",
+ context: "global"
+ });
+ } catch (e: any) {
+ ToastManager.show({
+ heading: e.message,
+ type: "success",
+ context: "global"
+ });
+ }
+ }, []);
+
+ const onPressFingerprintAuth = useCallback(
+ async (title?: string, description?: string) => {
+ try {
+ const credentials = await BiometricService.getCredentials(
+ title || state.title,
+ description || state.description || ""
+ );
+
+ if (!credentials) throw new Error("Failed to get user credentials");
+
+ if (credentials?.password) {
+ passwordRef.current = credentials.password;
+ onPress();
+ } else {
+ eSendEvent(eCloseActionSheet);
+ await sleep(300);
+ setState((prev) => ({ ...prev, visible: true }));
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ [state.title, state.description]
+ );
+
+ const onPress = useCallback(async () => {
+ if (state.revokeFingerprintAccess) {
+ await revokeFingerprintAccess();
+ close();
+ return;
+ }
+ if (state.loading) return;
+
+ if (!passwordRef.current) {
+ ToastManager.show({
+ heading: strings.passwordNotEntered(),
+ type: "error",
+ context: "local"
+ });
+ return;
+ }
+
+ if (!state.novault) {
+ if (passwordRef.current !== confirmPasswordRef.current) {
+ ToastManager.show({
+ heading: strings.passwordNotMatched(),
+ type: "error",
+ context: "local"
+ });
+ setState((prev) => ({ ...prev, passwordsDontMatch: true }));
+ return;
+ }
+
+ createVault();
+ } else if (state.changePassword) {
+ setState((prev) => ({ ...prev, loading: true }));
+
+ db.vault
+ .changePassword(passwordRef.current, newPasswordRef.current || "")
+ .then(() => {
+ setState((prev) => ({ ...prev, loading: false }));
+ if (state.biometricUnlock) {
+ enrollFingerprint(newPasswordRef.current || "");
+ }
+ ToastManager.show({
+ heading: strings.passwordUpdated(),
+ type: "success",
+ context: "global"
+ });
+ close();
+ })
+ .catch((e) => {
+ setState((prev) => ({ ...prev, loading: false }));
+ if (e.message === VAULT_ERRORS.wrongPassword) {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ } else {
+ ToastManager.error(e);
+ }
+ });
+ } else if (state.locked) {
+ if (!passwordRef.current || passwordRef.current.trim() === "") {
+ ToastManager.show({
+ heading: strings.passwordIncorrect(),
+ type: "error",
+ context: "local"
+ });
+ setState((prev) => ({ ...prev, wrongPassword: true }));
+ return;
+ }
+ if (state.noteLocked) {
+ await unlockNote();
+ } else {
+ db.vault
+ .unlock(passwordRef.current)
+ .then(async () => {
+ setState((prev) => ({ ...prev, wrongPassword: false }));
+ await lockNote();
+ })
+ .catch((e) => {
+ takeErrorAction();
+ });
+ }
+ } else if (state.fingerprintAccess) {
+ enrollFingerprint(passwordRef.current);
+ } else if (state.clearVault) {
+ await clearVault();
+ } else if (state.deleteVault) {
+ await deleteVault();
+ } else if (state.customAction) {
+ await unlockNote();
+ }
+ }, [
+ state.revokeFingerprintAccess,
+ state.loading,
+ state.novault,
+ state.changePassword,
+ state.locked,
+ state.noteLocked,
+ state.fingerprintAccess,
+ state.clearVault,
+ state.deleteVault,
+ state.customAction,
+ state.biometricUnlock,
+ revokeFingerprintAccess,
+ close,
+ createVault,
+ enrollFingerprint,
+ unlockNote,
+ lockNote,
+ takeErrorAction,
+ clearVault,
+ deleteVault
+ ]);
+
+ useEffect(() => {
+ eSubscribeEvent(eOpenVaultDialog, open);
+ eSubscribeEvent(eCloseVaultDialog, close);
+
+ return () => {
+ eUnSubscribeEvent(eOpenVaultDialog, open);
+ eUnSubscribeEvent(eCloseVaultDialog, close);
+ };
+ }, [open, close]);
+
+ if (!state.visible) return null;
+
+ const {
+ note,
+ novault,
+ deleteNote: shouldDeleteNote,
+ share,
+ goToEditor,
+ fingerprintAccess,
+ changePassword,
+ loading,
+ deleteVault: shouldDeleteVault,
+ clearVault: shouldClearVault,
+ customAction,
+ customActionTitle,
+ customActionParagraph
+ } = state;
+
+ return (
+ {
+ await sleep(100);
+ passInputRef.current?.focus();
+ }}
+ statusBarTranslucent={false}
+ onRequestClose={close}
+ visible={true}
+ >
+
+
+
+
+
+ {(novault ||
+ changePassword ||
+ shouldClearVault ||
+ shouldDeleteVault ||
+ customAction) &&
+ !state.revokeFingerprintAccess ? (
+ <>
+ {
+ passwordRef.current = value;
+ }}
+ marginBottom={
+ !state.biometricUnlock ||
+ !state.isBiometryEnrolled ||
+ !novault ||
+ changePassword ||
+ customAction
+ ? 0
+ : 10
+ }
+ onSubmit={() => {
+ if (changePassword) {
+ confirmPassRef.current?.focus();
+ } else {
+ onPress();
+ }
+ }}
+ autoComplete="password"
+ returnKeyLabel={changePassword ? strings.next() : state.title}
+ returnKeyType={changePassword ? "next" : "done"}
+ secureTextEntry
+ placeholder={
+ changePassword
+ ? strings.currentPassword()
+ : strings.password()
+ }
+ />
+
+ {!state.biometricUnlock ||
+ !state.isBiometryEnrolled ||
+ !novault ||
+ changePassword ||
+ customAction ? null : (
+
+ onPressFingerprintAuth(strings.unlockNote(), "")
+ }
+ icon="fingerprint"
+ width="100%"
+ title={strings.unlockWithBiometrics()}
+ type="transparent"
+ />
+ )}
+ >
+ ) : null}
+
+ {shouldDeleteVault && (
+
+ setState((prev) => ({
+ ...prev,
+ deleteAll: !prev.deleteAll
+ }))
+ }
+ icon={
+ state.deleteAll
+ ? "check-circle-outline"
+ : "checkbox-blank-circle-outline"
+ }
+ style={{
+ marginTop: DefaultAppStyles.GAP_VERTICAL
+ }}
+ width="100%"
+ title={strings.deleteAllNotes()}
+ type="errorShade"
+ />
+ )}
+
+ {changePassword ? (
+ <>
+
+ {
+ newPasswordRef.current = value;
+ }}
+ autoComplete="password"
+ onSubmit={() => {
+ onPress();
+ }}
+ returnKeyLabel="Change"
+ returnKeyType="done"
+ secureTextEntry
+ placeholder={strings.newPassword()}
+ />
+ >
+ ) : null}
+
+ {!novault ? (
+
+ {
+ passwordRef.current = value;
+ }}
+ autoComplete="password"
+ returnKeyLabel={strings.next()}
+ returnKeyType="next"
+ secureTextEntry
+ onSubmit={() => {
+ confirmPassRef.current?.focus();
+ }}
+ placeholder={strings.password()}
+ />
+
+ passwordRef.current || ""}
+ errorMessage="Passwords do not match."
+ onErrorCheck={() => null}
+ marginBottom={0}
+ autoComplete="password"
+ returnKeyLabel="Create"
+ returnKeyType="done"
+ onChangeText={(value) => {
+ confirmPasswordRef.current = value;
+ if (value !== passwordRef.current) {
+ setState((prev) => ({ ...prev, passwordsDontMatch: true }));
+ } else {
+ setState((prev) => ({
+ ...prev,
+ passwordsDontMatch: false
+ }));
+ }
+ }}
+ onSubmit={() => {
+ onPress();
+ }}
+ placeholder={strings.confirmPassword()}
+ />
+
+ ) : null}
+
+ {state.biometricUnlock && !state.isBiometryEnrolled && novault ? (
+ {strings.vaultEnableBiometrics()}
+ ) : null}
+
+ {state.isBiometryAvailable &&
+ !state.fingerprintAccess &&
+ !shouldClearVault &&
+ !shouldDeleteVault &&
+ !customAction &&
+ ((!state.biometricUnlock && !changePassword) || !novault) ? (
+ {
+ setState((prev) => ({
+ ...prev,
+ biometricUnlock: !prev.biometricUnlock
+ }));
+ }}
+ style={{
+ marginTop: DefaultAppStyles.GAP_VERTICAL
+ }}
+ icon="fingerprint"
+ width="100%"
+ title={strings.unlockWithBiometrics()}
+ iconColor={
+ state.biometricUnlock
+ ? colors.selected.accent
+ : colors.primary.icon
+ }
+ type={state.biometricUnlock ? "transparent" : "plain"}
+ />
+ ) : null}
+
+
+
+
+
+
+ );
+};
diff --git a/apps/mobile/app/components/merge-conflicts/index.jsx b/apps/mobile/app/components/merge-conflicts/index.tsx
similarity index 63%
rename from apps/mobile/app/components/merge-conflicts/index.jsx
rename to apps/mobile/app/components/merge-conflicts/index.tsx
index 64dcc6b6e..35a5cad23 100644
--- a/apps/mobile/app/components/merge-conflicts/index.jsx
+++ b/apps/mobile/app/components/merge-conflicts/index.tsx
@@ -18,8 +18,16 @@ along with this program. If not, see .
*/
import { getFormattedDate } from "@notesnook/common";
+import {
+ EncryptedContentItem,
+ isEncryptedContent,
+ Note,
+ UnencryptedContentItem
+} from "@notesnook/core";
+import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import KeepAwake from "@sayem314/react-native-keep-awake";
+import { diff } from "diffblazer";
import React, { useEffect, useRef, useState } from "react";
import { SafeAreaView, Text, View } from "react-native";
import Animated from "react-native-reanimated";
@@ -32,13 +40,16 @@ import { DDS } from "../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
- eUnSubscribeEvent
+ eUnSubscribeEvent,
+ openVault
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useSettingStore } from "../../stores/use-setting-store";
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
+import { DefaultAppStyles } from "../../utils/styles";
+import { Dialog } from "../dialog";
import BaseDialog from "../dialog/base-dialog";
import DialogButtons from "../dialog/dialog-buttons";
import DialogContainer from "../dialog/dialog-container";
@@ -47,46 +58,55 @@ import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
-import { diff } from "diffblazer";
-import { strings } from "@notesnook/intl";
-import { DefaultAppStyles } from "../../utils/styles";
+import { presentDialog } from "../dialog/functions";
const MergeConflicts = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
- const [keep, setKeep] = useState(null);
- const [copy, setCopy] = useState(null);
+ const [selectedContent, setSelectedContent] =
+ useState();
+ const [copyOfDiscardedContent, setCopyOfDiscardedContent] =
+ useState();
const [dialogVisible, setDialogVisible] = useState(false);
const insets = useGlobalSafeAreaInsets();
- const content = useRef(null);
- const isKeepingConflicted = !keep?.conflicted;
- const isKeeping = !!keep;
+ const content = useRef(null);
const { height } = useSettingStore((state) => state.dimensions);
const applyChanges = async () => {
- let _content = keep;
- let note = await db.notes.note(_content.noteId);
+ let contentToSave = selectedContent;
+ if (!contentToSave) return;
+ let note = await db.notes.note(contentToSave.noteId);
+ if (!note) return;
await db.notes.add({
id: note.id,
conflicted: false,
- dateEdited: _content.dateEdited
+ dateEdited: contentToSave.dateEdited
});
- await db.content.add({
- id: note.contentId,
- data: _content.data,
- type: _content.type,
- dateResolved: content.current?.conflicted?.dateModified || Date.now(),
- sessionId: Date.now(),
- conflicted: false
- });
+ const noteLocked = await db.vaults.itemExists(note);
- if (copy) {
+ if (noteLocked) {
+ await db.vault.save({
+ ...contentToSave,
+ sessionId: `${Date.now()}`
+ });
+ } else {
+ await db.content.add({
+ id: note.contentId,
+ data: contentToSave.data,
+ type: contentToSave.type,
+ dateResolved: content.current?.conflicted?.dateModified || Date.now(),
+ sessionId: `${Date.now()}`,
+ conflicted: undefined
+ });
+ }
+
+ if (copyOfDiscardedContent) {
await db.notes.add({
title: note.title + " (Copy)",
content: {
- data: copy.data,
- type: copy.type
+ data: copyOfDiscardedContent.data,
+ type: copyOfDiscardedContent.type
}
});
}
@@ -103,15 +123,86 @@ const MergeConflicts = () => {
Sync.run();
};
- const show = async (item) => {
- let noteContent = await db.content.get(item.contentId);
- content.current = { ...noteContent };
- if (__DEV__) {
- if (!noteContent.conflicted) {
- content.current.conflicted = { ...noteContent };
+ const show = async (item: Note) => {
+ const isLocked = await db.vaults.itemExists(item);
+ let noteContent: UnencryptedContentItem;
+ if (isLocked) {
+ openVault({
+ item: item,
+ novault: true,
+ customActionTitle: "Unlock note",
+ customActionParagraph: "Unlock note to merge conflicts",
+ onUnlock: async (item, password) => {
+ if (!item || !password) return;
+ const currentContent = await db.content.get(item.contentId!);
+
+ try {
+ noteContent = {
+ ...(await db.content.get(item.contentId!)),
+ ...item.content,
+ conflicted: currentContent?.conflicted
+ ? await db.vault.decryptContent(
+ currentContent?.conflicted as EncryptedContentItem,
+ password
+ )
+ : undefined
+ } as UnencryptedContentItem;
+
+ content.current = noteContent;
+ if (__DEV__) {
+ if (!noteContent?.conflicted) {
+ content.current.conflicted = noteContent;
+ }
+ }
+ setVisible(true);
+ } catch (e) {
+ presentDialog({
+ input: true,
+ inputPlaceholder: strings.enterPassword(),
+ title: strings.unlockIncomingNote(),
+ paragraph: strings.unlockIncomingNoteDesc(),
+ positiveText: "Unlock",
+ positivePress: async (password) => {
+ try {
+ noteContent = {
+ ...(await db.content.get(item.contentId!)),
+ ...item.content,
+ conflicted: currentContent?.conflicted
+ ? await db.vault.decryptContent(
+ currentContent?.conflicted as EncryptedContentItem,
+ password
+ )
+ : undefined
+ } as UnencryptedContentItem;
+
+ content.current = noteContent;
+ if (__DEV__) {
+ if (!noteContent?.conflicted) {
+ content.current.conflicted = noteContent;
+ }
+ }
+ setVisible(true);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ });
+ }
+ }
+ });
+ } else {
+ noteContent = (await db.content.get(
+ item.contentId!
+ )) as UnencryptedContentItem;
+ content.current = noteContent;
+ if (__DEV__) {
+ if (!noteContent?.conflicted) {
+ content.current.conflicted = noteContent;
+ }
}
+ setVisible(true);
}
- setVisible(true);
};
useEffect(() => {
@@ -123,8 +214,8 @@ const MergeConflicts = () => {
const close = () => {
setVisible(false);
- setCopy(null);
- setKeep(null);
+ setCopyOfDiscardedContent(undefined);
+ setSelectedContent(undefined);
setDialogVisible(false);
};
@@ -134,6 +225,12 @@ const MergeConflicts = () => {
back,
isCurrent,
contentToKeep
+ }: {
+ isDiscarded: boolean;
+ keeping: boolean;
+ back: boolean;
+ isCurrent: boolean;
+ contentToKeep: UnencryptedContentItem;
}) => {
return (
{
{isDiscarded ? (
{
- setCopy(contentToKeep);
+ setCopyOfDiscardedContent(contentToKeep);
setDialogVisible(true);
}}
title={strings.saveACopy()}
@@ -222,7 +319,6 @@ const MergeConflicts = () => {
paddingHorizontal: DefaultAppStyles.GAP
}}
fontSize={AppFontSize.xs}
- color={colors.error.paragraph}
onPress={() => {
setDialogVisible(true);
}}
@@ -244,7 +340,9 @@ const MergeConflicts = () => {
keeping && !isDiscarded ? strings.undo() : strings.keep()
}
onPress={() => {
- setKeep(keeping && !isDiscarded ? null : contentToKeep);
+ setSelectedContent(
+ keeping && !isDiscarded ? undefined : contentToKeep
+ );
}}
/>
>
@@ -258,7 +356,6 @@ const MergeConflicts = () => {
{
@@ -266,15 +363,9 @@ const MergeConflicts = () => {
}}
centered={false}
background={colors?.primary.background}
- supportedOrientations={[
- "portrait",
- "portrait-upside-down",
- "landscape",
- "landscape-left",
- "landscape-right"
- ]}
visible={true}
>
+
{
style={{
height: "100%",
width: "100%",
- backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : null
+ backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : undefined
}}
>
{
{
- const note = await db.notes.note(content.current?.noteId);
+ const note = await db.notes.note(content.current!.noteId);
if (!note) return;
- loadContent({
- id: note.id,
- data: diff(
- content.current.conflicted.data,
- content.current.data
- )
- });
+ if (content.current && content.current.conflicted) {
+ loadContent({
+ id: note.id,
+ data: diff(
+ (content.current.conflicted as UnencryptedContentItem)
+ .data,
+ content.current.data
+ )
+ });
+ }
}}
/>
@@ -339,9 +433,11 @@ const MergeConflicts = () => {
{
{
- const note = await db.notes.note(content.current?.noteId);
+ const note = await db.notes.note(content.current?.noteId!);
if (!note) return;
loadContent({
id: note.id,
- data: content.current.conflicted.data
+ data: (content.current!.conflicted as UnencryptedContentItem)
+ .data
});
}}
/>
diff --git a/apps/mobile/app/services/event-manager.ts b/apps/mobile/app/services/event-manager.ts
index a7dc36bd3..82c7fb299 100644
--- a/apps/mobile/app/services/event-manager.ts
+++ b/apps/mobile/app/services/event-manager.ts
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import { EventHandler, EventManager } from "@notesnook/core";
+import { EventHandler, EventManager, Note, NoteContent } from "@notesnook/core";
import Clipboard from "@react-native-clipboard/clipboard";
import { RefObject } from "react";
import { ActionSheetRef } from "react-native-actions-sheet";
@@ -32,8 +32,8 @@ import {
} from "../utils/events";
import { strings } from "@notesnook/intl";
-type Vault = {
- item: unknown;
+export type Vault = {
+ item: Note;
novault: boolean;
title: string;
description: string;
@@ -48,6 +48,14 @@ type Vault = {
clearVault: boolean;
deleteVault: boolean;
copyNote: boolean;
+ customActionTitle: string;
+ customActionParagraph: string;
+ onUnlock: (
+ item: Note & {
+ content?: NoteContent;
+ },
+ password: string
+ ) => void;
};
type NoteEdit = {
diff --git a/apps/mobile/e2e/tests/vault.e2e.ts b/apps/mobile/e2e/tests/vault.e2e.ts
index 690ac9f31..5e31adee5 100644
--- a/apps/mobile/e2e/tests/vault.e2e.ts
+++ b/apps/mobile/e2e/tests/vault.e2e.ts
@@ -94,7 +94,7 @@ describe("VAULT", () => {
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.typeTextById(notesnook.ids.dialogs.vault.changePwd, "2362")
.waitAndTapByText("Change")
- .pressBack(4)
+ .pressBack(3)
.addStep(async () => await openLockedNote("2362"))
.run();
});
diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po
index 1919422eb..cb78824ce 100644
--- a/packages/intl/locale/en.po
+++ b/packages/intl/locale/en.po
@@ -6439,6 +6439,10 @@ msgstr "Thank you. You are the proof that privacy always comes first."
msgid "The {title} at {url} is not compatible with this client."
msgstr "The {title} at {url} is not compatible with this client."
+#: src/strings.ts:2629
+msgid "The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note"
+msgstr "The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note"
+
#: src/strings.ts:231
msgid "The information above will be publically available at"
msgstr "The information above will be publically available at"
@@ -6738,6 +6742,10 @@ msgstr "Unlink notebook"
msgid "Unlock"
msgstr "Unlock"
+#: src/strings.ts:2627
+msgid "Unlock incoming note"
+msgstr "Unlock incoming note"
+
#: src/strings.ts:1383
msgid "Unlock more colors with Notesnook Pro"
msgstr "Unlock more colors with Notesnook Pro"
diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po
index d9680b3df..34fd93ce3 100644
--- a/packages/intl/locale/pseudo-LOCALE.po
+++ b/packages/intl/locale/pseudo-LOCALE.po
@@ -6398,6 +6398,10 @@ msgstr ""
msgid "The {title} at {url} is not compatible with this client."
msgstr ""
+#: src/strings.ts:2629
+msgid "The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note"
+msgstr ""
+
#: src/strings.ts:231
msgid "The information above will be publically available at"
msgstr ""
@@ -6697,6 +6701,10 @@ msgstr ""
msgid "Unlock"
msgstr ""
+#: src/strings.ts:2627
+msgid "Unlock incoming note"
+msgstr ""
+
#: src/strings.ts:1383
msgid "Unlock more colors with Notesnook Pro"
msgstr ""
diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts
index 320ada774..3c51ab746 100644
--- a/packages/intl/src/strings.ts
+++ b/packages/intl/src/strings.ts
@@ -2623,5 +2623,8 @@ Use this if changes from other devices are not appearing on this device. This wi
weekFormat: () => t`Week format`,
weekFormatDesc: () =>
t`Choose what day to display as the first day of the week`,
- editCreationDate: () => t`Edit creation date`
+ editCreationDate: () => t`Edit creation date`,
+ unlockIncomingNote: () => t`Unlock incoming note`,
+ unlockIncomingNoteDesc: () =>
+ t`The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note`
};