mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-09 15:09:02 +00:00
transfer validation to at runtime for better module dev + add buttons to open the menu
This commit is contained in:
parent
68ae14d967
commit
05760882b5
30
extension/icons/blackwhite.svg
Normal file
30
extension/icons/blackwhite.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
64
extension/icons/colour.svg
Normal file
64
extension/icons/colour.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 24 KiB |
@ -11,15 +11,13 @@
|
|||||||
"128": "icons/colour-x128.png"
|
"128": "icons/colour-x128.png"
|
||||||
},
|
},
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
|
"action": {},
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "worker.js"
|
"service_worker": "worker.js"
|
||||||
},
|
},
|
||||||
"action": {
|
|
||||||
"default_popup": "src/gui.html"
|
|
||||||
},
|
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{
|
||||||
"resources": ["registry.json", "src/*", "repo/*"],
|
"resources": ["icons/*", "src/*", "repo/*"],
|
||||||
"matches": ["https://*.notion.so/*"]
|
"matches": ["https://*.notion.so/*"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -29,6 +27,5 @@
|
|||||||
"js": ["content-loader.js"]
|
"js": ["content-loader.js"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"permissions": ["activeTab"],
|
|
||||||
"host_permissions": ["https://*.notion.so/*"]
|
"host_permissions": ["https://*.notion.so/*"]
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
[{"name":"theming","id":"0f0bf8b6-eae6-4273-b307-8fc43f2ee082","description":"the default theme variables, required by other themes & extensions.","version":"0.11.0","tags":["core","theme"],"authors":[{"name":"dragonwocky","email":"thedragonring.bod@gmail.com","url":"https://dragonwocky.me/","icon":"https://dragonwocky.me/avatar.jpg"}],"css":{"client":["client.css"]},"js":{"client":["test.js"]},"dir":"theming@0f0bf8b6-eae6-4273-b307-8fc43f2ee082"}]
|
|
1
extension/repo/registry.json
Normal file
1
extension/repo/registry.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
["theming@0f0bf8b6-eae6-4273-b307-8fc43f2ee082"]
|
@ -1,207 +0,0 @@
|
|||||||
/*
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// used to validate mod.json files available in a local repository,
|
|
||||||
// the options those files reference, & then generate a registry.json from that
|
|
||||||
|
|
||||||
// it also enforces the name@id naming scheme for mod dirs
|
|
||||||
|
|
||||||
const fs = require('fs'),
|
|
||||||
fsp = fs.promises,
|
|
||||||
colour = require('chalk');
|
|
||||||
|
|
||||||
let currentFolder = '';
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
const prefix = (status = '') =>
|
|
||||||
colour.whiteBright(`<notion-enhancer repo scan${status ? `: ${status}` : ''}>`);
|
|
||||||
function error(msg) {
|
|
||||||
const err = `${msg} in ${colour.italic(currentFolder)}`;
|
|
||||||
console.error(`${prefix(colour.red('error'))} ${err}`);
|
|
||||||
errors.push(err);
|
|
||||||
}
|
|
||||||
const isFile = (filepath, extension = '') =>
|
|
||||||
typeof filepath === 'string' &&
|
|
||||||
filepath.endsWith(extension) &&
|
|
||||||
fs.existsSync(`./repo/${currentFolder}/${filepath}`, 'file');
|
|
||||||
|
|
||||||
const regexers = {
|
|
||||||
uuid(str) {
|
|
||||||
const match = str.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
||||||
if (match && match.length) return true;
|
|
||||||
error(`invalid uuid ${str}`);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
semver(str) {
|
|
||||||
const match = str.match(
|
|
||||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i
|
|
||||||
);
|
|
||||||
if (match && match.length) return true;
|
|
||||||
error(`invalid semver ${str}`);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
email(str) {
|
|
||||||
const match = str.match(
|
|
||||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i
|
|
||||||
);
|
|
||||||
if (match && match.length) return true;
|
|
||||||
error(`invalid email ${str}`);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
url(str) {
|
|
||||||
const match = str.match(
|
|
||||||
/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i
|
|
||||||
);
|
|
||||||
if (match && match.length) return true;
|
|
||||||
error(`invalid url ${str}`);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function validate(mod) {
|
|
||||||
mod.tags = mod.tags ?? [];
|
|
||||||
mod.css = mod.css ?? [];
|
|
||||||
mod.js = mod.js ?? {};
|
|
||||||
const check = (prop, value, condition) =>
|
|
||||||
new Promise((res, rej) =>
|
|
||||||
condition ? res(value) : error(`invalid ${prop} ${JSON.stringify(value)}`)
|
|
||||||
);
|
|
||||||
return Promise.all([
|
|
||||||
check('name', mod.name, typeof mod.name === 'string'),
|
|
||||||
check('id', mod.id, typeof mod.id === 'string').then((id) => regexers.uuid(id)),
|
|
||||||
check('description', mod.description, typeof mod.description === 'string'),
|
|
||||||
check('version', mod.version, typeof mod.version === 'string').then((version) =>
|
|
||||||
regexers.semver(version)
|
|
||||||
),
|
|
||||||
check('tags', mod.tags, Array.isArray(mod.tags)).then((tags) =>
|
|
||||||
Promise.all(tags.map((tag) => check('tag', tag, typeof tag === 'string')))
|
|
||||||
),
|
|
||||||
check('authors', mod.authors, Array.isArray(mod.authors)).then((authors) =>
|
|
||||||
Promise.all(
|
|
||||||
authors
|
|
||||||
.map((author) => [
|
|
||||||
check('author.name', author.name, typeof author.name === 'string'),
|
|
||||||
check(
|
|
||||||
'author.email',
|
|
||||||
author.email,
|
|
||||||
typeof author.email === 'string'
|
|
||||||
).then((email) => regexers.email(email)),
|
|
||||||
check('author.url', author.url, typeof author.url === 'string').then((url) =>
|
|
||||||
regexers.url(url)
|
|
||||||
),
|
|
||||||
check('author.icon', author.icon, typeof author.icon === 'string').then((icon) =>
|
|
||||||
regexers.url(icon)
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.flat()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
check(
|
|
||||||
'css',
|
|
||||||
mod.css,
|
|
||||||
!!mod.css && typeof mod.css === 'object' && !Array.isArray(mod.css)
|
|
||||||
).then(async (css) => {
|
|
||||||
for (const dest of ['frame', 'client', 'gui']) {
|
|
||||||
const destFiles = css[dest];
|
|
||||||
if (destFiles) {
|
|
||||||
await check(`css.${dest}`, destFiles, Array.isArray(destFiles)).then((files) =>
|
|
||||||
Promise.all(
|
|
||||||
files.map(async (file) =>
|
|
||||||
check(`css.${dest} file`, file, await isFile(file, '.css'))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
check('js', mod.js, !!mod.js && typeof mod.js === 'object' && !Array.isArray(mod.js)).then(
|
|
||||||
async (js) => {
|
|
||||||
const client = js.client;
|
|
||||||
if (client) {
|
|
||||||
await check('js.client', client, Array.isArray(client)).then((files) =>
|
|
||||||
Promise.all(
|
|
||||||
files.map(async (file) =>
|
|
||||||
check('js.client file', file, await isFile(file, '.js'))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const electron = js.electron;
|
|
||||||
if (electron) {
|
|
||||||
await check('js.electron', electron, Array.isArray(electron)).then((files) =>
|
|
||||||
Promise.all(
|
|
||||||
files.map((file) =>
|
|
||||||
check(
|
|
||||||
'js.electron file',
|
|
||||||
file,
|
|
||||||
!!file && typeof file === 'object' && !Array.isArray(file)
|
|
||||||
).then(async (file) => {
|
|
||||||
const source = file.source;
|
|
||||||
await check('js.electron file source', source, await isFile(source, '.js'));
|
|
||||||
// referencing the file within the electron app
|
|
||||||
// existence can't be validated, so only format is
|
|
||||||
const target = file.target;
|
|
||||||
await check(
|
|
||||||
'js.electron file target',
|
|
||||||
target,
|
|
||||||
typeof target === 'string' && target.endsWith('.js')
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
check('options', mod.options, !mod.options || (await isFile(mod.options, '.json'))).then(
|
|
||||||
async (filepath) => {
|
|
||||||
if (!filepath) return;
|
|
||||||
let options;
|
|
||||||
try {
|
|
||||||
options = JSON.parse(await fsp.readFile(`./repo/${currentFolder}/${filepath}`));
|
|
||||||
} catch {
|
|
||||||
error(`invalid options ${filepath}`);
|
|
||||||
}
|
|
||||||
// todo: validate options
|
|
||||||
}
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generate() {
|
|
||||||
const mods = [];
|
|
||||||
for (const folder of await fsp.readdir('./repo')) {
|
|
||||||
let mod;
|
|
||||||
try {
|
|
||||||
mod = JSON.parse(await fsp.readFile(`./repo/${folder}/mod.json`));
|
|
||||||
mod.dir = folder;
|
|
||||||
currentFolder = folder;
|
|
||||||
await validate(mod);
|
|
||||||
mods.push(mod);
|
|
||||||
} catch {
|
|
||||||
error('invalid mod.json');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errors.length) {
|
|
||||||
for (const mod of mods) {
|
|
||||||
const oldDir = `./repo/${mod.dir}`;
|
|
||||||
mod.dir = `${mod.name.replace(/[^A-Za-z0-9]/, '-')}@${mod.id}`;
|
|
||||||
await fsp.rename(oldDir, `./repo/${mod.dir}`);
|
|
||||||
}
|
|
||||||
await fsp.writeFile('./registry.json', JSON.stringify(mods));
|
|
||||||
console.info(
|
|
||||||
`${prefix(
|
|
||||||
colour.green('success')
|
|
||||||
)} all mod configuration valid, registry saved to ./registry.json & folder naming scheme enforced`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync('./repo', 'dir')) {
|
|
||||||
generate();
|
|
||||||
} else {
|
|
||||||
console.error(`${prefix(colour.red('error'))} no repo folder found`);
|
|
||||||
}
|
|
46
extension/src/client.css
Normal file
46
extension/src/client.css
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* notion-enhancer
|
||||||
|
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
.enhancer--sidebarMenuTrigger {
|
||||||
|
user-select: none;
|
||||||
|
transition: background 20ms ease-in 0s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.enhancer--sidebarMenuTrigger:hover {
|
||||||
|
background: var(--theme--button-hover);
|
||||||
|
}
|
||||||
|
.enhancer--sidebarMenuTrigger svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.enhancer--sidebarMenuTrigger > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 27px;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2px 14px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.enhancer--sidebarMenuTrigger > div > div:first-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--theme--text_sidebar);
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.enhancer--sidebarMenuTrigger > div > div:last-child {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Hello, World!</title>
|
<title>notion-enhancer</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Hello, World!</p>
|
<p>Hello, World!</p>
|
||||||
|
@ -5,25 +5,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
const ERROR = Symbol();
|
||||||
const registry = fetch(chrome.runtime.getURL('/registry.json')).then((response) =>
|
|
||||||
response.json()
|
|
||||||
);
|
|
||||||
|
|
||||||
const web = {};
|
const web = {};
|
||||||
web.whenReady = (func = () => {}) => {
|
web.whenReady = (selectors = [], callback = () => {}) => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
if (document.readyState !== 'complete') {
|
function onLoad() {
|
||||||
document.addEventListener('readystatechange', (event) => {
|
let isReadyInt;
|
||||||
if (document.readyState === 'complete') {
|
isReadyInt = setInterval(isReadyTest, 100);
|
||||||
func();
|
function isReadyTest() {
|
||||||
|
if (selectors.every((selector) => document.querySelector(selector))) {
|
||||||
|
clearInterval(isReadyInt);
|
||||||
|
callback();
|
||||||
res(true);
|
res(true);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else {
|
isReadyTest();
|
||||||
func();
|
|
||||||
res(true);
|
|
||||||
}
|
}
|
||||||
|
if (document.readyState !== 'complete') {
|
||||||
|
document.addEventListener('readystatechange', (event) => {
|
||||||
|
if (document.readyState === 'complete') onLoad();
|
||||||
|
});
|
||||||
|
} else onLoad();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
web.createElement = (html) => {
|
web.createElement = (html) => {
|
||||||
@ -38,4 +41,210 @@ web.loadStyleset = (sheet) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { registry, web };
|
//
|
||||||
|
|
||||||
|
const fs = {};
|
||||||
|
|
||||||
|
fs.getJSON = (path) => fetch(chrome.runtime.getURL(path)).then((res) => res.json());
|
||||||
|
fs.getText = (path) => fetch(chrome.runtime.getURL(path)).then((res) => res.text());
|
||||||
|
|
||||||
|
fs.isFile = async (path) => {
|
||||||
|
try {
|
||||||
|
await fetch(chrome.runtime.getURL(`/repo/${path}`));
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
const regexers = {
|
||||||
|
uuid(str) {
|
||||||
|
const match = str.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
||||||
|
if (match && match.length) return true;
|
||||||
|
error(`invalid uuid ${str}`);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
semver(str) {
|
||||||
|
const match = str.match(
|
||||||
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i
|
||||||
|
);
|
||||||
|
if (match && match.length) return true;
|
||||||
|
error(`invalid semver ${str}`);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
email(str) {
|
||||||
|
const match = str.match(
|
||||||
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i
|
||||||
|
);
|
||||||
|
if (match && match.length) return true;
|
||||||
|
error(`invalid email ${str}`);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
url(str) {
|
||||||
|
const match = str.match(
|
||||||
|
/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i
|
||||||
|
);
|
||||||
|
if (match && match.length) return true;
|
||||||
|
error(`invalid url ${str}`);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
const registry = {};
|
||||||
|
|
||||||
|
registry.validate = async (mod, err, check) =>
|
||||||
|
Promise.all(
|
||||||
|
[
|
||||||
|
check('name', mod.name, typeof mod.name === 'string'),
|
||||||
|
check('id', mod.id, typeof mod.id === 'string').then((id) =>
|
||||||
|
id === ERROR ? ERROR : regexers.uuid(id)
|
||||||
|
),
|
||||||
|
check('description', mod.description, typeof mod.description === 'string'),
|
||||||
|
check('version', mod.version, typeof mod.version === 'string').then((version) =>
|
||||||
|
version === ERROR ? ERROR : regexers.semver(version)
|
||||||
|
),
|
||||||
|
check('tags', mod.tags, Array.isArray(mod.tags)).then((tags) =>
|
||||||
|
tags === ERROR ? ERROR : tags.map((tag) => check('tag', tag, typeof tag === 'string'))
|
||||||
|
),
|
||||||
|
check('authors', mod.authors, Array.isArray(mod.authors)).then((authors) =>
|
||||||
|
authors === ERROR
|
||||||
|
? ERROR
|
||||||
|
: authors.map((author) => [
|
||||||
|
check('author.name', author.name, typeof author.name === 'string'),
|
||||||
|
check(
|
||||||
|
'author.email',
|
||||||
|
author.email,
|
||||||
|
typeof author.email === 'string'
|
||||||
|
).then((email) => (email === ERROR ? ERROR : regexers.email(email))),
|
||||||
|
check('author.url', author.url, typeof author.url === 'string').then((url) =>
|
||||||
|
url === ERROR ? ERROR : regexers.url(url)
|
||||||
|
),
|
||||||
|
check('author.icon', author.icon, typeof author.icon === 'string').then((icon) =>
|
||||||
|
icon === ERROR ? ERROR : regexers.url(icon)
|
||||||
|
),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
check(
|
||||||
|
'css',
|
||||||
|
mod.css,
|
||||||
|
!!mod.css && typeof mod.css === 'object' && !Array.isArray(mod.css)
|
||||||
|
).then((css) => {
|
||||||
|
if (css === ERROR) return ERROR;
|
||||||
|
if (!css) return undefined;
|
||||||
|
return ['frame', 'client', 'gui']
|
||||||
|
.filter((dest) => css[dest])
|
||||||
|
.map(async (dest) =>
|
||||||
|
check(`css.${dest}`, css[dest], Array.isArray(css[dest])).then((files) =>
|
||||||
|
files === ERROR
|
||||||
|
? ERROR
|
||||||
|
: files.map(async (file) =>
|
||||||
|
check(
|
||||||
|
`css.${dest} file`,
|
||||||
|
file,
|
||||||
|
await fs.isFile(`${mod._dir}/${file}`, '.css')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
check(
|
||||||
|
'js',
|
||||||
|
mod.js,
|
||||||
|
!!mod.js && typeof mod.js === 'object' && !Array.isArray(mod.js)
|
||||||
|
).then(async (js) => {
|
||||||
|
if (js === ERROR) return ERROR;
|
||||||
|
if (!js) return undefined;
|
||||||
|
return [
|
||||||
|
check('js.client', js.client, !js.client ?? Array.isArray(js.client)).then(
|
||||||
|
(client) => {
|
||||||
|
if (client === ERROR) return ERROR;
|
||||||
|
if (!client) return undefined;
|
||||||
|
return client.map(async (file) =>
|
||||||
|
check('js.client file', file, await fs.isFile(file, '.js'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
check('js.electron', js.electron, !js.electron ?? Array.isArray(js.electron)).then(
|
||||||
|
(electron) => {
|
||||||
|
if (electron === ERROR) return ERROR;
|
||||||
|
if (!electron) return undefined;
|
||||||
|
return electron.map((file) =>
|
||||||
|
check(
|
||||||
|
'js.electron file',
|
||||||
|
file,
|
||||||
|
!!file && typeof file === 'object' && !Array.isArray(file)
|
||||||
|
).then(async (file) =>
|
||||||
|
file === ERROR
|
||||||
|
? ERROR
|
||||||
|
: [
|
||||||
|
check(
|
||||||
|
'js.electron file source',
|
||||||
|
file.source,
|
||||||
|
await fs.isFile(file.source, '.js')
|
||||||
|
),
|
||||||
|
// referencing the file within the electron app
|
||||||
|
// existence can't be validated, so only format is
|
||||||
|
check(
|
||||||
|
'js.electron file target',
|
||||||
|
file.target,
|
||||||
|
typeof file.target === 'string' && file.target.endsWith('.js')
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
check(
|
||||||
|
'options',
|
||||||
|
mod.options,
|
||||||
|
!mod.options ?? (await fs.isFile(mod.options, '.json'))
|
||||||
|
).then(async (filepath) => {
|
||||||
|
if (filepath === ERROR) return ERROR;
|
||||||
|
if (!filepath) return undefined;
|
||||||
|
try {
|
||||||
|
const options = await fs.getJSON(`/repo/${mod._dir}/${mod.options}`);
|
||||||
|
// todo: validate options
|
||||||
|
} catch {
|
||||||
|
err(`invalid options ${filepath}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
].flat(Infinity)
|
||||||
|
);
|
||||||
|
|
||||||
|
registry.get = async (callback = () => {}) => {
|
||||||
|
registry._list = [];
|
||||||
|
if (!registry._errors) registry._errors = [];
|
||||||
|
for (const dir of await fs.getJSON('/repo/registry.json')) {
|
||||||
|
const err = (message) => [registry._errors.push({ source: dir, message }), ERROR][1];
|
||||||
|
try {
|
||||||
|
const mod = await fs.getJSON(`/repo/${dir}/mod.json`);
|
||||||
|
mod._dir = dir;
|
||||||
|
mod.tags = mod.tags ?? [];
|
||||||
|
mod.css = mod.css ?? [];
|
||||||
|
mod.js = mod.js ?? {};
|
||||||
|
|
||||||
|
const check = (prop, value, condition) =>
|
||||||
|
Promise.resolve(condition ? value : err(`invalid ${prop} ${JSON.stringify(value)}`)),
|
||||||
|
validation = await registry.validate(mod, err, check);
|
||||||
|
if (validation.every((condition) => condition !== ERROR)) registry._list.push(mod);
|
||||||
|
} catch (e) {
|
||||||
|
err('invalid mod.json');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(registry._list);
|
||||||
|
return registry._list;
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.errors = async (callback = () => {}) => {
|
||||||
|
if (!registry._errors) await registry.get();
|
||||||
|
callback(registry._errors);
|
||||||
|
return registry._errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { web, fs, regexers, registry };
|
||||||
|
@ -6,17 +6,31 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { registry, web } from './helpers.js';
|
import { web, fs, registry } from './helpers.js';
|
||||||
|
|
||||||
export default async () => {
|
export default () => {
|
||||||
web.whenReady().then(async () => {
|
web.whenReady([], async () => {
|
||||||
for (let mod of await registry) {
|
web.loadStyleset('/src/client.css');
|
||||||
|
for (let mod of await registry.get()) {
|
||||||
for (let sheet of mod.css?.client || []) {
|
for (let sheet of mod.css?.client || []) {
|
||||||
web.loadStyleset(`repo/${mod.dir}/${sheet}`);
|
web.loadStyleset(`/repo/${mod._dir}/${sheet}`);
|
||||||
}
|
}
|
||||||
for (let script of mod.js?.client || []) {
|
for (let script of mod.js?.client || []) {
|
||||||
import(chrome.runtime.getURL(`repo/${mod.dir}/${script}`));
|
import(chrome.runtime.getURL(`/repo/${mod._dir}/${script}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sidebarSelector =
|
||||||
|
'#notion-app > div > div.notion-cursor-listener > div.notion-sidebar-container > div > div > div > div:nth-child(4)';
|
||||||
|
web.whenReady([sidebarSelector], async () => {
|
||||||
|
const enhancerIcon = await fs.getText('/icons/colour.svg'),
|
||||||
|
enhancerSidebarElement = web.createElement(
|
||||||
|
`<div class="enhancer--sidebarMenuTrigger" role="button" tabindex="0"><div><div>${enhancerIcon}</div><div><div>notion-enhancer</div></div></div></div>`
|
||||||
|
);
|
||||||
|
enhancerSidebarElement.addEventListener('click', (event) =>
|
||||||
|
chrome.runtime.sendMessage({ type: 'openEnhancerMenu' })
|
||||||
|
);
|
||||||
|
document.querySelector(sidebarSelector).appendChild(enhancerSidebarElement);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -3,3 +3,30 @@
|
|||||||
* (c) 2021 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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
let _enhancerMenuTab;
|
||||||
|
async function openEnhancerMenu() {
|
||||||
|
if (!_enhancerMenuTab) {
|
||||||
|
_enhancerMenuTab = await new Promise((res, rej) => {
|
||||||
|
chrome.tabs.create(
|
||||||
|
{
|
||||||
|
url: chrome.runtime.getURL('/src/gui.html'),
|
||||||
|
},
|
||||||
|
res
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
chrome.tabs.highlight({ 'tabs': _enhancerMenuTab.index }, function () {});
|
||||||
|
}
|
||||||
|
chrome.action.onClicked.addListener(openEnhancerMenu);
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
switch (request.type) {
|
||||||
|
case 'openEnhancerMenu':
|
||||||
|
openEnhancerMenu();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user