From da54fc09376197b26ecb36a6505e3cc4266fe8ad Mon Sep 17 00:00:00 2001
From: dragonwocky <thedragonring.bod@gmail.com>
Date: Mon, 10 Apr 2023 00:02:15 +1000
Subject: [PATCH] feat: #10 add quick notes via tray or hotkey

---
 README.md |  16 ++++--
 main.js   | 157 ++++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 130 insertions(+), 43 deletions(-)

diff --git a/README.md b/README.md
index 672e56b..cd7bdf2 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
 <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
-on system startup and run it in the background, adding an icon to the system tray that it
-can be minimised to and a global hotkey to toggle visibility of the app's windows.
+on system startup and run it in the background, adding global hotkeys and a tray menu that
+toggle app window visibility and can create quick notes from anywhere in your operating system.
 
 ## Configuration
 
+### Window management
+
 | Option                     | Description                                                                                                                                                                                                                        | Default             |
 | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
 | Launch on startup          | Open Obsidian automatically whenever you log into your computer.                                                                                                                                                                   | Disabled            |
@@ -13,7 +15,15 @@ can be minimised to and a global hotkey to toggle visibility of the app's window
 | 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            |
 | 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             |
-| Toggle window focus hotkey | Format: [Electron accelerator](https://www.electronjs.org/docs/latest/api/accelerator)                                                                                                                                             | CmdOrCtrl+Shift+Tab |
+| 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 |
+
+### Quick notes
+
+| Option                 | Description                                                                                                                                                                                   | Default    |
+| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
+| 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 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) | Disabled   |
 
 ## Installation
 
diff --git a/main.js b/main.js
index 8354517..4e61a45 100644
--- a/main.js
+++ b/main.js
@@ -3,6 +3,8 @@
 
 "use strict";
 
