Compare commits

...

4 commits

Author SHA1 Message Date
Felvesthe
4a2fbbbb9d fix: Reset edit mode when navigating to another locked note
Signed-off-by: Felvesthe <23108389+Felvesthe@users.noreply.github.com>
2025-12-05 13:10:01 +01:00
Felvesthe
1bbbb63f90 Use different icon for strictly locked notes
Signed-off-by: Felvesthe <23108389+Felvesthe@users.noreply.github.com>
2025-12-05 12:40:04 +01:00
Felvesthe
7d377dc99d Add file existence check & fix an issue that allowed notifications to show even if disabled for strict lock
Signed-off-by: Felvesthe <23108389+Felvesthe@users.noreply.github.com>
2025-12-02 00:12:34 +01:00
Felvesthe
cd4ecd7821 Change Title Case to Sentence case to match Obsidian's plugin guidelines
Signed-off-by: Felvesthe <23108389+Felvesthe@users.noreply.github.com>
2025-12-02 00:02:48 +01:00
5 changed files with 63 additions and 42 deletions

View file

@ -8,6 +8,7 @@ import {
WorkspaceLeaf, WorkspaceLeaf,
TAbstractFile, TAbstractFile,
TFolder, TFolder,
setIcon,
} from "obsidian"; } from "obsidian";
import { NoteLockerSettings, DEFAULT_SETTINGS } from "./models/types"; import { NoteLockerSettings, DEFAULT_SETTINGS } from "./models/types";
@ -44,7 +45,7 @@ export default class NoteLockerPlugin extends Plugin {
// hotkey // hotkey
this.addCommand({ this.addCommand({
id: 'toggle-note-lock', id: 'toggle-note-lock',
name: 'Toggle Lock for current note', name: 'Toggle lock for current note',
checkCallback: (checking: boolean) => { checkCallback: (checking: boolean) => {
const file = this.app.workspace.getActiveFile(); const file = this.app.workspace.getActiveFile();
if (file && file.extension === 'md') { if (file && file.extension === 'md') {
@ -65,7 +66,7 @@ export default class NoteLockerPlugin extends Plugin {
this.addCommand({ this.addCommand({
id: 'switch-to-edit-mode-intercept', id: 'switch-to-edit-mode-intercept',
name: 'Switch to Edit Mode (Strict Lock Check)', name: 'Switch to edit mode (strict lock check)',
hotkeys: [{ modifiers: ["Mod"], key: "e" }], hotkeys: [{ modifiers: ["Mod"], key: "e" }],
checkCallback: (checking: boolean) => { checkCallback: (checking: boolean) => {
const file = this.app.workspace.getActiveFile(); const file = this.app.workspace.getActiveFile();
@ -113,7 +114,7 @@ export default class NoteLockerPlugin extends Plugin {
this.registerEvent( this.registerEvent(
this.app.workspace.on("active-leaf-change", (leaf) => { this.app.workspace.on("active-leaf-change", (leaf) => {
this.updateLeafMode(leaf); this.updateLeafMode(leaf, true);
this.statusBarUI.updateStatusBarButton(); this.statusBarUI.updateStatusBarButton();
}) })
); );
@ -147,6 +148,7 @@ export default class NoteLockerPlugin extends Plugin {
this.app.workspace.on("file-open", () => { this.app.workspace.on("file-open", () => {
this.statusBarUI.updateStatusBarButton(); this.statusBarUI.updateStatusBarButton();
this.fileExplorerUI.updateFileExplorerIcons(); this.fileExplorerUI.updateFileExplorerIcons();
this.updateLeafMode(this.app.workspace.getLeaf(false), true);
}) })
); );
@ -161,7 +163,7 @@ export default class NoteLockerPlugin extends Plugin {
private initializeExistingLeaves() { private initializeExistingLeaves() {
this.app.workspace this.app.workspace
.getLeavesOfType("markdown") .getLeavesOfType("markdown")
.forEach((leaf) => this.updateLeafMode(leaf)); .forEach((leaf) => this.updateLeafMode(leaf, true));
} }
private addBulkLockMenuItem(menu: Menu, files: TAbstractFile[]) { private addBulkLockMenuItem(menu: Menu, files: TAbstractFile[]) {
@ -201,7 +203,7 @@ export default class NoteLockerPlugin extends Plugin {
new StrictUnlockModal( new StrictUnlockModal(
this.app, this.app,
() => this.toggleBulkLock(validItems, false), () => this.toggleBulkLock(validItems, false),
"Strictly Locked Items", "Strictly locked items",
"At least one of the selected items is strictly locked." "At least one of the selected items is strictly locked."
).open(); ).open();
} else { } else {
@ -223,7 +225,7 @@ export default class NoteLockerPlugin extends Plugin {
if (allUnlocked || allLocked) { if (allUnlocked || allLocked) {
menu.addItem((item) => menu.addItem((item) =>
item item
.setTitle(`Strict Lock ${validItems.length} items`) .setTitle(`Strict lock ${validItems.length} items`)
.setIcon("lock") .setIcon("lock")
.onClick(() => this.toggleBulkStrictLock(validItems, true)) .onClick(() => this.toggleBulkStrictLock(validItems, true))
); );
@ -310,14 +312,14 @@ export default class NoteLockerPlugin extends Plugin {
if (!isStrictLocked) { if (!isStrictLocked) {
menu.addItem((item) => menu.addItem((item) =>
item item
.setTitle("Strict Lock") .setTitle("Strict lock")
.setIcon("lock") .setIcon("lock")
.onClick(() => this.toggleStrictLock(path)) .onClick(() => this.toggleStrictLock(path))
); );
} else { } else {
menu.addItem((item) => menu.addItem((item) =>
item item
.setTitle("Strict Unlock") .setTitle("Strict unlock")
.setIcon("unlock") .setIcon("unlock")
.onClick(() => { .onClick(() => {
new StrictUnlockModal(this.app, () => { new StrictUnlockModal(this.app, () => {
@ -409,15 +411,26 @@ export default class NoteLockerPlugin extends Plugin {
async toggleStrictLock(notePath: string) { async toggleStrictLock(notePath: string) {
const isStrictLocked = this.settings.strictLockedNotes.has(notePath); const isStrictLocked = this.settings.strictLockedNotes.has(notePath);
if (isStrictLocked) { isStrictLocked
this.settings.strictLockedNotes.delete(notePath); ? this.settings.strictLockedNotes.delete(notePath)
} else { : this.settings.strictLockedNotes.add(notePath);
this.settings.strictLockedNotes.add(notePath);
}
await this.saveSettings(); await this.saveSettings();
new Notice(`${isStrictLocked ? '🔓 Strictly Unlocked' : '🔒 Strictly Locked'}: ${notePath}`); const file = this.app.vault.getAbstractFileByPath(notePath);
if (!file) {
new Notice("Error: Note not found");
return;
}
if (this.settings.showNotifications) {
const fileName = file instanceof TFile ? file.basename :
notePath.split('/').pop()?.replace(/\..+$/, '') || notePath;
const displayName = this.truncateFileName(fileName);
new Notice(`${isStrictLocked ? '🔓 Strictly unlocked' : '🔒 Strictly locked'}: ${displayName}`);
}
this.updateAllNoteInstances(notePath); this.updateAllNoteInstances(notePath);
this.statusBarUI.updateStatusBarButton(); this.statusBarUI.updateStatusBarButton();
@ -459,7 +472,7 @@ export default class NoteLockerPlugin extends Plugin {
return false; return false;
} }
public updateLeafMode(leaf: WorkspaceLeaf | null) { public updateLeafMode(leaf: WorkspaceLeaf | null, fromNavigation: boolean = false) {
if (!leaf || !(leaf.view instanceof MarkdownView)) return; if (!leaf || !(leaf.view instanceof MarkdownView)) return;
const { view } = leaf; const { view } = leaf;
@ -476,17 +489,11 @@ export default class NoteLockerPlugin extends Plugin {
...state, ...state,
state: { ...state.state, mode: 'preview' }, state: { ...state.state, mode: 'preview' },
}); });
if (this.app.workspace.activeLeaf === leaf) {
new StrictUnlockModal(this.app, () => {
this.toggleStrictLock(path);
}).open();
}
return; return;
} }
} else if (isLocked) { } else if (isLocked) {
const state = leaf.getViewState(); const state = leaf.getViewState();
if (this.settings.preventEditInLockedNotes && state.state?.mode === 'source') { if ((fromNavigation || this.settings.preventEditInLockedNotes) && state.state?.mode === 'source') {
leaf.setViewState({ leaf.setViewState({
...state, ...state,
state: { ...state.state, mode: 'preview' }, state: { ...state.state, mode: 'preview' },
@ -538,12 +545,12 @@ export default class NoteLockerPlugin extends Plugin {
private addStrictLockButton(view: MarkdownView, path: string) { private addStrictLockButton(view: MarkdownView, path: string) {
const container = view.containerEl; const container = view.containerEl;
let lockBtn = container.querySelector('.note-locker-strict-btn'); let lockBtn = container.querySelector('.note-locker-strict-btn') as HTMLElement;
if (!lockBtn) { if (!lockBtn) {
lockBtn = document.createElement('div'); lockBtn = document.createElement('div');
lockBtn.addClass('view-action', 'clickable-icon', 'note-locker-strict-btn'); lockBtn.addClass('view-action', 'clickable-icon', 'note-locker-strict-btn');
lockBtn.setAttribute('aria-label', 'Strictly Locked'); lockBtn.setAttribute('aria-label', 'Strictly locked');
lockBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-lock"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>'; setIcon(lockBtn, 'lock-keyhole');
lockBtn.addEventListener('click', () => { lockBtn.addEventListener('click', () => {
new StrictUnlockModal(this.app, () => { new StrictUnlockModal(this.app, () => {
@ -564,7 +571,6 @@ export default class NoteLockerPlugin extends Plugin {
if (lockBtn) lockBtn.remove(); if (lockBtn) lockBtn.remove();
} }
async loadSettings() { async loadSettings() {
const loaded = await this.loadData(); const loaded = await this.loadData();
if (loaded) { if (loaded) {

View file

@ -95,7 +95,7 @@ export class NoteLockerSettingTab extends PluginSettingTab {
}); });
hotkeyInfo.style.fontStyle = 'italic'; hotkeyInfo.style.fontStyle = 'italic';
containerEl.createEl('h3', { text: 'Locked Notes & Folders Statistics' }); containerEl.createEl('h3', { text: 'Locked notes & folders statistics' });
const lockedNotesCount = this.plugin.settings.lockedNotes.size; const lockedNotesCount = this.plugin.settings.lockedNotes.size;
const lockedFoldersCount = this.plugin.settings.lockedFolders.size; const lockedFoldersCount = this.plugin.settings.lockedFolders.size;
const strictLockedNotesCount = this.plugin.settings.strictLockedNotes.size; const strictLockedNotesCount = this.plugin.settings.strictLockedNotes.size;

View file

@ -141,14 +141,20 @@ export class FileExplorerUI {
const path = titleEl.getAttribute('data-path'); const path = titleEl.getAttribute('data-path');
if (!path) return; if (!path) return;
let shouldHaveIcon = false; let iconName: string | null = null;
if (item.classList.contains('nav-file')) { if (item.classList.contains('nav-file')) {
shouldHaveIcon = this.plugin.settings.lockedNotes.has(path) || this.plugin.settings.strictLockedNotes.has(path); if (this.plugin.settings.strictLockedNotes.has(path)) {
iconName = 'lock-keyhole';
} else if (this.plugin.settings.lockedNotes.has(path)) {
iconName = 'lock';
}
} else if (item.classList.contains('nav-folder')) { } else if (item.classList.contains('nav-folder')) {
shouldHaveIcon = this.plugin.settings.lockedFolders.has(path); if (this.plugin.settings.lockedFolders.has(path)) {
iconName = 'lock';
}
} }
this.updateIconState(titleEl, shouldHaveIcon); this.updateIconState(titleEl, iconName);
} }
public updateFileExplorerIcons(): void { public updateFileExplorerIcons(): void {
@ -166,8 +172,13 @@ export class FileExplorerUI {
const filePath = titleEl.getAttribute('data-path'); const filePath = titleEl.getAttribute('data-path');
if (!filePath) return; if (!filePath) return;
const shouldHaveIcon = this.plugin.settings.lockedNotes.has(filePath) || this.plugin.settings.strictLockedNotes.has(filePath); let iconName: string | null = null;
this.updateIconState(titleEl, shouldHaveIcon); if (this.plugin.settings.strictLockedNotes.has(filePath)) {
iconName = 'lock-keyhole';
} else if (this.plugin.settings.lockedNotes.has(filePath)) {
iconName = 'lock';
}
this.updateIconState(titleEl, iconName);
}); });
// Handle folders // Handle folders
@ -179,20 +190,25 @@ export class FileExplorerUI {
const folderPath = titleEl.getAttribute('data-path'); const folderPath = titleEl.getAttribute('data-path');
if (!folderPath) return; if (!folderPath) return;
const shouldHaveIcon = this.plugin.settings.lockedFolders.has(folderPath); let iconName: string | null = null;
this.updateIconState(titleEl, shouldHaveIcon); if (this.plugin.settings.lockedFolders.has(folderPath)) {
iconName = 'lock';
}
this.updateIconState(titleEl, iconName);
}); });
} }
private updateIconState(targetEl: Element, shouldHaveIcon: boolean): void { private updateIconState(targetEl: Element, iconName: string | null): void {
const existingIcon = targetEl.querySelector('.note-locker-icon'); const existingIcon = targetEl.querySelector('.note-locker-icon');
if (shouldHaveIcon) { if (iconName) {
if (!existingIcon) { if (!existingIcon) {
const iconEl = document.createElement('div'); const iconEl = document.createElement('div');
iconEl.addClass('note-locker-icon'); iconEl.addClass('note-locker-icon');
setIcon(iconEl, 'lock'); setIcon(iconEl, iconName);
targetEl.appendChild(iconEl); targetEl.appendChild(iconEl);
} else {
setIcon(existingIcon as HTMLElement, iconName);
} }
} else { } else {
if (existingIcon) { if (existingIcon) {

View file

@ -56,9 +56,8 @@ export class StatusBarUI {
const iconSpan = this.statusBarItemEl.createSpan({ cls: 'locker-icon' }); const iconSpan = this.statusBarItemEl.createSpan({ cls: 'locker-icon' });
if (isStrictLocked) { if (isStrictLocked) {
setIcon(iconSpan, 'lock'); setIcon(iconSpan, 'lock-keyhole');
iconSpan.setText('🔓'); this.statusBarItemEl.createSpan({ text: ' Strictly locked' });
this.statusBarItemEl.createSpan({ text: ' Strictly Locked' });
this.statusBarItemEl.setAttribute('aria-label', 'Strictly locked. Click to unlock.'); this.statusBarItemEl.setAttribute('aria-label', 'Strictly locked. Click to unlock.');
} else { } else {
setIcon(iconSpan, isLocked ? 'lock' : 'unlock'); setIcon(iconSpan, isLocked ? 'lock' : 'unlock');

View file

@ -8,7 +8,7 @@ export class StrictUnlockModal extends Modal {
constructor( constructor(
app: App, app: App,
onUnlock: () => void, onUnlock: () => void,
title: string = "Strictly Locked Note", title: string = "Strictly locked note",
message: string = "This note is strictly locked to prevent accidental editing.") { message: string = "This note is strictly locked to prevent accidental editing.") {
super(app); super(app);
this.onUnlock = onUnlock; this.onUnlock = onUnlock;