From 92501bd7e1f2c20b5a27ca8f5a8b9bce5e039849 Mon Sep 17 00:00:00 2001
From: dragonwocky <thedragonring.bod@gmail.com>
Date: Tue, 4 Aug 2020 15:32:04 +1000
Subject: [PATCH] menu options: toggles + selects

---
 repo/core/buttons.js   | 169 +++++++++++-----------
 repo/core/client.js    | 218 ++++++++++++++--------------
 repo/core/create.js    | 150 ++++++++++---------
 repo/core/css/menu.css |  49 ++++++-
 repo/core/menu.js      | 146 +++++++++++++------
 repo/core/mod.js       |  64 ++++++---
 repo/core/render.js    |  42 +++---
 repo/core/tray.js      | 320 ++++++++++++++++++++---------------------
 8 files changed, 646 insertions(+), 512 deletions(-)

diff --git a/repo/core/buttons.js b/repo/core/buttons.js
index c1c57d2..1dcf728 100644
--- a/repo/core/buttons.js
+++ b/repo/core/buttons.js
@@ -6,93 +6,96 @@
 
 'use strict';
 
-const __mod = require('./mod.js'),
-  store = require('../../pkg/store.js'),
-  settings = store(__mod.id, __mod.defaults),
-  path = require('path'),
-  fs = require('fs-extra'),
-  browser = require('electron').remote.getCurrentWindow(),
-  is_mac = process.platform === 'darwin',
-  buttons = {
-    element: document.createElement('div'),
-    insert: [
-      'alwaysontop',
-      ...(settings.frameless && !is_mac
-        ? ['minimize', 'maximize', 'close']
-        : []),
-    ],
-    icons: {
-      raw: {
-        alwaysontop: {
-          on: fs.readFile(
-            path.resolve(`${__dirname}/icons/alwaysontop_on.svg`)
-          ),
-          off: fs.readFile(
-            path.resolve(`${__dirname}/icons/alwaysontop_off.svg`)
+module.exports = (store) => {
+  const path = require('path'),
+    fs = require('fs-extra'),
+    browser = require('electron').remote.getCurrentWindow(),
+    is_mac = process.platform === 'darwin',
+    buttons = {
+      element: document.createElement('div'),
+      insert: [
+        'alwaysontop',
+        ...(store().frameless && !is_mac
+          ? ['minimize', 'maximize', 'close']
+          : []),
+      ],
+      icons: {
+        raw: {
+          alwaysontop: {
+            on: fs.readFile(
+              path.resolve(`${__dirname}/icons/alwaysontop_on.svg`)
+            ),
+            off: fs.readFile(
+              path.resolve(`${__dirname}/icons/alwaysontop_off.svg`)
+            ),
+          },
+          minimize: fs.readFile(
+            path.resolve(`${__dirname}/icons/minimize.svg`)
           ),
+          maximize: {
+            on: fs.readFile(path.resolve(`${__dirname}/icons/maximize_on.svg`)),
+            off: fs.readFile(
+              path.resolve(`${__dirname}/icons/maximize_off.svg`)
+            ),
+          },
+          close: fs.readFile(path.resolve(`${__dirname}/icons/close.svg`)),
         },
-        minimize: fs.readFile(path.resolve(`${__dirname}/icons/minimize.svg`)),
-        maximize: {
-          on: fs.readFile(path.resolve(`${__dirname}/icons/maximize_on.svg`)),
-          off: fs.readFile(path.resolve(`${__dirname}/icons/maximize_off.svg`)),
+        alwaysontop() {
+          return browser.isAlwaysOnTop()
+            ? buttons.icons.raw.alwaysontop.on
+            : buttons.icons.raw.alwaysontop.off; // '🠙' : '🠛'
+        },
+        minimize() {
+          return buttons.icons.raw.minimize; // '⚊'
+        },
+        maximize() {
+          return browser.isMaximized()
+            ? buttons.icons.raw.maximize.on
+            : buttons.icons.raw.maximize.off; // '🗗' : '🗖'
+        },
+        close() {
+          return buttons.icons.raw.close; // '⨉'
         },
-        close: fs.readFile(path.resolve(`${__dirname}/icons/close.svg`)),
       },
-      alwaysontop() {
-        return browser.isAlwaysOnTop()
-          ? buttons.icons.raw.alwaysontop.on
-          : buttons.icons.raw.alwaysontop.off; // '🠙' : '🠛'
+      actions: {
+        async alwaysontop() {
+          browser.setAlwaysOnTop(!browser.isAlwaysOnTop());
+          this.innerHTML = await buttons.icons.alwaysontop();
+        },
+        minimize() {
+          browser.minimize();
+        },
+        async maximize() {
+          browser.isMaximized() ? browser.unmaximize() : browser.maximize();
+          this.innerHTML = await buttons.icons.maximize();
+        },
+        close() {
+          browser.close();
+        },
       },
-      minimize() {
-        return buttons.icons.raw.minimize; // '⚊'
-      },
-      maximize() {
-        return browser.isMaximized()
-          ? buttons.icons.raw.maximize.on
-          : buttons.icons.raw.maximize.off; // '🗗' : '🗖'
-      },
-      close() {
-        return buttons.icons.raw.close; // '⨉'
-      },
-    },
-    actions: {
-      async alwaysontop() {
-        browser.setAlwaysOnTop(!browser.isAlwaysOnTop());
-        this.innerHTML = await buttons.icons.alwaysontop();
-      },
-      minimize() {
-        browser.minimize();
-      },
-      async maximize() {
-        browser.isMaximized() ? browser.unmaximize() : browser.maximize();
-        this.innerHTML = await buttons.icons.maximize();
-      },
-      close() {
-        browser.close();
-      },
-    },
-  };
+    };
 
-(async () => {
-  buttons.element.className = 'window-buttons-area';
-  for (let btn of buttons.insert) {
-    buttons.element.innerHTML += `<button class="window-button" id="btn-${btn}">${await buttons.icons[
-      btn
-    ]()}</button>`;
-    setTimeout(
-      () =>
-        (document.querySelector(`.window-button#btn-${btn}`).onclick =
-          buttons.actions[btn]),
-      10
-    );
-  }
-  if (settings.frameless && !is_mac) {
-    setInterval(async () => {
-      const icon = (await buttons.icons.maximize()).toString(),
-        el = buttons.element.querySelector('#btn-maximize');
-      if (el.innerHTML != icon) el.innerHTML = icon;
-    }, 100);
-  }
-})();
+  (async () => {
+    buttons.element.className = 'window-buttons-area';
+    for (let btn of buttons.insert) {
+      buttons.element.innerHTML += `<button class="window-button" id="btn-${btn}">${await buttons.icons[
+        btn
+      ]()}</button>`;
+      setTimeout(
+        () =>
+          (document.querySelector(`.window-button#btn-${btn}`).onclick =
+            buttons.actions[btn]),
+        10
+      );
+    }
+    if (store().frameless && !is_mac) {
+      setInterval(async () => {
+        const icon = (await buttons.icons.maximize()).toString(),
+          el = buttons.element.querySelector('#btn-maximize');
+        if (el.innerHTML != icon) el.innerHTML = icon;
+      }, 100);
+    }
+  })();
 
-module.exports = buttons;
+  return buttons;
+};
diff --git a/repo/core/client.js b/repo/core/client.js
index 1c535e9..85b3cfe 100644
--- a/repo/core/client.js
+++ b/repo/core/client.js
@@ -7,125 +7,123 @@
 
 'use strict';
 
-module.exports = (defaults) =>
-  function (store, __exports) {
-    const electron = require('electron'),
-      settings = store(defaults),
-      helpers = require('../../pkg/helpers.js'),
-      __notion = helpers.getNotion(),
-      notionIpc = require(`${__notion.replace(
-        /\\/g,
-        '/'
-      )}/app/helpers/notionIpc.js`);
+module.exports = (store, __exports) => {
+  const electron = require('electron'),
+    helpers = require('../../pkg/helpers.js'),
+    __notion = helpers.getNotion(),
+    notionIpc = require(`${__notion.replace(
+      /\\/g,
+      '/'
+    )}/app/helpers/notionIpc.js`);
 
-    // additional hotkeys
-    document.defaultView.addEventListener('keyup', (event) => {
-      if (event.code === 'F5') window.reload();
-      if (event.key === 'e' && (event.ctrlKey || event.metaKey))
-        electron.ipcRenderer.send('enhancer:open-extension-menu');
-    });
+  // additional hotkeys
+  document.defaultView.addEventListener('keyup', (event) => {
+    if (event.code === 'F5') window.reload();
+    if (event.key === 'e' && (event.ctrlKey || event.metaKey))
+      electron.ipcRenderer.send('enhancer:open-extension-menu');
+  });
 
-    const attempt_interval = setInterval(enhance, 500);
-    async function enhance() {
-      if (!document.querySelector('.notion-frame')) return;
-      clearInterval(attempt_interval);
+  const attempt_interval = setInterval(enhance, 500);
+  async function enhance() {
+    if (!document.querySelector('.notion-frame')) return;
+    clearInterval(attempt_interval);
 
-      // scrollbars
-      if (settings.smooth_scrollbars) {
-        document.body.classList.add('smooth-scrollbars');
-        // interval_attempts.patchScrollbars = setInterval(patchScrollbars, 100);
-        // function patchScrollbars() {
-        //   const sidebar = document.querySelector(
-        //     '.notion-scroller.vertical[style*="overflow: hidden auto;"]'
-        //   );
-        //   if (!sidebar) return;
-        //   clearInterval(interval_attempts.patchScrollbars);
-        //   sidebar.style.overflow = '';
-        //   setTimeout(() => {
-        //     sidebar.style.overflow = 'hidden auto';
-        //   }, 10);
-        // }
-      }
+    // scrollbars
+    if (store().smooth_scrollbars) {
+      document.body.classList.add('smooth-scrollbars');
+      // interval_attempts.patchScrollbars = setInterval(patchScrollbars, 100);
+      // function patchScrollbars() {
+      //   const sidebar = document.querySelector(
+      //     '.notion-scroller.vertical[style*="overflow: hidden auto;"]'
+      //   );
+      //   if (!sidebar) return;
+      //   clearInterval(interval_attempts.patchScrollbars);
+      //   sidebar.style.overflow = '';
+      //   setTimeout(() => {
+      //     sidebar.style.overflow = 'hidden auto';
+      //   }, 10);
+      // }
+    }
 
-      // frameless
-      if (settings.frameless) {
-        document.body.classList.add('frameless');
-        // draggable area
-        const dragarea = document.createElement('div');
-        dragarea.className = 'window-dragarea';
-        document.querySelector('.notion-topbar').prepend(dragarea);
-        document.documentElement.style.setProperty(
-          '--configured-dragarea_height',
-          `${settings.dragarea_height + 2}px`
-        );
-      }
+    // frameless
+    if (store().frameless) {
+      document.body.classList.add('frameless');
+      // draggable area
+      const dragarea = document.createElement('div');
+      dragarea.className = 'window-dragarea';
+      document.querySelector('.notion-topbar').prepend(dragarea);
+      document.documentElement.style.setProperty(
+        '--configured-dragarea_height',
+        `${store().dragarea_height + 2}px`
+      );
+    }
 
-      // window buttons
-      const buttons = require('./buttons.js');
-      document
-        .querySelector('.notion-topbar > div[style*="display: flex"]')
-        .appendChild(buttons.element);
-      document
-        .querySelector('.notion-history-back-button')
-        .parentElement.nextElementSibling.classList.add(
-          'notion-topbar-breadcrumb'
-        );
-      document
-        .querySelector('.notion-topbar-share-menu')
-        .parentElement.classList.add('notion-topbar-actions');
+    // window buttons
+    const buttons = require('./buttons.js')(store);
+    document
+      .querySelector('.notion-topbar > div[style*="display: flex"]')
+      .appendChild(buttons.element);
+    document
+      .querySelector('.notion-history-back-button')
+      .parentElement.nextElementSibling.classList.add(
+        'notion-topbar-breadcrumb'
+      );
+    document
+      .querySelector('.notion-topbar-share-menu')
+      .parentElement.classList.add('notion-topbar-actions');
 
-      let sidebar_width;
-      function communicationLoop() {
-        const getStyle = (prop) =>
-            getComputedStyle(document.body).getPropertyValue(prop),
-          mode = JSON.parse(localStorage.theme).mode;
+    let sidebar_width;
+    function communicationLoop() {
+      const getStyle = (prop) =>
+          getComputedStyle(document.body).getPropertyValue(prop),
+        mode = JSON.parse(localStorage.theme).mode;
 
-        // ctrl+f theming
-        notionIpc.sendNotionToIndex('search:set-theme', {
-          'mode': mode,
-          'colors': {
-            'white': getStyle(`--theme_${mode}--todo_ticked-fill`),
-            'blue': getStyle(`--theme_${mode}--primary`),
-          },
-          'borderRadius': 3,
-          'textColor': getStyle(`--theme_${mode}--text`),
-          'popoverBackgroundColor': getStyle(`--theme_${mode}--card`),
-          'popoverBoxShadow': `0 0 0 1px ${getStyle(
-            `--theme_${mode}--overlay`
-          )}, 0 3px 6px ${getStyle(`--theme_${mode}--overlay`)}`,
-          'inputBoxShadow': `box-shadow: ${getStyle(
-            `--theme_${mode}--primary`
-          )} 0px 0px 0px 1px inset, ${getStyle(
-            `--theme_${mode}--primary_hover`
-          )} 0px 0px 0px 2px !important`,
-          'inputBackgroundColor': getStyle(`--theme_${mode}--main`),
-          'dividerColor': getStyle(`--theme_${mode}--table-border`),
-          'shadowOpacity': 0.2,
-        });
+      // ctrl+f theming
+      notionIpc.sendNotionToIndex('search:set-theme', {
+        'mode': mode,
+        'colors': {
+          'white': getStyle(`--theme_${mode}--todo_ticked-fill`),
+          'blue': getStyle(`--theme_${mode}--primary`),
+        },
+        'borderRadius': 3,
+        'textColor': getStyle(`--theme_${mode}--text`),
+        'popoverBackgroundColor': getStyle(`--theme_${mode}--card`),
+        'popoverBoxShadow': `0 0 0 1px ${getStyle(
+          `--theme_${mode}--overlay`
+        )}, 0 3px 6px ${getStyle(`--theme_${mode}--overlay`)}`,
+        'inputBoxShadow': `box-shadow: ${getStyle(
+          `--theme_${mode}--primary`
+        )} 0px 0px 0px 1px inset, ${getStyle(
+          `--theme_${mode}--primary_hover`
+        )} 0px 0px 0px 2px !important`,
+        'inputBackgroundColor': getStyle(`--theme_${mode}--main`),
+        'dividerColor': getStyle(`--theme_${mode}--table-border`),
+        'shadowOpacity': 0.2,
+      });
 
-        // enhancer menu
-        electron.ipcRenderer.send('enhancer:set-theme', {
-          mode,
-          rules: require('./css/variables.json').map((rule) => [
-            rule,
-            getStyle(rule),
-          ]),
-        });
+      // enhancer menu
+      electron.ipcRenderer.send('enhancer:set-theme', {
+        mode,
+        rules: require('./css/variables.json').map((rule) => [
+          rule,
+          getStyle(rule),
+        ]),
+      });
 
-        // draggable area resizing
-        const sidebar = document.querySelector('.notion-sidebar');
-        if (settings.frameless && sidebar) {
-          let new_sidebar_width =
-            sidebar.style.height === 'auto' ? '0px' : sidebar.style.width;
-          if (sidebar_width !== new_sidebar_width) {
-            sidebar_width = new_sidebar_width;
-            electron.ipcRenderer.sendToHost(
-              'enhancer:sidebar-width',
-              sidebar_width
-            );
-          }
+      // draggable area resizing
+      const sidebar = document.querySelector('.notion-sidebar');
+      if (store().frameless && sidebar) {
+        let new_sidebar_width =
+          sidebar.style.height === 'auto' ? '0px' : sidebar.style.width;
+        if (sidebar_width !== new_sidebar_width) {
+          sidebar_width = new_sidebar_width;
+          electron.ipcRenderer.sendToHost(
+            'enhancer:sidebar-width',
+            sidebar_width
+          );
         }
       }
-      setInterval(communicationLoop, 500);
     }
-  };
+    setInterval(communicationLoop, 500);
+  }
+};
diff --git a/repo/core/create.js b/repo/core/create.js
index 4a2a1e9..1343670 100644
--- a/repo/core/create.js
+++ b/repo/core/create.js
@@ -7,84 +7,78 @@
 
 'use strict';
 
-module.exports = (defaults) =>
-  function (store, __exports) {
-    const electron = require('electron'),
-      allWindows = () =>
-        electron.BrowserWindow.getAllWindows().filter(
-          (win) => win.getTitle() !== 'notion-enhancer menu'
-        ),
-      // createWindow = __exports.createWindow,
-      path = require('path'),
-      settings = store(defaults),
-      helpers = require('../../pkg/helpers.js'),
-      __notion = helpers.getNotion();
+module.exports = (store, __exports) => {
+  const electron = require('electron'),
+    allWindows = () =>
+      electron.BrowserWindow.getAllWindows().filter(
+        (win) => win.getTitle() !== 'notion-enhancer menu'
+      ),
+    // createWindow = __exports.createWindow,
+    path = require('path'),
+    helpers = require('../../pkg/helpers.js'),
+    __notion = helpers.getNotion();
 
-    __exports.createWindow = function (relativeUrl) {
-      if (!relativeUrl) relativeUrl = '';
-      const window_state = require(`${__notion.replace(
-          /\\/g,
-          '/'
-        )}/app/node_modules/electron-window-state/index.js`)({
-          defaultWidth: 1320,
-          defaultHeight: 860,
-        }),
-        rect = {
-          x: window_state.x,
-          y: window_state.y,
-          width: window_state.width,
-          height: window_state.height,
-        },
-        focused_window = electron.BrowserWindow.getFocusedWindow();
-      if (focused_window && !focused_window.isMaximized()) {
-        rect.x = focused_window.getPosition()[0] + 20;
-        rect.y = focused_window.getPosition()[1] + 20;
-        rect.width = focused_window.getSize()[0];
-        rect.height = focused_window.getSize()[1];
+  __exports.createWindow = function (relativeUrl) {
+    if (!relativeUrl) relativeUrl = '';
+    const window_state = require(`${__notion.replace(
+        /\\/g,
+        '/'
+      )}/app/node_modules/electron-window-state/index.js`)({
+        defaultWidth: 1320,
+        defaultHeight: 860,
+      }),
+      rect = {
+        x: window_state.x,
+        y: window_state.y,
+        width: window_state.width,
+        height: window_state.height,
+      },
+      focused_window = electron.BrowserWindow.getFocusedWindow();
+    if (focused_window && !focused_window.isMaximized()) {
+      rect.x = focused_window.getPosition()[0] + 20;
+      rect.y = focused_window.getPosition()[1] + 20;
+      rect.width = focused_window.getSize()[0];
+      rect.height = focused_window.getSize()[1];
+    }
+    const window = new electron.BrowserWindow({
+      show: false,
+      backgroundColor: '#ffffff',
+      titleBarStyle: 'hiddenInset',
+      frame: !store().frameless,
+      webPreferences: {
+        preload: path.resolve(`${__notion}/app/renderer/index.js`),
+        webviewTag: true,
+        session: electron.session.fromPartition('persist:notion'),
+      },
+      ...rect,
+    });
+    window.once('ready-to-show', function () {
+      if (
+        !store().openhidden ||
+        allWindows().some((win) => win.isVisible() && win.id != window.id)
+      ) {
+        window.show();
+        window.focus();
+        if (store().maximized) window.maximize();
+        if (
+          (focused_window && focused_window.isFullScreen()) ||
+          window_state.isFullScreen
+        )
+          window.setFullScreen(true);
       }
-      const window = new electron.BrowserWindow({
-        show: false,
-        backgroundColor: '#ffffff',
-        titleBarStyle: 'hiddenInset',
-        frame: !settings.frameless,
-        webPreferences: {
-          preload: path.resolve(`${__notion}/app/renderer/index.js`),
-          webviewTag: true,
-          session: electron.session.fromPartition('persist:notion'),
-        },
-        ...rect,
-      });
-      window.once('ready-to-show', function () {
-        if (
-          !settings.openhidden ||
-          allWindows().some((win) => win.isVisible() && win.id != window.id)
-        ) {
-          window.show();
-          window.focus();
-          if (settings.maximized) window.maximize();
-          if (
-            (focused_window && focused_window.isFullScreen()) ||
-            window_state.isFullScreen
-          )
-            window.setFullScreen(true);
-        }
-      });
-      let intended_quit = false;
-      window.on('close', (e) => {
-        if (
-          intended_quit ||
-          !settings.close_to_tray ||
-          allWindows().length > 1
-        ) {
-          window_state.saveState(window);
-          window = null;
-        } else {
-          e.preventDefault();
-          window.hide();
-        }
-      });
-      electron.app.on('before-quit', () => (intended_quit = true));
-      window.loadURL(__exports.getIndexUrl(relativeUrl));
-      return window;
-    };
+    });
+    let intended_quit = false;
+    window.on('close', (e) => {
+      if (intended_quit || !store().close_to_tray || allWindows().length > 1) {
+        window_state.saveState(window);
+        window = null;
+      } else {
+        e.preventDefault();
+        window.hide();
+      }
+    });
+    electron.app.on('before-quit', () => (intended_quit = true));
+    window.loadURL(__exports.getIndexUrl(relativeUrl));
+    return window;
   };
+};
diff --git a/repo/core/css/menu.css b/repo/core/css/menu.css
index 019b43d..58ee971 100644
--- a/repo/core/css/menu.css
+++ b/repo/core/css/menu.css
@@ -60,7 +60,6 @@ main {
 }
 main section {
   border-radius: 2px;
-  padding: 0.75em;
   margin-bottom: 0.75em;
 }
 
@@ -118,6 +117,7 @@ s {
 
 #alerts [role='alert'] {
   display: flex;
+  padding: 0.75em;
 }
 #alerts [role='alert']::before {
   content: '!';
@@ -180,6 +180,12 @@ s {
   background: var(--theme_local--sidebar);
   border: 1px solid var(--theme_local--table-border);
 }
+#modules section > div {
+  padding: 0.75em;
+}
+.notion-light-theme #modules section {
+  background: var(--theme_local--main);
+}
 
 #modules section h3,
 #modules section p {
@@ -187,6 +193,13 @@ s {
   font-size: 1rem;
 }
 
+/* #modules section .meta .toggle input + label .switch:before {
+  background: linear-gradient(
+    90deg,
+    var(--theme_local--text_green),
+    var(--theme_local--bg_green)
+  );
+} */
 #modules section .desc {
   margin: 0.3em 0 0.4em 0;
   font-size: 0.9em;
@@ -236,19 +249,51 @@ s {
 
 /* module options */
 
+#modules .disabled .options {
+  display: none;
+}
+#modules section .options {
+  border-top: 1px solid var(--theme_local--table-border);
+  background: var(--theme_local--card);
+}
+#modules section .options p {
+  font-size: 0.9em;
+}
+#modules section .options p:not(:last-child) {
+  padding-bottom: 0.5em;
+  border-bottom: 0.5px solid var(--theme_local--table-border);
+  margin-bottom: 0.5em;
+}
+
+select {
+  width: 100%;
+  margin: 0.25em 0;
+  font-size: 0.9rem;
+  padding: 0.4rem 0.2rem;
+  border: none;
+  color: var(--theme_local--text);
+  background: var(--theme_local--main);
+}
+
 .toggle * {
   cursor: pointer;
 }
 .toggle input {
   display: none;
 }
+.toggle input + label {
+  display: flex;
+}
+.toggle input + label .name {
+  flex-basis: calc(100% - 2.25em);
+}
 .toggle input + label .switch {
   position: relative;
   margin-top: 0.5em;
   float: right;
   height: 0.65em;
   width: 2em;
-  background: var(--theme_local--card);
+  background: var(--theme_local--main);
   border-radius: 5px;
 }
 .toggle input + label .switch:before {
diff --git a/repo/core/menu.js b/repo/core/menu.js
index 4ad2b65..f5cea1f 100644
--- a/repo/core/menu.js
+++ b/repo/core/menu.js
@@ -6,14 +6,13 @@
 
 'use strict';
 
-const __mod = require('./mod.js'),
-  store = require('../../pkg/store.js'),
+const store = require('../../pkg/store.js'),
   helpers = require('../../pkg/helpers.js'),
   electron = require('electron'),
   browser = electron.remote.getCurrentWindow();
 
 window['__start'] = async () => {
-  const buttons = require('./buttons.js');
+  const buttons = require('./buttons.js')(() => ({ frameless: true }));
   document.querySelector('#menu-titlebar').appendChild(buttons.element);
 
   document.defaultView.addEventListener('keyup', (event) => {
@@ -61,10 +60,11 @@ window['__start'] = async () => {
   )
     .then((res) => res.json())
     .then((res) => {
-      const version = {
-        local: __mod.version.split(/[~-]/g)[0],
-        repo: res.tag_name.slice(1),
-      };
+      const raw_v = require('./mod.js').version,
+        version = {
+          local: raw_v.split(/[~-]/g)[0],
+          repo: res.tag_name.slice(1),
+        };
       if (version.local == version.repo) return;
       // compare func from https://github.com/substack/semver-compare
       version.sorted = [version.local, version.repo].sort((a, b) => {
@@ -87,7 +87,7 @@ window['__start'] = async () => {
              run <code>npm i -g notion-enhancer</code><br>
              (or <code>yarn global add notion-enhancer</code>),<br>
              <u>and</u> <code>notion-enhancer apply</code>.`
-          : `local build <b>v${__mod.version}</b> is unstable.`
+          : `local build <b>v${raw_v}</b> is unstable.`
       ).prepend();
     });
 
@@ -108,13 +108,12 @@ window['__start'] = async () => {
     ).append();
   }
 
-  // mod options
+  // mod info + options
   function markdown(string) {
     const parsed = string
       .split('\n')
       .map((line) =>
         line
-          // todo: stop e.g. whole chunk of ~~thin~~g~~ being selected
           .trim()
           .replace(/\s+/g, ' ')
           // > quote
@@ -148,56 +147,121 @@ window['__start'] = async () => {
       .join('');
     return parsed;
   }
+
+  let modified_notice;
+  function modified() {
+    if (modified_notice) return;
+    modified_notice = createAlert(
+      'info',
+      `changes may not apply until app restart.`
+    );
+    modified_notice.append();
+  }
+
   const $modules = document.querySelector('#modules');
-  for (let mod of modules.loaded.sort((a, b) => {
-    return a.tags.includes('core') ||
-      store('mods', { [a.id]: { pinned: false } }).pinned
+  for (let mod of modules.loaded.sort((a, b) =>
+    a.tags.includes('core') ||
+    store('mods', { [a.id]: { pinned: false } }).pinned
       ? -1
       : b.tags.includes('core') ||
         store('mods', { [b.id]: { pinned: false } }).pinned
       ? 1
-      : a.name.localeCompare(b.name);
-  })) {
+      : a.name.localeCompare(b.name)
+  )) {
     const menuStore = store('mods', { [mod.id]: { enabled: false } });
-    mod.store = store(mod.id);
     mod.elem = createElement(`
       <section class="${
         mod.tags.includes('core') || menuStore[mod.id].enabled
           ? 'enabled'
           : 'disabled'
       }" id="${mod.id}">
-        <h3 ${
-          mod.tags.includes('core')
-            ? `>${mod.name}`
-            : `class="toggle">
-          <input type="checkbox" id="enable_${mod.id}" ${
-                menuStore[mod.id].enabled ? 'checked' : ''
-              } />
-          <label for="enable_${mod.id}">
-            ${mod.name}
-            <div class="switch">
-              <div class="dot"></div>
-            </div>
-          </label>`
-        }</h3>
-        <p class="tags">${mod.tags
-          .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`))
-          .join(' ')}</p>
-        <div class="desc">${markdown(mod.desc)}</div>
-        <p>
-          <a href="https://github.com/${mod.author}" class="author">
-            <img src="https://github.com/${mod.author}.png" />
-            ${mod.author}
-          </a>
-          <span class="version">v${mod.version}</span>
-        </p>
+        <div class="meta">
+          <h3 ${
+            mod.tags.includes('core')
+              ? `>${mod.name}`
+              : `class="toggle">
+            <input type="checkbox" id="enable_${mod.id}"
+            ${menuStore[mod.id].enabled ? 'checked' : ''} />
+            <label for="enable_${mod.id}">
+              <span class="name">${mod.name}</span>
+              <span class="switch"><span class="dot"></span></span>
+            </label>`
+          }</h3>
+          <p class="tags">${mod.tags
+            .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`))
+            .join(' ')}</p>
+          <div class="desc">${markdown(mod.desc)}</div>
+          <p>
+            <a href="https://github.com/${mod.author}" class="author">
+              <img src="https://github.com/${mod.author}.png" />
+              ${mod.author}
+            </a>
+            <span class="version">v${mod.version}</span>
+          </p>
+        </div>
+        ${
+          mod.options && mod.options.length ? '<div class="options"></div>' : ''
+        }
       </section>
     `);
     const $enable = mod.elem.querySelector(`#enable_${mod.id}`);
     if ($enable)
       $enable.addEventListener('click', (event) => {
         menuStore[mod.id].enabled = $enable.checked;
+        mod.elem.className = menuStore[mod.id].enabled ? 'enabled' : 'disabled';
       });
+    const $options = mod.elem.querySelector('.options');
+    if ($options)
+      for (const opt of mod.options) {
+        let $opt;
+        switch (opt.type) {
+          case 'toggle':
+            $opt = createElement(`
+              <p class="toggle">
+                <input type="checkbox" id="toggle_${mod.id}--${opt.key}"
+                ${store(mod.id)[opt.key] ? 'checked' : ''} />
+                <label for="toggle_${mod.id}--${opt.key}">
+                  <span class="name">${opt.label}</span>
+                  <span class="switch"><span class="dot"></span></span>
+                </label>
+              </p>
+            `);
+            const $opt_checkbox = $opt.querySelector(
+              `#toggle_${mod.id}--${opt.key}`
+            );
+            $opt_checkbox.addEventListener('change', (event) => {
+              store(mod.id)[opt.key] = $opt_checkbox.checked;
+              modified();
+            });
+            $options.appendChild($opt);
+            break;
+          case 'select':
+            $opt = createElement(`
+              <p class="select">
+                <label for="select_${mod.id}--${opt.key}">${opt.label}</label>
+                <select id="select_${mod.id}--${opt.key}">
+                  ${opt.value
+                    .map((val) => `<option value="${val}">${val}</option>`)
+                    .join('')}
+                </select>
+              </p>
+            `);
+            const $opt_select = $opt.querySelector(
+              `#select_${mod.id}--${opt.key}`
+            );
+            $opt_select.value = store(mod.id)[opt.key];
+            $opt_select.addEventListener('change', (event) => {
+              store(mod.id)[opt.key] = $opt_select.value;
+              modified();
+            });
+            $options.appendChild($opt);
+            break;
+          case 'input':
+            break;
+          case 'file':
+            break;
+        }
+      }
     $modules.append(mod.elem);
   }
 };
diff --git a/repo/core/mod.js b/repo/core/mod.js
index 574d4d4..6d02054 100644
--- a/repo/core/mod.js
+++ b/repo/core/mod.js
@@ -6,16 +6,6 @@
 
 'use strict';
 
-const defaults = {
-  openhidden: false,
-  maximized: false,
-  close_to_tray: true,
-  frameless: true,
-  dragarea_height: 15,
-  smooth_scrollbars: true,
-  hotkey: 'CmdOrCtrl+Shift+A',
-};
-
 module.exports = {
   id: '0f0bf8b6-eae6-4273-b307-8fc43f2ee082',
   tags: ['core', 'extension'],
@@ -24,12 +14,54 @@ module.exports = {
     ![](https://preview.redd.it/vtiw9ulqlt951.png?width=1368&format=png&auto=webp&s=733d8b27ec62151c7858b4eca463f809ead6395a)`,
   version: require('../../package.json').version,
   author: 'dragonwocky',
-  options: [],
+  options: [
+    {
+      key: 'openhidden',
+      label: 'hide app on open',
+      type: 'toggle',
+      value: false,
+    },
+    {
+      key: 'maximized',
+      label: 'auto-maximise windows',
+      type: 'toggle',
+      value: false,
+    },
+    {
+      key: 'close_to_tray',
+      label: 'close window to the tray',
+      type: 'toggle',
+      value: true,
+    },
+    {
+      key: 'frameless',
+      label: 'integrate titlebar into notion',
+      type: 'toggle',
+      value: true,
+    },
+    {
+      key: 'dragarea_height',
+      label: 'height of frameless dragarea',
+      type: 'input',
+      value: 15,
+    },
+    {
+      key: 'smooth_scrollbars',
+      label: 'integrate scrollbars into notion',
+      type: 'toggle',
+      value: true,
+    },
+    {
+      key: 'hotkey',
+      label: 'window display hotkey',
+      type: 'input',
+      value: 'CmdOrCtrl+Shift+A',
+    },
+  ],
   hacks: {
-    'main/main.js': require('./tray.js')(defaults),
-    'main/createWindow.js': require('./create.js')(defaults),
-    'renderer/index.js': require('./render.js')(defaults),
-    'renderer/preload.js': require('./client.js')(defaults),
+    'main/main.js': require('./tray.js'),
+    'main/createWindow.js': require('./create.js'),
+    'renderer/index.js': require('./render.js'),
+    'renderer/preload.js': require('./client.js'),
   },
-  defaults,
 };
diff --git a/repo/core/render.js b/repo/core/render.js
index d77e17a..f9eb7f8 100644
--- a/repo/core/render.js
+++ b/repo/core/render.js
@@ -6,28 +6,28 @@
 
 'use strict';
 
-module.exports = (defaults) =>
-  function (store, __exports) {
-    const __start = window['__start'],
-      settings = store(defaults);
+module.exports = (store, __exports) => {
+  const __start = window['__start'];
 
-    window['__start'] = function () {
-      __start();
-      const dragarea = document.querySelector(
-          '#root [style*="-webkit-app-region: drag"]'
-        ),
-        default_styles = dragarea.getAttribute('style');
+  window['__start'] = function () {
+    __start();
+    const dragarea = document.querySelector(
+        '#root [style*="-webkit-app-region: drag"]'
+      ),
+      default_styles = dragarea.getAttribute('style');
 
-      // document.body.innerText = document.body.innerHTML;
+    // document.body.innerText = document.body.innerHTML;
 
-      document
-        .getElementById('notion')
-        .addEventListener('ipc-message', (event) => {
-          if (event.channel !== 'enhancer:sidebar-width') return;
-          dragarea.setAttribute(
-            'style',
-            `${default_styles} top: 2px; height: ${settings.dragarea_height}px; left: ${event.args[0]};`
-          );
-        });
-    };
+    document
+      .getElementById('notion')
+      .addEventListener('ipc-message', (event) => {
+        if (event.channel !== 'enhancer:sidebar-width') return;
+        dragarea.setAttribute(
+          'style',
+          `${default_styles} top: 2px; height: ${
+            store().dragarea_height
+          }px; left: ${event.args[0]};`
+        );
+      });
   };
+};
diff --git a/repo/core/tray.js b/repo/core/tray.js
index 91c8a48..cc7ddcf 100644
--- a/repo/core/tray.js
+++ b/repo/core/tray.js
@@ -9,176 +9,174 @@
 
 let tray, enhancer_menu;
 
-module.exports = (defaults) =>
-  function (store, __exports) {
-    const electron = require('electron'),
-      path = require('path'),
-      is_mac = process.platform === 'darwin',
-      is_win = process.platform === 'win32',
-      settings = store(defaults),
-      helpers = require('../../pkg/helpers.js'),
-      __notion = helpers.getNotion();
+module.exports = (store, __exports) => {
+  const electron = require('electron'),
+    path = require('path'),
+    is_mac = process.platform === 'darwin',
+    is_win = process.platform === 'win32',
+    helpers = require('../../pkg/helpers.js'),
+    __notion = helpers.getNotion();
 
-    electron.app.on('ready', () => {
-      tray = new electron.Tray(
-        is_win
-          ? path.resolve(`${__dirname}/icons/windows.ico`)
-          : new electron.nativeImage.createFromPath(
-              path.resolve(`${__dirname}/icons/mac+linux.png`)
-            ).resize({
-              width: 16,
-              height: 16,
-            })
-      );
+  electron.app.on('ready', () => {
+    tray = new electron.Tray(
+      is_win
+        ? path.resolve(`${__dirname}/icons/windows.ico`)
+        : new electron.nativeImage.createFromPath(
+            path.resolve(`${__dirname}/icons/mac+linux.png`)
+          ).resize({
+            width: 16,
+            height: 16,
+          })
+    );
 
-      electron.ipcMain.on('enhancer:set-theme', (event, arg) => {
-        if (!enhancer_menu) return;
-        enhancer_menu.webContents.send('enhancer:set-theme', arg);
+    electron.ipcMain.on('enhancer:set-theme', (event, arg) => {
+      if (!enhancer_menu) return;
+      enhancer_menu.webContents.send('enhancer:set-theme', arg);
+    });
+    electron.ipcMain.on('enhancer:open-extension-menu', openExtensionMenu);
+
+    function calculateWindowPos(width, height) {
+      const screen = electron.screen.getDisplayNearestPoint({
+        x: tray.getBounds().x,
+        y: tray.getBounds().y,
       });
-      electron.ipcMain.on('enhancer:open-extension-menu', openExtensionMenu);
-
-      function calculateWindowPos(width, height) {
-        const screen = electron.screen.getDisplayNearestPoint({
-          x: tray.getBounds().x,
-          y: tray.getBounds().y,
-        });
-        // left
-        if (screen.workArea.x > 0)
-          return {
-            x: screen.workArea.x,
-            y: screen.workArea.height - height,
-          };
-        // top
-        if (screen.workArea.y > 0)
-          return {
-            x: Math.round(
-              tray.getBounds().x + tray.getBounds().width / 2 - width / 2
-            ),
-            y: screen.workArea.y,
-          };
-        // right
-        if (screen.workArea.width < screen.bounds.width)
-          return {
-            x: screen.workArea.width - width,
-            y: screen.bounds.height - height,
-          };
-        // bottom
+      // left
+      if (screen.workArea.x > 0)
+        return {
+          x: screen.workArea.x,
+          y: screen.workArea.height - height,
+        };
+      // top
+      if (screen.workArea.y > 0)
         return {
           x: Math.round(
             tray.getBounds().x + tray.getBounds().width / 2 - width / 2
           ),
-          y: screen.workArea.height - height,
+          y: screen.workArea.y,
         };
-      }
+      // right
+      if (screen.workArea.width < screen.bounds.width)
+        return {
+          x: screen.workArea.width - width,
+          y: screen.bounds.height - height,
+        };
+      // bottom
+      return {
+        x: Math.round(
+          tray.getBounds().x + tray.getBounds().width / 2 - width / 2
+        ),
+        y: screen.workArea.height - height,
+      };
+    }
 
-      function openExtensionMenu() {
-        if (enhancer_menu) return enhancer_menu.show();
-        const window_state = require(`${__notion.replace(
-          /\\/g,
-          '/'
-        )}/app/node_modules/electron-window-state/index.js`)({
-          file: 'menu-windowstate.json',
-          path: helpers.data_folder,
-          defaultWidth: 275,
-          defaultHeight: 600,
-        });
-        electron.shell.openExternal(JSON.stringify(window_state));
-        enhancer_menu = new electron.BrowserWindow({
-          show: true,
-          frame: false,
-          titleBarStyle: 'hiddenInset',
-          x:
-            window_state.x ||
-            calculateWindowPos(window_state.width, window_state.height).x,
-          y:
-            window_state.y ||
-            calculateWindowPos(window_state.width, window_state.height).y,
-          width: window_state.width,
-          height: window_state.height,
-          webPreferences: {
-            preload: path.resolve(`${__dirname}/menu.js`),
-            nodeIntegration: true,
-            session: electron.session.fromPartition('persist:notion'),
-          },
-        });
-        enhancer_menu.loadURL('enhancement://core/menu.html');
-        enhancer_menu.on('close', (e) => {
-          window_state.saveState(enhancer_menu);
-          enhancer_menu = null;
-        });
-      }
-
-      const contextMenu = electron.Menu.buildFromTemplate([
-        {
-          type: 'normal',
-          label: 'Bug Report',
-          click: () => {
-            electron.shell.openExternal(
-              'https://github.com/dragonwocky/notion-enhancer/issues/new?labels=bug&template=bug-report.md'
-            );
-          },
-        },
-        {
-          type: 'normal',
-          label: 'Feature Request',
-          click: () => {
-            electron.shell.openExternal(
-              'https://github.com/dragonwocky/notion-enhancer/issues/new?labels=enhancement&template=feature-request.md'
-            );
-          },
-        },
-        {
-          type: 'separator',
-        },
-        {
-          type: 'normal',
-          label: 'Docs',
-          click: () => {
-            electron.shell.openExternal(
-              'https://github.com/dragonwocky/notion-enhancer/tree/js'
-            );
-          },
-        },
-        {
-          type: 'normal',
-          label: 'Enhancements',
-          accelerator: 'CommandOrControl+E',
-          click: openExtensionMenu,
-        },
-        {
-          type: 'separator',
-        },
-        {
-          label: 'Quit',
-          role: 'quit',
-        },
-      ]);
-      tray.setContextMenu(contextMenu);
-      tray.setToolTip('Notion');
-
-      function showWindows() {
-        const windows = electron.BrowserWindow.getAllWindows();
-        if (is_mac) electron.app.show();
-        if (settings.maximized) windows.forEach((win) => [win.maximize()]);
-        else windows.forEach((win) => win.show());
-        electron.app.focus({ steal: true });
-      }
-      function hideWindows() {
-        const windows = electron.BrowserWindow.getAllWindows();
-        windows.forEach((win) => [win.isFocused() && win.blur(), win.hide()]);
-        if (is_mac) electron.app.hide();
-      }
-
-      tray.on('click', () => {
-        const windows = electron.BrowserWindow.getAllWindows();
-        if (windows.some((win) => win.isVisible())) hideWindows();
-        else showWindows();
+    function openExtensionMenu() {
+      if (enhancer_menu) return enhancer_menu.show();
+      const window_state = require(`${__notion.replace(
+        /\\/g,
+        '/'
+      )}/app/node_modules/electron-window-state/index.js`)({
+        file: 'menu-windowstate.json',
+        path: helpers.data_folder,
+        defaultWidth: 275,
+        defaultHeight: 600,
       });
-      electron.globalShortcut.register(settings.hotkey, () => {
-        const windows = electron.BrowserWindow.getAllWindows();
-        if (windows.some((win) => win.isFocused() && win.isVisible()))
-          hideWindows();
-        else showWindows();
+      electron.shell.openExternal(JSON.stringify(window_state));
+      enhancer_menu = new electron.BrowserWindow({
+        show: true,
+        frame: false,
+        titleBarStyle: 'hiddenInset',
+        x:
+          window_state.x ||
+          calculateWindowPos(window_state.width, window_state.height).x,
+        y:
+          window_state.y ||
+          calculateWindowPos(window_state.width, window_state.height).y,
+        width: window_state.width,
+        height: window_state.height,
+        webPreferences: {
+          preload: path.resolve(`${__dirname}/menu.js`),
+          nodeIntegration: true,
+          session: electron.session.fromPartition('persist:notion'),
+        },
       });
+      enhancer_menu.loadURL('enhancement://core/menu.html');
+      enhancer_menu.on('close', (e) => {
+        window_state.saveState(enhancer_menu);
+        enhancer_menu = null;
+      });
+    }
+
+    const contextMenu = electron.Menu.buildFromTemplate([
+      {
+        type: 'normal',
+        label: 'Bug Report',
+        click: () => {
+          electron.shell.openExternal(
+            'https://github.com/dragonwocky/notion-enhancer/issues/new?labels=bug&template=bug-report.md'
+          );
+        },
+      },
+      {
+        type: 'normal',
+        label: 'Feature Request',
+        click: () => {
+          electron.shell.openExternal(
+            'https://github.com/dragonwocky/notion-enhancer/issues/new?labels=enhancement&template=feature-request.md'
+          );
+        },
+      },
+      {
+        type: 'separator',
+      },
+      {
+        type: 'normal',
+        label: 'Docs',
+        click: () => {
+          electron.shell.openExternal(
+            'https://github.com/dragonwocky/notion-enhancer/tree/js'
+          );
+        },
+      },
+      {
+        type: 'normal',
+        label: 'Enhancements',
+        accelerator: 'CommandOrControl+E',
+        click: openExtensionMenu,
+      },
+      {
+        type: 'separator',
+      },
+      {
+        label: 'Quit',
+        role: 'quit',
+      },
+    ]);
+    tray.setContextMenu(contextMenu);
+    tray.setToolTip('Notion');
+
+    function showWindows() {
+      const windows = electron.BrowserWindow.getAllWindows();
+      if (is_mac) electron.app.show();
+      if (store().maximized) windows.forEach((win) => [win.maximize()]);
+      else windows.forEach((win) => win.show());
+      electron.app.focus({ steal: true });
+    }
+    function hideWindows() {
+      const windows = electron.BrowserWindow.getAllWindows();
+      windows.forEach((win) => [win.isFocused() && win.blur(), win.hide()]);
+      if (is_mac) electron.app.hide();
+    }
+
+    tray.on('click', () => {
+      const windows = electron.BrowserWindow.getAllWindows();
+      if (windows.some((win) => win.isVisible())) hideWindows();
+      else showWindows();
     });
-  };
+    electron.globalShortcut.register(store().hotkey, () => {
+      const windows = electron.BrowserWindow.getAllWindows();
+      if (windows.some((win) => win.isFocused() && win.isVisible()))
+        hideWindows();
+      else showWindows();
+    });
+  });
+};