From c37877c6dae6eb2bdcfd2e0ca1c15ab7110cef9b Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 30 Dec 2022 15:19:47 +1100 Subject: [PATCH] feat: load api/styles into menu, chore: separate mod types into folders --- scripts/enhance-desktop-app.mjs | 2 +- scripts/generate-theme-css.mjs | 25 +- scripts/vendor-dependencies.mjs | 2 +- src/{browser/api.mjs => api/browser.js} | 7 +- .../components/corner-action.css | 0 .../components/corner-action.mjs | 0 src/{common => api}/components/panel.css | 0 src/{common => api}/components/panel.mjs | 0 src/{common => api}/components/tooltip.css | 0 src/{common => api}/components/tooltip.mjs | 0 src/{electron/api.cjs => api/electron.cjs} | 30 +- src/api/events.js | 115 + src/api/interface.js | 153 ++ src/{common/api.js => api/mods.js} | 51 +- src/{common/notion.mjs => api/notion.js} | 0 src/browser/init.mjs | 12 - src/browser/worker.mjs | 47 - src/common/dom.mjs | 99 - src/common/events.mjs | 156 -- src/common/loader.mjs | 39 - src/common/utils.js | 116 - src/core/client.css | 59 - src/core/client.mjs | 114 +- src/core/menu.html | 13 - src/core/menu.mjs | 0 src/core/menu/index.html | 28 + src/core/menu/menu.css | 15 +- src/core/menu/menu.mjs | 471 +--- src/core/{menu => menuu}/client.css | 0 src/core/{menu => menuu}/client.mjs | 0 src/core/{menu => menuu}/components.mjs | 0 src/core/{menu => menuu}/markdown.css | 0 src/core/{ => menuu}/menu.css | 8 +- src/core/{menu => menuu}/menu.html | 0 src/core/menuu/menu.mjs | 448 ++++ src/core/{menu => menuu}/mod.json | 0 src/core/{menu => menuu}/notifications.mjs | 0 src/core/{menu => menuu}/router.mjs | 0 src/core/{menu => menuu}/styles.mjs | 0 src/core/mod.json | 5 +- src/core/theme.css | 1997 +---------------- src/core/variables.css | 40 +- src/electron/init.cjs | 43 - .../always-on-top/always-on-top.jpg | Bin .../always-on-top/button.css | 0 .../always-on-top/button.mjs | 0 .../always-on-top/client.mjs | 0 .../always-on-top/menu.mjs | 0 .../always-on-top/mod.json | 0 .../bypass-preview/client.css | 0 .../bypass-preview/client.mjs | 0 .../bypass-preview/mod.json | 0 .../calendar-scroll/calendar-scroll.png | Bin .../calendar-scroll/client.css | 0 .../calendar-scroll/client.mjs | 0 .../calendar-scroll/mod.json | 0 .../code-line-numbers/client.css | 0 .../code-line-numbers/client.mjs | 0 .../code-line-numbers/code-line-numbers.png | Bin .../code-line-numbers/mod.json | 0 .../collapsible-properties/client.css | 0 .../collapsible-properties/client.mjs | 0 .../collapsible-properties.jpg | Bin .../collapsible-properties/mod.json | 0 .../emoji-sets/client.css | 0 .../emoji-sets/client.mjs | 0 .../emoji-sets/emoji-sets.jpg | Bin src/{mods => extensions}/emoji-sets/mod.json | 0 .../focus-mode/client.css | 0 .../focus-mode/client.mjs | 0 src/{mods => extensions}/focus-mode/mod.json | 0 src/{mods => extensions}/focus-mode/tabs.css | 0 src/{mods => extensions}/focus-mode/tabs.mjs | 0 .../font-chooser/fonts.css | 0 .../font-chooser/fonts.mjs | 0 .../font-chooser/mod.json | 0 .../global-block-links/client.css | 0 .../global-block-links/client.mjs | 0 .../global-block-links/global-block-links.jpg | Bin .../global-block-links/mod.json | 0 .../indentation-lines/client.css | 0 .../indentation-lines/client.mjs | 0 .../indentation-lines/indentation-lines.jpg | Bin .../indentation-lines/mod.json | 0 .../integrated-titlebar/buttons.css | 0 .../integrated-titlebar/buttons.mjs | 0 .../integrated-titlebar/client.mjs | 0 .../integrated-titlebar/createWindow.cjs | 0 .../integrated-titlebar/frame.mjs | 0 .../integrated-titlebar.jpg | Bin .../integrated-titlebar/menu.mjs | 0 .../integrated-titlebar/mod.json | 0 src/{mods => extensions}/neutral/app.css | 0 src/{mods => extensions}/neutral/mod.json | 0 src/{mods => extensions}/neutral/neutral.png | Bin .../neutral/variables.css | 0 src/{mods => extensions}/nord/mod.json | 0 src/{mods => extensions}/nord/nord.png | Bin src/{mods => extensions}/nord/variables.css | 0 src/{mods => extensions}/outliner/client.css | 0 src/{mods => extensions}/outliner/client.mjs | 0 src/{mods => extensions}/outliner/mod.json | 0 .../outliner/outliner.png | Bin .../right-to-left/client.css | 0 .../right-to-left/client.mjs | 0 .../right-to-left/mod.json | 0 .../right-to-left/right-to-left.jpg | Bin .../scroll-to-top/client.mjs | 0 .../scroll-to-top/mod.json | 0 .../scroll-to-top/scroll-to-top.png | Bin .../simpler-databases/client.css | 0 .../simpler-databases/client.mjs | 0 .../simpler-databases/mod.json | 0 .../simpler-databases/simpler-databases.jpg | Bin src/{mods => extensions}/tabs/client.mjs | 0 .../tabs/createWindow.cjs | 0 src/{mods => extensions}/tabs/main.cjs | 0 src/{mods => extensions}/tabs/mod.json | 0 .../tabs/rendererIndex.cjs | 0 src/{mods => extensions}/tabs/systemMenu.cjs | 0 src/{mods => extensions}/tabs/tab.cjs | 0 src/{mods => extensions}/tabs/tabs.css | 0 src/{mods => extensions}/tabs/tabs.jpg | Bin .../topbar-icons/client.mjs | 0 .../topbar-icons/mod.json | 0 .../topbar-icons/topbar-icons.jpg | Bin src/{mods => extensions}/tray/client.mjs | 0 .../tray/createWindow.cjs | 0 src/{mods => extensions}/tray/main.cjs | 0 src/{mods => extensions}/tray/mod.json | 0 src/{mods => extensions}/tray/tray.jpg | Bin .../truncated-titles/client.mjs | 0 .../truncated-titles/mod.json | 0 .../truncated-titles/truncated-titles.jpg | Bin .../view-scale/client.css | 0 .../view-scale/client.mjs | 0 src/{mods => extensions}/view-scale/mod.json | 0 .../view-scale/view-scale.jpg | Bin .../weekly-view/client.mjs | 0 src/{mods => extensions}/weekly-view/mod.json | 0 .../weekly-view/weekly-view.jpg | Bin .../word-counter/client.css | 0 .../word-counter/client.mjs | 0 .../word-counter/mod.json | 0 .../word-counter/word-counter.jpg | Bin src/init.js | 56 + .../icon-sets/client.css | 0 .../icon-sets/client.mjs | 0 .../icon-sets/icon-sets.jpg | Bin src/{mods => integrations}/icon-sets/mod.json | 0 .../quick-note/client.mjs | 0 .../quick-note/mod.json | 0 .../quick-note/quick-note.png | Bin src/load.mjs | 46 + src/manifest.json | 30 +- src/mods/registry.json | 1 - src/registry.json | 1 + .../cherry-cola/cherry-cola.png | Bin src/{mods => themes}/cherry-cola/mod.json | 0 .../cherry-cola/variables.css | 0 src/{mods => themes}/dark+/dark+.png | Bin src/{mods => themes}/dark+/mod.json | 0 src/{mods => themes}/dark+/theme.mjs | 0 src/{mods => themes}/dark+/variables.css | 0 src/{mods => themes}/dracula/app.css | 0 src/{mods => themes}/dracula/dracula.png | Bin src/{mods => themes}/dracula/mod.json | 0 src/{mods => themes}/dracula/variables.css | 0 .../gruvbox-dark/gruvbox-dark.png | Bin src/{mods => themes}/gruvbox-dark/mod.json | 0 .../gruvbox-dark/variables.css | 0 .../gruvbox-light/gruvbox-light.png | Bin src/{mods => themes}/gruvbox-light/mod.json | 0 .../gruvbox-light/variables.css | 0 src/{mods => themes}/light+/light+.png | Bin src/{mods => themes}/light+/mod.json | 0 src/{mods => themes}/light+/theme.mjs | 0 src/{mods => themes}/light+/variables.css | 0 .../material-ocean/material-ocean.png | Bin src/{mods => themes}/material-ocean/mod.json | 0 .../material-ocean/variables.css | 0 src/{mods => themes}/pastel-dark/app.css | 0 src/{mods => themes}/pastel-dark/mod.json | 0 .../pastel-dark/pastel-dark.png | Bin .../pastel-dark/variables.css | 0 src/{mods => themes}/pinky-boom/mod.json | 0 .../pinky-boom/pinky-boom.png | Bin src/{mods => themes}/pinky-boom/variables.css | 0 src/{mods => themes}/playful-purple/mod.json | 0 .../playful-purple/playful-purple.png | Bin .../playful-purple/variables.css | 0 src/vendor/htm.min.js | 2 +- src/worker.mjs | 47 + 193 files changed, 1131 insertions(+), 3147 deletions(-) rename src/{browser/api.mjs => api/browser.js} (90%) rename src/{common => api}/components/corner-action.css (100%) rename src/{common => api}/components/corner-action.mjs (100%) rename src/{common => api}/components/panel.css (100%) rename src/{common => api}/components/panel.mjs (100%) rename src/{common => api}/components/tooltip.css (100%) rename src/{common => api}/components/tooltip.mjs (100%) rename src/{electron/api.cjs => api/electron.cjs} (77%) create mode 100644 src/api/events.js create mode 100644 src/api/interface.js rename src/{common/api.js => api/mods.js} (52%) rename src/{common/notion.mjs => api/notion.js} (100%) delete mode 100644 src/browser/init.mjs delete mode 100644 src/browser/worker.mjs delete mode 100644 src/common/dom.mjs delete mode 100644 src/common/events.mjs delete mode 100644 src/common/loader.mjs delete mode 100644 src/common/utils.js delete mode 100644 src/core/menu.html delete mode 100644 src/core/menu.mjs create mode 100644 src/core/menu/index.html rename src/core/{menu => menuu}/client.css (100%) rename src/core/{menu => menuu}/client.mjs (100%) rename src/core/{menu => menuu}/components.mjs (100%) rename src/core/{menu => menuu}/markdown.css (100%) rename src/core/{ => menuu}/menu.css (83%) rename src/core/{menu => menuu}/menu.html (100%) create mode 100644 src/core/menuu/menu.mjs rename src/core/{menu => menuu}/mod.json (100%) rename src/core/{menu => menuu}/notifications.mjs (100%) rename src/core/{menu => menuu}/router.mjs (100%) rename src/core/{menu => menuu}/styles.mjs (100%) delete mode 100644 src/electron/init.cjs rename src/{mods => extensions}/always-on-top/always-on-top.jpg (100%) rename src/{mods => extensions}/always-on-top/button.css (100%) rename src/{mods => extensions}/always-on-top/button.mjs (100%) rename src/{mods => extensions}/always-on-top/client.mjs (100%) rename src/{mods => extensions}/always-on-top/menu.mjs (100%) rename src/{mods => extensions}/always-on-top/mod.json (100%) rename src/{mods => extensions}/bypass-preview/client.css (100%) rename src/{mods => extensions}/bypass-preview/client.mjs (100%) rename src/{mods => extensions}/bypass-preview/mod.json (100%) rename src/{mods => extensions}/calendar-scroll/calendar-scroll.png (100%) rename src/{mods => extensions}/calendar-scroll/client.css (100%) rename src/{mods => extensions}/calendar-scroll/client.mjs (100%) rename src/{mods => extensions}/calendar-scroll/mod.json (100%) rename src/{mods => extensions}/code-line-numbers/client.css (100%) rename src/{mods => extensions}/code-line-numbers/client.mjs (100%) rename src/{mods => extensions}/code-line-numbers/code-line-numbers.png (100%) rename src/{mods => extensions}/code-line-numbers/mod.json (100%) rename src/{mods => extensions}/collapsible-properties/client.css (100%) rename src/{mods => extensions}/collapsible-properties/client.mjs (100%) rename src/{mods => extensions}/collapsible-properties/collapsible-properties.jpg (100%) rename src/{mods => extensions}/collapsible-properties/mod.json (100%) rename src/{mods => extensions}/emoji-sets/client.css (100%) rename src/{mods => extensions}/emoji-sets/client.mjs (100%) rename src/{mods => extensions}/emoji-sets/emoji-sets.jpg (100%) rename src/{mods => extensions}/emoji-sets/mod.json (100%) rename src/{mods => extensions}/focus-mode/client.css (100%) rename src/{mods => extensions}/focus-mode/client.mjs (100%) rename src/{mods => extensions}/focus-mode/mod.json (100%) rename src/{mods => extensions}/focus-mode/tabs.css (100%) rename src/{mods => extensions}/focus-mode/tabs.mjs (100%) rename src/{mods => extensions}/font-chooser/fonts.css (100%) rename src/{mods => extensions}/font-chooser/fonts.mjs (100%) rename src/{mods => extensions}/font-chooser/mod.json (100%) rename src/{mods => extensions}/global-block-links/client.css (100%) rename src/{mods => extensions}/global-block-links/client.mjs (100%) rename src/{mods => extensions}/global-block-links/global-block-links.jpg (100%) rename src/{mods => extensions}/global-block-links/mod.json (100%) rename src/{mods => extensions}/indentation-lines/client.css (100%) rename src/{mods => extensions}/indentation-lines/client.mjs (100%) rename src/{mods => extensions}/indentation-lines/indentation-lines.jpg (100%) rename src/{mods => extensions}/indentation-lines/mod.json (100%) rename src/{mods => extensions}/integrated-titlebar/buttons.css (100%) rename src/{mods => extensions}/integrated-titlebar/buttons.mjs (100%) rename src/{mods => extensions}/integrated-titlebar/client.mjs (100%) rename src/{mods => extensions}/integrated-titlebar/createWindow.cjs (100%) rename src/{mods => extensions}/integrated-titlebar/frame.mjs (100%) rename src/{mods => extensions}/integrated-titlebar/integrated-titlebar.jpg (100%) rename src/{mods => extensions}/integrated-titlebar/menu.mjs (100%) rename src/{mods => extensions}/integrated-titlebar/mod.json (100%) rename src/{mods => extensions}/neutral/app.css (100%) rename src/{mods => extensions}/neutral/mod.json (100%) rename src/{mods => extensions}/neutral/neutral.png (100%) rename src/{mods => extensions}/neutral/variables.css (100%) rename src/{mods => extensions}/nord/mod.json (100%) rename src/{mods => extensions}/nord/nord.png (100%) rename src/{mods => extensions}/nord/variables.css (100%) rename src/{mods => extensions}/outliner/client.css (100%) rename src/{mods => extensions}/outliner/client.mjs (100%) rename src/{mods => extensions}/outliner/mod.json (100%) rename src/{mods => extensions}/outliner/outliner.png (100%) rename src/{mods => extensions}/right-to-left/client.css (100%) rename src/{mods => extensions}/right-to-left/client.mjs (100%) rename src/{mods => extensions}/right-to-left/mod.json (100%) rename src/{mods => extensions}/right-to-left/right-to-left.jpg (100%) rename src/{mods => extensions}/scroll-to-top/client.mjs (100%) rename src/{mods => extensions}/scroll-to-top/mod.json (100%) rename src/{mods => extensions}/scroll-to-top/scroll-to-top.png (100%) rename src/{mods => extensions}/simpler-databases/client.css (100%) rename src/{mods => extensions}/simpler-databases/client.mjs (100%) rename src/{mods => extensions}/simpler-databases/mod.json (100%) rename src/{mods => extensions}/simpler-databases/simpler-databases.jpg (100%) rename src/{mods => extensions}/tabs/client.mjs (100%) rename src/{mods => extensions}/tabs/createWindow.cjs (100%) rename src/{mods => extensions}/tabs/main.cjs (100%) rename src/{mods => extensions}/tabs/mod.json (100%) rename src/{mods => extensions}/tabs/rendererIndex.cjs (100%) rename src/{mods => extensions}/tabs/systemMenu.cjs (100%) rename src/{mods => extensions}/tabs/tab.cjs (100%) rename src/{mods => extensions}/tabs/tabs.css (100%) rename src/{mods => extensions}/tabs/tabs.jpg (100%) rename src/{mods => extensions}/topbar-icons/client.mjs (100%) rename src/{mods => extensions}/topbar-icons/mod.json (100%) rename src/{mods => extensions}/topbar-icons/topbar-icons.jpg (100%) rename src/{mods => extensions}/tray/client.mjs (100%) rename src/{mods => extensions}/tray/createWindow.cjs (100%) rename src/{mods => extensions}/tray/main.cjs (100%) rename src/{mods => extensions}/tray/mod.json (100%) rename src/{mods => extensions}/tray/tray.jpg (100%) rename src/{mods => extensions}/truncated-titles/client.mjs (100%) rename src/{mods => extensions}/truncated-titles/mod.json (100%) rename src/{mods => extensions}/truncated-titles/truncated-titles.jpg (100%) rename src/{mods => extensions}/view-scale/client.css (100%) rename src/{mods => extensions}/view-scale/client.mjs (100%) rename src/{mods => extensions}/view-scale/mod.json (100%) rename src/{mods => extensions}/view-scale/view-scale.jpg (100%) rename src/{mods => extensions}/weekly-view/client.mjs (100%) rename src/{mods => extensions}/weekly-view/mod.json (100%) rename src/{mods => extensions}/weekly-view/weekly-view.jpg (100%) rename src/{mods => extensions}/word-counter/client.css (100%) rename src/{mods => extensions}/word-counter/client.mjs (100%) rename src/{mods => extensions}/word-counter/mod.json (100%) rename src/{mods => extensions}/word-counter/word-counter.jpg (100%) create mode 100644 src/init.js rename src/{mods => integrations}/icon-sets/client.css (100%) rename src/{mods => integrations}/icon-sets/client.mjs (100%) rename src/{mods => integrations}/icon-sets/icon-sets.jpg (100%) rename src/{mods => integrations}/icon-sets/mod.json (100%) rename src/{mods => integrations}/quick-note/client.mjs (100%) rename src/{mods => integrations}/quick-note/mod.json (100%) rename src/{mods => integrations}/quick-note/quick-note.png (100%) create mode 100644 src/load.mjs delete mode 100644 src/mods/registry.json create mode 100644 src/registry.json rename src/{mods => themes}/cherry-cola/cherry-cola.png (100%) rename src/{mods => themes}/cherry-cola/mod.json (100%) rename src/{mods => themes}/cherry-cola/variables.css (100%) rename src/{mods => themes}/dark+/dark+.png (100%) rename src/{mods => themes}/dark+/mod.json (100%) rename src/{mods => themes}/dark+/theme.mjs (100%) rename src/{mods => themes}/dark+/variables.css (100%) rename src/{mods => themes}/dracula/app.css (100%) rename src/{mods => themes}/dracula/dracula.png (100%) rename src/{mods => themes}/dracula/mod.json (100%) rename src/{mods => themes}/dracula/variables.css (100%) rename src/{mods => themes}/gruvbox-dark/gruvbox-dark.png (100%) rename src/{mods => themes}/gruvbox-dark/mod.json (100%) rename src/{mods => themes}/gruvbox-dark/variables.css (100%) rename src/{mods => themes}/gruvbox-light/gruvbox-light.png (100%) rename src/{mods => themes}/gruvbox-light/mod.json (100%) rename src/{mods => themes}/gruvbox-light/variables.css (100%) rename src/{mods => themes}/light+/light+.png (100%) rename src/{mods => themes}/light+/mod.json (100%) rename src/{mods => themes}/light+/theme.mjs (100%) rename src/{mods => themes}/light+/variables.css (100%) rename src/{mods => themes}/material-ocean/material-ocean.png (100%) rename src/{mods => themes}/material-ocean/mod.json (100%) rename src/{mods => themes}/material-ocean/variables.css (100%) rename src/{mods => themes}/pastel-dark/app.css (100%) rename src/{mods => themes}/pastel-dark/mod.json (100%) rename src/{mods => themes}/pastel-dark/pastel-dark.png (100%) rename src/{mods => themes}/pastel-dark/variables.css (100%) rename src/{mods => themes}/pinky-boom/mod.json (100%) rename src/{mods => themes}/pinky-boom/pinky-boom.png (100%) rename src/{mods => themes}/pinky-boom/variables.css (100%) rename src/{mods => themes}/playful-purple/mod.json (100%) rename src/{mods => themes}/playful-purple/playful-purple.png (100%) rename src/{mods => themes}/playful-purple/variables.css (100%) create mode 100644 src/worker.mjs diff --git a/scripts/enhance-desktop-app.mjs b/scripts/enhance-desktop-app.mjs index 70f044c..5cdfb54 100755 --- a/scripts/enhance-desktop-app.mjs +++ b/scripts/enhance-desktop-app.mjs @@ -145,7 +145,7 @@ const unpackApp = async () => { // create package.json // prettier-ignore const manifestPath = getResourcePath("app/node_modules/notion-enhancer/package.json"), - jsManifest = { ...manifest, main: "electron/init.cjs" }; + jsManifest = { ...manifest, main: "init.js" }; // remove cli-specific fields delete jsManifest.bin; delete jsManifest.type; diff --git a/scripts/generate-theme-css.mjs b/scripts/generate-theme-css.mjs index 056eea1..844e75d 100644 --- a/scripts/generate-theme-css.mjs +++ b/scripts/generate-theme-css.mjs @@ -10,8 +10,9 @@ // the variables at the top of the file should be placed in core/variables.css // as a reference for theme developers, but not loaded into notion. -// the css body below should be passed through https://css.github.io/csso/csso.html -// and then saved to core/theme.css. repeat this process for both light and dark modes +// the css body below should be passed through https://css-minifier.com/ and +// https://css.github.io/csso/csso.html, then saved to core/theme.css. +// repeat this process for both light and dark modes. // not yet themed: notion's new svg icons @@ -452,11 +453,11 @@ const generateBackgroundStyles = () => { if (!innerText || innerText.includes(" ")) continue; const pageVar = `--theme--bg-${innerText}`, pageColor = getComputedPropertyValue(page, "background-color"), - groupVar = `--theme--bg_dim-${innerText}`, + groupVar = `--theme--dim-${innerText}`, groupColor = group .getAttribute("style") .match(/background(?:-color)?:\s*([^;]+);?/)[1]; - // get bg_dim variable values + // get dim variable values cssRoot += `${groupVar}: ${groupColor};`; // in light mode pages in board views all have bg "white" // by default, must be styled based on parent @@ -476,12 +477,12 @@ const generateBackgroundStyles = () => { refs[`--theme--bg-yellow, rgba(255, 212, 0, 0.14)`].push( `.notion-body${modeSelector} [style*="background: rgba(255, 212, 0, 0.14)"]` ); - // use bg_dim for callout blocks + // use dim for callout blocks for (const el of document.querySelectorAll( '.notion-callout-block > div > [style*="background:"]' )) { if (!el.innerText || el.innerText.includes(" ")) continue; - const cssVar = `--theme--bg_dim-${el.innerText}`, + const cssVar = `--theme--dim-${el.innerText}`, colorVal = getComputedPropertyValue(el, "background-color"), styleAttr = el .getAttribute("style") @@ -771,6 +772,18 @@ const prismTokens = [ color: var(${cssVar}, ${colorVal}) !important; }`; } + // patch: remove backgrounds from prism tokens + if (!darkMode) { + cssBody += ` + .notion-body${modeSelector} .token.operator, + .notion-body${modeSelector} .token.entity, + .notion-body${modeSelector} .token.url, + .notion-body${modeSelector} .language-css .token.string, + .notion-body${modeSelector} .style .token.string { + background: transparent !important; + } + `; + } }; generateCodeStyles(); diff --git a/scripts/vendor-dependencies.mjs b/scripts/vendor-dependencies.mjs index da52bb7..f155378 100644 --- a/scripts/vendor-dependencies.mjs +++ b/scripts/vendor-dependencies.mjs @@ -10,7 +10,7 @@ import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; const dependencies = { - "htm.min.js": "https://unpkg.com/htm@3.1.1/mini/index.module.js", + "htm.min.js": "https://unpkg.com/htm@3.1.1/mini/index.js", "twind.min.js": "https://unpkg.com/@twind/cdn@1.0.4/cdn.global.js", "lucide.min.js": "https://unpkg.com/lucide@0.104.0/dist/umd/lucide.min.js", "jscolor.min.js": diff --git a/src/browser/api.mjs b/src/api/browser.js similarity index 90% rename from src/browser/api.mjs rename to src/api/browser.js index c21c01b..67ac0dd 100644 --- a/src/browser/api.mjs +++ b/src/api/browser.js @@ -22,14 +22,17 @@ const readFile = async (file) => { }, reloadApp = () => chrome.runtime.sendMessage({ action: "reload" }); -const initDatabase = (namespace) => { +const initDatabase = (namespace, fallbacks = {}) => { if (Array.isArray(namespace)) namespace = namespace.join("__"); namespace = namespace ? namespace + "__" : ""; return { get: async (key) => { + const fallback = fallbacks[key]; key = key.startsWith(namespace) ? key : namespace + key; return new Promise((res, _rej) => { - chrome.storage.local.get(key, (value) => res(value)); + chrome.storage.local.get([key], ({ [key]: value }) => + res(value ?? fallback) + ); }); }, set: async (key, value) => { diff --git a/src/common/components/corner-action.css b/src/api/components/corner-action.css similarity index 100% rename from src/common/components/corner-action.css rename to src/api/components/corner-action.css diff --git a/src/common/components/corner-action.mjs b/src/api/components/corner-action.mjs similarity index 100% rename from src/common/components/corner-action.mjs rename to src/api/components/corner-action.mjs diff --git a/src/common/components/panel.css b/src/api/components/panel.css similarity index 100% rename from src/common/components/panel.css rename to src/api/components/panel.css diff --git a/src/common/components/panel.mjs b/src/api/components/panel.mjs similarity index 100% rename from src/common/components/panel.mjs rename to src/api/components/panel.mjs diff --git a/src/common/components/tooltip.css b/src/api/components/tooltip.css similarity index 100% rename from src/common/components/tooltip.css rename to src/api/components/tooltip.css diff --git a/src/common/components/tooltip.mjs b/src/api/components/tooltip.mjs similarity index 100% rename from src/common/components/tooltip.mjs rename to src/api/components/tooltip.mjs diff --git a/src/electron/api.cjs b/src/api/electron.cjs similarity index 77% rename from src/electron/api.cjs rename to src/api/electron.cjs index 1818a7f..667238b 100644 --- a/src/electron/api.cjs +++ b/src/api/electron.cjs @@ -9,31 +9,26 @@ const fs = require("fs"), os = require("os"), path = require("path"), - platform = process.platform; + notionRequire = (target) => require(`../../../${target}`); -const notionRequire = (target) => require(`../../../${target}`), - notionPath = (target) => path.resolve(`${__dirname}/../../../${target}`); - -const enhancerRequire = (target) => require(`notion-enhancer/${target}`), - enhancerPath = (target) => path.resolve(`${__dirname}/../${target}`), +const platform = process.platform, + enhancerVersion = require("notion-enhancer/package.json").version, enhancerUrl = (target) => - `notion://www.notion.so/__notion-enhancer/${target}`, - enhancerVersion = enhancerRequire("package.json").version, - enhancerConfig = path.resolve(`${os.homedir()}/.notion-enhancer.db`); + `notion://www.notion.so/__notion-enhancer/${target.replace(/^\//, "")}`; const readFile = (file) => { // prettier-ignore file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/"); const useFetch = file.startsWith("http") || file.startsWith("notion://"); if (useFetch) return fetch(file).then((res) => res.text()); - return fs.readFileSync(enhancerPath(file)); + return fs.readFileSync(path.resolve(`${__dirname}/../${file}`), "utf-8"); }, readJson = (file) => { // prettier-ignore file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/"); const useFetch = file.startsWith("http") || file.startsWith("notion://"); if (useFetch) return fetch(file).then((res) => res.json()); - return require(enhancerPath(file)); + return require(path.resolve(`${__dirname}/../${file}`)); }, reloadApp = () => { const { app } = require("electron"), @@ -43,13 +38,13 @@ const readFile = (file) => { }; let __db; -const initDatabase = (namespace) => { +const initDatabase = (namespace, fallbacks = {}) => { if (Array.isArray(namespace)) namespace = namespace.join("__"); namespace = namespace ? namespace + "__" : ""; const table = "settings", sqlite = require("better-sqlite3"), - db = __db ?? sqlite(enhancerConfig), + db = __db ?? sqlite(path.resolve(`${os.homedir()}/.notion-enhancer.db`)), init = db.prepare(`CREATE TABLE IF NOT EXISTS ${table} ( key TEXT PRIMARY KEY, value TEXT @@ -72,8 +67,9 @@ const initDatabase = (namespace) => { return { get: (key) => { + const fallback = fallbacks[key]; key = key.startsWith(namespace) ? key : namespace + key; - return select.get(key)?.value; + return select.get(key)?.value ?? fallback; }, set: (key, value) => { key = key.startsWith(namespace) ? key : namespace + key; @@ -94,14 +90,10 @@ const initDatabase = (namespace) => { globalThis.__enhancerApi ??= {}; Object.assign(globalThis.__enhancerApi, { - platform, notionRequire, - notionPath, - enhancerRequire, - enhancerPath, + platform, enhancerUrl, enhancerVersion, - enhancerConfig, readFile, readJson, reloadApp, diff --git a/src/api/events.js b/src/api/events.js new file mode 100644 index 0000000..cd76ba3 --- /dev/null +++ b/src/api/events.js @@ -0,0 +1,115 @@ +/** + * notion-enhancer + * (c) 2022 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +"use strict"; + +let documentObserver, + mutationListeners = []; +const mutationQueue = [], + addMutationListener = (selector, callback) => { + mutationListeners.push([selector, callback]); + }, + removeMutationListener = (callback) => { + mutationListeners = mutationListeners.filter(([, c]) => c !== callback); + }, + onSelectorMutated = (mutation, selector) => + mutation.target?.matches(`${selector}, ${selector} *`) || + [...(mutation.addedNodes || [])].some( + (node) => + node instanceof HTMLElement && + (node?.matches(`${selector}, ${selector} *`) || + node?.querySelector(selector)) + ), + handleMutations = () => { + while (mutationQueue.length) { + const mutation = mutationQueue.shift(); + for (const [selector, callback] of mutationListeners) { + if (onSelectorMutated(mutation, selector)) callback(mutation); + } + } + }, + attachObserver = () => { + if (document.readyState !== "complete") return; + documentObserver ??= new MutationObserver((mutations, _observer) => { + if (!mutationQueue.length) requestIdleCallback(handleMutations); + mutationQueue.push(...mutations); + }); + documentObserver.observe(document.body, { + childList: true, + subtree: true, + }); + }; +document.addEventListener("readystatechange", attachObserver); +attachObserver(); + +let keyListeners = []; +// accelerators approximately match electron accelerators. +// logic used when recording hotkeys in menu matches logic used +// when triggering hotkeys ∴ detection should be reliable. +// default hotkeys using "alt" may trigger an altcode or +// accented character on some keyboard layouts (not recommended). +const modifierAliases = [ + ["metaKey", ["meta", "os", "win", "cmd", "command"]], + ["ctrlKey", ["ctrl", "control"]], + ["shiftKey", ["shift"]], + ["altKey", ["alt"]], + ], + addKeyListener = (accelerator, callback, waitForKeyup = false) => { + if (typeof accelerator === "string") accelerator = accelerator.split("+"); + accelerator = accelerator.map((key) => key.toLowerCase()); + keyListeners.push([accelerator, callback, waitForKeyup]); + }, + removeKeyListener = (callback) => { + keyListeners = keyListeners.filter(([, c]) => c !== callback); + }, + handleKeypress = (event, keyListeners) => { + for (const [accelerator, callback] of keyListeners) { + const acceleratorModifiers = [], + combinationTriggered = + accelerator.every((key) => { + for (const [modifier, aliases] of modifierAliases) { + if (aliases.includes(key)) { + acceleratorModifiers.push(modifier); + return true; + } + } + if (key === "plus") key = "+"; + const keyPressed = [ + event.key.toLowerCase(), + event.code.toLowerCase(), + ].includes(key); + return keyPressed; + }) && + modifierAliases.every(([modifier]) => { + // required && used -> matches accelerator + // !required && !used -> matches accelerator + // (required && !used) || (!required && used) -> no match + // differentiates e.g.ctrl + x from ctrl + shift + x + return acceleratorModifiers.includes(modifier) === event[modifier]; + }); + if (combinationTriggered) callback(event); + } + }; +document.addEventListener("keyup", (event) => { + const keyupListeners = keyListeners.filter( + ([, , waitForKeyup]) => waitForKeyup + ); + handleKeypress(event, keyupListeners); +}); +document.addEventListener("keydown", (event) => { + const keydownListeners = keyListeners.filter( + ([, , waitForKeyup]) => !waitForKeyup + ); + handleKeypress(event, keydownListeners); +}); + +globalThis.__enhancerApi ??= {}; +Object.assign(globalThis.__enhancerApi, { + addMutationListener, + removeMutationListener, + addKeyListener, + removeKeyListener, +}); diff --git a/src/api/interface.js b/src/api/interface.js new file mode 100644 index 0000000..3a309ca --- /dev/null +++ b/src/api/interface.js @@ -0,0 +1,153 @@ +/** + * notion-enhancer + * (c) 2022 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +"use strict"; + +const { twind, htm } = globalThis, + { readFile } = globalThis.__enhancerApi; + +let iconColour, iconMonochrome; +(async () => { + iconColour = await readFile("/assets/colour.svg"); + iconMonochrome = await readFile("/assets/monochrome.svg"); +})(); + +const kebabToPascalCase = (string) => + string[0].toUpperCase() + + string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1), + hToString = (type, props, ...children) => + `<${type}${Object.entries(props) + .map(([attr, value]) => ` ${attr}="${value}"`) + .join("")}>${children + .flat(Infinity) + .map(([tag, attrs, children]) => hToString(tag, attrs, children)) + .join("")}`; + +// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 +const encodeSvg = (svg) => + svg + .replace( + "/g, "%3E") + .replace(/\s+/g, " "); + +twind.install({ + theme: { + fontFamily: { + sans: ["var(--theme--font-sans)"], + mono: ["var(--theme--font-code)"], + }, + colors: { + "fg-primary": "var(--theme--fg-primary)", + "fg-secondary": "var(--theme--fg-secondary)", + "fg-border": "var(--theme--fg-border)", + "fg-gray": "var(--theme--fg-gray)", + "fg-brown": "var(--theme--fg-brown)", + "fg-orange": "var(--theme--fg-orange)", + "fg-yellow": "var(--theme--fg-yellow)", + "fg-green": "var(--theme--fg-green)", + "fg-blue": "var(--theme--fg-blue)", + "fg-purple": "var(--theme--fg-purple)", + "fg-pink": "var(--theme--fg-pink)", + "fg-red": "var(--theme--fg-red)", + "bg-primary": "var(--theme--bg-primary)", + "bg-secondary": "var(--theme--bg-secondary)", + "bg-overlay": "var(--theme--bg-overlay)", + "bg-hover": "var(--theme--bg-hover)", + "bg-light_gray": "var(--theme--bg-light_gray)", + "bg-gray": "var(--theme--bg-gray)", + "bg-brown": "var(--theme--bg-brown)", + "bg-orange": "var(--theme--bg-orange)", + "bg-yellow": "var(--theme--bg-yellow)", + "bg-green": "var(--theme--bg-green)", + "bg-blue": "var(--theme--bg-blue)", + "bg-purple": "var(--theme--bg-purple)", + "bg-pink": "var(--theme--bg-pink)", + "bg-red": "var(--theme--bg-red)", + "dim-light_gray": "var(--theme--dim-light_gray)", + "dim-gray": "var(--theme--dim-gray)", + "dim-brown": "var(--theme--dim-brown)", + "dim-orange": "var(--theme--dim-orange)", + "dim-yellow": "var(--theme--dim-yellow)", + "dim-green": "var(--theme--dim-green)", + "dim-blue": "var(--theme--dim-blue)", + "dim-purple": "var(--theme--dim-purple)", + "dim-pink": "var(--theme--dim-pink)", + "dim-red": "var(--theme--dim-red)", + "accent-primary": "var(--theme--accent-primary)", + "accent-primary_hover": "var(--theme--accent-primary_hover)", + "accent-primary_contrast": "var(--theme--accent-primary_contrast)", + "accent-primary_transparent": "var(--theme--accent-primary_transparent)", + "accent-secondary": "var(--theme--accent-secondary)", + "accent-secondary_contrast": "var(--theme--accent-secondary_contrast)", + "accent-secondary_transparent": + "var(--theme--accent-secondary_transparent)", + }, + }, + rules: [ + [ + /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, + ([, icon, mode]) => { + let svg; + // manually register i-notion-enhancer: renders the colour + // version by default, renders the monochrome version when + // mask mode is requested via i-notion-enhancer?mask + if (icon === "notion-enhancer") { + svg = mode === "mask" ? iconMonochrome : iconColour; + } else { + icon = kebabToPascalCase(icon); + if (!globalThis.lucide[icon]) return; + svg = hToString(...globalThis.lucide[icon]); + } + // https://antfu.me/posts/icons-in-pure-css + const dataUri = `url("data:image/svg+xml;utf8,${encodeSvg(svg)}")`; + if (mode === "auto") mode = undefined; + mode ??= svg.includes("currentColor") ? "mask" : "bg"; + return mode === "mask" + ? { + mask: `${dataUri} no-repeat`, + "mask-size": "100% 100%", + "background-color": "currentColor", + color: "inherit", + height: "1em", + width: "1em", + } + : { + background: `${dataUri} no-repeat`, + "background-size": "100% 100%", + "background-color": "transparent", + height: "1em", + width: "1em", + }; + }, + ], + ], +}); + +// construct elements via tagged tagged +// e.g. html`
` +const h = (type, props, ...children) => { + const elem = document.createElement(type); + for (const prop in props) { + if (["string", "number", "boolean"].includes(typeof props[prop])) { + elem.setAttribute(prop, props[prop]); + } else elem[prop] = props[prop]; + } + for (const child of children) elem.append(child); + return elem; + }, + html = htm.bind(h); + +globalThis.__enhancerApi ??= {}; +Object.assign(globalThis.__enhancerApi, { html }); diff --git a/src/common/api.js b/src/api/mods.js similarity index 52% rename from src/common/api.js rename to src/api/mods.js index 7c7f76f..fb51fc4 100644 --- a/src/common/api.js +++ b/src/api/mods.js @@ -6,35 +6,33 @@ "use strict"; -let _core, _mods; -const getCore = async () => { - _core ??= await globalThis.__enhancerApi.readJson("core/mod.json"); - _core._src = "core"; - return _core; - }, - getMods = async () => { +let _mods; +const getMods = async () => { const { readJson } = globalThis.__enhancerApi; - // prettier-ignore - _mods ??= (await Promise.all([ - getCore(), - ...(await readJson("mods/registry.json")).map(async (modFolder) => { - const modManifest = await readJson(`mods/${modFolder}/mod.json`); - return {...modManifest, _src: `mods/${modFolder}` }; - }), - ])); + _mods ??= await Promise.all( + // prettier-ignore + (await readJson("registry.json")).map(async (_src) => { + const modManifest = await readJson(`${_src}/mod.json`); + return { ...modManifest, _src }; + }) + ); return _mods; }, + getCore = async () => { + const mods = await getMods(); + return mods.find(({ _src }) => _src === "core"); + }, getThemes = async () => { const mods = await getMods(); - return mods.filter(({ tags }) => tags.includes("theme")); + return mods.find(({ _src }) => _src.startsWith("themes/")); }, getExtensions = async () => { const mods = await getMods(); - return mods.filter(({ tags }) => tags.includes("extension")); + return mods.find(({ _src }) => _src.startsWith("extensions/")); }, getIntegrations = async () => { const mods = await getMods(); - return mods.filter(({ tags }) => tags.includes("integration")); + return mods.find(({ _src }) => _src.startsWith("integrations/")); }; const getProfile = async () => { @@ -43,13 +41,27 @@ const getProfile = async () => { return currentProfile ?? "default"; }, isEnabled = async (id) => { - if (id === (await getCore()).id) return true; const { platform } = globalThis.__enhancerApi, mod = (await getMods()).find((mod) => mod.id === id); + if (mod._src === "core") return true; if (mod.platforms && !mod.platforms.includes(platform)) return false; const { initDatabase } = globalThis.__enhancerApi, enabledMods = initDatabase([await getProfile(), "enabledMods"]); return Boolean(await enabledMods.get(id)); + }, + optionDefaults = async (id) => { + const mod = (await getMods()).find((mod) => mod.id === id), + optionEntries = mod.options + .map((opt) => { + if ( + ["toggle", "text", "number", "hotkey", "color"].includes(opt.type) + ) + return [opt.key, opt.value]; + if (opt.type === "select") return [opt.key, opt.values[0]]; + return undefined; + }) + .filter((opt) => opt); + return Object.fromEntries(optionEntries); }; globalThis.__enhancerApi ??= {}; @@ -61,4 +73,5 @@ Object.assign(globalThis.__enhancerApi, { getIntegrations, getProfile, isEnabled, + optionDefaults, }); diff --git a/src/common/notion.mjs b/src/api/notion.js similarity index 100% rename from src/common/notion.mjs rename to src/api/notion.js diff --git a/src/browser/init.mjs b/src/browser/init.mjs deleted file mode 100644 index d987ad9..0000000 --- a/src/browser/init.mjs +++ /dev/null @@ -1,12 +0,0 @@ -/** - * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -"use strict"; - -(async () => { - await import("./api.mjs"); - await import("../common/loader.mjs"); -})(); diff --git a/src/browser/worker.mjs b/src/browser/worker.mjs deleted file mode 100644 index 48e745c..0000000 --- a/src/browser/worker.mjs +++ /dev/null @@ -1,47 +0,0 @@ -/* - * notion-enhancer - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -"use strict"; - -function focusMenu() { - chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT }, (tabs) => { - const url = chrome.runtime.getURL("repo/menu/menu.html"), - menu = tabs.find((tab) => tab.url.startsWith(url)); - if (menu) { - chrome.tabs.highlight({ tabs: menu.index }); - } else chrome.tabs.create({ url }); - }); -} -chrome.browserAction.onClicked.addListener(focusMenu); - -function reload() { - chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT }, (tabs) => { - const menu = chrome.runtime.getURL("repo/menu/menu.html"); - tabs.forEach((tab) => { - const url = new URL(tab.url), - matches = - url.host.endsWith(".notion.so") || - url.host.endsWith(".notion.site") || - tab.url.startsWith(menu); - if (matches) chrome.tabs.reload(tab.id); - }); - }); -} - -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - switch (request.action) { - case "focusMenu": - focusMenu(); - break; - case "focusNotion": - focusNotion(); - break; - case "reload": - reload(); - break; - } - return true; -}); diff --git a/src/common/dom.mjs b/src/common/dom.mjs deleted file mode 100644 index c9630cb..0000000 --- a/src/common/dom.mjs +++ /dev/null @@ -1,99 +0,0 @@ -/** - * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -import "../vendor/twind.min.js"; -import "../vendor/lucide.min.js"; -import htm from "../vendor/htm.min.js"; - -const { readFile } = globalThis.__enhancerApi, - enhancerIconColour = await readFile("/assets/colour.svg"), - enhancerIconMonochrome = await readFile("/assets/monochrome.svg"); - -const kebabToPascalCase = (string) => - string[0].toUpperCase() + - string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1), - hToString = (type, props, ...children) => - `<${type}${Object.entries(props) - .map(([attr, value]) => ` ${attr}="${value}"`) - .join("")}>${children - .flat(Infinity) - .map(([tag, attrs, children]) => hToString(tag, attrs, children)) - .join("")}`; - -// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 -const encodeSvg = (svg) => - svg - .replace( - "/g, "%3E") - .replace(/\s+/g, " "); - -// https://antfu.me/posts/icons-in-pure-css -const presetIcons = () => ({ - rules: [ - [ - /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, - ([, icon, mode]) => { - let svg; - // manually register i-notion-enhancer: renders the colour - // version by default, renders the monochrome version when - // mask mode is requested via i-notion-enhancer?mask - if (icon === "notion-enhancer") { - svg = mode === "mask" ? enhancerIconMonochrome : enhancerIconColour; - } else { - icon = kebabToPascalCase(icon); - if (!globalThis.lucide[icon]) return; - svg = hToString(...globalThis.lucide[icon]); - } - const dataUri = `url("data:image/svg+xml;utf8,${encodeSvg(svg)}")`; - if (mode === "auto") mode = undefined; - mode ??= svg.includes("currentColor") ? "mask" : "bg"; - return mode === "mask" - ? { - mask: `${dataUri} no-repeat`, - "mask-size": "100% 100%", - "background-color": "currentColor", - color: "inherit", - height: "1em", - width: "1em", - } - : { - background: `${dataUri} no-repeat`, - "background-size": "100% 100%", - "background-color": "transparent", - height: "1em", - width: "1em", - }; - }, - ], - ], -}); - -const { twind } = globalThis; -twind.install({ presets: [presetIcons()] }); - -// constructs elements via html`tagged templates` -const h = (type, props, ...children) => { - const elem = document.createElement(type); - for (const prop in props) { - if (["string", "number", "boolean"].includes(typeof props[prop])) { - elem.setAttribute(prop, props[prop]); - } else elem[prop] = props[prop]; - } - for (const child of children) elem.append(child); - return elem; - }, - html = htm.bind(h); - -export { html, twind }; diff --git a/src/common/events.mjs b/src/common/events.mjs deleted file mode 100644 index 7995cec..0000000 --- a/src/common/events.mjs +++ /dev/null @@ -1,156 +0,0 @@ -/** - * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -let mutationListeners = []; -const documentMutations = [], - selectorMutated = (mutation, selector) => - mutation.target?.matches(`${selector}, ${selector} *`) || - [...(mutation.addedNodes || [])].some( - (node) => - node instanceof HTMLElement && - (node?.matches(`${selector}, ${selector} *`) || - node?.querySelector(selector)) - ), - handleMutations = () => { - while (documentMutations.length) { - const mutation = documentMutations.shift(); - for (const [selector, callback] of mutationListeners) { - if (selectorMutated(mutation, selector)) callback(mutation); - } - } - }; -const documentObserver = new MutationObserver((mutations, _observer) => { - if (!documentMutations.length) requestIdleCallback(handleMutations); - documentMutations.push(...mutations); - }), - attachObserver = () => { - if (document.readyState !== "complete") return; - documentObserver.observe(document.body, { - childList: true, - subtree: true, - }); - }; -document.addEventListener("readystatechange", attachObserver); -attachObserver(); - -const addMutationListener = (selector, callback) => { - mutationListeners.push([selector, callback]); - }, - removeMutationListener = (callback) => { - mutationListeners = mutationListeners.filter(([, c]) => c !== callback); - }; - -export { addMutationListener, removeMutationListener }; - -// let _hotkeyListenersActivated = false, -// _hotkeyEventListeners = [], -// _documentObserver, -// _documentObserverListeners = []; -// const _documentObserverEvents = []; - -// /** -// * wait until a page is loaded and ready for modification -// * @param {array=} selectors - wait for the existence of elements that match these css selectors -// * @returns {Promise} a promise that will resolve when the page is ready -// */ -// export const whenReady = (selectors = []) => { -// return new Promise((res, _rej) => { -// const onLoad = () => { -// const interval = setInterval(isReady, 100); -// function isReady() { -// const ready = selectors.every((selector) => document.querySelector(selector)); -// if (!ready) return; -// clearInterval(interval); -// res(true); -// } -// isReady(); -// }; -// if (document.readyState !== "complete") { -// document.addEventListener("readystatechange", (_event) => { -// if (document.readyState === "complete") onLoad(); -// }); -// } else onLoad(); -// }); -// }; - -// const triggerHotkeyListener = (event, hotkey) => { -// const inInput = document.activeElement.nodeName === "INPUT" && !hotkey.listenInInput; -// if (inInput) return; -// const modifiers = { -// metaKey: ["meta", "os", "win", "cmd", "command"], -// ctrlKey: ["ctrl", "control"], -// shiftKey: ["shift"], -// altKey: ["alt"], -// }, -// pressed = hotkey.keys.every((key) => { -// key = key.toLowerCase(); -// for (const modifier in modifiers) { -// const pressed = modifiers[modifier].includes(key) && event[modifier]; -// if (pressed) { -// // mark modifier as part of hotkey -// modifiers[modifier] = []; -// return true; -// } -// } -// if (key === "space") key = " "; -// if (key === "plus") key = "+"; -// if (key === event.key.toLowerCase()) return true; -// }); -// if (!pressed) return; -// // test for modifiers not in hotkey -// // e.g. to differentiate ctrl+x from ctrl+shift+x -// for (const modifier in modifiers) { -// const modifierPressed = event[modifier], -// modifierNotInHotkey = modifiers[modifier].length > 0; -// if (modifierPressed && modifierNotInHotkey) return; -// } -// hotkey.callback(event); -// }; - -// /** -// * register a hotkey listener to the page -// * @param {array|string} keys - the combination of keys that will trigger the hotkey. -// * key codes can be tested at http://keycode.info/ and are case-insensitive. -// * available modifiers are 'alt', 'ctrl', 'meta', and 'shift'. -// * can be provided as a + separated string. -// * @param {function} callback - called whenever the keys are pressed -// * @param {object=} opts - fine-tuned control over when the hotkey should be triggered -// * @param {boolean=} opts.listenInInput - whether the hotkey callback should be triggered -// * when an input is focused -// * @param {boolean=} opts.keydown - whether to listen for the hotkey on keydown. -// * by default, hotkeys are triggered by the keyup event. -// */ -// export const addHotkeyListener = ( -// keys, -// callback, -// { listenInInput = false, keydown = false } = {} -// ) => { -// if (typeof keys === "string") keys = keys.split("+"); -// _hotkeyEventListeners.push({ keys, callback, listenInInput, keydown }); - -// if (!_hotkeyListenersActivated) { -// _hotkeyListenersActivated = true; -// document.addEventListener("keyup", (event) => { -// for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => !keydown)) { -// triggerHotkeyListener(event, hotkey); -// } -// }); -// document.addEventListener("keydown", (event) => { -// for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => keydown)) { -// triggerHotkeyListener(event, hotkey); -// } -// }); -// } -// }; -// /** -// * remove a listener added with web.addHotkeyListener -// * @param {function} callback -// */ -// export const removeHotkeyListener = (callback) => { -// _hotkeyEventListeners = _hotkeyEventListeners.filter( -// (listener) => listener.callback !== callback -// ); -// }; diff --git a/src/common/loader.mjs b/src/common/loader.mjs deleted file mode 100644 index a3873e4..0000000 --- a/src/common/loader.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/** - * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -"use strict"; - -(async () => { - const signedIn = localStorage["LRU:KeyValueStore2:current-user-id"], - pageLoaded = /(^\/$)|((-|\/)[0-9a-f]{32}((\?.+)|$))/.test( - location.pathname - ); - if (!signedIn || !pageLoaded) return; - - await import("./api.js"); - await import("./dom.mjs"); - await import("./events.mjs"); - const { getMods, getProfile, isEnabled, enhancerUrl, initDatabase } = - globalThis.__enhancerApi; - for (const mod of await getMods()) { - if (!(await isEnabled(mod.id))) continue; - - // clientStyles - for (let stylesheet of mod.clientStyles ?? []) { - const $stylesheet = document.createElement("link"); - $stylesheet.rel = "stylesheet"; - $stylesheet.href = enhancerUrl(`${mod._src}/${stylesheet}`); - document.head.appendChild($stylesheet); - } - - // clientScripts - for (let script of mod.clientScripts ?? []) { - const db = initDatabase([await getProfile(), mod.id]); - script = await import(enhancerUrl(`${mod._src}/${script}`)); - script.default(globalThis.__enhancerApi, db); - } - } -})(); diff --git a/src/common/utils.js b/src/common/utils.js deleted file mode 100644 index acd270f..0000000 --- a/src/common/utils.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -const kebabToPascalCase = (string) => - string[0].toUpperCase() + - string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1), - camelToSentenceCase = (string) => - string[0].toUpperCase() + - string.replace(/[A-Z]/g, (match) => " " + match.toLowerCase()).slice(1); - -const hToString = (type, props, ...children) => - `<${type}${Object.entries(props) - .map(([attr, value]) => ` ${attr}="${value}"`) - .join("")}>${children - .flat(Infinity) - .map(([tag, attrs, children]) => hToString(tag, attrs, children)) - .join("")}`; - -// /** -// * log-based shading of an rgb color, from -// * https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors -// * @param {number} shade - a decimal amount to shade the color. -// * 1 = white, 0 = the original color, -1 = black -// * @param {string} color - the rgb color -// * @returns {string} the shaded color -// */ -// export const rgbLogShade = (shade, color) => { -// const int = parseInt, -// round = Math.round, -// [a, b, c, d] = color.split(","), -// t = shade < 0 ? 0 : shade * 255 ** 2, -// p = shade < 0 ? 1 + shade : 1 - shade; -// return ( -// "rgb" + -// (d ? "a(" : "(") + -// round((p * int(a[3] == "a" ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + -// "," + -// round((p * int(b) ** 2 + t) ** 0.5) + -// "," + -// round((p * int(c) ** 2 + t) ** 0.5) + -// (d ? "," + d : ")") -// ); -// }; - -// /** -// * pick a contrasting color e.g. for text on a variable color background -// * using the hsp (perceived brightness) constants from http://alienryderflex.com/hsp.html -// * @param {number} r - red (0-255) -// * @param {number} g - green (0-255) -// * @param {number} b - blue (0-255) -// * @returns {string} the contrasting rgb color, white or black -// */ -// export const rgbContrast = (r, g, b) => { -// return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 -// ? "rgb(0,0,0)" -// : "rgb(255,255,255)"; -// }; - -// /** -// * parse the current location search params into a usable form -// * @returns {Map} a map of the url search params -// */ -// export const queryParams = () => new URLSearchParams(window.location.search); - -// /** -// * replace special html characters with escaped versions -// * @param {string} str -// * @returns {string} escaped string -// */ -// export const escape = (str) => -// str -// .replace(/&/g, "&") -// .replace(//g, ">") -// .replace(/'/g, "'") -// .replace(/"/g, """) -// .replace(/\\/g, "\"); - -// /** -// * copy text to the clipboard -// * @param {string} str - the string to copy -// * @returns {Promise} -// */ -// export const copyToClipboard = async (str) => { -// try { -// await navigator.clipboard.writeText(str); -// } catch { -// const $el = document.createElement("textarea"); -// $el.value = str; -// $el.setAttribute("readonly", ""); -// $el.style.position = "absolute"; -// $el.style.left = "-9999px"; -// document.body.appendChild($el); -// $el.select(); -// document.execCommand("copy"); -// document.body.removeChild($el); -// } -// }; - -// /** -// * read text from the clipboard -// * @returns {Promise} -// */ -// export const readFromClipboard = () => { -// return navigator.clipboard.readText(); -// }; - -globalThis.__enhancerUtils ??= {}; -Object.assign(globalThis.__enhancerUtils, { - hToString, - kebabToPascalCase, - camelToSentenceCase, -}); diff --git a/src/core/client.css b/src/core/client.css index 9c8a44d..c2a37b6 100644 --- a/src/core/client.css +++ b/src/core/client.css @@ -5,61 +5,11 @@ */ @import url("./variables.css"); -@import url("./theme.css"); -.notion-enhancer--menu-button { - display: flex; - user-select: none; - cursor: pointer; - transition: background 20ms ease-in; - border-radius: 3px; - font-size: 14px; - margin: 1px 4px; - padding: 2px 10px; -} -.notion-enhancer--menu-button:hover { - background: var(--theme--bg-hover); -} -.notion-enhancer--menu-button > :nth-child(1) { - display: flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - margin-right: 8px; -} - -.notion-enhancer--menu-modal { - z-index: 999; - position: fixed; - inset: 0; - width: 100vw; - height: 100vh; - opacity: 0; - pointer-events: none; - transition: opacity 100ms ease-in; - /* style="display:none" is set to prevent pop-in fouc */ - display: auto !important; -} .notion-enhancer--menu-modal[data-open="true"] { pointer-events: auto; opacity: 1; } -.notion-enhancer--menu-modal > :nth-child(1) { - position: fixed; - inset: 0; - background: var(--theme--bg-overlay); -} -.notion-enhancer--menu-modal > :nth-child(2) { - position: fixed; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; - pointer-events: none; -} .notion-enhancer--menu-modal > :nth-child(2) > iframe { background: var(--theme--bg-secondary); box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, @@ -80,12 +30,3 @@ transform: scale(1); transition: transform 80ms ease-in; } - -/* patch: remove backgrounds from prism tokens */ -.notion-light-theme .token.operator, -.notion-light-theme .token.entity, -.notion-light-theme .token.url, -.notion-light-theme .language-css .token.string, -.notion-light-theme .style .token.string { - background: transparent !important; -} diff --git a/src/core/client.mjs b/src/core/client.mjs index d09e565..496c4c0 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -4,45 +4,97 @@ * (https://notion-enhancer.github.io/) under the MIT license */ -import { html } from "../common/dom.mjs"; -import { addMutationListener } from "../common/events.mjs"; +const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`; -export default async () => { - const { enhancerUrl } = globalThis.__enhancerApi, - menuButtonIconStyle = ""; +export default async (api, db) => { + const { enhancerUrl, platform } = api, + { html, addMutationListener, addKeyListener } = api, + openMenuHotkey = await db.get("openMenuHotkey"), + menuButtonIconStyle = await db.get("menuButtonIconStyle"), + loadThemeOverrides = await db.get("loadThemeOverrides"), + customStyles = await db.get("customStyles"); - const icon = `i-notion-enhancer${ - menuButtonIconStyle === "monochrome" ? "?mask" : " text-[16px]" - }`; + // appearance - const menuModal = html`