Compare commits

..

No commits in common. "main" and "0.2.0" have entirely different histories.
main ... 0.2.0

5 changed files with 204 additions and 344 deletions

View File

@ -1,39 +1,33 @@
<img alt="" src="tray.png" align="right" height="128px"> <img alt="" src="tray.png" align="right" height="128px">
**Tray** is an [Obsidian](https://obsidian.md/) plugin that can be used to launch the app **Tray** is an [Obsidian](https://obsidian.md/) plugin that can be used to launch the app
on system startup and run it in the background, adding global hotkeys and a tray menu to on system startup and run it in the background, adding global hotkeys and a tray menu that
toggle window visibility and create quick notes from anywhere in your operating system. toggle app window visibility and can create quick notes from anywhere in your operating system.
## Configuration ## Configuration
### Window management ### Window management
| Option | Description | Default | | Option | Description | Default |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
| Launch on startup | Open Obsidian automatically whenever you log into your computer. | Disabled | | Launch on startup | Open Obsidian automatically whenever you log into your computer. | Disabled |
| Hide on launch | Minimises Obsidian automatically whenever the app is launched. If the "Run in background" option is enabled, windows will be hidden to the system tray/menubar instead of minimised to the taskbar/dock. | Disabled | | Hide on launch | Minimises Obsidian automatically whenever the app is launched. If the "Run in background" option is enabled, windows will be hidden to the system tray/menubar instead of minimised to the taskbar/dock. | Disabled |
| Run in background | Hide the app and continue to run it in the background instead of quitting it when pressing the window close button or toggle focus hotkey. | Disabled | | Run in background | Hide the app and continue to run it in the background instead of quitting it when pressing the window close button or toggle focus hotkey. | Disabled |
| Hide taskbar icon | Hides the window's icon from from the dock/taskbar. This may not work on Linux-based OSes. | Disabled | | Hide taskbar icon | Hides the window's icon from from the dock/taskbar. This may not work on all Linux-based OSes. | Disabled |
| Create tray icon | Add an icon to your system tray/menubar to bring hidden Obsidian windows back into focus on click or force a full quit/relaunch of the app through the right-click menu. | Enabled | | Create tray icon | Add an icon to your system tray/menubar to bring hidden Obsidian windows back into focus on click or force a full quit/relaunch of the app through the right-click menu. _Changing this option requires a restart to take effect._ | Enabled |
| Tray icon image | Set the image used by the tray/menubar icon. Recommended size: 16x16 | ![](obsidian.png) | | Toggle window focus hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | CmdOrCtrl+Shift+Tab |
| Tray icon tooltip | Set a title to identify the tray/menubar icon by. The `{{vault}}` placeholder will be replaced by the vault name. | `{{vault}} \| Obsidian` |
| Toggle window focus hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | <kbd>CmdOrCtrl+Shift+Tab</kbd> |
The `Relaunch Obsidian` and `Close Vault` actions can be triggered from the tray/menubar context menu,
or with the in-app command palette (search for "Tray: Relaunch Obsidian" or "Tray: Close Vault").
Hotkeys can be assigned to the commands via Obsidian's built-in hotkey manager.
### Quick notes ### Quick notes
| Option | Description | Default | | Option | Description | Default |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| Quick note location | New quick notes will be placed in this folder. | | | Quick note location | New quick notes will be placed in this folder. | |
| Quick note date format | New quick notes will use a filename of this pattern. Format: [Moment.js format string](https://momentjs.com/docs/#/displaying/format/) | `YYYY-MM-DD` | | Quick note date format | New quick notes will use a filename of this pattern. Format: [Moment.js format string](https://momentjs.com/docs/#/displaying/format/) | YYYY-MM-DD |
| Quick note hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | <kbd>CmdOrCtrl+Shift+Q</kbd> | | Quick note hotkey | This hotkey is registered globally and will be detected even if Obsidian does not have keyboard focus. Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator) | CmdOrCtrl+Shift+Q |
## Installation ## Installation
### Obsidian Marketplace ### Obsidian Marketplace (Coming Soon)
1. In Obsidian, navigate to **Settings****Community plugins**. 1. In Obsidian, navigate to **Settings****Community plugins**.
2. Press the **Browse** button beside the **Community plugins** option. 2. Press the **Browse** button beside the **Community plugins** option.
@ -61,7 +55,3 @@ This plugin is provided as-is and is designed for personal use. It has not
been tested on every platform and may not work as expected with all future updates. been tested on every platform and may not work as expected with all future updates.
If you notice something is not working as intended, please open a bug report or If you notice something is not working as intended, please open a bug report or
pull request so it can be fixed. pull request so it can be fixed.
The Obsidian logo is distributed with this plugin as the default image for the system
tray/menubar icon, intended to be used within Obsidian. This logo remains the property
of the Obsidian project and is not under the same license as the plugin's source code.

454
main.js
View File

@ -1,249 +1,160 @@
/** /* obsidian-tray v0.2.0
* obsidian-tray v0.3.5 by @dragonwocky */
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://github.com/dragonwocky/obsidian-tray/) under the MIT license
*/
"use strict"; "use strict";
const LOG_PREFIX = "obsidian-tray", const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
LOG_LOADING = "loading",
LOG_CLEANUP = "cleaning up",
LOG_SHOWING_WINDOWS = "showing windows",
LOG_HIDING_WINDOWS = "hiding windows",
LOG_WINDOW_CLOSE = "intercepting window close",
LOG_TRAY_ICON = "creating tray icon",
LOG_REGISTER_HOTKEY = "registering hotkey",
LOG_UNREGISTER_HOTKEY = "unregistering hotkey",
ACTION_QUICK_NOTE = "Quick Note",
ACTION_SHOW = "Show Vault",
ACTION_HIDE = "Hide Vault",
ACTION_RELAUNCH = "Relaunch Obsidian",
ACTION_CLOSE = "Close Vault",
DEFAULT_DATE_FORMAT = "YYYY-MM-DD",
ACCELERATOR_FORMAT = `
This hotkey is registered globally and will be detected even if Obsidian does
not have keyboard focus. Format:
<a href="https://www.electronjs.org/docs/latest/api/accelerator" target="_blank" rel="noopener">
Electron accelerator</a>
`,
MOMENT_FORMAT = `
Format:
<a href="https://momentjs.com/docs/#/displaying/format/" target="_blank" rel="noopener">
Moment.js format string</a>
`,
// 16x16 base64 obsidian icon: generated from obsidian.asar/icon.png
OBSIDIAN_BASE64_ICON = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHZSURBVDhPlZKxTxRBFMa/XZcF7nIG7mjxjoRCwomJxgsFdhaASqzQxFDzB1AQKgstLGxIiBQGJBpiCCGx8h+wgYaGgAWNd0dyHofeEYVwt/PmOTMZV9aDIL/s5pvZvPfN9yaL/+HR3eXcypta0m4juFbP5GHuXc9IbunDFc9db/G81/ZzhDMN7g8td47mll4R5BfHwZN4LOaA+fHa259PbUmIYzWkt3e2NZNo3/V9v1vvU6kkstk+tLW3ItUVr/m+c3N8MlkwxYqmBFcbwUQQCNOcyVzDwEAWjuPi5DhAMV/tKOYPX5hCyz8Gz1zX5SmWjBvZfmTSaRBJkGAIoxJHv+pVW2yIGNxOJ8bUVNcFEWLxuG1ia6JercTbttwQTeDwPS0kCMXiXtgk/jQrFUw7ptYSMWApF40yo/ytjHq98fdk3ayVE+cn2CxMb6ruz9qAJKFUKoWza1VJSi/n0+ffgYHdWW2gHuxXymg0gjCB0sjpmiaDnkL3RzDyzLqBUKns2ztQqUR0fk2TwSrGSf1eczqF5vsPZRCQSSAFLk6gqctgQRkc6TWRQLV2YMYQki9OoNkqzFQ9r+WOGuW5CrJbOzyAlPKr6MSGLbkcDwbf35oY/jRkt6cAfgNwowruAMz9AgAAAABJRU5ErkJggg==`,
log = (message) => console.log(`${LOG_PREFIX}: ${message}`);
let tray, plugin; let tray;
const obsidian = require("obsidian"), const obsidian = require("obsidian"),
{ app, Tray, Menu } = require("electron").remote, {
{ nativeImage, BrowserWindow } = require("electron").remote, app,
{ getCurrentWindow, globalShortcut } = require("electron").remote; BrowserWindow,
getCurrentWindow,
globalShortcut,
Tray,
Menu,
nativeImage,
} = require("electron").remote;
const vaultWindows = new Set(), const showWindows = () => {
maximizedWindows = new Set(), console.log("obsidian-tray: showing windows");
getWindows = () => [...vaultWindows], const windows = BrowserWindow.getAllWindows(),
observeWindows = () => { currentWindow = windows.find((win) => win.isFocused()) || windows[0];
const onWindowCreation = (win) => { windows.forEach((win) => win.show());
vaultWindows.add(win); currentWindow.focus();
win.setSkipTaskbar(plugin.settings.hideTaskbarIcon);
win.on("close", () => {
if (win !== getCurrentWindow()) vaultWindows.delete(win);
});
// preserve maximised windows after minimisation
if (win.isMaximized()) maximizedWindows.add(win);
win.on("maximize", () => maximizedWindows.add(win));
win.on("unmaximize", () => maximizedWindows.delete(win));
};
onWindowCreation(getCurrentWindow());
getCurrentWindow().webContents.on("did-create-window", onWindowCreation);
if (process.platform === "darwin") {
// on macos, the "hide taskbar icon" option is implemented
// via app.dock.hide(): thus, the app as a whole will be
// hidden from the dock, including windows from other vaults.
// when a vault is closed via the "close vault" button,
// the cleanup process will call app.dock.show() to restore
// access to any other open vaults w/out the tray enabled
// => thus, this listener is required to re-hide the dock
// if switching to another vault with the option enabled
getCurrentWindow().on("focus", () => {
if (plugin.settings.hideTaskbarIcon) hideTaskbarIcons();
});
}
}, },
showWindows = () => { hideWindows = (runInBackground) => {
log(LOG_SHOWING_WINDOWS); console.log("obsidian-tray: hiding windows");
getWindows().forEach((win) => { const windows = BrowserWindow.getAllWindows();
if (maximizedWindows.has(win)) { windows.forEach((win) => [
win.maximize();
win.focus();
} else win.show();
});
},
hideWindows = () => {
log(LOG_HIDING_WINDOWS);
getWindows().forEach((win) => [
win.isFocused() && win.blur(), win.isFocused() && win.blur(),
plugin.settings.runInBackground ? win.hide() : win.minimize(), runInBackground ? win.hide() : win.minimize(),
]); ]);
}, },
toggleWindows = (checkForFocus = true) => { toggleWindows = (runInBackground, checkForFocus = true) => {
const openWindows = getWindows().some((win) => { const windows = BrowserWindow.getAllWindows(),
openWindows = windows.some((win) => {
return (!checkForFocus || win.isFocused()) && win.isVisible(); return (!checkForFocus || win.isFocused()) && win.isVisible();
}); });
if (openWindows) hideWindows(); if (openWindows) {
else showWindows(); hideWindows(runInBackground);
} else showWindows();
}; };
const onWindowClose = (event) => event.preventDefault(), // let _onbeforeunload;
onWindowUnload = (event) => { const onWindowClose = (event) => {
log(LOG_WINDOW_CLOSE);
getCurrentWindow().hide();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
// setting return value manually is more reliable than // event.preventDefault();
// via `return false` according to electron console.log("obsidian-tray: intercepting window close");
event.returnValue = false; const windows = BrowserWindow.getAllWindows(),
currentWindow = windows.find((win) => win.isFocused());
currentWindow.hide();
}, },
interceptWindowClose = () => { interceptWindowClose = () => {
// intercept in renderer // _onbeforeunload = window.onbeforeunload;
window.addEventListener("beforeunload", onWindowUnload, true); // window.onbeforeunload = onWindowClose;
// intercept in main: is asynchronously executed when registered const closeBtn = document.querySelector(".mod-close");
// from renderer, so won't prevent close by itself, but counteracts closeBtn.addEventListener("click", onWindowClose, true);
// the 3-second delayed window force close in obsidian.asar/main.js
getCurrentWindow().on("close", onWindowClose);
}, },
allowWindowClose = () => { cleanupWindowClose = () => {
getCurrentWindow().removeListener("close", onWindowClose); // window.onbeforeunload = _onbeforeunload;
window.removeEventListener("beforeunload", onWindowUnload, true); const closeBtn = document.querySelector(".mod-close");
closeBtn.removeEventListener("click", onWindowClose, true);
}; };
const hideTaskbarIcons = () => { const addQuickNote = (plugin) => {
getWindows().forEach((win) => win.setSkipTaskbar(true)); const { quickNoteLocation, quickNoteDateFormat } = plugin.settings,
if (process.platform === "darwin") app.dock.hide(); format = quickNoteDateFormat || DEFAULT_DATE_FORMAT,
date = obsidian.moment().format(format),
name = obsidian
.normalizePath(`${quickNoteLocation ?? ""}/${date}`)
.replace(/\*|"|\\|<|>|:|\||\?/g, "-");
plugin.app.fileManager.createAndOpenMarkdownFile(name);
showWindows();
}, },
showTaskbarIcons = () => { setHideTaskbarIcon = (plugin) => {
getWindows().forEach((win) => win.setSkipTaskbar(false)); const win = getCurrentWindow();
if (process.platform === "darwin") app.dock.show(); win.setSkipTaskbar(plugin.settings.hideTaskbarIcon);
}, },
setLaunchOnStartup = () => { setLaunchOnStartup = (plugin) => {
const { launchOnStartup, runInBackground, hideOnLaunch } = plugin.settings; const { launchOnStartup, runInBackground, hideOnLaunch } = plugin.settings;
app.setLoginItemSettings({ app.setLoginItemSettings({
openAtLogin: launchOnStartup, openAtLogin: launchOnStartup,
openAsHidden: runInBackground && hideOnLaunch, openAsHidden: runInBackground && hideOnLaunch,
}); });
};
const cleanup = () => {
log(LOG_CLEANUP);
unregisterHotkeys();
showTaskbarIcons();
allowWindowClose();
destroyTray();
}, },
relaunchApp = () => { relaunchObsidian = () => {
app.relaunch(); app.relaunch();
app.exit(0); app.exit(0);
},
closeVault = () => {
log(LOG_CLEANUP);
cleanup();
const vaultWindows = getWindows(),
obsidianWindows = BrowserWindow.getAllWindows();
if (obsidianWindows.length === vaultWindows.length) {
// quit app directly if only remaining windows are in the
// current vault - necessary for successful quit on macos
app.quit();
} else vaultWindows.forEach((win) => win.destroy());
}; };
const addQuickNote = () => { const createTrayIcon = (plugin) => {
const { quickNoteLocation, quickNoteDateFormat } = plugin.settings, console.log("obsidian-tray: creating tray icon");
pattern = quickNoteDateFormat || DEFAULT_DATE_FORMAT,
date = obsidian.moment().format(pattern),
name = obsidian
.normalizePath(`${quickNoteLocation ?? ""}/${date}`)
.replace(/\*|"|\\|<|>|:|\||\?/g, "-"),
// manually create and open file instead of depending
// on createAndOpenMarkdownFile to force file creation
// relative to the root instead of the active file
// (in case user has default location for new notes
// set to "same folder as current file")
leaf = plugin.app.workspace.getLeaf(),
root = plugin.app.fileManager.getNewFileParent(""),
openMode = { active: true, state: { mode: "source" } };
plugin.app.fileManager
.createNewMarkdownFile(root, name)
.then((file) => leaf.openFile(file, openMode));
showWindows();
},
replaceVaultName = (str) => {
return str.replace(/{{vault}}/g, plugin.app.vault.getName());
},
destroyTray = () => {
tray?.destroy();
tray = undefined;
},
createTrayIcon = () => {
destroyTray();
if (!plugin.settings.createTrayIcon) return;
log(LOG_TRAY_ICON);
const obsidianIcon = nativeImage.createFromDataURL( const obsidianIcon = nativeImage.createFromDataURL(
plugin.settings.trayIconImage ?? OBSIDIAN_BASE64_ICON // 16x16 base64 obsidian icon: generated from obsidian.asar/icon.png
`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHZSURBVDhPlZKxTxRBFMa/XZcF7nIG7mjxjoRCwomJxgsFdhaASqzQxFDzB1AQKgstLGxIiBQGJBpiCCGx8h+wgYaGgAWNd0dyHofeEYVwt/PmOTMZV9aDIL/s5pvZvPfN9yaL/+HR3eXcypta0m4juFbP5GHuXc9IbunDFc9db/G81/ZzhDMN7g8td47mll4R5BfHwZN4LOaA+fHa259PbUmIYzWkt3e2NZNo3/V9v1vvU6kkstk+tLW3ItUVr/m+c3N8MlkwxYqmBFcbwUQQCNOcyVzDwEAWjuPi5DhAMV/tKOYPX5hCyz8Gz1zX5SmWjBvZfmTSaRBJkGAIoxJHv+pVW2yIGNxOJ8bUVNcFEWLxuG1ia6JercTbttwQTeDwPS0kCMXiXtgk/jQrFUw7ptYSMWApF40yo/ytjHq98fdk3ayVE+cn2CxMb6ruz9qAJKFUKoWza1VJSi/n0+ffgYHdWW2gHuxXymg0gjCB0sjpmiaDnkL3RzDyzLqBUKns2ztQqUR0fk2TwSrGSf1eczqF5vsPZRCQSSAFLk6gqctgQRkc6TWRQLV2YMYQki9OoNkqzFQ9r+WOGuW5CrJbOzyAlPKr6MSGLbkcDwbf35oY/jRkt6cAfgNwowruAMz9AgAAAABJRU5ErkJggg==`
), ),
contextMenu = Menu.buildFromTemplate([ contextMenu = Menu.buildFromTemplate([
{ {
type: "normal", type: "normal",
label: ACTION_QUICK_NOTE, label: "Add Quick Note",
accelerator: plugin.settings.quickNoteHotkey, accelerator: plugin.settings.quickNoteHotkey,
click: addQuickNote, click: () => addQuickNote(plugin),
}, },
{ {
type: "normal", type: "normal",
label: ACTION_SHOW, label: "Open Obsidian",
accelerator: plugin.settings.toggleWindowFocusHotkey, accelerator: plugin.settings.toggleWindowFocusHotkey,
click: showWindows, click: showWindows,
}, },
{ {
type: "normal", type: "normal",
label: ACTION_HIDE, label: "Hide Obsidian",
accelerator: plugin.settings.toggleWindowFocusHotkey, accelerator: plugin.settings.toggleWindowFocusHotkey,
click: hideWindows, click: hideWindows,
}, },
{ type: "separator" }, { type: "separator" },
{ label: ACTION_RELAUNCH, click: relaunchApp }, {
{ label: ACTION_CLOSE, click: closeVault }, label: "Relaunch Obsidian",
click: relaunchObsidian,
},
{
label: "Quit Obsidian",
role: "quit",
},
]); ]);
tray = new Tray(obsidianIcon); tray = new Tray(obsidianIcon);
tray.setContextMenu(contextMenu); tray.setContextMenu(contextMenu);
tray.setToolTip(replaceVaultName(plugin.settings.trayIconTooltip)); tray.setToolTip("Obsidian");
tray.on("click", () => { tray.on("click", () => toggleWindows(plugin.settings.runInBackground, false));
if (process.platform === "darwin") { };
// macos does not register separate left/right click actions
// for menu items, icon should open menu w/out causing toggle
tray.popUpContextMenu();
} else toggleWindows(false);
});
};
const registerHotkeys = () => { const registerHotkeys = (plugin) => {
log(LOG_REGISTER_HOTKEY); console.log("obsidian-tray: registering hotkey");
try { try {
const { toggleWindowFocusHotkey, quickNoteHotkey } = plugin.settings; const toggleAccelerator = plugin.settings.toggleWindowFocusHotkey,
if (toggleWindowFocusHotkey) { quicknoteAccelerator = plugin.settings.quickNoteHotkey;
globalShortcut.register(toggleWindowFocusHotkey, toggleWindows); if (toggleAccelerator) {
globalShortcut.register(toggleAccelerator, () => {
const runInBackground = plugin.settings.runInBackground;
toggleWindows(runInBackground);
});
} }
if (quickNoteHotkey) { if (quicknoteAccelerator) {
globalShortcut.register(quickNoteHotkey, addQuickNote); globalShortcut.register(quicknoteAccelerator, () => {
addQuickNote(plugin);
});
} }
} catch {} } catch {}
}, },
unregisterHotkeys = () => { unregisterHotkeys = (plugin) => {
log(LOG_UNREGISTER_HOTKEY); console.log("obsidian-tray: unregistering hotkey");
try { try {
globalShortcut.unregister(plugin.settings.toggleWindowFocusHotkey); const toggle = plugin.settings.toggleWindowFocusHotkey,
globalShortcut.unregister(plugin.settings.quickNoteHotkey); quicknote = plugin.settings.quickNoteHotkey;
globalShortcut.unregister(toggle);
globalShortcut.unregister(quicknote);
} catch {} } catch {}
}; };
@ -274,24 +185,21 @@ const OPTIONS = [
`, `,
type: "toggle", type: "toggle",
default: false, default: false,
onChange() { onChange: (plugin) => {
setLaunchOnStartup(); setLaunchOnStartup(plugin);
if (plugin.settings.runInBackground) interceptWindowClose(); const runInBackground = plugin.settings.runInBackground;
else [allowWindowClose(), showWindows()]; if (!runInBackground) showWindows();
}, },
}, },
{ {
key: "hideTaskbarIcon", key: "hideTaskbarIcon",
desc: ` desc: `
Hides the window's icon from from the dock/taskbar. Enabling the tray icon first Hides the window's icon from from the dock/taskbar. Enabling the tray icon first
is recommended if using this option. This may not work on Linux-based OSes. is recommended if using this option. This may not work on all Linux-based OSes.
`, `,
type: "toggle", type: "toggle",
default: false, default: false,
onChange() { onChange: setHideTaskbarIcon,
if (plugin.settings.hideTaskbarIcon) hideTaskbarIcons();
else showTaskbarIcons();
},
}, },
{ {
key: "createTrayIcon", key: "createTrayIcon",
@ -299,40 +207,15 @@ const OPTIONS = [
Adds an icon to your system tray/menubar to bring hidden Obsidian windows Adds an icon to your system tray/menubar to bring hidden Obsidian windows
back into focus on click or force a full quit/relaunch of the app through back into focus on click or force a full quit/relaunch of the app through
the right-click menu. the right-click menu.
<br><span class="mod-warning">Changing this option requires a restart to take effect.</span>
`, `,
type: "toggle", type: "toggle",
default: true, default: true,
onChange: createTrayIcon,
},
{
key: "trayIconImage",
desc: `
Set the image used by the tray/menubar icon. Recommended size: 16x16
<br>Preview: <img data-preview style="height: 16px; vertical-align: bottom;">
`,
type: "image",
default: OBSIDIAN_BASE64_ICON,
onChange: createTrayIcon,
},
{
key: "trayIconTooltip",
desc: `
Set a title to identify the tray/menubar icon by. The
<code>{{vault}}</code> placeholder will be replaced by the vault name.
<br>Preview: <b class="u-pop" data-preview></b>
`,
type: "text",
default: "{{vault}} | Obsidian",
postprocessor: replaceVaultName,
onChange: createTrayIcon,
}, },
{ {
key: "toggleWindowFocusHotkey", key: "toggleWindowFocusHotkey",
desc: ACCELERATOR_FORMAT,
type: "hotkey", type: "hotkey",
default: "CmdOrCtrl+Shift+Tab", default: "CmdOrCtrl+Shift+Tab",
onBeforeChange: unregisterHotkeys,
onChange: registerHotkeys,
}, },
"Quick notes", "Quick notes",
{ {
@ -343,20 +226,14 @@ const OPTIONS = [
}, },
{ {
key: "quickNoteDateFormat", key: "quickNoteDateFormat",
desc: ` desc: "New quick notes will use a filename of this pattern.",
New quick notes will use a filename of this pattern. ${MOMENT_FORMAT}
<br>Preview: <b class="u-pop" data-preview></b>
`,
type: "moment", type: "moment",
default: DEFAULT_DATE_FORMAT, default: DEFAULT_DATE_FORMAT,
}, },
{ {
key: "quickNoteHotkey", key: "quickNoteHotkey",
desc: ACCELERATOR_FORMAT,
type: "hotkey", type: "hotkey",
default: "CmdOrCtrl+Shift+Q", default: "CmdOrCtrl+Shift+Q",
onBeforeChange: unregisterHotkeys,
onChange: registerHotkeys,
}, },
]; ];
@ -372,7 +249,23 @@ const keyToLabel = (key) =>
.createRange() .createRange()
.createContextualFragment((html ?? "").replace(/\s+/g, " ")); .createContextualFragment((html ?? "").replace(/\s+/g, " "));
const acceleratorFormat = `
This hotkey is registered globally and will be detected even if Obsidian does
not have keyboard focus. Format:
<a href="https://www.electronjs.org/docs/latest/api/accelerator" target="_blank" rel="noopener">
Electron accelerator</a>
`,
momentFormat = `
Format:
<a href="https://momentjs.com/docs/#/displaying/format/" target="_blank" rel="noopener">
Moment.js format string</a>
<br>Preview:
`;
class SettingsTab extends obsidian.PluginSettingTab { class SettingsTab extends obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() { display() {
this.containerEl.empty(); this.containerEl.empty();
for (const opt of OPTIONS) { for (const opt of OPTIONS) {
@ -381,61 +274,51 @@ class SettingsTab extends obsidian.PluginSettingTab {
setting.setName(opt); setting.setName(opt);
setting.setHeading(); setting.setHeading();
} else { } else {
if (opt.default) opt.placeholder ??= `Example: ${opt.default}`; if (opt.default) {
opt.placeholder ??= `Example: ${opt.default}`;
}
if (opt.type === "hotkey") {
opt.desc ??= "";
opt.desc += acceleratorFormat;
opt.onBeforeChange = unregisterHotkeys;
opt.onChange = registerHotkeys;
}
if (opt.type === "moment") {
opt.desc = `${opt.desc ? `${opt.desc}<br>` : ""}${momentFormat}`;
}
setting.setName(keyToLabel(opt.key)); setting.setName(keyToLabel(opt.key));
setting.setDesc(htmlToFragment(opt.desc)); setting.setDesc(htmlToFragment(opt.desc));
const onChange = async (value) => { const onChange = async (value) => {
await opt.onBeforeChange?.(); await opt.onBeforeChange?.(this.plugin);
plugin.settings[opt.key] = value; this.plugin.settings[opt.key] = value;
await plugin.saveSettings(); await this.plugin.saveSettings();
await opt.onChange?.(); await opt.onChange?.(this.plugin);
}; };
const value = plugin.settings[opt.key] ?? opt.default ?? "";
if (opt.type === "toggle") { if (opt.type === "toggle") {
setting.addToggle((toggle) => { setting.addToggle((toggle) => {
toggle.setValue(value).onChange(onChange); toggle
}); .setValue(this.plugin.settings[opt.key] ?? opt.default)
} else if (opt.type === "image") { .onChange(onChange);
const previewImg = setting.descEl.querySelector("img[data-preview");
if (previewImg) previewImg.src = value;
const fileUpload = setting.descEl.createEl("input");
fileUpload.style.visibility = "hidden";
fileUpload.type = "file";
fileUpload.onchange = (event) => {
const file = event.target.files[0],
reader = new FileReader();
reader.onloadend = () => {
onChange(reader.result);
if (previewImg) previewImg.src = reader.result;
};
reader.readAsDataURL(file);
};
setting.addButton((button) => {
button.setIcon("image").onClick(() => fileUpload.click());
}); });
} else if (opt.type === "moment") { } else if (opt.type === "moment") {
setting.addMomentFormat((moment) => { setting.addMomentFormat((moment) => {
const previewEl = setting.descEl.querySelector("[data-preview]"); const sampleEl = setting.descEl.createEl("b");
if (previewEl) moment.setSampleEl(previewEl); sampleEl.className = "u-pop";
moment moment
.setPlaceholder(opt.placeholder) .setPlaceholder(opt.placeholder)
.setDefaultFormat(opt.default ?? "") .setDefaultFormat(opt.default ?? "")
.setValue(value) .setValue(this.plugin.settings[opt.key] ?? opt.default ?? "")
.setSampleEl(sampleEl)
.onChange(onChange); .onChange(onChange);
}); });
} else { } else {
const previewEl = setting.descEl.querySelector("[data-preview]"),
updatePreview = (value) => {
if (!previewEl) return;
previewEl.innerText = opt?.postprocessor(value) ?? value;
};
updatePreview(value);
setting.addText((text) => { setting.addText((text) => {
text text
.setPlaceholder(opt.placeholder) .setPlaceholder(opt.placeholder)
.setValue(value) .setValue(this.plugin.settings[opt.key] ?? opt.default ?? "")
.onChange((value) => [onChange(value), updatePreview(value)]); .onChange(onChange);
}); });
} }
} }
@ -445,45 +328,32 @@ class SettingsTab extends obsidian.PluginSettingTab {
class TrayPlugin extends obsidian.Plugin { class TrayPlugin extends obsidian.Plugin {
async onload() { async onload() {
log(LOG_LOADING); console.log("obsidian-tray: loading");
await this.loadSettings(); await this.loadSettings();
this.addSettingTab(new SettingsTab(this.app, this)); this.addSettingTab(new SettingsTab(this.app, this));
const { settings } = this; const { settings } = this;
plugin = this; registerHotkeys(this);
createTrayIcon(); setHideTaskbarIcon(this);
registerHotkeys(); setLaunchOnStartup(this);
setLaunchOnStartup(); if (settings.createTrayIcon) createTrayIcon(this);
observeWindows();
if (settings.runInBackground) interceptWindowClose(); if (settings.runInBackground) interceptWindowClose();
if (settings.hideTaskbarIcon) hideTaskbarIcons();
if (settings.hideOnLaunch) { if (settings.hideOnLaunch) {
this.registerEvent(this.app.workspace.onLayoutReady(hideWindows)); let _hidden;
this.registerEvent(
this.app.workspace.onLayoutReady(() => {
if (_hidden) return;
_hidden = true;
hideWindows(settings.runInBackground);
})
);
} }
// add as command: can be called from command palette
// and can have non-global hotkey assigned via in-app menu
this.addCommand({
id: "relaunch-app",
name: ACTION_RELAUNCH,
callback: relaunchApp,
});
this.addCommand({
id: "close-vault",
name: ACTION_CLOSE,
callback: closeVault,
});
} }
onunload() { onunload() {
cleanup(); unregisterHotkeys(this);
cleanupWindowClose();
} }
getCurrentWindow = getCurrentWindow
getWindows = getWindows;
showWindows = showWindows;
hideWindows = hideWindows;
toggleWindows = toggleWindows;
async loadSettings() { async loadSettings() {
const DEFAULT_SETTINGS = OPTIONS.map((opt) => ({ [opt.key]: opt.default })); const DEFAULT_SETTINGS = OPTIONS.map((opt) => ({ [opt.key]: opt.default }));
this.settings = Object.assign(...DEFAULT_SETTINGS, await this.loadData()); this.settings = Object.assign(...DEFAULT_SETTINGS, await this.loadData());

View File

@ -1,10 +1,10 @@
{ {
"id": "tray", "id": "obsidian-tray",
"name": "Tray", "name": "Tray",
"author": "dragonwocky", "author": "dragonwocky",
"authorUrl": "https://dragonwocky.me/", "authorUrl": "https://dragonwocky.me/",
"description": "Run Obsidian from the system tray for customisable window management & global quick notes", "description": "Launch Obsidian on startup and run it in the background from the system tray.",
"version": "0.3.5", "version": "0.2.0",
"isDesktopOnly": true, "isDesktopOnly": true,
"minAppVersion": "1.0.0" "minAppVersion": "1.0.0"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 B

BIN
tray.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB