Compare commits

..

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

5 changed files with 143 additions and 258 deletions

View File

@ -1,39 +1,37 @@
<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, The `Relaunch Obsidian` action can be triggered from the tray/menubar context menu, or with the in-app
or with the in-app command palette (search for "Tray: Relaunch Obsidian" or "Tray: Close Vault"). command palette (search for "Tray: Relaunch Obsidian"). Hotkeys can be assigned to the command via
Hotkeys can be assigned to the commands via Obsidian's built-in hotkey manager. 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 +59,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.

351
main.js
View File

@ -1,5 +1,5 @@
/** /**
* obsidian-tray v0.3.5 * obsidian-tray v0.2.1
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://github.com/dragonwocky/obsidian-tray/) under the MIT license * (https://github.com/dragonwocky/obsidian-tray/) under the MIT license
*/ */
@ -15,11 +15,11 @@ const LOG_PREFIX = "obsidian-tray",
LOG_TRAY_ICON = "creating tray icon", LOG_TRAY_ICON = "creating tray icon",
LOG_REGISTER_HOTKEY = "registering hotkey", LOG_REGISTER_HOTKEY = "registering hotkey",
LOG_UNREGISTER_HOTKEY = "unregistering hotkey", LOG_UNREGISTER_HOTKEY = "unregistering hotkey",
ACTION_QUICK_NOTE = "Quick Note", ACTION_QUICK_NOTE = "Add Quick Note",
ACTION_SHOW = "Show Vault", ACTION_OPEN = "Open Obsidian",
ACTION_HIDE = "Hide Vault", ACTION_HIDE = "Hide Obsidian",
ACTION_RELAUNCH = "Relaunch Obsidian", ACTION_RELAUNCH = "Relaunch Obsidian",
ACTION_CLOSE = "Close Vault", ACTION_QUIT = "Quit Obsidian",
DEFAULT_DATE_FORMAT = "YYYY-MM-DD", DEFAULT_DATE_FORMAT = "YYYY-MM-DD",
ACCELERATOR_FORMAT = ` ACCELERATOR_FORMAT = `
This hotkey is registered globally and will be detected even if Obsidian does This hotkey is registered globally and will be detected even if Obsidian does
@ -31,70 +31,46 @@ const LOG_PREFIX = "obsidian-tray",
Format: Format:
<a href="https://momentjs.com/docs/#/displaying/format/" target="_blank" rel="noopener"> <a href="https://momentjs.com/docs/#/displaying/format/" target="_blank" rel="noopener">
Moment.js format string</a> Moment.js format string</a>
<br>Preview:
`, `,
// 16x16 base64 obsidian icon: generated from obsidian.asar/icon.png // 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==`, 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}`); 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(),
getWindows = () => [...vaultWindows],
observeWindows = () => {
const onWindowCreation = (win) => {
vaultWindows.add(win);
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 = () => {
log(LOG_SHOWING_WINDOWS); log(LOG_SHOWING_WINDOWS);
getWindows().forEach((win) => { const windows = BrowserWindow.getAllWindows();
if (maximizedWindows.has(win)) { windows.forEach((win) => win.show());
win.maximize(); getCurrentWindow().focus();
win.focus();
} else win.show();
});
}, },
hideWindows = () => { hideWindows = (runInBackground) => {
log(LOG_HIDING_WINDOWS); log(LOG_HIDING_WINDOWS);
getWindows().forEach((win) => [ const windows = BrowserWindow.getAllWindows();
windows.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(),
return (!checkForFocus || win.isFocused()) && win.isVisible(); openWindows = windows.some((win) => {
}); return (!checkForFocus || win.isFocused()) && win.isVisible();
if (openWindows) hideWindows(); });
else showWindows(); if (openWindows) {
hideWindows(runInBackground);
} else showWindows();
}; };
const onWindowClose = (event) => event.preventDefault(), const onWindowClose = (event) => event.preventDefault(),
@ -114,94 +90,49 @@ const onWindowClose = (event) => event.preventDefault(),
// the 3-second delayed window force close in obsidian.asar/main.js // the 3-second delayed window force close in obsidian.asar/main.js
getCurrentWindow().on("close", onWindowClose); getCurrentWindow().on("close", onWindowClose);
}, },
allowWindowClose = () => { cleanupWindowClose = () => {
getCurrentWindow().removeListener("close", onWindowClose); getCurrentWindow().removeListener("close", onWindowClose);
window.removeEventListener("beforeunload", onWindowUnload, true); window.removeEventListener("beforeunload", onWindowUnload, true);
}; };
const hideTaskbarIcons = () => { const setHideTaskbarIcon = (plugin) => {
getWindows().forEach((win) => win.setSkipTaskbar(true)); getCurrentWindow().setSkipTaskbar(plugin.settings.hideTaskbarIcon);
if (process.platform === "darwin") app.dock.hide();
}, },
showTaskbarIcons = () => { setLaunchOnStartup = (plugin) => {
getWindows().forEach((win) => win.setSkipTaskbar(false));
if (process.platform === "darwin") app.dock.show();
},
setLaunchOnStartup = () => {
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 addQuickNote = (plugin) => {
const { quickNoteLocation, quickNoteDateFormat } = plugin.settings, const { quickNoteLocation, quickNoteDateFormat } = plugin.settings,
pattern = quickNoteDateFormat || DEFAULT_DATE_FORMAT, pattern = quickNoteDateFormat || DEFAULT_DATE_FORMAT,
date = obsidian.moment().format(pattern), date = obsidian.moment().format(pattern),
name = obsidian name = obsidian
.normalizePath(`${quickNoteLocation ?? ""}/${date}`) .normalizePath(`${quickNoteLocation ?? ""}/${date}`)
.replace(/\*|"|\\|<|>|:|\||\?/g, "-"), .replace(/\*|"|\\|<|>|:|\||\?/g, "-");
// manually create and open file instead of depending plugin.app.fileManager.createAndOpenMarkdownFile(name);
// 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(); showWindows();
}, },
replaceVaultName = (str) => { createTrayIcon = (plugin) => {
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); log(LOG_TRAY_ICON);
const obsidianIcon = nativeImage.createFromDataURL( const obsidianIcon = nativeImage.createFromDataURL(OBSIDIAN_BASE64_ICON),
plugin.settings.trayIconImage ?? OBSIDIAN_BASE64_ICON
),
contextMenu = Menu.buildFromTemplate([ contextMenu = Menu.buildFromTemplate([
{ {
type: "normal", type: "normal",
label: ACTION_QUICK_NOTE, label: ACTION_QUICK_NOTE,
accelerator: plugin.settings.quickNoteHotkey, accelerator: plugin.settings.quickNoteHotkey,
click: addQuickNote, click: () => addQuickNote(plugin),
}, },
{ {
type: "normal", type: "normal",
label: ACTION_SHOW, label: ACTION_OPEN,
accelerator: plugin.settings.toggleWindowFocusHotkey, accelerator: plugin.settings.toggleWindowFocusHotkey,
click: showWindows, click: showWindows,
}, },
@ -212,38 +143,42 @@ const addQuickNote = () => {
click: hideWindows, click: hideWindows,
}, },
{ type: "separator" }, { type: "separator" },
{ label: ACTION_RELAUNCH, click: relaunchApp }, { label: ACTION_RELAUNCH, click: relaunchObsidian },
{ label: ACTION_CLOSE, click: closeVault }, { label: ACTION_QUIT, 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", () =>
if (process.platform === "darwin") { toggleWindows(plugin.settings.runInBackground, false)
// 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); log(LOG_REGISTER_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); log(LOG_UNREGISTER_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 +209,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 +231,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 +250,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,
}, },
]; ];
@ -373,6 +274,10 @@ const keyToLabel = (key) =>
.createContextualFragment((html ?? "").replace(/\s+/g, " ")); .createContextualFragment((html ?? "").replace(/\s+/g, " "));
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 +286,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 += ACCELERATOR_FORMAT;
opt.onBeforeChange = unregisterHotkeys;
opt.onChange = registerHotkeys;
}
if (opt.type === "moment") {
opt.desc = `${opt.desc ? `${opt.desc}<br>` : ""}${MOMENT_FORMAT}`;
}
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);
}); });
} }
} }
@ -450,15 +345,20 @@ class TrayPlugin extends obsidian.Plugin {
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 // add as command: can be called from command palette
@ -466,24 +366,15 @@ class TrayPlugin extends obsidian.Plugin {
this.addCommand({ this.addCommand({
id: "relaunch-app", id: "relaunch-app",
name: ACTION_RELAUNCH, name: ACTION_RELAUNCH,
callback: relaunchApp, callback: relaunchObsidian,
});
this.addCommand({
id: "close-vault",
name: ACTION_CLOSE,
callback: closeVault,
}); });
} }
onunload() { onunload() {
cleanup(); log(LOG_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.1",
"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