From 9ca99c84787b6a08919e209463f43719e327bd23 Mon Sep 17 00:00:00 2001 From: Verity <113909005+uthvah@users.noreply.github.com> Date: Sat, 4 Apr 2026 20:48:57 +0100 Subject: [PATCH] Custom navbar (#8) * Enhance description in manifest.json Updated the description to include AES-GCM encryption. * Custom navbar - background colour matching - custom window action buttons - obsidian frame detection (disabled on native) --- index.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- styles.css | 64 ++++++++++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index e3ac9f0..3301c18 100644 --- a/index.js +++ b/index.js @@ -779,6 +779,93 @@ class VaultGuardPlugin extends obsidian.Plugin { await this.app.vault.modify(file, decoded); } + // ======================================================================== + // WINDOW FRAME DETECTION & CONTROLS + // ======================================================================== + + getFrameStyle() { + // Detected from body classes set by Obsidian at startup — authoritative, + // unlike app.vault.config which does not expose this in the plugin API. + // is-frameless + is-hidden-frameless → 'hidden' (default) + // is-frameless only → 'obsidian' (Obsidian custom bar) + // neither → 'native' (OS titlebar) + const body = document.body; + if (!body.classList.contains('is-frameless')) return 'native'; + if (body.classList.contains('is-hidden-frameless')) return 'hidden'; + return 'obsidian'; + } + + _platform() { + try { return process.platform; } catch { return 'unknown'; } + } + + // Show a themed navbar for hidden and obsidian styles — native lets the OS handle it. + get needsNavbar() { + return this.getFrameStyle() !== 'native'; + } + + // On non-macOS with hidden or obsidian frame, our navbar covers Electron's own + // window controls (web content) — render replacements. + // macOS: traffic lights are native OS overlays above all web content, always visible. + get needsWindowControls() { + return this.needsNavbar && this._platform() !== 'darwin'; + } + + createWindowControls() { + const bar = document.createElement('div'); + bar.className = 'vg-window-controls'; + + const buttons = [ + { + cls: 'vg-wc-min', + label: 'Minimize', + svg: '', + action: 'minimize', + }, + { + cls: 'vg-wc-max', + label: 'Maximize', + svg: '', + action: 'maximize', + }, + { + cls: 'vg-wc-close', + label: 'Close', + svg: '', + action: 'close', + }, + ]; + + for (const { cls, label, svg, action } of buttons) { + const btn = bar.createEl('button', { + cls: `vg-wc-btn ${cls}`, + attr: { 'aria-label': label }, + }); + btn.innerHTML = svg; + btn.addEventListener('click', () => this._winAction(action)); + } + + return bar; + } + + _winAction(action) { + // Path 1: Electron remote module (Obsidian ≤ 0.x / some 1.x builds) + try { + const electron = window.require('electron'); + const win = electron.remote?.getCurrentWindow?.(); + if (win) { + if (action === 'minimize') { win.minimize(); return; } + if (action === 'maximize') { win.isMaximized() ? win.unmaximize() : win.maximize(); return; } + if (action === 'close') { win.close(); return; } + } + } catch {} + + // Path 2: Obsidian command fallback (close only) + if (action === 'close') { + try { this.app.commands.executeCommandById('app:quit'); } catch {} + } + } + // ======================================================================== // LOCK SCREEN UI // ======================================================================== @@ -801,7 +888,10 @@ class VaultGuardPlugin extends obsidian.Plugin { } _buildLockScreenUI(el) { - el.createDiv({ cls: 'vaultguard-titlebar' }); + if (this.needsNavbar) { + const navbar = el.createDiv({ cls: 'vaultguard-navbar' }); + if (this.needsWindowControls) navbar.appendChild(this.createWindowControls()); + } const mainContentEl = el.createDiv({ cls: 'vaultguard-main-content' }); mainContentEl.appendChild(this.createBackgroundElement()); mainContentEl.appendChild(this.createOverlayElement()); @@ -822,8 +912,10 @@ class VaultGuardPlugin extends obsidian.Plugin { } _buildTamperedUI(el) { - // Titlebar so the window remains draggable on desktop - el.createDiv({ cls: 'vaultguard-titlebar' }); + if (this.needsNavbar) { + const navbar = el.createDiv({ cls: 'vaultguard-navbar' }); + if (this.needsWindowControls) navbar.appendChild(this.createWindowControls()); + } const screen = el.createDiv({ cls: 'vaultguard-tamper-screen' }); const panel = screen.createDiv({ cls: 'vaultguard-tamper-panel' }); diff --git a/styles.css b/styles.css index 40222a0..e94fb80 100644 --- a/styles.css +++ b/styles.css @@ -42,16 +42,72 @@ } /* ============================================================================ - TITLEBAR (for window dragging) + NAVBAR — shown for hidden and obsidian frame styles. + Matches Obsidian's titlebar background so it blends seamlessly. + native frame: omitted entirely (OS chrome handles it). ============================================================================ */ -.vaultguard-titlebar { +.vaultguard-navbar { flex-shrink: 0; - height: 30px; + height: 34px; width: 100%; -webkit-app-region: drag; - background: transparent; + background-color: var(--titlebar-background, var(--background-secondary)); + position: relative; z-index: 1; + display: flex; + align-items: center; +} + +/* Match Obsidian's own focused-window titlebar colour */ +body.is-focused .vaultguard-navbar { + background-color: var(--titlebar-background-focused, var(--background-secondary-alt)); +} + +/* ============================================================================ + WINDOW CONTROLS — rendered inside .vaultguard-navbar on Windows/Linux + where Electron's own controls are web content covered by the lockscreen. + macOS: traffic lights are native OS overlays — always visible, never needed here. + ============================================================================ */ + +.vg-window-controls { + position: absolute; + top: 0; + right: 0; + height: 100%; + display: flex; + align-items: stretch; + -webkit-app-region: no-drag; +} + +.vg-wc-btn { + width: 46px; + border: none; + background: transparent; + color: var(--titlebar-text-color, rgba(255, 255, 255, 0.55)); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background-color 0.1s ease, color 0.1s ease; + padding: 0; + -webkit-app-region: no-drag; +} + +.vg-wc-btn svg { + width: 10px; + height: 10px; + pointer-events: none; +} + +.vg-wc-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--titlebar-text-color, white); +} + +.vg-wc-close:hover { + background: #c42b1c; + color: #fff; } /* ============================================================================