diff --git a/bin.mjs b/bin.mjs index 5cfad11..c1cad60 100644 --- a/bin.mjs +++ b/bin.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/insert/api b/insert/api index 02d4357..be9864f 160000 --- a/insert/api +++ b/insert/api @@ -1 +1 @@ -Subproject commit 02d4357c85075bfbb3ddbb7fa0fde3600ee5cb60 +Subproject commit be9864f90b60cc48837efb32bb4bc7f0d1a5acae diff --git a/insert/client.mjs b/insert/client.mjs index c7e940f..d6853e3 100644 --- a/insert/client.mjs +++ b/insert/client.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/insert/electronApi.cjs b/insert/electronApi.cjs index f6115ea..93f7b17 100644 --- a/insert/electronApi.cjs +++ b/insert/electronApi.cjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license @@ -6,19 +6,84 @@ 'use strict'; -const api = require('notion-enhancer/api/index.cjs'); +const os = require('os'), + path = require('path'), + fs = require('fs'), + _cacheFile = path.resolve(`${os.homedir()}/.notion-enhancer`), + _fsQueue = [], + _onDbChangeListeners = []; -window.__enhancerElectronApi = { - platform: api.env.name, - version: api.env.version, - db: { - get: api.storage.get, - set: api.storage.set, - addChangeListener: api.storage.addChangeListener, - removeChangeListener: api.storage.removeChangeListener, +// handle leftover cache from prev versions +if (fs.existsSync(_cacheFile) && fs.lstatSync(_cacheFile).isDirectory()) { + fs.rmdirSync(_cacheFile); +} +if (!fs.existsSync(_cacheFile)) fs.writeFileSync(_cacheFile, '{}', 'utf8'); + +const isRenderer = process && process.type === 'renderer'; + +const getData = () => { + try { + return JSON.parse(fs.readFileSync(_cacheFile)); + } catch (err) { + return {}; + } }, - browser: require('electron').remote.getCurrentWindow(), - webFrame: require('electron').webFrame, + saveData = (data) => fs.writeFileSync(_cacheFile, JSON.stringify(data)); + +const db = { + get: (path, fallback = undefined) => { + if (!path.length) return fallback; + const values = getData(); + let value = values; + while (path.length) { + if (value === undefined) { + value = fallback; + break; + } + value = value[path.shift()]; + } + return Promise.resolve(value ?? fallback); + }, + set: (path, value) => { + if (!path.length) return undefined; + const precursor = _fsQueue[_fsQueue.length - 1] || undefined, + interaction = new Promise(async (res, rej) => { + if (precursor !== undefined) { + await precursor; + _fsQueue.shift(); + } + const pathClone = [...path], + values = getData(); + let pointer = values, + old; + while (path.length) { + const key = path.shift(); + if (!path.length) { + old = pointer[key]; + pointer[key] = value; + break; + } + pointer[key] = pointer[key] ?? {}; + pointer = pointer[key]; + } + saveData(values); + _onDbChangeListeners.forEach((listener) => + listener({ path: pathClone, new: value, old }) + ); + res(value); + }); + _fsQueue.push(interaction); + return interaction; + }, + addChangeListener: (callback) => { + _onDbChangeListeners.push(callback); + }, + removeChangeListener: (callback) => { + _onDbChangeListeners = _onDbChangeListeners.filter((listener) => listener !== callback); + }, +}; + +const ipcRenderer = { sendMessage: (channel, data = undefined) => { const { ipcRenderer } = require('electron'); ipcRenderer.send(`notion-enhancer:${channel}`, data); @@ -29,6 +94,35 @@ window.__enhancerElectronApi = { }, onMessage: (channel, callback) => { const { ipcRenderer } = require('electron'); - ipcRenderer.on(`notion-enhancer:${channel}`, callback); + ipcRenderer.addListener(`notion-enhancer:${channel}`, callback); }, }; + +globalThis.__enhancerElectronApi = { + platform: process.platform, + version: require('notion-enhancer/package.json').version, + db, + + browser: isRenderer ? require('electron').remote.getCurrentWindow() : {}, + webFrame: isRenderer ? require('electron').webFrame : {}, + notionRequire: (path) => require(`../../${path}`), + nodeRequire: (path) => require(path), + + focusMenu: () => { + if (isRenderer) return ipcRenderer.sendMessage('focusMenu'); + const { focusMenu } = require('notion-enhancer/worker.cjs'); + return focusMenu(); + }, + focusNotion: () => { + if (isRenderer) return ipcRenderer.sendMessage('focusNotion'); + const { focusNotion } = require('notion-enhancer/worker.cjs'); + return focusNotion(); + }, + reload: () => { + if (isRenderer) return ipcRenderer.sendMessage('reload'); + const { reload } = require('notion-enhancer/worker.cjs'); + return reload(); + }, + + ipcRenderer, +}; diff --git a/insert/env/env.cjs b/insert/env/env.cjs deleted file mode 100644 index f6f2c94..0000000 --- a/insert/env/env.cjs +++ /dev/null @@ -1,54 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -/** - * environment-specific methods and constants - * @module notion-enhancer/api/env - */ - -module.exports = {}; - -const { focusMenu, focusNotion, reload } = require('notion-enhancer/worker.cjs'); - -/** - * the environment/platform name code is currently being executed in - * @constant - * @type {string} - */ -module.exports.name = process.platform; - -/** - * the current version of the enhancer - * @constant - * @type {string} - */ -module.exports.version = require('notion-enhancer/package.json').version; - -/** - * open the enhancer's menu - * @type {function} - */ -module.exports.focusMenu = focusMenu; - -/** - * focus an active notion tab - * @type {function} - */ -module.exports.focusNotion = focusNotion; - -/** - * reload all notion and enhancer menu tabs to apply changes - * @type {function} - */ -module.exports.reload = reload; - -/** - * require() notion app files - * @param {string} path - notion/resources/app/ e.g. main/createWindow.js - */ -module.exports.notionRequire = (path) => require(`../../../${path}`); diff --git a/insert/env/env.mjs b/insert/env/env.mjs index 7c65b35..5a2cfeb 100644 --- a/insert/env/env.mjs +++ b/insert/env/env.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -16,29 +16,35 @@ * @constant * @type {string} */ -export const name = window.__enhancerElectronApi.platform; +export const name = globalThis.__enhancerElectronApi.platform; /** * the current version of the enhancer * @constant * @type {string} */ -export const version = window.__enhancerElectronApi.version; +export const version = globalThis.__enhancerElectronApi.version; /** * open the enhancer's menu * @type {function} */ -export const focusMenu = () => window.__enhancerElectronApi.sendMessage('focusMenu'); +export const focusMenu = globalThis.__enhancerElectronApi.focusMenu; /** * focus an active notion tab * @type {function} */ -export const focusNotion = () => window.__enhancerElectronApi.sendMessage('focusNotion'); +export const focusNotion = globalThis.__enhancerElectronApi.focusNotion; /** * reload all notion and enhancer menu tabs to apply changes * @type {function} */ -export const reload = () => window.__enhancerElectronApi.sendMessage('reload'); +export const reload = globalThis.__enhancerElectronApi.reload; + +/** + * require() notion app files + * @param {string} path - within notion/resources/app e.g. main/createWindow.js + */ +export const notionRequire = globalThis.__enhancerElectronApi.notionRequire; diff --git a/insert/env/fs.cjs b/insert/env/fs.cjs deleted file mode 100644 index 01982e7..0000000 --- a/insert/env/fs.cjs +++ /dev/null @@ -1,61 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; -module.exports = {}; - -/** - * environment-specific file reading - * @module notion-enhancer/api/fs - */ - -const fs = require('fs'), - { resolve: resolvePath } = require('path'); - -/** - * transform a path relative to the enhancer root directory into an absolute path - * @param {string} path - a url or within-the-enhancer filepath - * @returns {string} an absolute filepath - */ -module.exports.localPath = (path) => `notion://www.notion.so/__notion-enhancer/${path}`; - -/** - * fetch and parse a json file's contents - * @param {string} path - a url or within-the-enhancer filepath - * @param {object} [opts] - the second argument of a fetch() request - * @returns {object} the json value of the requested file as a js object - */ -module.exports.getJSON = (path, opts = {}) => { - if (path.startsWith('http')) return fetch(path, opts).then((res) => res.json()); - return require(`notion-enhancer/${path}`); -}; - -/** - * fetch a text file's contents - * @param {string} path - a url or within-the-enhancer filepath - * @param {object} [opts] - the second argument of a fetch() request - * @returns {string} the text content of the requested file - */ -module.exports.getText = (path, opts = {}) => { - if (path.startsWith('http')) return fetch(path, opts).then((res) => res.text()); - return fs.readFileSync(resolvePath(`${__dirname}/../../${path}`)); -}; - -/** - * check if a file exists - * @param {string} path - a url or within-the-enhancer filepath - * @returns {boolean} whether or not the file exists - */ -module.exports.isFile = async (path) => { - try { - if (path.startsWith('http')) { - await fetch(path); - } else fs.existsSync(resolvePath(`${__dirname}/../../${path}`)); - return true; - } catch { - return false; - } -}; diff --git a/insert/env/fs.mjs b/insert/env/fs.mjs index 5b6cdcb..accf5e4 100644 --- a/insert/env/fs.mjs +++ b/insert/env/fs.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -24,8 +24,14 @@ export const localPath = (path) => `notion://www.notion.so/__notion-enhancer/${p * @param {object} [opts] - the second argument of a fetch() request * @returns {object} the json value of the requested file as a js object */ -export const getJSON = (path, opts = {}) => - fetch(path.startsWith('http') ? path : localPath(path), opts).then((res) => res.json()); +export const getJSON = (path, opts = {}) => { + if (path.startsWith('http')) return fetch(path, opts).then((res) => res.json()); + try { + return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${path}`); + } catch (err) { + return fetch(localPath(path), opts).then((res) => res.json()); + } +}; /** * fetch a text file's contents @@ -33,8 +39,16 @@ export const getJSON = (path, opts = {}) => * @param {object} [opts] - the second argument of a fetch() request * @returns {string} the text content of the requested file */ -export const getText = (path, opts = {}) => - fetch(path.startsWith('http') ? path : localPath(path), opts).then((res) => res.text()); +export const getText = (path, opts = {}) => { + if (path.startsWith('http')) return fetch(path, opts).then((res) => res.text()); + try { + const fs = globalThis.__enhancerElectronApi.nodeRequire('fs'), + { resolve: resolvePath } = globalThis.__enhancerElectronApi.nodeRequire('path'); + return fs.readFileSync(resolvePath(`${__dirname}/../../${path}`)); + } catch (err) { + return fetch(localPath(path), opts).then((res) => res.text()); + } +}; /** * check if a file exists @@ -43,7 +57,17 @@ export const getText = (path, opts = {}) => */ export const isFile = async (path) => { try { - await fetch(path.startsWith('http') ? path : localPath(path)); + const fs = globalThis.__enhancerElectronApi.nodeRequire('fs'), + { resolve: resolvePath } = globalThis.__enhancerElectronApi.nodeRequire('path'); + if (path.startsWith('http')) { + await fetch(path); + } else { + try { + fs.existsSync(resolvePath(`${__dirname}/../../${path}`)); + } catch (err) { + await fetch(localPath(path)); + } + } return true; } catch { return false; diff --git a/insert/env/storage.cjs b/insert/env/storage.cjs deleted file mode 100644 index bc0cb7f..0000000 --- a/insert/env/storage.cjs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; -module.exports = {}; - -/** - * environment-specific data persistence - * @module notion-enhancer/api/storage - */ - -const _queue = [], - _onChangeListeners = []; - -const os = require('os'), - path = require('path'), - fs = require('fs'), - _cacheFile = path.resolve(`${os.homedir()}/.notion-enhancer`); - -// handle leftover cache from prev versions -if (fs.existsSync(_cacheFile) && fs.lstatSync(_cacheFile).isDirectory()) { - fs.rmdirSync(_cacheFile); -} -if (!fs.existsSync(_cacheFile)) fs.writeFileSync(_cacheFile, '{}', 'utf8'); - -const getData = () => { - try { - return JSON.parse(fs.readFileSync(_cacheFile)); - } catch (err) { - return {}; - } - }, - saveData = (data) => fs.writeFileSync(_cacheFile, JSON.stringify(data)); - -/** - * get persisted data - * @param {array} path - the path of keys to the value being fetched - * @param {*} [fallback] - a default value if the path is not matched - * @returns {Promise} value ?? fallback - */ -module.exports.get = (path, fallback = undefined) => { - if (!path.length) return fallback; - const values = getData(); - let value = values; - while (path.length) { - if (value === undefined) { - value = fallback; - break; - } - value = value[path.shift()]; - } - return Promise.resolve(value ?? fallback); -}; - -/** - * persist data - * @param {array} path - the path of keys to the value being set - * @param {*} value - the data to save - * @returns {Promise} resolves when data has been saved - */ -module.exports.set = (path, value) => { - if (!path.length) return undefined; - const precursor = _queue[_queue.length - 1] || undefined, - interaction = new Promise(async (res, rej) => { - if (precursor !== undefined) { - await precursor; - _queue.shift(); - } - const pathClone = [...path], - values = getData(); - let pointer = values, - old; - while (path.length) { - const key = path.shift(); - if (!path.length) { - old = pointer[key]; - pointer[key] = value; - break; - } - pointer[key] = pointer[key] ?? {}; - pointer = pointer[key]; - } - saveData(values); - _onChangeListeners.forEach((listener) => listener({ path: pathClone, new: value, old })); - res(value); - }); - _queue.push(interaction); - return interaction; -}; - -/** - * create a wrapper for accessing a partition of the storage - * @param {array} namespace - the path of keys to prefix all storage requests with - * @param {function} [get] - the storage get function to be wrapped - * @param {function} [set] - the storage set function to be wrapped - * @returns {object} an object with the wrapped get/set functions - */ -module.exports.db = ( - namespace, - getFunc = module.exports.get, - setFunc = module.exports.set -) => { - if (typeof namespace === 'string') namespace = [namespace]; - return { - get: (path = [], fallback = undefined) => getFunc([...namespace, ...path], fallback), - set: (path, value) => setFunc([...namespace, ...path], value), - }; -}; - -/** - * add an event listener for changes in storage - * @param {onStorageChangeCallback} callback - called whenever a change in - * storage is initiated from the current process - */ -module.exports.addChangeListener = (callback) => { - _onChangeListeners.push(callback); -}; - -/** - * remove a listener added with storage.addChangeListener - * @param {onStorageChangeCallback} callback - */ -module.exports.removeChangeListener = (callback) => { - _onChangeListeners = _onChangeListeners.filter((listener) => listener !== callback); -}; - -/** - * @callback onStorageChangeCallback - * @param {object} event - * @param {string} event.path- the path of keys to the changed value - * @param {string} [event.new] - the new value being persisted to the store - * @param {string} [event.old] - the previous value associated with the key - */ diff --git a/insert/env/storage.mjs b/insert/env/storage.mjs index f4106ed..032436a 100644 --- a/insert/env/storage.mjs +++ b/insert/env/storage.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -18,7 +18,7 @@ * @returns {Promise} value ?? fallback */ export const get = (path, fallback = undefined) => { - return window.__enhancerElectronApi.db.get(path, fallback); + return globalThis.__enhancerElectronApi.db.get(path, fallback); }; /** @@ -28,7 +28,7 @@ export const get = (path, fallback = undefined) => { * @returns {Promise} resolves when data has been saved */ export const set = (path, value) => { - return window.__enhancerElectronApi.db.set(path, value); + return globalThis.__enhancerElectronApi.db.set(path, value); }; /** @@ -52,7 +52,7 @@ export const db = (namespace, getFunc = get, setFunc = set) => { * storage is initiated from the current process */ export const addChangeListener = (callback) => { - return window.__enhancerElectronApi.db.addChangeListener(callback); + return globalThis.__enhancerElectronApi.db.addChangeListener(callback); }; /** @@ -60,7 +60,7 @@ export const addChangeListener = (callback) => { * @param {onStorageChangeCallback} callback */ export const removeChangeListener = (callback) => { - return window.__enhancerElectronApi.db.removeChangeListener(callback); + return globalThis.__enhancerElectronApi.db.removeChangeListener(callback); }; /** diff --git a/insert/init.cjs b/insert/init.cjs index 6a47441..ce79035 100644 --- a/insert/init.cjs +++ b/insert/init.cjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license @@ -7,29 +7,36 @@ 'use strict'; module.exports = async function (target, __exports, __eval) { - if (target === 'renderer/preload') { + try { require('notion-enhancer/electronApi.cjs'); - document.addEventListener('readystatechange', (event) => { - if (document.readyState !== 'complete') return false; - const script = document.createElement('script'); - script.type = 'module'; - script.src = api.fs.localPath('client.mjs'); - document.head.appendChild(script); - }); - } + const api = require('notion-enhancer/api/index.cjs'), + { registry } = api; - if (target === 'main/main') { - const { app } = require('electron'); - app.whenReady().then(require('notion-enhancer/worker.cjs').listen); - } - - const api = require('notion-enhancer/api/index.cjs'), - { registry } = api; - for (const mod of await registry.list((mod) => registry.enabled(mod.id))) { - for (const { source, target: scriptTarget } of (mod.js ? mod.js.electron : []) || []) { - if (`${target}.js` !== scriptTarget) continue; - const script = require(`notion-enhancer/repo/${mod._dir}/${source}`); - script(api, await registry.db(mod.id), __exports, __eval); + if (target === 'renderer/preload') { + document.addEventListener('readystatechange', (event) => { + if (document.readyState !== 'complete') return false; + const script = document.createElement('script'); + script.type = 'module'; + script.src = api.fs.localPath('client.mjs'); + document.head.appendChild(script); + }); } + + if (target === 'main/main') { + const { app } = require('electron'); + app.whenReady().then(require('notion-enhancer/worker.cjs').listen); + } + + for (const mod of await registry.list((mod) => registry.enabled(mod.id))) { + for (const { source, target: scriptTarget } of (mod.js ? mod.js.electron : []) || []) { + if (`${target}.js` !== scriptTarget) continue; + const script = require(`notion-enhancer/repo/${mod._dir}/${source}`); + script(api, await registry.db(mod.id), __exports, __eval); + } + } + } catch (err) { + console.log(err); + const { app, Notification } = require('electron'); + app.whenReady().then(() => new Notification({ title: target, body: err.message }).show()); } }; diff --git a/insert/repo b/insert/repo index 8eb2810..d7b78f4 160000 --- a/insert/repo +++ b/insert/repo @@ -1 +1 @@ -Subproject commit 8eb2810a1bedb96ea82e20dadefdcd328b6978f9 +Subproject commit d7b78f4836aea7ea0754c3bf10b49488897d90a0 diff --git a/insert/worker.cjs b/insert/worker.cjs index 85e358c..9be59e0 100644 --- a/insert/worker.cjs +++ b/insert/worker.cjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license @@ -49,6 +49,8 @@ module.exports.focusMenu = async () => { appQuit = true; }); + // handle opening external links + // must have target="_blank" enhancerMenu.webContents.on('new-window', (e, url) => { e.preventDefault(); require('electron').shell.openExternal(url); @@ -72,7 +74,7 @@ module.exports.focusNotion = () => { { BrowserWindow } = require('electron'), { createWindow } = env.notionRequire('main/createWindow.js'); let window = BrowserWindow.getAllWindows().find((win) => win.id !== enhancerMenu.id); - if (!window) window = createWindow('', null, true); + if (!window) window = createWindow('/'); window.show(); }; diff --git a/pkg/apply.mjs b/pkg/apply.mjs index 8002b15..b1a8aa3 100644 --- a/pkg/apply.mjs +++ b/pkg/apply.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/pkg/check.mjs b/pkg/check.mjs index 64aabe6..a3140c0 100644 --- a/pkg/check.mjs +++ b/pkg/check.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/pkg/cli.mjs b/pkg/cli.mjs index f3dba4c..37518a2 100644 --- a/pkg/cli.mjs +++ b/pkg/cli.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/pkg/helpers.mjs b/pkg/helpers.mjs index 6b7fc58..ff05f7d 100644 --- a/pkg/helpers.mjs +++ b/pkg/helpers.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/pkg/remove.mjs b/pkg/remove.mjs index 561593d..de164f8 100644 --- a/pkg/remove.mjs +++ b/pkg/remove.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/pkg/replacers/main/main.mjs b/pkg/replacers/main/main.mjs index 2edfa6b..617e126 100644 --- a/pkg/replacers/main/main.mjs +++ b/pkg/replacers/main/main.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/pkg/replacers/main/schemeHandler.mjs b/pkg/replacers/main/schemeHandler.mjs index 5340c8d..02251dd 100644 --- a/pkg/replacers/main/schemeHandler.mjs +++ b/pkg/replacers/main/schemeHandler.mjs @@ -1,4 +1,4 @@ -/* +/** * notion-enhancer * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license