diff --git a/scripts/patch-desktop-app.mjs b/scripts/patch-desktop-app.mjs index a93d5c1..708171c 100755 --- a/scripts/patch-desktop-app.mjs +++ b/scripts/patch-desktop-app.mjs @@ -22,41 +22,49 @@ const mainScript = ".webpack/main/index", patches = { [mainScript]: (scriptContent) => { scriptContent = injectTriggerOnce(mainScript, scriptContent); + const replace = (...args) => + (scriptContent = replaceIfNotFound(scriptContent, ...args)); // https://github.com/notion-enhancer/notion-enhancer/issues/160: // enable the notion:// protocol, windows-style tab layouts, and // quitting the app when the last window is closed on linux - scriptContent = scriptContent.replace( - /(?:"win32"===process\.platform(?:(?=,isFullscreen)|(?=&&\w\.BrowserWindow)|(?=&&\(\w\.app\.requestSingleInstanceLock)))/g, - '["win32","linux"].includes(process.platform)' - ); + const isWindows = + /(?:"win32"===process\.platform(?:(?=,isFullscreen)|(?=&&\w\.BrowserWindow)|(?=&&\(\w\.app\.requestSingleInstanceLock)))/g, + isWindowsOrLinux = '["win32","linux"].includes(process.platform)'; + replace(isWindows, isWindowsOrLinux); // restore node integration in the renderer process // so the notion-enhancer can be require()-d into it - scriptContent = replaceIfNotFound( - scriptContent, - /spellcheck:!0,sandbox:!0/g, - "spellcheck:!0,nodeIntegration:true" - ); + replace("spellcheck:!0,sandbox:!0", "spellcheck:!0,nodeIntegration:true"); + + // bypass webRequest filter to load enhancer menu + replace("r.top!==r?t({cancel:!0})", "r.top!==r?t({})"); // https://github.com/notion-enhancer/desktop/issues/291 // bypass csp issues by intercepting the notion:// protocol - const protocolHandler = - "try{const t=await p.assetCache.handleRequest(e);"; - scriptContent = replaceIfNotFound( - scriptContent, - protocolHandler, - `{const n="notion://www.notion.so/__notion-enhancer/";if(e.url.startsWith(n))return require("electron").net.fetch(\`file://\${require("path").join(__dirname,"..","..","node_modules","notion-enhancer",e.url.slice(n.length))}\`)}${protocolHandler}` - ); + const protocolHandler = `try{const t=await p.assetCache.handleRequest(e);`, + protocolInterceptor = `{const n="notion://www.notion.so/__notion-enhancer/";if(e.url.startsWith(n))return require("electron").net.fetch(\`file://\${require("path").join(__dirname,"..","..","node_modules","notion-enhancer",e.url.slice(n.length))}\`)}`; + replace(protocolHandler, protocolInterceptor + protocolHandler); - // bypass webRequest filter to load enhancer menu - return replaceIfNotFound( - scriptContent, - /r\.top!==r\?t\({cancel:!0}\)/g, - "r.top!==r?t({})" - ); + // expose the app config to the global namespace for manipulation + // e.g. to enable development mode + const configDeclaration = `e.exports=JSON.parse('{"env":"production"`, + configInterceptor = `globalThis.__notionConfig=${configDeclaration}`; + replace(configDeclaration, configInterceptor); + + // expose the app store to the global namespace for reading + // e.g. to check if keep in background is enabled + const storeDeclaration = "t.Store=(0,p.configureStore)", + updateDeclaration = "t.updatePreferences=n.updatePreferences", + storeInterceptor = `globalThis.__notionStore=${storeDeclaration}`, + updateInterceptor = `globalThis.__updatePreferences=${updateDeclaration}`; + replace(storeDeclaration, storeInterceptor); + replace(updateDeclaration, updateInterceptor); + + return scriptContent; }, - [rendererScript]: (scriptContent) => injectTriggerOnce(rendererScript, scriptContent) + [rendererScript]: (scriptContent) => + injectTriggerOnce(rendererScript, scriptContent), }; export default (scriptId, scriptContent) => { diff --git a/src/_common/events.js b/src/_common/events.js index 7468be7..ea66dee 100644 --- a/src/_common/events.js +++ b/src/_common/events.js @@ -112,7 +112,6 @@ const modifierAliases = [ keyListeners = keyListeners.filter(([, c]) => c !== callback); }, handleKeypress = (event, keyListeners) => { - console.log(event); for (const [accelerator, callback] of keyListeners) { const acceleratorModifiers = [], combinationTriggered = diff --git a/src/core/electron.cjs b/src/core/electron.cjs new file mode 100644 index 0000000..7273220 --- /dev/null +++ b/src/core/electron.cjs @@ -0,0 +1,52 @@ +/** + * notion-enhancer + * (c) 2024 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +const getPreference = (key) => { + const { preferences = {} } = globalThis.__notionStore?.getState()?.app; + return preferences[key]; + }, + setPreference = (key, value) => { + const action = globalThis.__updatePreferences?.({ [key]: value }); + globalThis.__notionStore?.dispatch?.(action); + }; + +module.exports = async ({}, db) => { + const toggleWindowHotkey = await db.get("toggleWindowHotkey"), + developerMode = await db.get("developerMode"), + draggableTabs = await db.get("draggableTabs"); + + // experimental: enable tab reordering from notion's hidden preferences + setPreference("isDraggableTabsEnabled", draggableTabs); + + // enable developer mode, access extra debug tools + globalThis.__notionConfig ??= {}; + Object.assign(globalThis.__notionConfig, { + env: developerMode ? "development" : "production", + }); + + // listen for the global window toggle hotkey + const { app, globalShortcut, BrowserWindow } = require("electron"); + app.whenReady().then(() => { + globalShortcut.register(toggleWindowHotkey, () => { + const windows = BrowserWindow.getAllWindows() + // filter out quick search window + .filter((win) => win.fullScreenable), + focused = windows.some((win) => win.isFocused() && win.isVisible()); + windows.forEach((win) => + // check if notion is set to run in the background + getPreference("isHideLastWindowOnCloseEnabled") + ? focused + ? win.hide() + : win.show() + : focused + ? win.minimize() + : win.isMinimized() + ? win.restore() + : win.focus() + ); + }); + }); +}; diff --git a/src/core/mod.json b/src/core/mod.json index 506491b..da0295b 100644 --- a/src/core/mod.json +++ b/src/core/mod.json @@ -56,6 +56,17 @@ "description": "Sets whether the notion-enhancer icon added to Notion's sidebar should be coloured or monochrome. The latter style will match the theme's icon colour for users who would like the icon to be less noticeable.", "values": ["Colour", "Monochrome"] }, + { + "type": "heading", + "label": "Experimental" + }, + { + "type": "toggle", + "key": "draggableTabs", + "description": "Enables reordering tabs within the Notion app via drag and drop.", + "platforms": ["linux", "win32", "darwin"], + "value": false + }, { "type": "heading", "label": "Advanced", @@ -70,5 +81,6 @@ } ], "clientStyles": ["variables.css"], - "clientScripts": ["client.mjs"] + "clientScripts": ["client.mjs"], + "electronScripts": [[".webpack/main/index", "electron.cjs"]] } diff --git a/src/init.js b/src/init.js index 07e11ba..ecd73bb 100644 --- a/src/init.js +++ b/src/init.js @@ -1,6 +1,6 @@ /** * notion-enhancer - * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (c) 2024 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -44,8 +44,9 @@ if (isElectron()) { for (const mod of await getMods()) { if (!mod.electronScripts || !(await isEnabled(mod.id))) continue; const db = await modDatabase(mod.id); - for (let script of mod.clientScripts ?? []) { - script = require(`notion-enhancer/${mod._src}/${source}`); + for (let [scriptTarget, script] of mod.electronScripts ?? []) { + if (target !== scriptTarget) continue; + script = require(`./${mod._src}/${script}`); script(globalThis.__enhancerApi, db, __exports, __eval); } }