/* * Private Mode plugin for Obsidian * Copyright 2025 Markus Moser * Licensed under the MIT License (http://opensource.org/licenses/MIT) */ import {addIcon, Menu, Platform, Plugin, setIcon,} from "obsidian"; enum Level { HidePrivate = "hide-private", RevealOnHover = "reveal-on-hover", RevealAll = "reveal-all", } enum CssClass { RevealAll = "private-mode-reveal-all", RevealOnHover = "private-mode-reveal-on-hover", UnprotectedScreenshare = "private-mode-unprotected-screenshare", BlurLinksToo = "private-mode-blur-links-too" } interface PrivateModePluginSettings { currentScreenshareProtection: boolean; blurLinksToo: boolean; } const DEFAULT_SETTINGS: Partial = { currentScreenshareProtection: true, blurLinksToo: true, }; export default class PrivateModePlugin extends Plugin { statusBar: HTMLElement; statusBarSpan: HTMLSpanElement; settings: PrivateModePluginSettings; currentLevel: Level = Level.RevealOnHover; async onload() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); this.statusBar = this.addStatusBarItem(); this.statusBar.addClass("mod-clickable") this.statusBar.ariaLabel = "Toggle private mode" this.statusBar.setAttr("data-tooltip-position", "top") this.statusBar.onClickEvent((event) => { if (event.button != 0) { const menu = new Menu(); menu.addItem((item) => item .setTitle('Reveal all') .setIcon('ph--eye') .setChecked(this.currentLevel == Level.RevealAll) .onClick(() => { this.currentLevel = Level.RevealAll this.updateGlobalRevealStyle(); }) ); menu.addItem((item) => item .setTitle('Reveal on hover') .setIcon('ph--eye-hand') .setChecked(this.currentLevel == Level.RevealOnHover) .onClick(() => { this.currentLevel = Level.RevealOnHover this.updateGlobalRevealStyle(); }) ); menu.addItem((item) => item .setTitle('Reveal never') .setIcon('ph--eye-closed') .setChecked(this.currentLevel == Level.HidePrivate) .onClick(() => { this.currentLevel = Level.HidePrivate this.updateGlobalRevealStyle(); }) ); menu.addSeparator() menu.addItem((item) => item .setTitle('Blur Links too') .setIcon('ph--link') .setChecked(this.settings.blurLinksToo) .onClick(() => { this.settings.blurLinksToo = !this.settings.blurLinksToo; item.setChecked(!this.settings.blurLinksToo) this.updateGlobalRevealStyle(); }) ); menu.addItem((item) => item .setTitle('Visibility when screensharing') .setIcon('ph--screencast') .setChecked(!this.settings.currentScreenshareProtection) .onClick(() => { this.settings.currentScreenshareProtection = !this.settings.currentScreenshareProtection; item.setChecked(!this.settings.currentScreenshareProtection) this.updateGlobalRevealStyle(); }) ); menu.showAtMouseEvent(event); } else { // left click if (event.altKey) { this.settings.currentScreenshareProtection = !this.settings.currentScreenshareProtection; this.updateGlobalRevealStyle(); } else { this.cycleCurrentLevel(); this.updateGlobalRevealStyle(); } } }); this.statusBarSpan = this.statusBar.createSpan( { text: "" }); addIcon("ph--eye", eyeIcon); addIcon("ph--eye-hand", eyeHand); addIcon("ph--eye-closed", eyeClosedIcon); addIcon("ph--screencast", screencastIcon); addIcon("ph--link", linkIcon); this.addCommand({ id: "hide-private", name: "Hide #private", callback: () => { this.currentLevel = Level.HidePrivate; this.updateGlobalRevealStyle(); }, }); this.addCommand({ id: "reveal-on-hover", name: "Reveal #private on hover", callback: () => { this.currentLevel = Level.RevealOnHover; this.updateGlobalRevealStyle(); }, }); this.addCommand({ id: "reveal-all", name: "Reveal #private always", callback: () => { this.currentLevel = Level.RevealAll; this.updateGlobalRevealStyle(); }, }); this.addCommand({ id: "cycle-mode", name: "Cycle #private mode", callback: () => { this.cycleCurrentLevel(); this.updateGlobalRevealStyle(); }, }); this.addCommand({ id: "toggle-screenshare-protection", name: "Toggle screenshare protection", callback: () => { this.settings.currentScreenshareProtection = !this.settings.currentScreenshareProtection this.updateGlobalRevealStyle(); }, }) this.app.workspace.onLayoutReady(() => { this.updateGlobalRevealStyle(); }); } private cycleCurrentLevel() { switch (this.currentLevel) { case Level.HidePrivate: this.currentLevel = Level.RevealOnHover; break; case Level.RevealOnHover: this.currentLevel = Level.RevealAll; break; case Level.RevealAll: this.currentLevel = Level.HidePrivate; break; } } async saveSettings() { await this.saveData(this.settings); } updateGlobalRevealStyle() { this.saveSettings() this.removeAllClasses(); this.setClassToDocumentBody(); if (Platform.isDesktopApp) { window.require("electron").remote.getCurrentWindow().setContentProtection(this.settings.currentScreenshareProtection) } } removeAllClasses() { document.body.removeClass( CssClass.RevealAll, CssClass.RevealOnHover, CssClass.UnprotectedScreenshare, CssClass.BlurLinksToo ); } setClassToDocumentBody() { if (!this.settings.currentScreenshareProtection) { document.body.classList.add(CssClass.UnprotectedScreenshare) } if (this.settings.blurLinksToo) { document.body.classList.add(CssClass.BlurLinksToo) } switch (this.currentLevel) { case Level.HidePrivate: setIcon(this.statusBarSpan, "ph--eye-closed") break; case Level.RevealOnHover: document.body.classList.add(CssClass.RevealOnHover); setIcon(this.statusBarSpan, "ph--eye-hand") break; case Level.RevealAll: document.body.classList.add(CssClass.RevealAll); setIcon(this.statusBarSpan, "ph--eye") break; } } } // https://icon-sets.iconify.design/ph/eye-closed/ const eyeClosedIcon = ``; // https://icon-sets.iconify.design/ph/hand-eye/ const eyeHand = ``; // https://icon-sets.iconify.design/ph/eye/ const eyeIcon = ``; // https://icon-sets.iconify.design/ph/screencast/ const screencastIcon = ``; // https://icon-sets.iconify.design/ph/link/ const linkIcon = ``;