web: allow locked notes to stay unlocked if keep vault note unlocked is enabled

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
01zulfi
2026-02-10 16:17:13 +05:00
parent e87f5e5f89
commit f9ad0cefaf
13 changed files with 154 additions and 15 deletions

View File

@@ -193,4 +193,15 @@ export class SettingsViewModel {
.locator("input");
await titleFormatInput.fill(format);
}
async toggleKeepVaultNotesUnlocked() {
const item = await this.navigation.findItem("Vault");
await item?.click();
const keepVaultNotesUnlocked = this.page
.locator(getTestId("setting-keep-note-unlocked"))
.locator("label");
await keepVaultNotesUnlocked.click();
}
}

View File

@@ -125,3 +125,42 @@ test("clicking on vault unlocked status should lock the readonly note", async ({
expect(await note?.isLockedNotePasswordFieldVisible()).toBe(true);
});
test("when keep note unlocked setting is disabled (default), locked note should be locked again when it is closed after opening", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
await note?.contextMenu.lock(PASSWORD);
await note?.openLockedNote(PASSWORD);
const tabs = await notes.editor.getTabs();
await tabs[0].close();
await note?.click();
expect(await note?.isLockedNotePasswordFieldVisible()).toBe(true);
});
test("when keep note unlocked setting is enabled, locked note should not be locked again when it is closed after opening", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
await note?.contextMenu.lock(PASSWORD);
const settings = await app.goToSettings();
await settings.toggleKeepVaultNotesUnlocked();
await settings.close();
await note?.openLockedNote(PASSWORD);
const tabs = await notes.editor.getTabs();
await tabs[0].close();
await note?.click();
expect(await note?.isLockedNotePasswordFieldVisible()).toBe(false);
});

View File

@@ -22,6 +22,7 @@ import { showPasswordDialog } from "../dialogs/password-dialog";
import { showToast } from "../utils/toast";
import { VAULT_ERRORS } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useStore as useAppStore } from "../stores/app-store";
class Vault {
static async createVault() {
@@ -34,6 +35,7 @@ class Vault {
},
validate: async ({ password }) => {
await db.vault.create(password);
useAppStore.getState().setIsVaultCreated(true);
showToast("success", strings.vaultCreated());
return true;
}

View File

@@ -204,7 +204,7 @@ function StatusBar() {
}}
data-test-id="vault-unlocked"
>
<Unlock size={10} />
<Unlock size={12} />
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
{strings.vaultUnlocked()}
</Text>

View File

@@ -42,14 +42,29 @@ export const VaultSettings: SettingsGroup[] = [
type: "button",
title: strings.create(),
action: () => {
Vault.createVault().then((res) => {
useAppStore.getState().setIsVaultCreated(res);
});
Vault.createVault();
},
variant: "secondary"
}
]
},
{
key: "keep-note-unlocked",
title: strings.keepNoteUnlocked(),
description: strings.keepNoteUnlockedDesc(),
isHidden: () => !useAppStore.getState().isVaultCreated,
onStateChange: (listener) =>
useAppStore.subscribe((s) => s.isVaultCreated, listener),
components: [
{
type: "toggle",
isToggled: () => useAppStore.getState().keepVaultNotesUnlocked,
toggle: () => {
useAppStore.getState().toggleKeepVaultNotesUnlocked();
}
}
]
},
{
key: "change-vault-password",
title: strings.changeVaultPassword(),

View File

@@ -68,6 +68,7 @@ class AppStore extends BaseStore<AppStore> {
isListPaneVisible = true;
isNavPaneCollapsed = false;
isVaultCreated = false;
keepVaultNotesUnlocked = Config.get("vault:keepNotesUnlocked", false);
isAutoSyncEnabled = Config.get("autoSyncEnabled", true);
isSyncEnabled = Config.get("syncEnabled", true);
isRealtimeSyncEnabled = Config.get("isRealtimeSyncEnabled", true);
@@ -373,6 +374,12 @@ class AppStore extends BaseStore<AppStore> {
setNavigationTab = (tab: NavigationTabItem["id"]) => {
this.set((state) => (state.navigationTab = tab));
};
toggleKeepVaultNotesUnlocked = () => {
const newValue = !this.get().keepVaultNotesUnlocked;
Config.set("vault:keepNotesUnlocked", newValue);
this.set({ keepVaultNotesUnlocked: newValue });
};
}
const [useStore, store] = createStore<AppStore>(

View File

@@ -777,16 +777,37 @@ class EditorStore extends BaseStore<EditorStore> {
options
);
} else if (isLocked && note.type !== "trash") {
this.addSession(
{
type: "locked",
id: sessionId,
note,
activeBlockId: options.activeBlockId,
tabId
},
options
);
if (
appStore.get().keepVaultNotesUnlocked &&
db.vault.isNoteOpened(note.id)
) {
const tags = await db.notes.tags(note.id);
const noteFromVault = await db.vault.open(note.id);
if (noteFromVault) {
this.addSession({
type: note.readonly ? "readonly" : "default",
locked: true,
id: sessionId,
note: noteFromVault,
saveState: SaveState.Saved,
sessionId: `${Date.now()}`,
tags: tags,
tabId: tabId,
content: noteFromVault.content
});
}
} else {
this.addSession(
{
type: "locked",
id: sessionId,
note,
activeBlockId: options.activeBlockId,
tabId
},
options
);
}
} else {
const content = note.contentId
? await db.content.get(note.contentId)

View File

@@ -51,6 +51,16 @@ Adding notes to private vault is useful when you do not want anyone to read your
To open, edit or delete a locked note, you must provide the password for the vault to unlock it.
## Keep note unlocked when it is opened
By default, when you open a locked note, it will be locked again after you close the note or lock the vault. You can change this behavior, so that the note remains unlocked even after you close it. Now the note will only be locked when the vault is locked.
# [Desktop/Web](#/tab/web)
1. Go to Settings
2. Go to `Vault` in `Security & privacy` section
3. Toggle `Keep note unlocked`
## Unlock a note permanently
# [Desktop/Web](#/tab/web)

View File

@@ -83,6 +83,7 @@ test("lock a note", () =>
expect(content.data.cipher).toBeDefined();
expect(await db.relations.from(vault, "note").has(id)).toBe(true);
expect(db.vault.isNoteOpened(id)).toBeFalsy();
}));
test("locked note is not favorited", () =>
@@ -106,6 +107,7 @@ test("unlock a note", () =>
expect(note.content.data).toBeDefined();
expect(note.content.type).toBe(TEST_NOTE.content.type);
expect(await db.relations.from(vault, "note").has(id)).toBe(true);
expect(db.vault.isNoteOpened(id)).toBeTruthy();
}));
test("unlock a note permanently", () =>
@@ -124,6 +126,7 @@ test("unlock a note permanently", () =>
expect(content.data).toBeDefined();
expect(typeof content.data).toBe("string");
expect(await db.relations.from(vault, "note").has(id)).toBe(false);
expect(db.vault.isNoteOpened(id)).toBeFalsy();
}));
test("lock an empty note", () =>
@@ -141,6 +144,7 @@ test("lock an empty note", () =>
expect(content.data.iv).toBeDefined();
expect(content.data.cipher).toBeDefined();
expect(await db.relations.from(vault, "note").has(id)).toBe(true);
expect(db.vault.isNoteOpened(id)).toBeFalsy();
}));
test("save a locked note", () =>
@@ -154,6 +158,7 @@ test("save a locked note", () =>
const content = await db.content.get(note.contentId);
expect(content.data.cipher).toBeTypeOf("string");
expect(db.vault.isNoteOpened(id)).toBeFalsy();
}));
test("save an edited locked note", () =>
@@ -173,6 +178,7 @@ test("save an edited locked note", () =>
expect(() => JSON.parse(content.data.cipher)).toThrow();
expect(note.dateEdited).toBeLessThan((await db.notes.note(id)).dateEdited);
expect(note.dateEdited).toBeLessThan(content.dateEdited);
expect(db.vault.isNoteOpened(id)).toBeFalsy();
}));
test("change vault password", () =>

View File

@@ -23,6 +23,7 @@ import { EV, EVENTS } from "../common.js";
import { isCipher } from "../utils/crypto.js";
import { Note, NoteContent } from "../types.js";
import { logger } from "../logger.js";
import { addItem } from "../utils/array.js";
export const VAULT_ERRORS = {
noVault: "ERR_NO_VAULT",
@@ -35,6 +36,7 @@ export default class Vault {
private vaultPassword?: string;
private erasureTimeout = 0;
private key = "svvaads1212#2123";
private openedNotes: string[] = [];
private get password() {
return this.vaultPassword;
@@ -66,6 +68,10 @@ export default class Vault {
return !!this.vaultPassword;
}
isNoteOpened(noteId: string) {
return this.openedNotes.includes(noteId);
}
async create(password: string) {
const vaultKey = await this.getKey();
if (!vaultKey || !isCipher(vaultKey)) {
@@ -80,6 +86,7 @@ export default class Vault {
async lock() {
this.password = undefined;
this.openedNotes = [];
EV.publish(EVENTS.vaultLocked);
return true;
}
@@ -202,6 +209,8 @@ export default class Vault {
false
);
addItem(this.openedNotes, noteId);
if (password) {
this.password = password;
if (!(await this.exists())) await this.create(password);

View File

@@ -3510,6 +3510,14 @@ msgstr "Jump to group"
msgid "Keep"
msgstr "Keep"
#: src/strings.ts:2639
msgid "Keep a vault note unlocked once it is opened. Note will be locked back once vault is locked."
msgstr "Keep a vault note unlocked once it is opened. Note will be locked back once vault is locked."
#: src/strings.ts:2637
msgid "Keep note unlocked"
msgstr "Keep note unlocked"
#: src/strings.ts:2021
msgid "Keep open"
msgstr "Keep open"

View File

@@ -3490,6 +3490,14 @@ msgstr ""
msgid "Keep"
msgstr ""
#: src/strings.ts:2639
msgid "Keep a vault note unlocked once it is opened. Note will be locked back once vault is locked."
msgstr ""
#: src/strings.ts:2637
msgid "Keep note unlocked"
msgstr ""
#: src/strings.ts:2021
msgid "Keep open"
msgstr ""

View File

@@ -2633,5 +2633,8 @@ Use this if changes from other devices are not appearing on this device. This wi
exportCsv: () => t`Export CSV`,
importCsv: () => t`Import CSV`,
noContent: () => t`This note is empty`,
deleteData: () => t`Delete data`
deleteData: () => t`Delete data`,
keepNoteUnlocked: () => t`Keep note unlocked`,
keepNoteUnlockedDesc: () =>
t`Keep a vault note unlocked once it is opened. Note will be locked back once vault is locked.`
};