mirror of
https://github.com/dragonwocky/obsidian-tray.git
synced 2025-04-08 05:49:02 +00:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
f4e827253d | |||
|
62fcf88fe6 | ||
|
09fe3c2d03 | ||
cc0baa3226 | |||
6a76f575ed | |||
b60e453b68 | |||
999213801f | |||
77ce4bd54a | |||
096bb34d54 | |||
fb15c25171 | |||
b62d8862ab | |||
4c0c3da898 | |||
b8326526ef | |||
5cef2b0ed7 | |||
78e819cc45 | |||
|
71ba850a15 | ||
a5abaf32e7 | |||
d49c49fe83 | |||
6f81acb0ad | |||
aac711518b | |||
aa4e824723 | |||
d37152dd4e | |||
fcce4935a3 | |||
b8834b1286 | |||
16d577d67e | |||
|
f0c6a5d49f | ||
4e097cca14 | |||
5cf55e3b7c | |||
44518be5a5 | |||
3270ed5d82 | |||
8cebeed926 | |||
1937bdb561 | |||
db671961c5 | |||
84a02f157a |
32
README.md
32
README.md
@ -1,37 +1,39 @@
|
|||||||
<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 that
|
on system startup and run it in the background, adding global hotkeys and a tray menu to
|
||||||
toggle app window visibility and can create quick notes from anywhere in your operating system.
|
toggle window visibility and 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 all Linux-based OSes. | Disabled |
|
| Hide taskbar icon | Hides the window's icon from from the dock/taskbar. This may not work on 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. _Changing this option requires a restart to take effect._ | 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. | Enabled |
|
||||||
| 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 image | Set the image used by the tray/menubar icon. Recommended size: 16x16 |  |
|
||||||
|
| 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` action can be triggered from the tray/menubar context menu, or with the in-app
|
The `Relaunch Obsidian` and `Close Vault` actions can be triggered from the tray/menubar context menu,
|
||||||
command palette (search for "Tray: Relaunch Obsidian"). Hotkeys can be assigned to the command via
|
or with the in-app command palette (search for "Tray: Relaunch Obsidian" or "Tray: Close Vault").
|
||||||
Obsidian's built-in hotkey manager.
|
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) | CmdOrCtrl+Shift+Q |
|
| 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> |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Obsidian Marketplace (Coming Soon)
|
### Obsidian Marketplace
|
||||||
|
|
||||||
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.
|
||||||
@ -59,3 +61,7 @@ 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.
|
||||||
|
355
main.js
355
main.js
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* obsidian-tray v0.2.1
|
* obsidian-tray v0.3.5
|
||||||
* (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 = "Add Quick Note",
|
ACTION_QUICK_NOTE = "Quick Note",
|
||||||
ACTION_OPEN = "Open Obsidian",
|
ACTION_SHOW = "Show Vault",
|
||||||
ACTION_HIDE = "Hide Obsidian",
|
ACTION_HIDE = "Hide Vault",
|
||||||
ACTION_RELAUNCH = "Relaunch Obsidian",
|
ACTION_RELAUNCH = "Relaunch Obsidian",
|
||||||
ACTION_QUIT = "Quit Obsidian",
|
ACTION_CLOSE = "Close Vault",
|
||||||
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,46 +31,70 @@ 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;
|
let tray, plugin;
|
||||||
const obsidian = require("obsidian"),
|
const obsidian = require("obsidian"),
|
||||||
{
|
{ app, Tray, Menu } = require("electron").remote,
|
||||||
app,
|
{ nativeImage, BrowserWindow } = require("electron").remote,
|
||||||
BrowserWindow,
|
{ getCurrentWindow, globalShortcut } = require("electron").remote;
|
||||||
getCurrentWindow,
|
|
||||||
globalShortcut,
|
|
||||||
Tray,
|
|
||||||
Menu,
|
|
||||||
nativeImage,
|
|
||||||
} = require("electron").remote;
|
|
||||||
|
|
||||||
const showWindows = () => {
|
const vaultWindows = new Set(),
|
||||||
log(LOG_SHOWING_WINDOWS);
|
maximizedWindows = new Set(),
|
||||||
const windows = BrowserWindow.getAllWindows();
|
getWindows = () => [...vaultWindows],
|
||||||
windows.forEach((win) => win.show());
|
observeWindows = () => {
|
||||||
getCurrentWindow().focus();
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
hideWindows = (runInBackground) => {
|
showWindows = () => {
|
||||||
|
log(LOG_SHOWING_WINDOWS);
|
||||||
|
getWindows().forEach((win) => {
|
||||||
|
if (maximizedWindows.has(win)) {
|
||||||
|
win.maximize();
|
||||||
|
win.focus();
|
||||||
|
} else win.show();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hideWindows = () => {
|
||||||
log(LOG_HIDING_WINDOWS);
|
log(LOG_HIDING_WINDOWS);
|
||||||
const windows = BrowserWindow.getAllWindows();
|
getWindows().forEach((win) => [
|
||||||
windows.forEach((win) => [
|
|
||||||
win.isFocused() && win.blur(),
|
win.isFocused() && win.blur(),
|
||||||
runInBackground ? win.hide() : win.minimize(),
|
plugin.settings.runInBackground ? win.hide() : win.minimize(),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
toggleWindows = (runInBackground, checkForFocus = true) => {
|
toggleWindows = (checkForFocus = true) => {
|
||||||
const windows = BrowserWindow.getAllWindows(),
|
const openWindows = getWindows().some((win) => {
|
||||||
openWindows = windows.some((win) => {
|
|
||||||
return (!checkForFocus || win.isFocused()) && win.isVisible();
|
return (!checkForFocus || win.isFocused()) && win.isVisible();
|
||||||
});
|
});
|
||||||
if (openWindows) {
|
if (openWindows) hideWindows();
|
||||||
hideWindows(runInBackground);
|
else showWindows();
|
||||||
} else showWindows();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onWindowClose = (event) => event.preventDefault(),
|
const onWindowClose = (event) => event.preventDefault(),
|
||||||
@ -90,49 +114,94 @@ 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);
|
||||||
},
|
},
|
||||||
cleanupWindowClose = () => {
|
allowWindowClose = () => {
|
||||||
getCurrentWindow().removeListener("close", onWindowClose);
|
getCurrentWindow().removeListener("close", onWindowClose);
|
||||||
window.removeEventListener("beforeunload", onWindowUnload, true);
|
window.removeEventListener("beforeunload", onWindowUnload, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setHideTaskbarIcon = (plugin) => {
|
const hideTaskbarIcons = () => {
|
||||||
getCurrentWindow().setSkipTaskbar(plugin.settings.hideTaskbarIcon);
|
getWindows().forEach((win) => win.setSkipTaskbar(true));
|
||||||
|
if (process.platform === "darwin") app.dock.hide();
|
||||||
},
|
},
|
||||||
setLaunchOnStartup = (plugin) => {
|
showTaskbarIcons = () => {
|
||||||
|
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,
|
||||||
});
|
});
|
||||||
},
|
|
||||||
relaunchObsidian = () => {
|
|
||||||
app.relaunch();
|
|
||||||
app.exit(0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addQuickNote = (plugin) => {
|
const cleanup = () => {
|
||||||
|
log(LOG_CLEANUP);
|
||||||
|
unregisterHotkeys();
|
||||||
|
showTaskbarIcons();
|
||||||
|
allowWindowClose();
|
||||||
|
destroyTray();
|
||||||
|
},
|
||||||
|
relaunchApp = () => {
|
||||||
|
app.relaunch();
|
||||||
|
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 { 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, "-"),
|
||||||
plugin.app.fileManager.createAndOpenMarkdownFile(name);
|
// 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();
|
showWindows();
|
||||||
},
|
},
|
||||||
createTrayIcon = (plugin) => {
|
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);
|
log(LOG_TRAY_ICON);
|
||||||
const obsidianIcon = nativeImage.createFromDataURL(OBSIDIAN_BASE64_ICON),
|
const obsidianIcon = nativeImage.createFromDataURL(
|
||||||
|
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(plugin),
|
click: addQuickNote,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "normal",
|
type: "normal",
|
||||||
label: ACTION_OPEN,
|
label: ACTION_SHOW,
|
||||||
accelerator: plugin.settings.toggleWindowFocusHotkey,
|
accelerator: plugin.settings.toggleWindowFocusHotkey,
|
||||||
click: showWindows,
|
click: showWindows,
|
||||||
},
|
},
|
||||||
@ -143,42 +212,38 @@ const addQuickNote = (plugin) => {
|
|||||||
click: hideWindows,
|
click: hideWindows,
|
||||||
},
|
},
|
||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{ label: ACTION_RELAUNCH, click: relaunchObsidian },
|
{ label: ACTION_RELAUNCH, click: relaunchApp },
|
||||||
{ label: ACTION_QUIT, role: "quit" },
|
{ label: ACTION_CLOSE, click: closeVault },
|
||||||
]);
|
]);
|
||||||
tray = new Tray(obsidianIcon);
|
tray = new Tray(obsidianIcon);
|
||||||
tray.setContextMenu(contextMenu);
|
tray.setContextMenu(contextMenu);
|
||||||
tray.setToolTip("Obsidian");
|
tray.setToolTip(replaceVaultName(plugin.settings.trayIconTooltip));
|
||||||
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 = (plugin) => {
|
const registerHotkeys = () => {
|
||||||
log(LOG_REGISTER_HOTKEY);
|
log(LOG_REGISTER_HOTKEY);
|
||||||
try {
|
try {
|
||||||
const toggleAccelerator = plugin.settings.toggleWindowFocusHotkey,
|
const { toggleWindowFocusHotkey, quickNoteHotkey } = plugin.settings;
|
||||||
quicknoteAccelerator = plugin.settings.quickNoteHotkey;
|
if (toggleWindowFocusHotkey) {
|
||||||
if (toggleAccelerator) {
|
globalShortcut.register(toggleWindowFocusHotkey, toggleWindows);
|
||||||
globalShortcut.register(toggleAccelerator, () => {
|
|
||||||
const runInBackground = plugin.settings.runInBackground;
|
|
||||||
toggleWindows(runInBackground);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (quicknoteAccelerator) {
|
if (quickNoteHotkey) {
|
||||||
globalShortcut.register(quicknoteAccelerator, () => {
|
globalShortcut.register(quickNoteHotkey, addQuickNote);
|
||||||
addQuickNote(plugin);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
},
|
},
|
||||||
unregisterHotkeys = (plugin) => {
|
unregisterHotkeys = () => {
|
||||||
log(LOG_UNREGISTER_HOTKEY);
|
log(LOG_UNREGISTER_HOTKEY);
|
||||||
try {
|
try {
|
||||||
const toggle = plugin.settings.toggleWindowFocusHotkey,
|
globalShortcut.unregister(plugin.settings.toggleWindowFocusHotkey);
|
||||||
quicknote = plugin.settings.quickNoteHotkey;
|
globalShortcut.unregister(plugin.settings.quickNoteHotkey);
|
||||||
globalShortcut.unregister(toggle);
|
|
||||||
globalShortcut.unregister(quicknote);
|
|
||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -209,21 +274,24 @@ const OPTIONS = [
|
|||||||
`,
|
`,
|
||||||
type: "toggle",
|
type: "toggle",
|
||||||
default: false,
|
default: false,
|
||||||
onChange: (plugin) => {
|
onChange() {
|
||||||
setLaunchOnStartup(plugin);
|
setLaunchOnStartup();
|
||||||
const runInBackground = plugin.settings.runInBackground;
|
if (plugin.settings.runInBackground) interceptWindowClose();
|
||||||
if (!runInBackground) showWindows();
|
else [allowWindowClose(), 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 all Linux-based OSes.
|
is recommended if using this option. This may not work on Linux-based OSes.
|
||||||
`,
|
`,
|
||||||
type: "toggle",
|
type: "toggle",
|
||||||
default: false,
|
default: false,
|
||||||
onChange: setHideTaskbarIcon,
|
onChange() {
|
||||||
|
if (plugin.settings.hideTaskbarIcon) hideTaskbarIcons();
|
||||||
|
else showTaskbarIcons();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "createTrayIcon",
|
key: "createTrayIcon",
|
||||||
@ -231,15 +299,40 @@ 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",
|
||||||
{
|
{
|
||||||
@ -250,14 +343,20 @@ const OPTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "quickNoteDateFormat",
|
key: "quickNoteDateFormat",
|
||||||
desc: "New quick notes will use a filename of this pattern.",
|
desc: `
|
||||||
|
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,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -274,10 +373,6 @@ 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) {
|
||||||
@ -286,51 +381,61 @@ class SettingsTab extends obsidian.PluginSettingTab {
|
|||||||
setting.setName(opt);
|
setting.setName(opt);
|
||||||
setting.setHeading();
|
setting.setHeading();
|
||||||
} else {
|
} else {
|
||||||
if (opt.default) {
|
if (opt.default) opt.placeholder ??= `Example: ${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?.(this.plugin);
|
await opt.onBeforeChange?.();
|
||||||
this.plugin.settings[opt.key] = value;
|
plugin.settings[opt.key] = value;
|
||||||
await this.plugin.saveSettings();
|
await plugin.saveSettings();
|
||||||
await opt.onChange?.(this.plugin);
|
await opt.onChange?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const value = plugin.settings[opt.key] ?? opt.default ?? "";
|
||||||
if (opt.type === "toggle") {
|
if (opt.type === "toggle") {
|
||||||
setting.addToggle((toggle) => {
|
setting.addToggle((toggle) => {
|
||||||
toggle
|
toggle.setValue(value).onChange(onChange);
|
||||||
.setValue(this.plugin.settings[opt.key] ?? opt.default)
|
});
|
||||||
.onChange(onChange);
|
} else if (opt.type === "image") {
|
||||||
|
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 sampleEl = setting.descEl.createEl("b");
|
const previewEl = setting.descEl.querySelector("[data-preview]");
|
||||||
sampleEl.className = "u-pop";
|
if (previewEl) moment.setSampleEl(previewEl);
|
||||||
moment
|
moment
|
||||||
.setPlaceholder(opt.placeholder)
|
.setPlaceholder(opt.placeholder)
|
||||||
.setDefaultFormat(opt.default ?? "")
|
.setDefaultFormat(opt.default ?? "")
|
||||||
.setValue(this.plugin.settings[opt.key] ?? opt.default ?? "")
|
.setValue(value)
|
||||||
.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(this.plugin.settings[opt.key] ?? opt.default ?? "")
|
.setValue(value)
|
||||||
.onChange(onChange);
|
.onChange((value) => [onChange(value), updatePreview(value)]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,20 +450,15 @@ 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;
|
||||||
|
|
||||||
registerHotkeys(this);
|
plugin = this;
|
||||||
setHideTaskbarIcon(this);
|
createTrayIcon();
|
||||||
setLaunchOnStartup(this);
|
registerHotkeys();
|
||||||
if (settings.createTrayIcon) createTrayIcon(this);
|
setLaunchOnStartup();
|
||||||
|
observeWindows();
|
||||||
if (settings.runInBackground) interceptWindowClose();
|
if (settings.runInBackground) interceptWindowClose();
|
||||||
|
if (settings.hideTaskbarIcon) hideTaskbarIcons();
|
||||||
if (settings.hideOnLaunch) {
|
if (settings.hideOnLaunch) {
|
||||||
let _hidden;
|
this.registerEvent(this.app.workspace.onLayoutReady(hideWindows));
|
||||||
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
|
||||||
@ -366,15 +466,24 @@ class TrayPlugin extends obsidian.Plugin {
|
|||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "relaunch-app",
|
id: "relaunch-app",
|
||||||
name: ACTION_RELAUNCH,
|
name: ACTION_RELAUNCH,
|
||||||
callback: relaunchObsidian,
|
callback: relaunchApp,
|
||||||
|
});
|
||||||
|
this.addCommand({
|
||||||
|
id: "close-vault",
|
||||||
|
name: ACTION_CLOSE,
|
||||||
|
callback: closeVault,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
log(LOG_CLEANUP);
|
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());
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-tray",
|
"id": "tray",
|
||||||
"name": "Tray",
|
"name": "Tray",
|
||||||
"author": "dragonwocky",
|
"author": "dragonwocky",
|
||||||
"authorUrl": "https://dragonwocky.me/",
|
"authorUrl": "https://dragonwocky.me/",
|
||||||
"description": "Launch Obsidian on startup and run it in the background from the system tray.",
|
"description": "Run Obsidian from the system tray for customisable window management & global quick notes",
|
||||||
"version": "0.2.1",
|
"version": "0.3.5",
|
||||||
"isDesktopOnly": true,
|
"isDesktopOnly": true,
|
||||||
"minAppVersion": "1.0.0"
|
"minAppVersion": "1.0.0"
|
||||||
}
|
}
|
||||||
|
BIN
obsidian.png
Normal file
BIN
obsidian.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 580 B |
Loading…
Reference in New Issue
Block a user