+const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
+
 let tray;
 const obsidian = require("obsidian"),
   {
@@ -61,7 +63,17 @@ const onWindowClose = (event) => {
     closeBtn.removeEventListener("click", onWindowClose, true);
   };
 
-const setHideTaskbarIcon = (plugin) => {
+const addQuickNote = (plugin) => {
+    const { quickNoteLocation, quickNoteDateFormat } = plugin.settings,
+      format = quickNoteDateFormat || DEFAULT_DATE_FORMAT,
+      date = obsidian.moment().format(format),
+      name = obsidian
+        .normalizePath(`${quickNoteLocation ?? ""}/${date}`)
+        .replace(/\*|"|\\|<|>|:|\||\?/g, "-");
+    plugin.app.fileManager.createAndOpenMarkdownFile(name);
+    showWindows();
+  },
+  setHideTaskbarIcon = (plugin) => {
     const win = getCurrentWindow();
     win.setSkipTaskbar(plugin.settings.hideTaskbarIcon);
   },
@@ -84,6 +96,12 @@ const createTrayIcon = (plugin) => {
       `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([
+      {
+        type: "normal",
+        label: "Add Quick Note",
+        accelerator: plugin.settings.quickNoteHotkey,
+        click: () => addQuickNote(plugin),
+      },
       {
         type: "normal",
         label: "Open Obsidian",
@@ -112,25 +130,36 @@ const createTrayIcon = (plugin) => {
   tray.on("click", () => toggleWindows(plugin.settings.runInBackground, false));
 };
 
-const registerHotkey = (plugin) => {
+const registerHotkeys = (plugin) => {
     console.log("obsidian-tray: registering hotkey");
     try {
-      const accelerator = plugin.settings.toggleWindowFocusHotkey;
-      globalShortcut.register(accelerator, () => {
-        const runInBackground = plugin.settings.runInBackground;
-        toggleWindows(runInBackground);
-      });
+      const toggleAccelerator = plugin.settings.toggleWindowFocusHotkey,
+        quicknoteAccelerator = plugin.settings.quickNoteHotkey;
+      if (toggleAccelerator) {
+        globalShortcut.register(toggleAccelerator, () => {
+          const runInBackground = plugin.settings.runInBackground;
+          toggleWindows(runInBackground);
+        });
+      }
+      if (quicknoteAccelerator) {
+        globalShortcut.register(quicknoteAccelerator, () => {
+          addQuickNote(plugin);
+        });
+      }
     } catch {}
   },
-  unregisterHotkey = (plugin) => {
+  unregisterHotkeys = (plugin) => {
     console.log("obsidian-tray: unregistering hotkey");
     try {
-      const accelerator = plugin.settings.toggleWindowFocusHotkey;
-      globalShortcut.unregister(accelerator);
+      const toggle = plugin.settings.toggleWindowFocusHotkey,
+        quicknote = plugin.settings.quickNoteHotkey;
+      globalShortcut.unregister(toggle);
+      globalShortcut.unregister(quicknote);
     } catch {}
   };
 
 const OPTIONS = [
+  "Window management",
   {
     key: "launchOnStartup",
     desc: "Open Obsidian automatically whenever you log into your computer.",
@@ -185,16 +214,26 @@ const OPTIONS = [
   },
   {
     key: "toggleWindowFocusHotkey",
-    desc: `
-      Format:
-      <a href="https://www.electronjs.org/docs/latest/api/accelerator">
-        Electron accelerator
-      </a>
-    `,
-    type: "text",
+    type: "hotkey",
     default: "CmdOrCtrl+Shift+Tab",
-    onBeforeChange: unregisterHotkey,
-    onChange: registerHotkey,
+  },
+  "Quick notes",
+  {
+    key: "quickNoteLocation",
+    desc: "New quick notes will be placed in this folder.",
+    type: "text",
+    placeholder: "Example: notes/quick",
+  },
+  {
+    key: "quickNoteDateFormat",
+    desc: "New quick notes will use a filename of this pattern.",
+    type: "moment",
+    default: DEFAULT_DATE_FORMAT,
+  },
+  {
+    key: "quickNoteHotkey",
+    type: "hotkey",
+    default: "CmdOrCtrl+Shift+Q",
   },
 ];
 
@@ -210,6 +249,18 @@ const keyToLabel = (key) =>
       .createRange()
       .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 {
   constructor(app, plugin) {
     super(app, plugin);
@@ -218,32 +269,58 @@ class SettingsTab extends obsidian.PluginSettingTab {
   display() {
     this.containerEl.empty();
     for (const opt of OPTIONS) {
-      const name = keyToLabel(opt.key),
-        desc = htmlToFragment(opt.desc),
-        onChange = async (value) => {
+      const setting = new obsidian.Setting(this.containerEl);
+      if (typeof opt === "string") {
+        setting.setName(opt);
+        setting.setHeading();
+      } else {
+        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.setDesc(htmlToFragment(opt.desc));
+        const onChange = async (value) => {
           await opt.onBeforeChange?.(this.plugin);
           this.plugin.settings[opt.key] = value;
           await this.plugin.saveSettings();
           await opt.onChange?.(this.plugin);
         };
 
-      const setting = new obsidian.Setting(this.containerEl)
-        .setName(name)
-        .setDesc(desc);
-      switch (opt.type) {
-        case "toggle":
-          setting.addToggle((toggle) =>
-            toggle.setValue(this.plugin.settings[opt.key]).onChange(onChange)
-          );
-          break;
-        case "text":
-        default:
-          setting.addText((text) =>
+        if (opt.type === "toggle") {
+          setting.addToggle((toggle) => {
+            toggle
+              .setValue(this.plugin.settings[opt.key] ?? opt.default)
+              .onChange(onChange);
+          });
+        } else if (opt.type === "moment") {
+          setting.addMomentFormat((moment) => {
+            const sampleEl = setting.descEl.createEl("b");
+            sampleEl.className = "u-pop";
+            moment
+              .setPlaceholder(opt.placeholder)
+              .setDefaultFormat(opt.default ?? "")
+              .setValue(this.plugin.settings[opt.key] ?? opt.default ?? "")
+              .setSampleEl(sampleEl)
+              .onChange(onChange);
+          });
+        } else {
+          setting.addText((text) => {
             text
-              .setPlaceholder(opt.default)
-              .setValue(this.plugin.settings[opt.key])
-              .onChange(onChange)
-          );
+              .setPlaceholder(opt.placeholder)
+              .setValue(this.plugin.settings[opt.key] ?? opt.default ?? "")
+              .onChange(onChange);
+          });
+        }
       }
     }
   }
@@ -256,7 +333,7 @@ class TrayPlugin extends obsidian.Plugin {
     this.addSettingTab(new SettingsTab(this.app, this));
     const { settings } = this;
 
-    registerHotkey(this);
+    registerHotkeys(this);
     setHideTaskbarIcon(this);
     setLaunchOnStartup(this);
     if (settings.createTrayIcon) createTrayIcon(this);
@@ -273,7 +350,7 @@ class TrayPlugin extends obsidian.Plugin {
     }
   }
   onunload() {
-    unregisterHotkey(this);
+    unregisterHotkeys(this);
     cleanupWindowClose();
   }