feat: load api/styles into menu, chore: separate mod types into folders
@ -145,7 +145,7 @@ const unpackApp = async () => {
|
|||||||
// create package.json
|
// create package.json
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const manifestPath = getResourcePath("app/node_modules/notion-enhancer/package.json"),
|
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
|
// remove cli-specific fields
|
||||||
delete jsManifest.bin;
|
delete jsManifest.bin;
|
||||||
delete jsManifest.type;
|
delete jsManifest.type;
|
||||||
|
@ -10,8 +10,9 @@
|
|||||||
// the variables at the top of the file should be placed in core/variables.css
|
// 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.
|
// 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
|
// the css body below should be passed through https://css-minifier.com/ and
|
||||||
// and then saved to core/theme.css. repeat this process for both light and dark modes
|
// 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
|
// not yet themed: notion's new svg icons
|
||||||
|
|
||||||
@ -452,11 +453,11 @@ const generateBackgroundStyles = () => {
|
|||||||
if (!innerText || innerText.includes(" ")) continue;
|
if (!innerText || innerText.includes(" ")) continue;
|
||||||
const pageVar = `--theme--bg-${innerText}`,
|
const pageVar = `--theme--bg-${innerText}`,
|
||||||
pageColor = getComputedPropertyValue(page, "background-color"),
|
pageColor = getComputedPropertyValue(page, "background-color"),
|
||||||
groupVar = `--theme--bg_dim-${innerText}`,
|
groupVar = `--theme--dim-${innerText}`,
|
||||||
groupColor = group
|
groupColor = group
|
||||||
.getAttribute("style")
|
.getAttribute("style")
|
||||||
.match(/background(?:-color)?:\s*([^;]+);?/)[1];
|
.match(/background(?:-color)?:\s*([^;]+);?/)[1];
|
||||||
// get bg_dim variable values
|
// get dim variable values
|
||||||
cssRoot += `${groupVar}: ${groupColor};`;
|
cssRoot += `${groupVar}: ${groupColor};`;
|
||||||
// in light mode pages in board views all have bg "white"
|
// in light mode pages in board views all have bg "white"
|
||||||
// by default, must be styled based on parent
|
// 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(
|
refs[`--theme--bg-yellow, rgba(255, 212, 0, 0.14)`].push(
|
||||||
`.notion-body${modeSelector} [style*="background: rgba(255, 212, 0, 0.14)"]`
|
`.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(
|
for (const el of document.querySelectorAll(
|
||||||
'.notion-callout-block > div > [style*="background:"]'
|
'.notion-callout-block > div > [style*="background:"]'
|
||||||
)) {
|
)) {
|
||||||
if (!el.innerText || el.innerText.includes(" ")) continue;
|
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"),
|
colorVal = getComputedPropertyValue(el, "background-color"),
|
||||||
styleAttr = el
|
styleAttr = el
|
||||||
.getAttribute("style")
|
.getAttribute("style")
|
||||||
@ -771,6 +772,18 @@ const prismTokens = [
|
|||||||
color: var(${cssVar}, ${colorVal}) !important;
|
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();
|
generateCodeStyles();
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { resolve } from "node:path";
|
|||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const dependencies = {
|
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",
|
"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",
|
"lucide.min.js": "https://unpkg.com/lucide@0.104.0/dist/umd/lucide.min.js",
|
||||||
"jscolor.min.js":
|
"jscolor.min.js":
|
||||||
|
@ -22,14 +22,17 @@ const readFile = async (file) => {
|
|||||||
},
|
},
|
||||||
reloadApp = () => chrome.runtime.sendMessage({ action: "reload" });
|
reloadApp = () => chrome.runtime.sendMessage({ action: "reload" });
|
||||||
|
|
||||||
const initDatabase = (namespace) => {
|
const initDatabase = (namespace, fallbacks = {}) => {
|
||||||
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
||||||
namespace = namespace ? namespace + "__" : "";
|
namespace = namespace ? namespace + "__" : "";
|
||||||
return {
|
return {
|
||||||
get: async (key) => {
|
get: async (key) => {
|
||||||
|
const fallback = fallbacks[key];
|
||||||
key = key.startsWith(namespace) ? key : namespace + key;
|
key = key.startsWith(namespace) ? key : namespace + key;
|
||||||
return new Promise((res, _rej) => {
|
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) => {
|
set: async (key, value) => {
|
@ -9,31 +9,26 @@
|
|||||||
const fs = require("fs"),
|
const fs = require("fs"),
|
||||||
os = require("os"),
|
os = require("os"),
|
||||||
path = require("path"),
|
path = require("path"),
|
||||||
platform = process.platform;
|
notionRequire = (target) => require(`../../../${target}`);
|
||||||
|
|
||||||
const notionRequire = (target) => require(`../../../${target}`),
|
const platform = process.platform,
|
||||||
notionPath = (target) => path.resolve(`${__dirname}/../../../${target}`);
|
enhancerVersion = require("notion-enhancer/package.json").version,
|
||||||
|
|
||||||
const enhancerRequire = (target) => require(`notion-enhancer/${target}`),
|
|
||||||
enhancerPath = (target) => path.resolve(`${__dirname}/../${target}`),
|
|
||||||
enhancerUrl = (target) =>
|
enhancerUrl = (target) =>
|
||||||
`notion://www.notion.so/__notion-enhancer/${target}`,
|
`notion://www.notion.so/__notion-enhancer/${target.replace(/^\//, "")}`;
|
||||||
enhancerVersion = enhancerRequire("package.json").version,
|
|
||||||
enhancerConfig = path.resolve(`${os.homedir()}/.notion-enhancer.db`);
|
|
||||||
|
|
||||||
const readFile = (file) => {
|
const readFile = (file) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/");
|
file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/");
|
||||||
const useFetch = file.startsWith("http") || file.startsWith("notion://");
|
const useFetch = file.startsWith("http") || file.startsWith("notion://");
|
||||||
if (useFetch) return fetch(file).then((res) => res.text());
|
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) => {
|
readJson = (file) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/");
|
file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/");
|
||||||
const useFetch = file.startsWith("http") || file.startsWith("notion://");
|
const useFetch = file.startsWith("http") || file.startsWith("notion://");
|
||||||
if (useFetch) return fetch(file).then((res) => res.json());
|
if (useFetch) return fetch(file).then((res) => res.json());
|
||||||
return require(enhancerPath(file));
|
return require(path.resolve(`${__dirname}/../${file}`));
|
||||||
},
|
},
|
||||||
reloadApp = () => {
|
reloadApp = () => {
|
||||||
const { app } = require("electron"),
|
const { app } = require("electron"),
|
||||||
@ -43,13 +38,13 @@ const readFile = (file) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let __db;
|
let __db;
|
||||||
const initDatabase = (namespace) => {
|
const initDatabase = (namespace, fallbacks = {}) => {
|
||||||
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
||||||
namespace = namespace ? namespace + "__" : "";
|
namespace = namespace ? namespace + "__" : "";
|
||||||
|
|
||||||
const table = "settings",
|
const table = "settings",
|
||||||
sqlite = require("better-sqlite3"),
|
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} (
|
init = db.prepare(`CREATE TABLE IF NOT EXISTS ${table} (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT
|
value TEXT
|
||||||
@ -72,8 +67,9 @@ const initDatabase = (namespace) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
get: (key) => {
|
get: (key) => {
|
||||||
|
const fallback = fallbacks[key];
|
||||||
key = key.startsWith(namespace) ? key : namespace + key;
|
key = key.startsWith(namespace) ? key : namespace + key;
|
||||||
return select.get(key)?.value;
|
return select.get(key)?.value ?? fallback;
|
||||||
},
|
},
|
||||||
set: (key, value) => {
|
set: (key, value) => {
|
||||||
key = key.startsWith(namespace) ? key : namespace + key;
|
key = key.startsWith(namespace) ? key : namespace + key;
|
||||||
@ -94,14 +90,10 @@ const initDatabase = (namespace) => {
|
|||||||
|
|
||||||
globalThis.__enhancerApi ??= {};
|
globalThis.__enhancerApi ??= {};
|
||||||
Object.assign(globalThis.__enhancerApi, {
|
Object.assign(globalThis.__enhancerApi, {
|
||||||
platform,
|
|
||||||
notionRequire,
|
notionRequire,
|
||||||
notionPath,
|
platform,
|
||||||
enhancerRequire,
|
|
||||||
enhancerPath,
|
|
||||||
enhancerUrl,
|
enhancerUrl,
|
||||||
enhancerVersion,
|
enhancerVersion,
|
||||||
enhancerConfig,
|
|
||||||
readFile,
|
readFile,
|
||||||
readJson,
|
readJson,
|
||||||
reloadApp,
|
reloadApp,
|
115
src/api/events.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* notion-enhancer
|
||||||
|
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (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,
|
||||||
|
});
|
153
src/api/interface.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* notion-enhancer
|
||||||
|
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (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("")}</${type}>`;
|
||||||
|
|
||||||
|
// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
|
||||||
|
const encodeSvg = (svg) =>
|
||||||
|
svg
|
||||||
|
.replace(
|
||||||
|
"<svg",
|
||||||
|
~svg.indexOf("xmlns") ? "<svg" : '<svg xmlns="http://www.w3.org/2000/svg"'
|
||||||
|
)
|
||||||
|
.replace(/"/g, "'")
|
||||||
|
.replace(/%/g, "%25")
|
||||||
|
.replace(/#/g, "%23")
|
||||||
|
.replace(/{/g, "%7B")
|
||||||
|
.replace(/}/g, "%7D")
|
||||||
|
.replace(/</g, "%3C")
|
||||||
|
.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`<div class=${className}></div>`
|
||||||
|
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 });
|
@ -6,35 +6,33 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let _core, _mods;
|
let _mods;
|
||||||
const getCore = async () => {
|
const getMods = async () => {
|
||||||
_core ??= await globalThis.__enhancerApi.readJson("core/mod.json");
|
|
||||||
_core._src = "core";
|
|
||||||
return _core;
|
|
||||||
},
|
|
||||||
getMods = async () => {
|
|
||||||
const { readJson } = globalThis.__enhancerApi;
|
const { readJson } = globalThis.__enhancerApi;
|
||||||
|
_mods ??= await Promise.all(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
_mods ??= (await Promise.all([
|
(await readJson("registry.json")).map(async (_src) => {
|
||||||
getCore(),
|
const modManifest = await readJson(`${_src}/mod.json`);
|
||||||
...(await readJson("mods/registry.json")).map(async (modFolder) => {
|
return { ...modManifest, _src };
|
||||||
const modManifest = await readJson(`mods/${modFolder}/mod.json`);
|
})
|
||||||
return {...modManifest, _src: `mods/${modFolder}` };
|
);
|
||||||
}),
|
|
||||||
]));
|
|
||||||
return _mods;
|
return _mods;
|
||||||
},
|
},
|
||||||
|
getCore = async () => {
|
||||||
|
const mods = await getMods();
|
||||||
|
return mods.find(({ _src }) => _src === "core");
|
||||||
|
},
|
||||||
getThemes = async () => {
|
getThemes = async () => {
|
||||||
const mods = await getMods();
|
const mods = await getMods();
|
||||||
return mods.filter(({ tags }) => tags.includes("theme"));
|
return mods.find(({ _src }) => _src.startsWith("themes/"));
|
||||||
},
|
},
|
||||||
getExtensions = async () => {
|
getExtensions = async () => {
|
||||||
const mods = await getMods();
|
const mods = await getMods();
|
||||||
return mods.filter(({ tags }) => tags.includes("extension"));
|
return mods.find(({ _src }) => _src.startsWith("extensions/"));
|
||||||
},
|
},
|
||||||
getIntegrations = async () => {
|
getIntegrations = async () => {
|
||||||
const mods = await getMods();
|
const mods = await getMods();
|
||||||
return mods.filter(({ tags }) => tags.includes("integration"));
|
return mods.find(({ _src }) => _src.startsWith("integrations/"));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProfile = async () => {
|
const getProfile = async () => {
|
||||||
@ -43,13 +41,27 @@ const getProfile = async () => {
|
|||||||
return currentProfile ?? "default";
|
return currentProfile ?? "default";
|
||||||
},
|
},
|
||||||
isEnabled = async (id) => {
|
isEnabled = async (id) => {
|
||||||
if (id === (await getCore()).id) return true;
|
|
||||||
const { platform } = globalThis.__enhancerApi,
|
const { platform } = globalThis.__enhancerApi,
|
||||||
mod = (await getMods()).find((mod) => mod.id === id);
|
mod = (await getMods()).find((mod) => mod.id === id);
|
||||||
|
if (mod._src === "core") return true;
|
||||||
if (mod.platforms && !mod.platforms.includes(platform)) return false;
|
if (mod.platforms && !mod.platforms.includes(platform)) return false;
|
||||||
const { initDatabase } = globalThis.__enhancerApi,
|
const { initDatabase } = globalThis.__enhancerApi,
|
||||||
enabledMods = initDatabase([await getProfile(), "enabledMods"]);
|
enabledMods = initDatabase([await getProfile(), "enabledMods"]);
|
||||||
return Boolean(await enabledMods.get(id));
|
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 ??= {};
|
globalThis.__enhancerApi ??= {};
|
||||||
@ -61,4 +73,5 @@ Object.assign(globalThis.__enhancerApi, {
|
|||||||
getIntegrations,
|
getIntegrations,
|
||||||
getProfile,
|
getProfile,
|
||||||
isEnabled,
|
isEnabled,
|
||||||
|
optionDefaults,
|
||||||
});
|
});
|
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await import("./api.mjs");
|
|
||||||
await import("../common/loader.mjs");
|
|
||||||
})();
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (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;
|
|
||||||
});
|
|
@ -1,99 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (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("")}</${type}>`;
|
|
||||||
|
|
||||||
// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
|
|
||||||
const encodeSvg = (svg) =>
|
|
||||||
svg
|
|
||||||
.replace(
|
|
||||||
"<svg",
|
|
||||||
~svg.indexOf("xmlns") ? "<svg" : '<svg xmlns="http://www.w3.org/2000/svg"'
|
|
||||||
)
|
|
||||||
.replace(/"/g, "'")
|
|
||||||
.replace(/%/g, "%25")
|
|
||||||
.replace(/#/g, "%23")
|
|
||||||
.replace(/{/g, "%7B")
|
|
||||||
.replace(/}/g, "%7D")
|
|
||||||
.replace(/</g, "%3C")
|
|
||||||
.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 };
|
|
@ -1,156 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (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
|
|
||||||
// );
|
|
||||||
// };
|
|
@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
@ -1,116 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (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("")}</${type}>`;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 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<string, string>} 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, """)
|
|
||||||
// .replace(/\\/g, "\");
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * copy text to the clipboard
|
|
||||||
// * @param {string} str - the string to copy
|
|
||||||
// * @returns {Promise<void>}
|
|
||||||
// */
|
|
||||||
// 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<string>}
|
|
||||||
// */
|
|
||||||
// export const readFromClipboard = () => {
|
|
||||||
// return navigator.clipboard.readText();
|
|
||||||
// };
|
|
||||||
|
|
||||||
globalThis.__enhancerUtils ??= {};
|
|
||||||
Object.assign(globalThis.__enhancerUtils, {
|
|
||||||
hToString,
|
|
||||||
kebabToPascalCase,
|
|
||||||
camelToSentenceCase,
|
|
||||||
});
|
|
@ -5,61 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@import url("./variables.css");
|
@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"] {
|
.notion-enhancer--menu-modal[data-open="true"] {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
opacity: 1;
|
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 {
|
.notion-enhancer--menu-modal > :nth-child(2) > iframe {
|
||||||
background: var(--theme--bg-secondary);
|
background: var(--theme--bg-secondary);
|
||||||
box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px,
|
box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px,
|
||||||
@ -80,12 +30,3 @@
|
|||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
transition: transform 80ms ease-in;
|
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;
|
|
||||||
}
|
|
||||||
|
@ -4,45 +4,97 @@
|
|||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { html } from "../common/dom.mjs";
|
const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`;
|
||||||
import { addMutationListener } from "../common/events.mjs";
|
|
||||||
|
|
||||||
export default async () => {
|
export default async (api, db) => {
|
||||||
const { enhancerUrl } = globalThis.__enhancerApi,
|
const { enhancerUrl, platform } = api,
|
||||||
menuButtonIconStyle = "";
|
{ 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${
|
// appearance
|
||||||
menuButtonIconStyle === "monochrome" ? "?mask" : " text-[16px]"
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const menuModal = html`<div
|
if (loadThemeOverrides) {
|
||||||
class="notion-enhancer--menu-modal"
|
const $themeOverrides = html`<link
|
||||||
style="display:none"
|
rel="stylesheet"
|
||||||
|
href=${enhancerUrl("core/theme.css")}
|
||||||
|
/>`;
|
||||||
|
document.head.append($themeOverrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customStyles) {
|
||||||
|
const $cssInsert = html`<style>
|
||||||
|
${customStyles}
|
||||||
|
</style>`;
|
||||||
|
document.head.append($cssInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// menu
|
||||||
|
|
||||||
|
let $menuModal, $menuFrame;
|
||||||
|
const getTheme = () => {
|
||||||
|
return document.body.classList.contains("dark") ? "dark" : "light";
|
||||||
|
},
|
||||||
|
openMenu = () => {
|
||||||
|
if (!$menuFrame) return;
|
||||||
|
const msg = { namespace: "notion-enhancer", mode: getTheme() };
|
||||||
|
if (platform !== "browser") $menuFrame.contentWindow.__enhancerApi = api;
|
||||||
|
$menuFrame.contentWindow.postMessage(msg, "*");
|
||||||
|
$menuModal.setAttribute("data-open", true);
|
||||||
|
},
|
||||||
|
closeMenu = () => $menuModal.removeAttribute("data-open");
|
||||||
|
|
||||||
|
const $menuButton = html`<div
|
||||||
|
onclick=${openMenu}
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
class="notion-enhancer--menu-button
|
||||||
|
flex select-none cursor-pointer rounded-[3px]
|
||||||
|
text-[14px] my-px mx-[4px] py-[2px] px-[10px]
|
||||||
|
transition hover:bg-bg-hover"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center w-[22px] h-[22px] mr-[8px]">
|
||||||
|
<i
|
||||||
|
class="i-notion-enhancer${menuButtonIconStyle === "monochrome"
|
||||||
|
? "?mask"
|
||||||
|
: " text-[16px]"}"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
<div>notion-enhancer</div>
|
||||||
|
</div>`;
|
||||||
|
addMutationListener(notionSidebar, () => {
|
||||||
|
if (document.contains($menuButton)) return;
|
||||||
|
document.querySelector(notionSidebar)?.append($menuButton);
|
||||||
|
});
|
||||||
|
document.querySelector(notionSidebar)?.append($menuButton);
|
||||||
|
|
||||||
|
$menuModal = html`<div
|
||||||
|
class="notion-enhancer--menu-modal
|
||||||
|
z-[999] fixed inset-0 w-screen h-screen
|
||||||
|
transition pointer-events-none opacity-0"
|
||||||
|
>
|
||||||
|
<div class="fixed inset-0 bg-bg-overlay" onclick=${closeMenu}></div>
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 flex w-screen h-screen
|
||||||
|
items-center justify-center point-events-none"
|
||||||
>
|
>
|
||||||
<div onclick=${() => menuModal.removeAttribute("data-open")}></div>
|
|
||||||
<div>
|
|
||||||
<iframe
|
<iframe
|
||||||
title="notion-enhancer menu"
|
title="notion-enhancer menu"
|
||||||
src="${enhancerUrl("core/menu.html")}"
|
src="${enhancerUrl("core/menu/index.html")}"
|
||||||
|
onload=${(event) => ($menuFrame = event.target)}
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.append(menuModal);
|
document.body.append($menuModal);
|
||||||
|
|
||||||
const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`,
|
addKeyListener(openMenuHotkey, (event) => {
|
||||||
menuButton = html`<div
|
event.preventDefault();
|
||||||
tabindex="0"
|
openMenu();
|
||||||
role="button"
|
});
|
||||||
class="notion-enhancer--menu-button"
|
addKeyListener("Escape", () => {
|
||||||
onclick=${() => menuModal.setAttribute("data-open", true)}
|
if (document.activeElement?.nodeName === "INPUT") return;
|
||||||
>
|
closeMenu();
|
||||||
<div><i class=${icon}></i></div>
|
});
|
||||||
<div>notion-enhancer</div>
|
|
||||||
</div>`,
|
|
||||||
addToSidebar = () => {
|
|
||||||
if (document.contains(menuButton)) return;
|
|
||||||
document.querySelector(notionSidebar)?.append(menuButton);
|
|
||||||
};
|
|
||||||
addMutationListener(notionSidebar, addToSidebar);
|
|
||||||
addToSidebar();
|
|
||||||
};
|
};
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>notion-enhancer menu</title>
|
|
||||||
<link rel="stylesheet" href="./menu.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="./menu.mjs" type="module"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
28
src/core/menu/index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>notion-enhancer menu</title>
|
||||||
|
<link rel="stylesheet" href="./menu.css" />
|
||||||
|
</head>
|
||||||
|
<body class="w-screen h-screen bg-bg-primary text-fg-primary font-sans">
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<p>lorem ipsum</p>
|
||||||
|
<script src="./menu.mjs" type="module" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer: menu
|
* notion-enhancer
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--theme--accent_blue-selection);
|
background: var(--theme--accent-primary_transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@ -15,11 +15,14 @@
|
|||||||
}
|
}
|
||||||
::-webkit-scrollbar-track,
|
::-webkit-scrollbar-track,
|
||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
background: var(--theme--scrollbar_track) !important;
|
background: var(--theme--scrollbar-track) !important;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: var(--theme--scrollbar_thumb) !important;
|
background: var(--theme--scrollbar-thumb) !important;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--theme--scrollbar_thumb-hover) !important;
|
background: var(--theme--scrollbar-thumb_hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
||||||
|
*,::after,::before{box-sizing:border-box}html{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}
|
@ -1,448 +1,41 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer: menu
|
* notion-enhancer
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
let stylesLoaded = false;
|
||||||
|
const importApi = async () => {
|
||||||
import * as api from '../../api/index.mjs';
|
// chrome extensions run in an isolated execution context
|
||||||
import { notifications, $changelogModal } from './notifications.mjs';
|
// but extension:// pages can access chrome apis
|
||||||
import { modComponents, options } from './components.mjs';
|
// ∴ notion-enhancer api is imported directly
|
||||||
import * as router from './router.mjs';
|
if (typeof globalThis.__enhancerApi === "undefined") {
|
||||||
import './styles.mjs';
|
await import("../../api/browser.js");
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const { env, fs, storage, electron, registry, web, components } = api;
|
|
||||||
|
|
||||||
for (const mod of await registry.list((mod) => registry.enabled(mod.id))) {
|
|
||||||
for (let script of mod.js?.menu || []) {
|
|
||||||
script = await import(fs.localPath(`repo/${mod._dir}/${script}`));
|
|
||||||
script.default(api, await registry.db(mod.id));
|
|
||||||
}
|
}
|
||||||
|
// in electron this is not necessary, as a) scripts are
|
||||||
|
// not running in an isolated execution context and b)
|
||||||
|
// the notion:// protocol csp bypass allows scripts to
|
||||||
|
// set iframe globals via $iframe.contentWindow
|
||||||
|
},
|
||||||
|
importStyles = async () => {
|
||||||
|
if (!stylesLoaded) {
|
||||||
|
// clientStyles + twind/htm/etc.
|
||||||
|
await import("../../load.mjs");
|
||||||
|
stylesLoaded = true;
|
||||||
}
|
}
|
||||||
const errors = await registry.errors();
|
},
|
||||||
if (errors.length) {
|
updateTheme = (mode) => {
|
||||||
console.error('[notion-enhancer] registry errors:');
|
if (mode === "dark") {
|
||||||
console.table(errors);
|
document.body.classList.add("dark");
|
||||||
const $errNotification = await notifications.add({
|
} else if (mode === "light") {
|
||||||
icon: 'alert-circle',
|
document.body.classList.remove("dark");
|
||||||
message: 'Failed to load mods (check console).',
|
|
||||||
color: 'red',
|
|
||||||
});
|
|
||||||
if (['win32', 'linux', 'darwin'].includes(env.name)) {
|
|
||||||
$errNotification.addEventListener('click', () => electron.browser.openDevTools());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e'),
|
|
||||||
profileName = await registry.profileName(),
|
|
||||||
profileDB = await registry.profileDB();
|
|
||||||
|
|
||||||
web.addHotkeyListener(await db.get(['hotkey']), env.focusNotion);
|
|
||||||
|
|
||||||
globalThis.addEventListener('beforeunload', (_event) => {
|
|
||||||
// trigger input save
|
|
||||||
document.activeElement.blur();
|
|
||||||
});
|
|
||||||
|
|
||||||
const $main = web.html`<main class="main"></main>`,
|
|
||||||
$sidebar = web.html`<article class="sidebar"></article>`,
|
|
||||||
$options = web.html`<div class="options-container">
|
|
||||||
<p class="options-placeholder">Select a mod to view and configure its options.</p>
|
|
||||||
</div>`,
|
|
||||||
$profile = web.html`<button class="profile-trigger">
|
|
||||||
Profile: ${web.escape(profileName)}
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
// profile
|
|
||||||
|
|
||||||
let _$profileConfig;
|
|
||||||
const openProfileMenu = async () => {
|
|
||||||
if (!_$profileConfig) {
|
|
||||||
const profileNames = [
|
|
||||||
...new Set([
|
|
||||||
...Object.keys(await storage.get(['profiles'], { default: {} })),
|
|
||||||
profileName,
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
$options = profileNames.map(
|
|
||||||
(profile) => web.raw`<option
|
|
||||||
class="select-option"
|
|
||||||
value="${web.escape(profile)}"
|
|
||||||
${profile === profileName ? 'selected' : ''}
|
|
||||||
>${web.escape(profile)}</option>`
|
|
||||||
),
|
|
||||||
$select = web.html`<select class="input">
|
|
||||||
<option class="select-option" value="--">-- new --</option>
|
|
||||||
${$options.join('')}
|
|
||||||
</select>`,
|
|
||||||
$edit = web.html`<input
|
|
||||||
type="text"
|
|
||||||
class="input"
|
|
||||||
value="${web.escape(profileName)}"
|
|
||||||
pattern="/^[A-Za-z0-9_-]+$/"
|
|
||||||
>`,
|
|
||||||
$export = web.html`<button class="profile-export">
|
|
||||||
${await components.feather('download', { class: 'profile-icon-action' })}
|
|
||||||
</button>`,
|
|
||||||
$import = web.html`<label class="profile-import">
|
|
||||||
<input type="file" class="hidden" accept="application/json">
|
|
||||||
${await components.feather('upload', { class: 'profile-icon-action' })}
|
|
||||||
</label>`,
|
|
||||||
$save = web.html`<button class="profile-save">
|
|
||||||
${await components.feather('save', { class: 'profile-icon-text' })} Save
|
|
||||||
</button>`,
|
|
||||||
$delete = web.html`<button class="profile-delete">
|
|
||||||
${await components.feather('trash-2', { class: 'profile-icon-text' })} Delete
|
|
||||||
</button>`,
|
|
||||||
$error = web.html`<p class="profile-error"></p>`;
|
|
||||||
|
|
||||||
$export.addEventListener('click', async (_event) => {
|
|
||||||
const now = new Date(),
|
|
||||||
$a = web.html`<a
|
|
||||||
class="hidden"
|
|
||||||
download="notion-enhancer_${web.escape($select.value)}_${now.getFullYear()}-${
|
|
||||||
now.getMonth() + 1
|
|
||||||
}-${now.getDate()}.json"
|
|
||||||
href="data:text/plain;charset=utf-8,${encodeURIComponent(
|
|
||||||
JSON.stringify(await storage.get(['profiles', $select.value], {}), null, 2)
|
|
||||||
)}"
|
|
||||||
></a>`;
|
|
||||||
web.render(document.body, $a);
|
|
||||||
$a.click();
|
|
||||||
$a.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
$import.addEventListener('change', (event) => {
|
|
||||||
const file = event.target.files[0],
|
|
||||||
reader = new FileReader();
|
|
||||||
reader.onload = async (progress) => {
|
|
||||||
try {
|
|
||||||
const profileUpload = JSON.parse(progress.currentTarget.result);
|
|
||||||
if (!profileUpload) throw Error;
|
|
||||||
await storage.set(['profiles', $select.value], profileUpload);
|
|
||||||
env.reload();
|
|
||||||
} catch {
|
|
||||||
web.render(web.empty($error), 'Invalid JSON uploaded.');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
|
||||||
|
window.addEventListener("message", async (event) => {
|
||||||
|
if (event.data?.namespace !== "notion-enhancer") return;
|
||||||
|
updateTheme(event.data?.mode);
|
||||||
|
await importApi();
|
||||||
|
await importStyles();
|
||||||
|
console.log(globalThis.__enhancerApi);
|
||||||
});
|
});
|
||||||
|
|
||||||
$select.addEventListener('change', (_event) => {
|
|
||||||
if ($select.value === '--') {
|
|
||||||
$edit.value = '';
|
|
||||||
} else $edit.value = $select.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
$save.addEventListener('click', async (_event) => {
|
|
||||||
if (profileNames.includes($edit.value) && $select.value !== $edit.value) {
|
|
||||||
web.render(
|
|
||||||
web.empty($error),
|
|
||||||
`The profile "${web.escape($edit.value)}" already exists.`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!$edit.value || !$edit.value.match(/^[A-Za-z0-9_-]+$/)) {
|
|
||||||
web.render(
|
|
||||||
web.empty($error),
|
|
||||||
'Profile names may not be empty & may only contain letters, numbers, hyphens and underscores.'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await storage.set(['currentprofile'], $edit.value);
|
|
||||||
if ($select.value === '--') {
|
|
||||||
await storage.set(['profiles', $edit.value], {});
|
|
||||||
} else if ($select.value !== $edit.value) {
|
|
||||||
await storage.set(
|
|
||||||
['profiles', $edit.value],
|
|
||||||
await storage.get(['profiles', $select.value], {})
|
|
||||||
);
|
|
||||||
await storage.set(['profiles', $select.value], undefined);
|
|
||||||
}
|
|
||||||
env.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
$delete.addEventListener('click', async (_event) => {
|
|
||||||
await storage.set(['profiles', $select.value], undefined);
|
|
||||||
await storage.set(
|
|
||||||
['currentprofile'],
|
|
||||||
profileNames.find((profile) => profile !== $select.value) || 'default'
|
|
||||||
);
|
|
||||||
env.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
_$profileConfig = web.render(
|
|
||||||
web.html`<div></div>`,
|
|
||||||
web.html`<p class="options-placeholder">
|
|
||||||
Profiles are used to switch entire configurations.<br>
|
|
||||||
Be careful - deleting a profile deletes all configuration
|
|
||||||
related to it.<br>
|
|
||||||
</p>`,
|
|
||||||
web.render(
|
|
||||||
web.html`<label class="input-label"></label>`,
|
|
||||||
$select,
|
|
||||||
web.html`${await components.feather('chevron-down', { class: 'input-icon' })}`
|
|
||||||
),
|
|
||||||
web.render(
|
|
||||||
web.html`<label class="input-label"></label>`,
|
|
||||||
$edit,
|
|
||||||
web.html`${await components.feather('type', { class: 'input-icon' })}`
|
|
||||||
),
|
|
||||||
web.render(
|
|
||||||
web.html`<p class="profile-actions"></p>`,
|
|
||||||
$export,
|
|
||||||
$import,
|
|
||||||
$save,
|
|
||||||
$delete
|
|
||||||
),
|
|
||||||
$error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
web.render(web.empty($options), _$profileConfig);
|
|
||||||
};
|
|
||||||
$profile.addEventListener('click', () => openSidebarMenu('profile'));
|
|
||||||
|
|
||||||
// mods
|
|
||||||
|
|
||||||
const $modLists = {},
|
|
||||||
generators = {
|
|
||||||
options: async (mod) => {
|
|
||||||
const $fragment = document.createDocumentFragment();
|
|
||||||
for (const opt of mod.options) {
|
|
||||||
if (!opt.environments.includes(env.name)) continue;
|
|
||||||
web.render($fragment, await options[opt.type](mod, opt));
|
|
||||||
}
|
|
||||||
if (!mod.options.length) {
|
|
||||||
web.render($fragment, web.html`<p class="options-placeholder">No options.</p>`);
|
|
||||||
}
|
|
||||||
return $fragment;
|
|
||||||
},
|
|
||||||
mod: async (mod) => {
|
|
||||||
const $mod = web.html`<div class="mod" data-id="${web.escape(mod.id)}"></div>`,
|
|
||||||
$toggle = modComponents.toggle('', await registry.enabled(mod.id));
|
|
||||||
$toggle.addEventListener('change', async (event) => {
|
|
||||||
if (event.target.checked && mod.tags.includes('theme')) {
|
|
||||||
const mode = mod.tags.includes('light') ? 'light' : 'dark',
|
|
||||||
id = mod.id,
|
|
||||||
mods = await registry.list(
|
|
||||||
async (mod) =>
|
|
||||||
(await registry.enabled(mod.id)) &&
|
|
||||||
mod.tags.includes('theme') &&
|
|
||||||
mod.tags.includes(mode) &&
|
|
||||||
mod.id !== id
|
|
||||||
);
|
|
||||||
for (const mod of mods) {
|
|
||||||
profileDB.set(['_mods', mod.id], false);
|
|
||||||
document.querySelector(
|
|
||||||
`[data-id="${web.escape(mod.id)}"] .toggle-check`
|
|
||||||
).checked = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
profileDB.set(['_mods', mod.id], event.target.checked);
|
|
||||||
notifications.onChange();
|
|
||||||
});
|
|
||||||
$mod.addEventListener('click', () => openSidebarMenu(mod.id));
|
|
||||||
return web.render(
|
|
||||||
web.html`<article class="mod-container"></article>`,
|
|
||||||
web.render(
|
|
||||||
$mod,
|
|
||||||
mod.preview
|
|
||||||
? modComponents.preview(
|
|
||||||
mod.preview.startsWith('http')
|
|
||||||
? mod.preview
|
|
||||||
: fs.localPath(`repo/${mod._dir}/${mod.preview}`)
|
|
||||||
)
|
|
||||||
: '',
|
|
||||||
web.render(
|
|
||||||
web.html`<div class="mod-body"></div>`,
|
|
||||||
web.render(modComponents.title(mod.name), modComponents.version(mod.version)),
|
|
||||||
modComponents.tags(mod.tags),
|
|
||||||
modComponents.description(mod.description),
|
|
||||||
modComponents.authors(mod.authors),
|
|
||||||
mod.environments.includes(env.name) && !registry.core.includes(mod.id)
|
|
||||||
? $toggle
|
|
||||||
: ''
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
modList: async (category, message = '') => {
|
|
||||||
if (!$modLists[category]) {
|
|
||||||
const $search = web.html`<input type="search" class="search"
|
|
||||||
placeholder="Search ('/' to focus)">`,
|
|
||||||
$list = web.html`<div class="mods-list"></div>`,
|
|
||||||
mods = await registry.list(
|
|
||||||
(mod) => mod.environments.includes(env.name) && mod.tags.includes(category)
|
|
||||||
);
|
|
||||||
web.addHotkeyListener(['/'], () => $search.focus());
|
|
||||||
$search.addEventListener('input', (_event) => {
|
|
||||||
const query = $search.value.toLowerCase();
|
|
||||||
for (const $mod of $list.children) {
|
|
||||||
const matches = !query || $mod.innerText.toLowerCase().includes(query);
|
|
||||||
$mod.classList[matches ? 'remove' : 'add']('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (const mod of mods) {
|
|
||||||
mod.tags = mod.tags.filter((tag) => tag !== category);
|
|
||||||
web.render($list, await generators.mod(mod));
|
|
||||||
mod.tags.unshift(category);
|
|
||||||
}
|
|
||||||
$modLists[category] = web.render(
|
|
||||||
web.html`<div></div>`,
|
|
||||||
web.render(
|
|
||||||
web.html`<label class="search-container"></label>`,
|
|
||||||
$search,
|
|
||||||
web.html`${await components.feather('search', { class: 'input-icon' })}`
|
|
||||||
),
|
|
||||||
message ? web.render(web.html`<p class="main-message"></p>`, message) : '',
|
|
||||||
$list
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $modLists[category];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function openModMenu(id) {
|
|
||||||
let $mod;
|
|
||||||
for (const $list of Object.values($modLists)) {
|
|
||||||
$mod = $list.querySelector(`[data-id="${web.escape(id)}"]`);
|
|
||||||
if ($mod) break;
|
|
||||||
}
|
|
||||||
const mod = await registry.get(id);
|
|
||||||
if (!$mod || !mod || $mod.className === 'mod-selected') return;
|
|
||||||
|
|
||||||
$mod.className = 'mod-selected';
|
|
||||||
const fragment = [
|
|
||||||
web.render(modComponents.title(mod.name), modComponents.version(mod.version)),
|
|
||||||
modComponents.tags(mod.tags),
|
|
||||||
await generators.options(mod),
|
|
||||||
];
|
|
||||||
web.render(web.empty($options), ...fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
// views
|
|
||||||
|
|
||||||
const $notionNavItem = web.html`<h1 class="nav-notion">
|
|
||||||
${(await fs.getText('media/colour.svg')).replace(
|
|
||||||
/width="\d+" height="\d+"/,
|
|
||||||
`class="nav-notion-icon"`
|
|
||||||
)}
|
|
||||||
<span>notion-enhancer</span>
|
|
||||||
</h1>`;
|
|
||||||
$notionNavItem.addEventListener('click', env.focusNotion);
|
|
||||||
|
|
||||||
const $coreNavItem = web.html`<a href="?view=core" class="nav-item">core</a>`,
|
|
||||||
$extensionsNavItem = web.html`<a href="?view=extensions" class="nav-item">extensions</a>`,
|
|
||||||
$themesNavItem = web.html`<a href="?view=themes" class="nav-item">themes</a>`,
|
|
||||||
$integrationsNavItem = web.html`<a href="?view=integrations" class="nav-item">integrations</a>`,
|
|
||||||
$changelogNavItem = web.html`<button class="nav-item nav-changelog">
|
|
||||||
${await components.feather('clock', { class: 'nav-changelog-icon' })}
|
|
||||||
</button>`;
|
|
||||||
components.addTooltip($changelogNavItem, '**Update changelog & welcome message**');
|
|
||||||
$changelogNavItem.addEventListener('click', () => {
|
|
||||||
$changelogModal.scrollTop = 0;
|
|
||||||
$changelogModal.classList.add('modal-visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
web.render(
|
|
||||||
document.body,
|
|
||||||
web.render(
|
|
||||||
web.html`<div class="body-container"></div>`,
|
|
||||||
web.render(
|
|
||||||
web.html`<div class="content-container"></div>`,
|
|
||||||
web.render(
|
|
||||||
web.html`<nav class="nav"></nav>`,
|
|
||||||
$notionNavItem,
|
|
||||||
$coreNavItem,
|
|
||||||
$extensionsNavItem,
|
|
||||||
$themesNavItem,
|
|
||||||
$integrationsNavItem,
|
|
||||||
web.html`<a href="https://notion-enhancer.github.io" target="_blank" class="nav-item">docs</a>`,
|
|
||||||
web.html`<a href="https://discord.gg/sFWPXtA" target="_blank" class="nav-item">community</a>`,
|
|
||||||
$changelogNavItem
|
|
||||||
),
|
|
||||||
$main
|
|
||||||
),
|
|
||||||
web.render($sidebar, $profile, $options)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
function selectNavItem($item) {
|
|
||||||
for (const $selected of document.querySelectorAll('.nav-item-selected')) {
|
|
||||||
$selected.className = 'nav-item';
|
|
||||||
}
|
|
||||||
$item.className = 'nav-item-selected';
|
|
||||||
}
|
|
||||||
|
|
||||||
await generators.modList(
|
|
||||||
'core',
|
|
||||||
`Core mods provide the basics required for
|
|
||||||
all other extensions and themes to work. They
|
|
||||||
can't be disabled, but they can be configured
|
|
||||||
- just click on a mod to access its options.`
|
|
||||||
);
|
|
||||||
router.addView('core', async () => {
|
|
||||||
web.empty($main);
|
|
||||||
selectNavItem($coreNavItem);
|
|
||||||
return web.render($main, await generators.modList('core'));
|
|
||||||
});
|
|
||||||
|
|
||||||
await generators.modList(
|
|
||||||
'extension',
|
|
||||||
`Extensions build on the functionality and layout of
|
|
||||||
the Notion client, modifying and interacting with
|
|
||||||
existing interfaces.`
|
|
||||||
);
|
|
||||||
router.addView('extensions', async () => {
|
|
||||||
web.empty($main);
|
|
||||||
selectNavItem($extensionsNavItem);
|
|
||||||
return web.render($main, await generators.modList('extension'));
|
|
||||||
});
|
|
||||||
|
|
||||||
await generators.modList(
|
|
||||||
'theme',
|
|
||||||
`Themes change Notion's colour scheme.
|
|
||||||
Dark themes will only work when Notion is in dark mode,
|
|
||||||
and light themes will only work when Notion is in light mode.
|
|
||||||
Only one theme of each mode can be enabled at a time.`
|
|
||||||
);
|
|
||||||
router.addView('themes', async () => {
|
|
||||||
web.empty($main);
|
|
||||||
selectNavItem($themesNavItem);
|
|
||||||
return web.render($main, await generators.modList('theme'));
|
|
||||||
});
|
|
||||||
|
|
||||||
await generators.modList(
|
|
||||||
'integration',
|
|
||||||
web.html`<span class="danger">Integrations are extensions that use an unofficial API
|
|
||||||
to access and modify content. They are used just like
|
|
||||||
normal extensions, but may be more dangerous to use.</span>`
|
|
||||||
);
|
|
||||||
router.addView('integrations', async () => {
|
|
||||||
web.empty($main);
|
|
||||||
selectNavItem($integrationsNavItem);
|
|
||||||
return web.render($main, await generators.modList('integration'));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.setDefaultView('extensions');
|
|
||||||
|
|
||||||
router.addQueryListener('id', openSidebarMenu);
|
|
||||||
function openSidebarMenu(id) {
|
|
||||||
if (!id) return;
|
|
||||||
id = web.escape(id);
|
|
||||||
|
|
||||||
const deselectedMods = `.mod-selected:not([data-id="${id}"])`;
|
|
||||||
for (const $list of Object.values($modLists)) {
|
|
||||||
for (const $selected of $list.querySelectorAll(deselectedMods)) {
|
|
||||||
$selected.className = 'mod';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
router.updateQuery(`?id=${id}`);
|
|
||||||
|
|
||||||
if (id === 'profile') {
|
|
||||||
openProfileMenu();
|
|
||||||
} else openModMenu(id);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer
|
* notion-enhancer: menu
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ::selection {
|
::selection {
|
||||||
background: var(--theme--accent_blue-selection);
|
background: var(--theme--accent_blue-selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,4 +22,4 @@
|
|||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--theme--scrollbar_thumb-hover) !important;
|
background: var(--theme--scrollbar_thumb-hover) !important;
|
||||||
} */
|
}
|
448
src/core/menuu/menu.mjs
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
/**
|
||||||
|
* notion-enhancer: menu
|
||||||
|
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as api from '../../api/index.mjs';
|
||||||
|
import { notifications, $changelogModal } from './notifications.mjs';
|
||||||
|
import { modComponents, options } from './components.mjs';
|
||||||
|
import * as router from './router.mjs';
|
||||||
|
import './styles.mjs';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { env, fs, storage, electron, registry, web, components } = api;
|
||||||
|
|
||||||
|
for (const mod of await registry.list((mod) => registry.enabled(mod.id))) {
|
||||||
|
for (let script of mod.js?.menu || []) {
|
||||||
|
script = await import(fs.localPath(`repo/${mod._dir}/${script}`));
|
||||||
|
script.default(api, await registry.db(mod.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const errors = await registry.errors();
|
||||||
|
if (errors.length) {
|
||||||
|
console.error('[notion-enhancer] registry errors:');
|
||||||
|
console.table(errors);
|
||||||
|
const $errNotification = await notifications.add({
|
||||||
|
icon: 'alert-circle',
|
||||||
|
message: 'Failed to load mods (check console).',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
if (['win32', 'linux', 'darwin'].includes(env.name)) {
|
||||||
|
$errNotification.addEventListener('click', () => electron.browser.openDevTools());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e'),
|
||||||
|
profileName = await registry.profileName(),
|
||||||
|
profileDB = await registry.profileDB();
|
||||||
|
|
||||||
|
web.addHotkeyListener(await db.get(['hotkey']), env.focusNotion);
|
||||||
|
|
||||||
|
globalThis.addEventListener('beforeunload', (_event) => {
|
||||||
|
// trigger input save
|
||||||
|
document.activeElement.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
const $main = web.html`<main class="main"></main>`,
|
||||||
|
$sidebar = web.html`<article class="sidebar"></article>`,
|
||||||
|
$options = web.html`<div class="options-container">
|
||||||
|
<p class="options-placeholder">Select a mod to view and configure its options.</p>
|
||||||
|
</div>`,
|
||||||
|
$profile = web.html`<button class="profile-trigger">
|
||||||
|
Profile: ${web.escape(profileName)}
|
||||||
|
</button>`;
|
||||||
|
|
||||||
|
// profile
|
||||||
|
|
||||||
|
let _$profileConfig;
|
||||||
|
const openProfileMenu = async () => {
|
||||||
|
if (!_$profileConfig) {
|
||||||
|
const profileNames = [
|
||||||
|
...new Set([
|
||||||
|
...Object.keys(await storage.get(['profiles'], { default: {} })),
|
||||||
|
profileName,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
$options = profileNames.map(
|
||||||
|
(profile) => web.raw`<option
|
||||||
|
class="select-option"
|
||||||
|
value="${web.escape(profile)}"
|
||||||
|
${profile === profileName ? 'selected' : ''}
|
||||||
|
>${web.escape(profile)}</option>`
|
||||||
|
),
|
||||||
|
$select = web.html`<select class="input">
|
||||||
|
<option class="select-option" value="--">-- new --</option>
|
||||||
|
${$options.join('')}
|
||||||
|
</select>`,
|
||||||
|
$edit = web.html`<input
|
||||||
|
type="text"
|
||||||
|
class="input"
|
||||||
|
value="${web.escape(profileName)}"
|
||||||
|
pattern="/^[A-Za-z0-9_-]+$/"
|
||||||
|
>`,
|
||||||
|
$export = web.html`<button class="profile-export">
|
||||||
|
${await components.feather('download', { class: 'profile-icon-action' })}
|
||||||
|
</button>`,
|
||||||
|
$import = web.html`<label class="profile-import">
|
||||||
|
<input type="file" class="hidden" accept="application/json">
|
||||||
|
${await components.feather('upload', { class: 'profile-icon-action' })}
|
||||||
|
</label>`,
|
||||||
|
$save = web.html`<button class="profile-save">
|
||||||
|
${await components.feather('save', { class: 'profile-icon-text' })} Save
|
||||||
|
</button>`,
|
||||||
|
$delete = web.html`<button class="profile-delete">
|
||||||
|
${await components.feather('trash-2', { class: 'profile-icon-text' })} Delete
|
||||||
|
</button>`,
|
||||||
|
$error = web.html`<p class="profile-error"></p>`;
|
||||||
|
|
||||||
|
$export.addEventListener('click', async (_event) => {
|
||||||
|
const now = new Date(),
|
||||||
|
$a = web.html`<a
|
||||||
|
class="hidden"
|
||||||
|
download="notion-enhancer_${web.escape($select.value)}_${now.getFullYear()}-${
|
||||||
|
now.getMonth() + 1
|
||||||
|
}-${now.getDate()}.json"
|
||||||
|
href="data:text/plain;charset=utf-8,${encodeURIComponent(
|
||||||
|
JSON.stringify(await storage.get(['profiles', $select.value], {}), null, 2)
|
||||||
|
)}"
|
||||||
|
></a>`;
|
||||||
|
web.render(document.body, $a);
|
||||||
|
$a.click();
|
||||||
|
$a.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
$import.addEventListener('change', (event) => {
|
||||||
|
const file = event.target.files[0],
|
||||||
|
reader = new FileReader();
|
||||||
|
reader.onload = async (progress) => {
|
||||||
|
try {
|
||||||
|
const profileUpload = JSON.parse(progress.currentTarget.result);
|
||||||
|
if (!profileUpload) throw Error;
|
||||||
|
await storage.set(['profiles', $select.value], profileUpload);
|
||||||
|
env.reload();
|
||||||
|
} catch {
|
||||||
|
web.render(web.empty($error), 'Invalid JSON uploaded.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
$select.addEventListener('change', (_event) => {
|
||||||
|
if ($select.value === '--') {
|
||||||
|
$edit.value = '';
|
||||||
|
} else $edit.value = $select.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
$save.addEventListener('click', async (_event) => {
|
||||||
|
if (profileNames.includes($edit.value) && $select.value !== $edit.value) {
|
||||||
|
web.render(
|
||||||
|
web.empty($error),
|
||||||
|
`The profile "${web.escape($edit.value)}" already exists.`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!$edit.value || !$edit.value.match(/^[A-Za-z0-9_-]+$/)) {
|
||||||
|
web.render(
|
||||||
|
web.empty($error),
|
||||||
|
'Profile names may not be empty & may only contain letters, numbers, hyphens and underscores.'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await storage.set(['currentprofile'], $edit.value);
|
||||||
|
if ($select.value === '--') {
|
||||||
|
await storage.set(['profiles', $edit.value], {});
|
||||||
|
} else if ($select.value !== $edit.value) {
|
||||||
|
await storage.set(
|
||||||
|
['profiles', $edit.value],
|
||||||
|
await storage.get(['profiles', $select.value], {})
|
||||||
|
);
|
||||||
|
await storage.set(['profiles', $select.value], undefined);
|
||||||
|
}
|
||||||
|
env.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
$delete.addEventListener('click', async (_event) => {
|
||||||
|
await storage.set(['profiles', $select.value], undefined);
|
||||||
|
await storage.set(
|
||||||
|
['currentprofile'],
|
||||||
|
profileNames.find((profile) => profile !== $select.value) || 'default'
|
||||||
|
);
|
||||||
|
env.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
_$profileConfig = web.render(
|
||||||
|
web.html`<div></div>`,
|
||||||
|
web.html`<p class="options-placeholder">
|
||||||
|
Profiles are used to switch entire configurations.<br>
|
||||||
|
Be careful - deleting a profile deletes all configuration
|
||||||
|
related to it.<br>
|
||||||
|
</p>`,
|
||||||
|
web.render(
|
||||||
|
web.html`<label class="input-label"></label>`,
|
||||||
|
$select,
|
||||||
|
web.html`${await components.feather('chevron-down', { class: 'input-icon' })}`
|
||||||
|
),
|
||||||
|
web.render(
|
||||||
|
web.html`<label class="input-label"></label>`,
|
||||||
|
$edit,
|
||||||
|
web.html`${await components.feather('type', { class: 'input-icon' })}`
|
||||||
|
),
|
||||||
|
web.render(
|
||||||
|
web.html`<p class="profile-actions"></p>`,
|
||||||
|
$export,
|
||||||
|
$import,
|
||||||
|
$save,
|
||||||
|
$delete
|
||||||
|
),
|
||||||
|
$error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
web.render(web.empty($options), _$profileConfig);
|
||||||
|
};
|
||||||
|
$profile.addEventListener('click', () => openSidebarMenu('profile'));
|
||||||
|
|
||||||
|
// mods
|
||||||
|
|
||||||
|
const $modLists = {},
|
||||||
|
generators = {
|
||||||
|
options: async (mod) => {
|
||||||
|
const $fragment = document.createDocumentFragment();
|
||||||
|
for (const opt of mod.options) {
|
||||||
|
if (!opt.environments.includes(env.name)) continue;
|
||||||
|
web.render($fragment, await options[opt.type](mod, opt));
|
||||||
|
}
|
||||||
|
if (!mod.options.length) {
|
||||||
|
web.render($fragment, web.html`<p class="options-placeholder">No options.</p>`);
|
||||||
|
}
|
||||||
|
return $fragment;
|
||||||
|
},
|
||||||
|
mod: async (mod) => {
|
||||||
|
const $mod = web.html`<div class="mod" data-id="${web.escape(mod.id)}"></div>`,
|
||||||
|
$toggle = modComponents.toggle('', await registry.enabled(mod.id));
|
||||||
|
$toggle.addEventListener('change', async (event) => {
|
||||||
|
if (event.target.checked && mod.tags.includes('theme')) {
|
||||||
|
const mode = mod.tags.includes('light') ? 'light' : 'dark',
|
||||||
|
id = mod.id,
|
||||||
|
mods = await registry.list(
|
||||||
|
async (mod) =>
|
||||||
|
(await registry.enabled(mod.id)) &&
|
||||||
|
mod.tags.includes('theme') &&
|
||||||
|
mod.tags.includes(mode) &&
|
||||||
|
mod.id !== id
|
||||||
|
);
|
||||||
|
for (const mod of mods) {
|
||||||
|
profileDB.set(['_mods', mod.id], false);
|
||||||
|
document.querySelector(
|
||||||
|
`[data-id="${web.escape(mod.id)}"] .toggle-check`
|
||||||
|
).checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profileDB.set(['_mods', mod.id], event.target.checked);
|
||||||
|
notifications.onChange();
|
||||||
|
});
|
||||||
|
$mod.addEventListener('click', () => openSidebarMenu(mod.id));
|
||||||
|
return web.render(
|
||||||
|
web.html`<article class="mod-container"></article>`,
|
||||||
|
web.render(
|
||||||
|
$mod,
|
||||||
|
mod.preview
|
||||||
|
? modComponents.preview(
|
||||||
|
mod.preview.startsWith('http')
|
||||||
|
? mod.preview
|
||||||
|
: fs.localPath(`repo/${mod._dir}/${mod.preview}`)
|
||||||
|
)
|
||||||
|
: '',
|
||||||
|
web.render(
|
||||||
|
web.html`<div class="mod-body"></div>`,
|
||||||
|
web.render(modComponents.title(mod.name), modComponents.version(mod.version)),
|
||||||
|
modComponents.tags(mod.tags),
|
||||||
|
modComponents.description(mod.description),
|
||||||
|
modComponents.authors(mod.authors),
|
||||||
|
mod.environments.includes(env.name) && !registry.core.includes(mod.id)
|
||||||
|
? $toggle
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
modList: async (category, message = '') => {
|
||||||
|
if (!$modLists[category]) {
|
||||||
|
const $search = web.html`<input type="search" class="search"
|
||||||
|
placeholder="Search ('/' to focus)">`,
|
||||||
|
$list = web.html`<div class="mods-list"></div>`,
|
||||||
|
mods = await registry.list(
|
||||||
|
(mod) => mod.environments.includes(env.name) && mod.tags.includes(category)
|
||||||
|
);
|
||||||
|
web.addHotkeyListener(['/'], () => $search.focus());
|
||||||
|
$search.addEventListener('input', (_event) => {
|
||||||
|
const query = $search.value.toLowerCase();
|
||||||
|
for (const $mod of $list.children) {
|
||||||
|
const matches = !query || $mod.innerText.toLowerCase().includes(query);
|
||||||
|
$mod.classList[matches ? 'remove' : 'add']('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (const mod of mods) {
|
||||||
|
mod.tags = mod.tags.filter((tag) => tag !== category);
|
||||||
|
web.render($list, await generators.mod(mod));
|
||||||
|
mod.tags.unshift(category);
|
||||||
|
}
|
||||||
|
$modLists[category] = web.render(
|
||||||
|
web.html`<div></div>`,
|
||||||
|
web.render(
|
||||||
|
web.html`<label class="search-container"></label>`,
|
||||||
|
$search,
|
||||||
|
web.html`${await components.feather('search', { class: 'input-icon' })}`
|
||||||
|
),
|
||||||
|
message ? web.render(web.html`<p class="main-message"></p>`, message) : '',
|
||||||
|
$list
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $modLists[category];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function openModMenu(id) {
|
||||||
|
let $mod;
|
||||||
|
for (const $list of Object.values($modLists)) {
|
||||||
|
$mod = $list.querySelector(`[data-id="${web.escape(id)}"]`);
|
||||||
|
if ($mod) break;
|
||||||
|
}
|
||||||
|
const mod = await registry.get(id);
|
||||||
|
if (!$mod || !mod || $mod.className === 'mod-selected') return;
|
||||||
|
|
||||||
|
$mod.className = 'mod-selected';
|
||||||
|
const fragment = [
|
||||||
|
web.render(modComponents.title(mod.name), modComponents.version(mod.version)),
|
||||||
|
modComponents.tags(mod.tags),
|
||||||
|
await generators.options(mod),
|
||||||
|
];
|
||||||
|
web.render(web.empty($options), ...fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// views
|
||||||
|
|
||||||
|
const $notionNavItem = web.html`<h1 class="nav-notion">
|
||||||
|
${(await fs.getText('media/colour.svg')).replace(
|
||||||
|
/width="\d+" height="\d+"/,
|
||||||
|
`class="nav-notion-icon"`
|
||||||
|
)}
|
||||||
|
<span>notion-enhancer</span>
|
||||||
|
</h1>`;
|
||||||
|
$notionNavItem.addEventListener('click', env.focusNotion);
|
||||||
|
|
||||||
|
const $coreNavItem = web.html`<a href="?view=core" class="nav-item">core</a>`,
|
||||||
|
$extensionsNavItem = web.html`<a href="?view=extensions" class="nav-item">extensions</a>`,
|
||||||
|
$themesNavItem = web.html`<a href="?view=themes" class="nav-item">themes</a>`,
|
||||||
|
$integrationsNavItem = web.html`<a href="?view=integrations" class="nav-item">integrations</a>`,
|
||||||
|
$changelogNavItem = web.html`<button class="nav-item nav-changelog">
|
||||||
|
${await components.feather('clock', { class: 'nav-changelog-icon' })}
|
||||||
|
</button>`;
|
||||||
|
components.addTooltip($changelogNavItem, '**Update changelog & welcome message**');
|
||||||
|
$changelogNavItem.addEventListener('click', () => {
|
||||||
|
$changelogModal.scrollTop = 0;
|
||||||
|
$changelogModal.classList.add('modal-visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
web.render(
|
||||||
|
document.body,
|
||||||
|
web.render(
|
||||||
|
web.html`<div class="body-container"></div>`,
|
||||||
|
web.render(
|
||||||
|
web.html`<div class="content-container"></div>`,
|
||||||
|
web.render(
|
||||||
|
web.html`<nav class="nav"></nav>`,
|
||||||
|
$notionNavItem,
|
||||||
|
$coreNavItem,
|
||||||
|
$extensionsNavItem,
|
||||||
|
$themesNavItem,
|
||||||
|
$integrationsNavItem,
|
||||||
|
web.html`<a href="https://notion-enhancer.github.io" target="_blank" class="nav-item">docs</a>`,
|
||||||
|
web.html`<a href="https://discord.gg/sFWPXtA" target="_blank" class="nav-item">community</a>`,
|
||||||
|
$changelogNavItem
|
||||||
|
),
|
||||||
|
$main
|
||||||
|
),
|
||||||
|
web.render($sidebar, $profile, $options)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
function selectNavItem($item) {
|
||||||
|
for (const $selected of document.querySelectorAll('.nav-item-selected')) {
|
||||||
|
$selected.className = 'nav-item';
|
||||||
|
}
|
||||||
|
$item.className = 'nav-item-selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
await generators.modList(
|
||||||
|
'core',
|
||||||
|
`Core mods provide the basics required for
|
||||||
|
all other extensions and themes to work. They
|
||||||
|
can't be disabled, but they can be configured
|
||||||
|
- just click on a mod to access its options.`
|
||||||
|
);
|
||||||
|
router.addView('core', async () => {
|
||||||
|
web.empty($main);
|
||||||
|
selectNavItem($coreNavItem);
|
||||||
|
return web.render($main, await generators.modList('core'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await generators.modList(
|
||||||
|
'extension',
|
||||||
|
`Extensions build on the functionality and layout of
|
||||||
|
the Notion client, modifying and interacting with
|
||||||
|
existing interfaces.`
|
||||||
|
);
|
||||||
|
router.addView('extensions', async () => {
|
||||||
|
web.empty($main);
|
||||||
|
selectNavItem($extensionsNavItem);
|
||||||
|
return web.render($main, await generators.modList('extension'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await generators.modList(
|
||||||
|
'theme',
|
||||||
|
`Themes change Notion's colour scheme.
|
||||||
|
Dark themes will only work when Notion is in dark mode,
|
||||||
|
and light themes will only work when Notion is in light mode.
|
||||||
|
Only one theme of each mode can be enabled at a time.`
|
||||||
|
);
|
||||||
|
router.addView('themes', async () => {
|
||||||
|
web.empty($main);
|
||||||
|
selectNavItem($themesNavItem);
|
||||||
|
return web.render($main, await generators.modList('theme'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await generators.modList(
|
||||||
|
'integration',
|
||||||
|
web.html`<span class="danger">Integrations are extensions that use an unofficial API
|
||||||
|
to access and modify content. They are used just like
|
||||||
|
normal extensions, but may be more dangerous to use.</span>`
|
||||||
|
);
|
||||||
|
router.addView('integrations', async () => {
|
||||||
|
web.empty($main);
|
||||||
|
selectNavItem($integrationsNavItem);
|
||||||
|
return web.render($main, await generators.modList('integration'));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.setDefaultView('extensions');
|
||||||
|
|
||||||
|
router.addQueryListener('id', openSidebarMenu);
|
||||||
|
function openSidebarMenu(id) {
|
||||||
|
if (!id) return;
|
||||||
|
id = web.escape(id);
|
||||||
|
|
||||||
|
const deselectedMods = `.mod-selected:not([data-id="${id}"])`;
|
||||||
|
for (const $list of Object.values($modLists)) {
|
||||||
|
for (const $selected of $list.querySelectorAll(deselectedMods)) {
|
||||||
|
$selected.className = 'mod';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.updateQuery(`?id=${id}`);
|
||||||
|
|
||||||
|
if (id === 'profile') {
|
||||||
|
openProfileMenu();
|
||||||
|
} else openModMenu(id);
|
||||||
|
}
|
||||||
|
})();
|
@ -20,7 +20,7 @@
|
|||||||
"type": "hotkey",
|
"type": "hotkey",
|
||||||
"key": "openMenuHotkey",
|
"key": "openMenuHotkey",
|
||||||
"description": "Opens the notion-enhancer menu from within Notion.",
|
"description": "Opens the notion-enhancer menu from within Notion.",
|
||||||
"value": "CmdOrCtrl+Shift+,"
|
"value": "Ctrl+Shift+Comma"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "heading",
|
"type": "heading",
|
||||||
@ -52,10 +52,11 @@
|
|||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"key": "debugMode",
|
"key": "debugMode",
|
||||||
"description": "Activates built-in debugging tools accessible through the application menu.",
|
"description": "Activates built-in debugging tools accessible through the application menu.",
|
||||||
|
"targets": ["darwin", "win32", "linux"],
|
||||||
"value": false
|
"value": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"clientStyles": ["client.css"],
|
"clientStyles": ["client.css", "variables.css"],
|
||||||
"clientScripts": ["client.mjs"],
|
"clientScripts": ["client.mjs"],
|
||||||
"electronScripts": []
|
"electronScripts": []
|
||||||
}
|
}
|
||||||
|
1997
src/core/theme.css
@ -42,16 +42,16 @@ body.dark {
|
|||||||
--theme--bg-purple: rgb(73, 47, 100);
|
--theme--bg-purple: rgb(73, 47, 100);
|
||||||
--theme--bg-pink: rgb(105, 49, 76);
|
--theme--bg-pink: rgb(105, 49, 76);
|
||||||
--theme--bg-red: rgb(110, 54, 48);
|
--theme--bg-red: rgb(110, 54, 48);
|
||||||
--theme--bg_dim-light_gray: rgb(28, 28, 28);
|
--theme--dim-light_gray: rgb(28, 28, 28);
|
||||||
--theme--bg_dim-gray: rgb(32, 32, 32);
|
--theme--dim-gray: rgb(32, 32, 32);
|
||||||
--theme--bg_dim-brown: rgb(35, 30, 28);
|
--theme--dim-brown: rgb(35, 30, 28);
|
||||||
--theme--bg_dim-orange: rgb(37, 31, 27);
|
--theme--dim-orange: rgb(37, 31, 27);
|
||||||
--theme--bg_dim-yellow: rgb(35, 31, 26);
|
--theme--dim-yellow: rgb(35, 31, 26);
|
||||||
--theme--bg_dim-green: rgb(29, 34, 32);
|
--theme--dim-green: rgb(29, 34, 32);
|
||||||
--theme--bg_dim-blue: rgb(27, 31, 34);
|
--theme--dim-blue: rgb(27, 31, 34);
|
||||||
--theme--bg_dim-purple: rgb(31, 29, 33);
|
--theme--dim-purple: rgb(31, 29, 33);
|
||||||
--theme--bg_dim-pink: rgb(35, 28, 31);
|
--theme--dim-pink: rgb(35, 28, 31);
|
||||||
--theme--bg_dim-red: rgb(36, 30, 29);
|
--theme--dim-red: rgb(36, 30, 29);
|
||||||
|
|
||||||
--theme--accent-primary: rgb(35, 131, 226);
|
--theme--accent-primary: rgb(35, 131, 226);
|
||||||
--theme--accent-primary_hover: rgb(0, 117, 211);
|
--theme--accent-primary_hover: rgb(0, 117, 211);
|
||||||
@ -139,16 +139,16 @@ body:not(.dark) {
|
|||||||
--theme--bg-purple: rgb(232, 222, 238);
|
--theme--bg-purple: rgb(232, 222, 238);
|
||||||
--theme--bg-pink: rgb(245, 224, 233);
|
--theme--bg-pink: rgb(245, 224, 233);
|
||||||
--theme--bg-red: rgb(255, 226, 221);
|
--theme--bg-red: rgb(255, 226, 221);
|
||||||
--theme--bg_dim-light_gray: rgba(249, 249, 245, 0.5);
|
--theme--dim-light_gray: rgba(249, 249, 245, 0.5);
|
||||||
--theme--bg_dim-gray: rgba(247, 247, 245, 0.7);
|
--theme--dim-gray: rgba(247, 247, 245, 0.7);
|
||||||
--theme--bg_dim-brown: rgba(250, 246, 245, 0.7);
|
--theme--dim-brown: rgba(250, 246, 245, 0.7);
|
||||||
--theme--bg_dim-orange: rgba(252, 245, 242, 0.7);
|
--theme--dim-orange: rgba(252, 245, 242, 0.7);
|
||||||
--theme--bg_dim-yellow: rgba(250, 247, 237, 0.7);
|
--theme--dim-yellow: rgba(250, 247, 237, 0.7);
|
||||||
--theme--bg_dim-green: rgba(244, 248, 243, 0.7);
|
--theme--dim-green: rgba(244, 248, 243, 0.7);
|
||||||
--theme--bg_dim-blue: rgba(241, 248, 251, 0.7);
|
--theme--dim-blue: rgba(241, 248, 251, 0.7);
|
||||||
--theme--bg_dim-purple: rgba(249, 246, 252, 0.7);
|
--theme--dim-purple: rgba(249, 246, 252, 0.7);
|
||||||
--theme--bg_dim-pink: rgba(251, 245, 251, 0.7);
|
--theme--dim-pink: rgba(251, 245, 251, 0.7);
|
||||||
--theme--bg_dim-red: rgba(253, 245, 243, 0.7);
|
--theme--dim-red: rgba(253, 245, 243, 0.7);
|
||||||
|
|
||||||
--theme--accent-primary: rgb(35, 131, 226);
|
--theme--accent-primary: rgb(35, 131, 226);
|
||||||
--theme--accent-primary_hover: rgb(0, 117, 211);
|
--theme--accent-primary_hover: rgb(0, 117, 211);
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
require("./api.cjs");
|
|
||||||
require("../common/api.js");
|
|
||||||
|
|
||||||
module.exports = async (target, __exports, __eval) => {
|
|
||||||
const {
|
|
||||||
getMods,
|
|
||||||
getProfile,
|
|
||||||
isEnabled,
|
|
||||||
enhancerRequire,
|
|
||||||
enhancerUrl,
|
|
||||||
initDatabase,
|
|
||||||
} = globalThis.__enhancerApi;
|
|
||||||
|
|
||||||
// clientScripts
|
|
||||||
if (target === "renderer/preload") {
|
|
||||||
document.addEventListener("readystatechange", (event) => {
|
|
||||||
if (document.readyState !== "complete") return false;
|
|
||||||
const $script = document.createElement("script");
|
|
||||||
$script.type = "module";
|
|
||||||
$script.src = enhancerUrl("common/loader.mjs");
|
|
||||||
document.head.appendChild($script);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// electronScripts
|
|
||||||
for (const mod of await getMods()) {
|
|
||||||
if (!mod.electronScripts || !(await isEnabled(mod.id))) continue;
|
|
||||||
for (const { source, target: targetScript } of mod.electronScripts) {
|
|
||||||
if (`${target}.js` !== targetScript) continue;
|
|
||||||
const script = enhancerRequire(`${mod._src}/${source}`),
|
|
||||||
db = initDatabase([await getProfile(), mod.id]);
|
|
||||||
script(globalThis.__enhancerApi, db, __exports, __eval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |