vault backup: 2026-09-28 07:57:07
This commit is contained in:
+1
-1
@@ -10,6 +10,6 @@
|
||||
"autoLockInterval": 0,
|
||||
"pwdHintQuestion": "",
|
||||
"isLastVerifyPasswordCorrect": false,
|
||||
"timeOnUnload": "2026-01-13T06:43:44.115Z",
|
||||
"timeOnUnload": "2026-09-21T02:29:36.398Z",
|
||||
"forbidClosePassVerifyModal": false
|
||||
}
|
||||
+250
-11
@@ -528,6 +528,8 @@ var en_default = {
|
||||
setting_pwd_hint_question_name: "Password prompt",
|
||||
setting_pwd_hint_question_desc: "Please type a question to help you remember your password.",
|
||||
place_holder_enter_pwd_hint_question: "Password prompt question",
|
||||
setting_verify_password_blur_name: "Password modal background blur",
|
||||
setting_verify_password_blur_desc: "Background blur applied when the password verification modal opens. Unit: px.",
|
||||
setting_more_path: "More folders or files to be protected (up to 6)",
|
||||
setting_add_path: "Add protected path",
|
||||
setting_add_path_name: "Protected path",
|
||||
@@ -569,6 +571,8 @@ var es_default = {
|
||||
setting_pwd_hint_question_name: "Mensaje para recordar",
|
||||
setting_pwd_hint_question_desc: "Escribe una frase para ayudarte recordar la contrase\xF1a.",
|
||||
place_holder_enter_pwd_hint_question: "Mensaje para recordar",
|
||||
setting_verify_password_blur_name: "Desenfoque del modal de contrase\xF1a",
|
||||
setting_verify_password_blur_desc: "Desenfoque aplicado al fondo cuando se abre el modal de verificaci\xF3n de contrase\xF1a. Unidad: px.",
|
||||
setting_more_path: "M\xE1s carpetas o archivos a proteger (hasta 6)",
|
||||
setting_add_path: "A\xF1adir ruta protegida",
|
||||
setting_add_path_name: "Ruta protegida",
|
||||
@@ -609,6 +613,8 @@ var zh_cn_default = {
|
||||
setting_pwd_hint_question_name: "\u5BC6\u7801\u63D0\u793A\u95EE\u9898",
|
||||
setting_pwd_hint_question_desc: "\u5F53\u4F60\u5FD8\u8BB0\u5BC6\u7801\u65F6\uFF0C\u53EF\u4EE5\u901A\u8FC7\u8FD9\u4E2A\u95EE\u9898\u6765\u63D0\u793A\u4F60\u7684\u5BC6\u7801.",
|
||||
place_holder_enter_pwd_hint_question: "\u8F93\u5165\u5BC6\u7801\u63D0\u793A\u95EE\u9898",
|
||||
setting_verify_password_blur_name: "\u5BC6\u7801\u9A8C\u8BC1\u6846\u80CC\u666F\u6A21\u7CCA",
|
||||
setting_verify_password_blur_desc: "\u5BC6\u7801\u9A8C\u8BC1\u6846\u5F39\u51FA\u65F6\u80CC\u666F\u7684\u6A21\u7CCA\u7A0B\u5EA6\uFF0C\u5355\u4F4D\uFF1Apx\u3002",
|
||||
setting_more_path: "\u66F4\u591A\u9700\u8981\u4FDD\u62A4\u7684\u6587\u4EF6\u5939\u6216\u6587\u4EF6(\u6700\u591A6\u4E2A)",
|
||||
setting_add_path: "\u6DFB\u52A0\u4FDD\u62A4\u8DEF\u5F84",
|
||||
setting_add_path_name: "\u4FDD\u62A4\u8DEF\u5F84",
|
||||
@@ -649,6 +655,8 @@ var zh_tw_default = {
|
||||
setting_pwd_hint_question_name: "\u5BC6\u78BC\u63D0\u793A\u554F\u984C",
|
||||
setting_pwd_hint_question_desc: "\u7576\u4F60\u5FD8\u8A18\u5BC6\u78BC\u6642\uFF0C\u53EF\u4EE5\u901A\u904E\u9019\u500B\u554F\u984C\u4F86\u63D0\u793A\u4F60\u7684\u5BC6\u78BC.",
|
||||
place_holder_enter_pwd_hint_question: "\u8F38\u5165\u5BC6\u78BC\u63D0\u793A\u554F\u984C",
|
||||
setting_verify_password_blur_name: "\u5BC6\u78BC\u9A57\u8B49\u6846\u80CC\u666F\u6A21\u7CCA",
|
||||
setting_verify_password_blur_desc: "\u5BC6\u78BC\u9A57\u8B49\u6846\u5F48\u51FA\u6642\u80CC\u666F\u7684\u6A21\u7CCA\u7A0B\u5EA6\uFF0C\u55AE\u4F4D\uFF1Apx\u3002",
|
||||
setting_more_path: "\u66F4\u591A\u9700\u8981\u4FDD\u8B77\u7684\u6587\u4EF6\u593E\u6216\u6587\u4EF6(\u6700\u591A6\u500B)",
|
||||
setting_add_path: "\u6DFB\u52A0\u4FDD\u8B77\u8DEF\u5F91",
|
||||
setting_add_path_name: "\u4FDD\u8B77\u8DEF\u5F91",
|
||||
@@ -707,6 +715,7 @@ var DEFAULT_SETTINGS = {
|
||||
lang: "auto",
|
||||
autoLockInterval: 0,
|
||||
pwdHintQuestion: "",
|
||||
verifyPasswordBlurPx: 8,
|
||||
isLastVerifyPasswordCorrect: false,
|
||||
timeOnUnload: 0
|
||||
};
|
||||
@@ -717,6 +726,8 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
this.isVerifyPasswordCorrect = false;
|
||||
this.isAutoLockRegistered = false;
|
||||
this.lastUnlockOrOpenFileTime = null;
|
||||
this.onceActionTimeoutId = null;
|
||||
this.fileExplorerProtectionRegistered = false;
|
||||
this.t = (x, vars) => {
|
||||
return this.i18n.t(x, vars);
|
||||
};
|
||||
@@ -765,14 +776,13 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
});
|
||||
this.addSettingTab(new PasswordSettingTab(this.app, this));
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
if (this.settings.protectEnabled && this.isIncludeRootPath()) {
|
||||
if (!this.isVerifyPasswordCorrect) {
|
||||
let curTime = (0, import_obsidian2.moment)();
|
||||
if (curTime.diff(this.settings.timeOnUnload, "second") <= 2 && this.settings.isLastVerifyPasswordCorrect) {
|
||||
this.isVerifyPasswordCorrect = true;
|
||||
} else {
|
||||
this.verifyPasswordProtection(false);
|
||||
}
|
||||
this.registerFileExplorerProtection();
|
||||
if (this.settings.protectEnabled && !this.isVerifyPasswordCorrect && this.isIncludeRootPath()) {
|
||||
let curTime = (0, import_obsidian2.moment)();
|
||||
if (curTime.diff(this.settings.timeOnUnload, "second") <= 2 && this.settings.isLastVerifyPasswordCorrect) {
|
||||
this.isVerifyPasswordCorrect = true;
|
||||
} else {
|
||||
this.verifyPasswordProtection(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -790,7 +800,7 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
this.registerEvent(this.app.workspace.on("active-leaf-change", (leaf) => {
|
||||
if (leaf != null && leaf.view != null) {
|
||||
let viewType = leaf.view.getViewType();
|
||||
if (viewType == "search") {
|
||||
if (this.isPasswordProtectedView(viewType)) {
|
||||
this.autoLockCheck();
|
||||
if (this.settings.protectEnabled && !this.isVerifyPasswordCorrect) {
|
||||
this.verifyPasswordProtection(true);
|
||||
@@ -804,8 +814,10 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
this.app.vault.on("rename", this.handleRename);
|
||||
this.app.vault.on("modify", this.handleFileModify);
|
||||
this.registerAutoLock();
|
||||
this.registerOnceAction();
|
||||
}
|
||||
async onunload() {
|
||||
this.unregisterOnceAction();
|
||||
this.app.vault.off("modify", this.handleFileModify);
|
||||
this.app.vault.off("rename", this.handleRename);
|
||||
this.settings.isLastVerifyPasswordCorrect = this.isVerifyPasswordCorrect;
|
||||
@@ -831,6 +843,216 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
registerOnceAction() {
|
||||
if (this.onceActionTimeoutId != null) {
|
||||
return;
|
||||
}
|
||||
this.onceActionTimeoutId = window.setTimeout(() => this.onceAction(), 1 * 1e3);
|
||||
}
|
||||
onceAction() {
|
||||
this.unregisterOnceAction();
|
||||
this.collapseAllProtectedFolders();
|
||||
}
|
||||
unregisterOnceAction() {
|
||||
if (this.onceActionTimeoutId == null) {
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(this.onceActionTimeoutId);
|
||||
this.onceActionTimeoutId = null;
|
||||
}
|
||||
// Check if the workspace view exposing protected note metadata is a password protected view, such as search or file-explorer.
|
||||
isPasswordProtectedView(viewType) {
|
||||
return viewType == "search" || viewType == "bookmarks";
|
||||
}
|
||||
// Check if any workspace view exposing protected note metadata is opened, such as search or file-explorer.
|
||||
isPasswordProtectedViewOpened() {
|
||||
let isOpened = false;
|
||||
this.app.workspace.iterateAllLeaves((leaf) => {
|
||||
if (leaf.view != null && this.isPasswordProtectedView(leaf.view.getViewType())) {
|
||||
isOpened = true;
|
||||
}
|
||||
});
|
||||
return isOpened;
|
||||
}
|
||||
// Register capture-phase handlers on the file explorer so protected folders can be checked before Obsidian expands them.
|
||||
registerFileExplorerProtection() {
|
||||
if (this.fileExplorerProtectionRegistered) {
|
||||
return;
|
||||
}
|
||||
this.fileExplorerProtectionRegistered = true;
|
||||
const captureOptions = { capture: true, passive: false };
|
||||
this.registerDomEvent(window, "pointerdown", (event) => {
|
||||
this.handleFileExplorerFolderExpand(event);
|
||||
}, captureOptions);
|
||||
this.registerDomEvent(window, "touchstart", (event) => {
|
||||
this.handleFileExplorerFolderExpand(event);
|
||||
}, captureOptions);
|
||||
this.registerDomEvent(window, "keydown", (event) => {
|
||||
this.handleFileExplorerFolderExpand(event);
|
||||
}, captureOptions);
|
||||
}
|
||||
// Stop attempts to expand a protected folder until the password has been verified.
|
||||
handleFileExplorerFolderExpand(event) {
|
||||
if (!this.settings.protectEnabled || this.isVerifyPasswordCorrect || this.isVerifyPasswordWaitting) {
|
||||
return;
|
||||
}
|
||||
console.log(`handleFileExplorerFolderExpand: event type: ${event.type}, key: ${event instanceof KeyboardEvent ? event.key : "N/A"}`);
|
||||
if (event instanceof KeyboardEvent && event.key != "Enter" && event.key != " " && event.key != "Spacebar" && event.key != "ArrowRight") {
|
||||
return;
|
||||
}
|
||||
const item = this.getFileExplorerFolderItemFromEvent(event);
|
||||
if (item == null || item.file == null || !(item.file instanceof import_obsidian2.TFolder)) {
|
||||
return;
|
||||
}
|
||||
if (!this.isFileExplorerItemCollapsed(item)) {
|
||||
return;
|
||||
}
|
||||
if (!this.isProtectedFile(item.file.path)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.verifyPasswordBeforeAction(() => {
|
||||
this.expandFileExplorerFolder(item);
|
||||
});
|
||||
}
|
||||
// Map a click or keyboard event in the file explorer DOM back to Obsidian's internal folder item.
|
||||
getFileExplorerFolderItemFromEvent(event) {
|
||||
let matchedItem = null;
|
||||
this.app.workspace.iterateAllLeaves((leaf) => {
|
||||
if (matchedItem != null || leaf.view == null || leaf.view.getViewType() != "file-explorer") {
|
||||
return;
|
||||
}
|
||||
const fileExplorerView = leaf.view;
|
||||
const item = this.getFileExplorerFolderItemFromEventInView(event, fileExplorerView);
|
||||
if (item != null) {
|
||||
matchedItem = item;
|
||||
}
|
||||
});
|
||||
return matchedItem;
|
||||
}
|
||||
getFileExplorerFolderItemFromEventInView(event, fileExplorerView) {
|
||||
var _a;
|
||||
const folderTitleEl = this.getFocusedFileExplorerFolderTitleEl(event, fileExplorerView);
|
||||
if (folderTitleEl == null || fileExplorerView.fileItems == null || !fileExplorerView.containerEl.contains(folderTitleEl)) {
|
||||
return null;
|
||||
}
|
||||
const path = folderTitleEl.getAttribute("data-path");
|
||||
if (path != null) {
|
||||
const item = fileExplorerView.fileItems[path];
|
||||
if ((item == null ? void 0 : item.file) instanceof import_obsidian2.TFolder) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
const folderEl = folderTitleEl.closest(".nav-folder");
|
||||
for (const fileItem of Object.values(fileExplorerView.fileItems)) {
|
||||
if (fileItem.file instanceof import_obsidian2.TFolder && (fileItem.titleEl == folderTitleEl || ((_a = fileItem.titleEl) == null ? void 0 : _a.contains(folderTitleEl)) || fileItem.selfEl == folderEl)) {
|
||||
return fileItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getFocusedFileExplorerFolderTitleEl(event, fileExplorerView) {
|
||||
var _a, _b;
|
||||
const candidates = [];
|
||||
const uiEvent = event;
|
||||
const eventDoc = (_a = uiEvent.doc) != null ? _a : document;
|
||||
const pushCandidate = (value) => {
|
||||
if (value != null && typeof value === "object" && "instanceOf" in value) {
|
||||
candidates.push(value);
|
||||
}
|
||||
};
|
||||
pushCandidate(event.target);
|
||||
pushCandidate(uiEvent.targetNode);
|
||||
pushCandidate(eventDoc.activeElement);
|
||||
const activeDescendantId = (_b = eventDoc.activeElement) == null ? void 0 : _b.getAttribute("aria-activedescendant");
|
||||
if (activeDescendantId != null) {
|
||||
pushCandidate(eventDoc.getElementById(activeDescendantId));
|
||||
}
|
||||
for (const value of event.composedPath()) {
|
||||
pushCandidate(value);
|
||||
}
|
||||
pushCandidate(fileExplorerView.containerEl.querySelector(".nav-folder-title.is-active, .tree-item-self.is-active"));
|
||||
for (const candidate of candidates) {
|
||||
const folderTitleEl = this.getFolderTitleElFromNode(candidate);
|
||||
if (folderTitleEl != null) {
|
||||
return folderTitleEl;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getFolderTitleElFromNode(node) {
|
||||
if (node == null || !node.instanceOf(Element)) {
|
||||
return null;
|
||||
}
|
||||
const element = node;
|
||||
if (element.hasClass("nav-folder-title") || element.hasClass("tree-item-self") || element.hasAttribute("data-path")) {
|
||||
return element;
|
||||
}
|
||||
const folderTitleEl = element.matchParent(".nav-folder-title, .tree-item-self, [data-path]");
|
||||
if (folderTitleEl != null && folderTitleEl.instanceOf(HTMLElement)) {
|
||||
return folderTitleEl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Prefer Obsidian's internal state, then fall back to the rendered DOM class.
|
||||
isFileExplorerItemCollapsed(item) {
|
||||
var _a, _b, _c;
|
||||
if (typeof item.collapsed == "boolean") {
|
||||
return item.collapsed;
|
||||
}
|
||||
if (typeof item.isCollapsed == "boolean") {
|
||||
return item.isCollapsed;
|
||||
}
|
||||
const folderEl = (_c = (_a = item.selfEl) == null ? void 0 : _a.closest(".nav-folder")) != null ? _c : (_b = item.titleEl) == null ? void 0 : _b.closest(".nav-folder");
|
||||
const isCollapsed = folderEl == null ? void 0 : folderEl.classList.contains("is-collapsed");
|
||||
return isCollapsed != null ? isCollapsed : false;
|
||||
}
|
||||
// Run an action only after a password modal completes successfully.
|
||||
verifyPasswordBeforeAction(onVerified) {
|
||||
if (!this.isVerifyPasswordWaitting) {
|
||||
const setModal = new VerifyPasswordModal(this.app, this, false, () => {
|
||||
if (this.isVerifyPasswordCorrect) {
|
||||
onVerified();
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
// Expand a folder through Obsidian's internal method, falling back to a title click for compatibility.
|
||||
expandFileExplorerFolder(item) {
|
||||
var _a;
|
||||
if (item.setCollapsed != null) {
|
||||
item.setCollapsed.call(item, false);
|
||||
return;
|
||||
}
|
||||
(_a = item.titleEl) == null ? void 0 : _a.click();
|
||||
}
|
||||
// Hide restored expanded protected folders before the user has verified the password.
|
||||
collapseProtectedFileExplorerFolders(fileExplorerView) {
|
||||
var _a;
|
||||
if (!this.settings.protectEnabled || this.isVerifyPasswordCorrect || fileExplorerView.fileItems == null) {
|
||||
return;
|
||||
}
|
||||
for (const fileItem of Object.values(fileExplorerView.fileItems)) {
|
||||
if (!this.isFileExplorerItemCollapsed(fileItem)) {
|
||||
if (fileItem.file instanceof import_obsidian2.TFolder && this.isProtectedFile(fileItem.file.path)) {
|
||||
if (fileItem.setCollapsed != null) {
|
||||
fileItem.setCollapsed.call(fileItem, true);
|
||||
} else {
|
||||
(_a = fileItem.titleEl) == null ? void 0 : _a.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
collapseAllProtectedFolders() {
|
||||
this.app.workspace.iterateAllLeaves((leaf) => {
|
||||
if (leaf.view != null && leaf.view.getViewType() == "file-explorer") {
|
||||
const fileExplorerView = leaf.view;
|
||||
this.collapseProtectedFileExplorerFolders(fileExplorerView);
|
||||
}
|
||||
});
|
||||
}
|
||||
// check if the file opened need to be protected.
|
||||
isProtectFileOpened() {
|
||||
let leaves = [];
|
||||
@@ -871,6 +1093,7 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
if (this.isVerifyPasswordCorrect) {
|
||||
this.isVerifyPasswordCorrect = false;
|
||||
this.closeLeaves();
|
||||
this.collapseAllProtectedFolders();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -893,6 +1116,7 @@ var PasswordPlugin = class extends import_obsidian2.Plugin {
|
||||
new import_obsidian2.Notice(this.t("password_protection_closed"));
|
||||
} else {
|
||||
this.closeLeaves();
|
||||
this.collapseAllProtectedFolders();
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
@@ -1048,6 +1272,7 @@ var PasswordSettingTab = class extends import_obsidian2.PluginSettingTab {
|
||||
this.plugin.saveSettings();
|
||||
this.plugin.closeLeaves();
|
||||
this.plugin.registerAutoLock();
|
||||
this.plugin.collapseAllProtectedFolders();
|
||||
}
|
||||
this.display();
|
||||
}).open();
|
||||
@@ -1080,6 +1305,16 @@ var PasswordSettingTab = class extends import_obsidian2.PluginSettingTab {
|
||||
}
|
||||
this.plugin.settings.pwdHintQuestion = value;
|
||||
})).setDisabled(this.plugin.settings.protectEnabled);
|
||||
new import_obsidian2.Setting(containerEl).setName(this.plugin.t("setting_verify_password_blur_name")).setDesc(this.plugin.t("setting_verify_password_blur_desc")).addText((text) => text.setPlaceholder("8").setValue(this.plugin.settings.verifyPasswordBlurPx.toString()).onChange(async (value) => {
|
||||
value = value.replace(/[^0-9]/g, "");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
const blurPx = parseInt(value);
|
||||
if (blurPx != null && blurPx >= 0) {
|
||||
this.plugin.settings.verifyPasswordBlurPx = blurPx;
|
||||
}
|
||||
})).setDisabled(this.plugin.settings.protectEnabled);
|
||||
new import_obsidian2.Setting(containerEl).setName(this.plugin.t("setting_folder_name")).setDesc(this.plugin.t("setting_folder_desc")).addText((text) => text.setPlaceholder(this.plugin.t("place_holder_enter_path")).setValue(this.plugin.settings.protectedPath).onChange(async (value) => {
|
||||
let path = value.trim();
|
||||
if (path == "") {
|
||||
@@ -1233,13 +1468,17 @@ var VerifyPasswordModal = class extends import_obsidian2.Modal {
|
||||
onOpen() {
|
||||
if (this.forbidCloseModal) {
|
||||
const { modalEl } = this;
|
||||
const closeButton = modalEl.getElementsByClassName("modal-close-button")[0];
|
||||
let closeButton = modalEl.getElementsByClassName("modal-close-button")[0];
|
||||
if (closeButton == null) {
|
||||
closeButton = modalEl.getElementsByClassName("modal-header-button")[0];
|
||||
}
|
||||
if (closeButton != null) {
|
||||
closeButton.setAttribute("style", "display: none;");
|
||||
}
|
||||
}
|
||||
const blurPx = this.plugin.settings.verifyPasswordBlurPx;
|
||||
Object.assign(this.app.workspace.containerEl.style, {
|
||||
filter: "blur(8px)"
|
||||
filter: `blur(${blurPx}px)`
|
||||
});
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "password-protection",
|
||||
"name": "Password Protection",
|
||||
"version": "1.1.33",
|
||||
"version": "1.1.34",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Lock and protect your private notes and diary with a password, no encrypt, no decrypt.",
|
||||
"author": "Qing Li",
|
||||
|
||||
Reference in New Issue
Block a user