mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-10 15:39:01 +00:00
feat: custom userscripts in-app and in-browser!
firefox support will involve some tweaking, manifest v3 not fully supported yet
This commit is contained in:
parent
607fcee4f8
commit
71f9ecc32b
@ -185,7 +185,7 @@ const renderMenu = async () => {
|
|||||||
const importApi = () => {
|
const importApi = () => {
|
||||||
return (_apiImport ??= (async () => {
|
return (_apiImport ??= (async () => {
|
||||||
const api = globalThis.__enhancerApi;
|
const api = globalThis.__enhancerApi;
|
||||||
if (typeof api === "undefined") await import("../../common/system.js");
|
if (typeof api === "undefined") await import("../../api/system.js");
|
||||||
await import("../../load.mjs").then((i) => i.default);
|
await import("../../load.mjs").then((i) => i.default);
|
||||||
})());
|
})());
|
||||||
},
|
},
|
||||||
|
@ -61,6 +61,13 @@
|
|||||||
"label": "Advanced",
|
"label": "Advanced",
|
||||||
"_autoremoveIfSectionEmpty": false
|
"_autoremoveIfSectionEmpty": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"label": "Custom JavaScript",
|
||||||
|
"key": "customScript",
|
||||||
|
"description": "Executes the uploaded userscript within Notion. Requires <a href='https://developer.chrome.com/docs/extensions/reference/api/userScripts#developer_mode_for_extension_users'>developer mode</a> to be enabled in your browser's extension settings to run in Chromium-based browsers.",
|
||||||
|
"extensions": ["js"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"key": "developerMode",
|
"key": "developerMode",
|
||||||
|
10
src/init.js
10
src/init.js
@ -19,7 +19,7 @@ if (isElectron()) {
|
|||||||
|
|
||||||
const { enhancerUrl } = globalThis.__enhancerApi,
|
const { enhancerUrl } = globalThis.__enhancerApi,
|
||||||
{ getMods, isEnabled, modDatabase } = globalThis.__enhancerApi,
|
{ getMods, isEnabled, modDatabase } = globalThis.__enhancerApi,
|
||||||
API_LOADED = new Promise((res, rej) => {
|
API_LOADED = new Promise((res) => {
|
||||||
const onReady = globalThis.__enhancerReady;
|
const onReady = globalThis.__enhancerReady;
|
||||||
globalThis.__enhancerReady = () => (onReady?.(), res());
|
globalThis.__enhancerReady = () => (onReady?.(), res());
|
||||||
});
|
});
|
||||||
@ -33,12 +33,18 @@ if (isElectron()) {
|
|||||||
contextBridge.exposeInMainWorld("__getEnhancerApi", __getApi);
|
contextBridge.exposeInMainWorld("__getEnhancerApi", __getApi);
|
||||||
|
|
||||||
// load clientStyles, clientScripts
|
// load clientStyles, clientScripts
|
||||||
document.addEventListener("readystatechange", () => {
|
document.addEventListener("readystatechange", async () => {
|
||||||
if (document.readyState !== "complete") return false;
|
if (document.readyState !== "complete") return false;
|
||||||
const $script = document.createElement("script");
|
const $script = document.createElement("script");
|
||||||
$script.type = "module";
|
$script.type = "module";
|
||||||
$script.src = enhancerUrl("load.mjs");
|
$script.src = enhancerUrl("load.mjs");
|
||||||
document.head.append($script);
|
document.head.append($script);
|
||||||
|
|
||||||
|
// register user-provided javascript for execution in-app
|
||||||
|
const { webFrame } = require("electron"),
|
||||||
|
db = await modDatabase("0f0bf8b6-eae6-4273-b307-8fc43f2ee082"),
|
||||||
|
customScript = (await db.get("customScript"))?.content;
|
||||||
|
if (customScript) webFrame.executeJavaScript(customScript);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ export default (async () => {
|
|||||||
// the dom must be re-imported
|
// the dom must be re-imported
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
IS_ELECTRON || import(enhancerUrl("common/registry.js")),
|
IS_ELECTRON || import(enhancerUrl("api/registry.js")),
|
||||||
import(enhancerUrl("api/interface.mjs")),
|
import(enhancerUrl("api/interface.mjs")),
|
||||||
import(enhancerUrl("api/state.js")),
|
import(enhancerUrl("api/state.js")),
|
||||||
]);
|
]);
|
||||||
|
@ -19,15 +19,13 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"tabs",
|
"tabs",
|
||||||
"storage",
|
"storage",
|
||||||
|
"userScripts",
|
||||||
"clipboardRead",
|
"clipboardRead",
|
||||||
"clipboardWrite",
|
"clipboardWrite",
|
||||||
"unlimitedStorage"
|
"unlimitedStorage"
|
||||||
],
|
],
|
||||||
"host_permissions": ["*://*.notion.so/*"],
|
"host_permissions": ["*://*.notion.so/*"],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{ "matches": ["*://*.notion.so/*"], "resources": ["/*"] }
|
||||||
"matches": ["*://*.notion.so/*"],
|
|
||||||
"resources": ["/*"]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ const initDatabase = async () => {
|
|||||||
value = JSON.parse(__statements.select.get(key)?.value);
|
value = JSON.parse(__statements.select.get(key)?.value);
|
||||||
} catch {}
|
} catch {}
|
||||||
} else value = (await chrome.storage.local.get([key]))[key];
|
} else value = (await chrome.storage.local.get([key]))[key];
|
||||||
return value ?? args.fallbacks[args.key];
|
return value ?? args.fallbacks?.[args.key];
|
||||||
}
|
}
|
||||||
case "set": {
|
case "set": {
|
||||||
const key = namespaceify(args.key),
|
const key = namespaceify(args.key),
|
||||||
@ -89,7 +89,6 @@ const initDatabase = async () => {
|
|||||||
? (__transactions.remove(keys), true)
|
? (__transactions.remove(keys), true)
|
||||||
: chrome.storage.local.remove(keys);
|
: chrome.storage.local.remove(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
case "export": {
|
case "export": {
|
||||||
// returns key/value pairs within scope w/out namespace
|
// returns key/value pairs within scope w/out namespace
|
||||||
// prefix e.g. to streamline importing from one profile and
|
// prefix e.g. to streamline importing from one profile and
|
||||||
@ -149,6 +148,39 @@ if (IS_ELECTRON) {
|
|||||||
.forEach((tab) => chrome.tabs.reload(tab.id));
|
.forEach((tab) => chrome.tabs.reload(tab.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const userScriptsAvailable = () => {
|
||||||
|
// manifest v3 userscripts require developer mode to be
|
||||||
|
// enabled in the browser's extension settings
|
||||||
|
try {
|
||||||
|
chrome.userScripts;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
registerCustomScript = async () => {
|
||||||
|
if (!userScriptsAvailable()) return;
|
||||||
|
// enhancer apis are not available in the worker in-browser,
|
||||||
|
// manual steps are required to get nested values from the db
|
||||||
|
const key = "customScript",
|
||||||
|
matches = ["*://*.notion.so/*"],
|
||||||
|
coreId = "0f0bf8b6-eae6-4273-b307-8fc43f2ee082",
|
||||||
|
profileId =
|
||||||
|
(await queryDatabase([], "get", { key: "activeProfile" })) ??
|
||||||
|
(await queryDatabase([], "get", { key: "profileIds" }))?.[0] ??
|
||||||
|
"default",
|
||||||
|
customScript = await queryDatabase([profileId, coreId], "get", { key }),
|
||||||
|
existingScripts = await chrome.userScripts.getScripts({ ids: [key] }),
|
||||||
|
code = customScript?.content || "";
|
||||||
|
if (existingScripts[0]) {
|
||||||
|
if (code === existingScripts[0]?.code) return;
|
||||||
|
chrome.userScripts.update([{ id: key, matches, js: [{ code }] }]);
|
||||||
|
} else if (code) {
|
||||||
|
chrome.userScripts.register([{ id: key, matches, js: [{ code }] }]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
registerCustomScript();
|
||||||
|
|
||||||
chrome.action.onClicked.addListener(openEnhancerMenu);
|
chrome.action.onClicked.addListener(openEnhancerMenu);
|
||||||
// long-lived connection for rapid two-way messaging
|
// long-lived connection for rapid two-way messaging
|
||||||
// b/w client and worker, primarily used for db wrapper:
|
// b/w client and worker, primarily used for db wrapper:
|
||||||
@ -163,6 +195,13 @@ if (IS_ELECTRON) {
|
|||||||
const { namespace, query, args } = message.data,
|
const { namespace, query, args } = message.data,
|
||||||
res = await queryDatabase(namespace, query, args);
|
res = await queryDatabase(namespace, query, args);
|
||||||
if (invocation) port.postMessage({ invocation, message: res });
|
if (invocation) port.postMessage({ invocation, message: res });
|
||||||
|
// re-register userscript on updates:
|
||||||
|
// profile change, db import, file upload, file deletion
|
||||||
|
const customScriptChanged =
|
||||||
|
query === "import" ||
|
||||||
|
(query === "set" &&
|
||||||
|
["activeProfile", "customScript"].includes(args.key));
|
||||||
|
if (customScriptChanged) registerCustomScript();
|
||||||
}
|
}
|
||||||
if (message === "load-complete") {
|
if (message === "load-complete") {
|
||||||
if (!openMenuInTabs.has(tabId)) return;
|
if (!openMenuInTabs.has(tabId)) return;
|
||||||
|
Loading…
Reference in New Issue
Block a user