Merge submodule contents for extension/dev

This commit is contained in:
dragonwocky 2022-11-25 15:37:32 +11:00
commit 9c51542e50
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
14 changed files with 396 additions and 0 deletions

16
.gitmodules vendored
View File

@ -0,0 +1,16 @@
[submodule "api"]
path = extension/api
url = git@github.com:notion-enhancer/api.git
branch = dev
[submodule "repo"]
path = extension/repo
url = git@github.com:notion-enhancer/repo.git
branch = dev
[submodule "media"]
path = extension/media
url = git@github.com:notion-enhancer/media.git
branch = main
[submodule "dep"]
path = extension/dep
url = git@github.com:notion-enhancer/dep.git
branch = main

View File

@ -0,0 +1,20 @@
name: 'update submodules'
on:
workflow_dispatch:
jobs:
sync:
name: 'update submodules'
runs-on: ubuntu-latest
steps:
- name: checkout repo
uses: actions/checkout@v2
with:
submodules: true
- name: pull updates
run: |
git pull --recurse-submodules
git submodule update --remote --recursive
- name: commit changes
uses: stefanzweifel/git-auto-commit-action@v4

21
extension/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
extension/README.md Normal file
View File

@ -0,0 +1,5 @@
# notion-enhancer/extension
an enhancer/customiser for the all-in-one productivity workspace notion.so (browser)
[read the docs online](https://notion-enhancer.github.io/)

1
extension/api Submodule

@ -0,0 +1 @@
Subproject commit 9815d73b9277e96864654a8d8dd48762039c9845

1
extension/dep Submodule

@ -0,0 +1 @@
Subproject commit 1a4762550fe185706be26678f734b0475066c3e4

41
extension/env/env.mjs vendored Normal file
View File

@ -0,0 +1,41 @@
/*
* notion-enhancer: api
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
'use strict';
/** environment-specific methods and constants */
/**
* the environment/platform name code is currently being executed in
* @constant
* @type {string}
*/
export const name = 'extension';
/**
* the current version of the enhancer
* @constant
* @type {string}
*/
export const version = chrome.runtime.getManifest().version;
/**
* open the enhancer's menu
* @type {function}
*/
export const focusMenu = () => chrome.runtime.sendMessage({ action: 'focusMenu' });
/**
* focus an active notion tab
* @type {function}
*/
export const focusNotion = () => chrome.runtime.sendMessage({ action: 'focusNotion' });
/**
* reload all notion and enhancer menu tabs to apply changes
* @type {function}
*/
export const reload = () => chrome.runtime.sendMessage({ action: 'reload' });

48
extension/env/fs.mjs vendored Normal file
View File

@ -0,0 +1,48 @@
/*
* notion-enhancer: api
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
'use strict';
/** environment-specific file reading */
/**
* 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
*/
export const localPath = chrome.runtime.getURL;
/**
* 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
*/
export const getJSON = (path, opts = {}) =>
fetch(path.startsWith('http') ? path : localPath(path), opts).then((res) => res.json());
/**
* 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
*/
export const getText = (path, opts = {}) =>
fetch(path.startsWith('http') ? path : localPath(path), opts).then((res) => res.text());
/**
* check if a file exists
* @param {string} path - a url or within-the-enhancer filepath
* @returns {boolean} whether or not the file exists
*/
export const isFile = async (path) => {
try {
await fetch(path.startsWith('http') ? path : localPath(path));
return true;
} catch {
return false;
}
};

116
extension/env/storage.mjs vendored Normal file
View File

@ -0,0 +1,116 @@
/*
* notion-enhancer: api
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
'use strict';
/** environment-specific data persistence */
const _queue = [],
_onChangeListeners = [];
/**
* get persisted data
* @param {string[]} path - the path of keys to the value being fetched
* @param {unknown=} fallback - a default value if the path is not matched
* @returns {Promise} value ?? fallback
*/
export const get = (path, fallback = undefined) => {
if (!path.length) return fallback;
return new Promise((res, rej) =>
chrome.storage.local.get(async (values) => {
let value = values;
while (path.length) {
if (value === undefined) {
value = fallback;
break;
}
value = value[path.shift()];
}
res(value ?? fallback);
})
);
};
/**
* persist data
* @param {string[]} path - the path of keys to the value being set
* @param {unknown} value - the data to save
* @returns {Promise} resolves when data has been saved
*/
export const 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],
namespace = path[0];
chrome.storage.local.get(async (values) => {
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];
}
chrome.storage.local.set({ [namespace]: values[namespace] }, () => {
_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 {string[]} 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
*/
export const db = (namespace, getFunc = get, setFunc = 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
*/
export const addChangeListener = (callback) => {
_onChangeListeners.push(callback);
};
/**
* remove a listener added with storage.addChangeListener
* @param {onStorageChangeCallback} callback
*/
export const 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
*/

35
extension/init.js Normal file
View File

@ -0,0 +1,35 @@
/*
* notion-enhancer
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
'use strict';
(async () => {
const site = location.host.endsWith('.notion.site'),
page = location.pathname.split(/[/-]/g).reverse()[0].length === 32,
whitelisted = ['/', '/onboarding'].includes(location.pathname),
signedIn = localStorage['LRU:KeyValueStore2:current-user-id'];
if (site || page || (whitelisted && signedIn)) {
const api = await import(chrome.runtime.getURL('api/index.mjs')),
{ fs, registry, web } = api;
for (const mod of await registry.list((mod) => registry.enabled(mod.id))) {
for (const sheet of mod.css?.client || []) {
web.loadStylesheet(`repo/${mod._dir}/${sheet}`);
}
for (let script of mod.js?.client || []) {
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.log('[notion-enhancer] registry errors:');
console.table(errors);
}
}
})();

30
extension/manifest.json Normal file
View File

@ -0,0 +1,30 @@
{
"manifest_version": 2,
"name": "notion-enhancer",
"version": "0.11.0",
"author": "dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)",
"description": "an enhancer/customiser for the all-in-one productivity workspace notion.so",
"homepage_url": "https://notion-enhancer.github.io",
"icons": {
"16": "media/colour-x16.png",
"32": "media/colour-x32.png",
"48": "media/colour-x48.png",
"128": "media/colour-x128.png",
"256": "media/colour-x256.png",
"512": "media/colour-x512.png"
},
"browser_action": {},
"background": { "scripts": ["worker.js"] },
"options_ui": {
"page": "repo/menu/menu.html",
"open_in_tab": true
},
"web_accessible_resources": ["env/*", "api/*", "dep/*", "media/*", "repo/*"],
"content_scripts": [
{
"matches": ["https://*.notion.so/*", "https://*.notion.site/*"],
"js": ["init.js"]
}
],
"permissions": ["tabs", "storage", "clipboardRead", "clipboardWrite", "unlimitedStorage"]
}

1
extension/media Submodule

@ -0,0 +1 @@
Subproject commit 2a0a17998385f1d86148b9213451b3a5deff6bae

1
extension/repo Submodule

@ -0,0 +1 @@
Subproject commit 3a67243fd5caec24b484276e563bdb8da7a0adcd

60
extension/worker.js Normal file
View File

@ -0,0 +1,60 @@
/*
* 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 focusNotion() {
chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT }, (tabs) => {
const notion = tabs.find((tab) => {
const url = new URL(tab.url),
matches = url.host.endsWith('.notion.so') || url.host.endsWith('.notion.site');
return matches;
});
if (notion) {
chrome.tabs.highlight({ 'tabs': notion.index });
} else chrome.tabs.create({ url: 'https://notion.so/' });
});
}
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;
});