mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-09 15:09:02 +00:00
refactor api ready for submodule
This commit is contained in:
parent
abbfce9af1
commit
ac12b164da
@ -9,17 +9,18 @@
|
|||||||
/** @module notion-enhancer/api */
|
/** @module notion-enhancer/api */
|
||||||
|
|
||||||
/** environment-specific methods and constants */
|
/** environment-specific methods and constants */
|
||||||
export * as env from './env.mjs';
|
import * as env from './env.mjs';
|
||||||
/** environment-specific filesystem reading */
|
|
||||||
export * as fs from './fs.mjs';
|
|
||||||
/** environment-specific data persistence */
|
|
||||||
export * as storage from './storage.mjs';
|
|
||||||
|
|
||||||
/** helpers for formatting or parsing text */
|
/** environment-specific filesystem reading */
|
||||||
export * as fmt from './fmt.mjs';
|
const fs = env.name === 'extension' ? await import('./extension-fs.mjs') : {};
|
||||||
|
/** environment-specific data persistence */
|
||||||
|
const storage = env.name === 'extension' ? await import('./extension-storage.mjs') : {};
|
||||||
|
|
||||||
|
/** helpers for formatting, validating and parsing values */
|
||||||
|
import * as fmt from './fmt.mjs';
|
||||||
/** interactions with the enhancer's repository of mods */
|
/** interactions with the enhancer's repository of mods */
|
||||||
export * as registry from './registry.mjs';
|
import * as registry from './registry.mjs';
|
||||||
/** pattern and type validators */
|
|
||||||
export * as validation from './validation.mjs';
|
|
||||||
/** helpers for manipulation of a webpage */
|
/** helpers for manipulation of a webpage */
|
||||||
export * as web from './web.mjs';
|
import * as web from './web.mjs';
|
||||||
|
|
||||||
|
export { env, fs, storage, fmt, registry, web };
|
||||||
|
@ -11,39 +11,36 @@
|
|||||||
* @module notion-enhancer/api/env
|
* @module notion-enhancer/api/env
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
import env from '../env.mjs';
|
||||||
* the environment/platform name code is currently being executed in
|
|
||||||
* @constant {string}
|
|
||||||
*/
|
|
||||||
export const name = 'extension';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* all environments/platforms currently supported by the enhancer
|
* the environment/platform name code is currently being executed in
|
||||||
* @constant {array<string>}
|
* @constant
|
||||||
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
export const supported = ['linux', 'win32', 'darwin', 'extension'];
|
export const name = env.name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the current version of the enhancer
|
* the current version of the enhancer
|
||||||
* @constant {string}
|
* @constant
|
||||||
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
export const version = chrome.runtime.getManifest().version;
|
export const version = env.version;
|
||||||
|
|
||||||
/** open the enhancer's menu */
|
/**
|
||||||
export const focusMenu = () => chrome.runtime.sendMessage({ action: 'focusMenu' });
|
* open the enhancer's menu
|
||||||
|
* @type {function}
|
||||||
|
*/
|
||||||
|
export const focusMenu = env.focusMenu;
|
||||||
|
|
||||||
/** focus an active notion tab */
|
/**
|
||||||
export const focusNotion = () => chrome.runtime.sendMessage({ action: 'focusNotion' });
|
* focus an active notion tab
|
||||||
|
* @type {function}
|
||||||
|
*/
|
||||||
|
export const focusNotion = env.focusNotion;
|
||||||
|
|
||||||
/** reload all notion and enhancer menu tabs to apply changes */
|
/**
|
||||||
export const reload = () => chrome.runtime.sendMessage({ action: 'reload' });
|
* reload all notion and enhancer menu tabs to apply changes
|
||||||
|
* @type {function}
|
||||||
/** a notification displayed when the menu is opened for the first time */
|
*/
|
||||||
export const welcomeNotification = {
|
export const reload = env.reload;
|
||||||
id: '84e2d49b-c3dc-44b4-a154-cf589676bfa0',
|
|
||||||
color: 'purple',
|
|
||||||
icon: 'message-circle',
|
|
||||||
message: 'Welcome! Come chat with us on Discord.',
|
|
||||||
link: 'https://discord.gg/sFWPXtA',
|
|
||||||
version,
|
|
||||||
};
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* @module notion-enhancer/api/fmt
|
* @module notion-enhancer/api/fmt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as web from './web.mjs';
|
import { web, fs } from './_.mjs';
|
||||||
|
|
||||||
import '../dep/prism.min.js';
|
import '../dep/prism.min.js';
|
||||||
/** syntax highlighting using https://prismjs.com/ */
|
/** syntax highlighting using https://prismjs.com/ */
|
||||||
@ -80,3 +80,53 @@ export const slugger = (heading, slugs = new Set()) => {
|
|||||||
}
|
}
|
||||||
return slug;
|
return slug;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const patterns = {
|
||||||
|
alphanumeric: /^[\w\.-]+$/,
|
||||||
|
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
||||||
|
semver:
|
||||||
|
/^(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,
|
||||||
|
email:
|
||||||
|
/^(([^<>()\[\]\\.,;:\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,
|
||||||
|
url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,
|
||||||
|
color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i,
|
||||||
|
};
|
||||||
|
function test(str, pattern) {
|
||||||
|
const match = str.match(pattern);
|
||||||
|
return !!(match && match.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test the type of a value. unifies builtin, regex, and environment/api checks
|
||||||
|
* @param {*} value - the value to check
|
||||||
|
* @param {string|array<values>} type - the type the value should be or a list of allowed values
|
||||||
|
* @returns {boolean} whether or not the value matches the type
|
||||||
|
*/
|
||||||
|
export const is = async (value, type, { extension = '' } = {}) => {
|
||||||
|
extension = !value || !value.endsWith || value.endsWith(extension);
|
||||||
|
if (Array.isArray(type)) {
|
||||||
|
return type.includes(value);
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 'array':
|
||||||
|
return Array.isArray(value);
|
||||||
|
case 'object':
|
||||||
|
return value && typeof value === 'object' && !Array.isArray(value);
|
||||||
|
case 'undefined':
|
||||||
|
case 'boolean':
|
||||||
|
case 'number':
|
||||||
|
return typeof value === type && extension;
|
||||||
|
case 'string':
|
||||||
|
return typeof value === type && value.length && extension;
|
||||||
|
case 'alphanumeric':
|
||||||
|
case 'uuid':
|
||||||
|
case 'semver':
|
||||||
|
case 'email':
|
||||||
|
case 'url':
|
||||||
|
case 'color':
|
||||||
|
return typeof value === 'string' && test(value, patterns[type]) && extension;
|
||||||
|
case 'file':
|
||||||
|
return typeof value === 'string' && value && (await fs.isFile(value)) && extension;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
221
extension/api/registry-validation.mjs
Normal file
221
extension/api/registry-validation.mjs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* notion-enhancer: api
|
||||||
|
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { fmt, registry } from './_.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internally used to validate mod.json files and provide helpful errors
|
||||||
|
* @private
|
||||||
|
* @param {object} mod - a mod's mod.json in object form
|
||||||
|
* @returns {boolean} whether or not the mod has passed validation
|
||||||
|
*/
|
||||||
|
export async function validate(mod) {
|
||||||
|
const check = async (
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
types,
|
||||||
|
{
|
||||||
|
extension = '',
|
||||||
|
error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify(
|
||||||
|
value
|
||||||
|
)}`,
|
||||||
|
optional = false,
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
let test;
|
||||||
|
for (const type of Array.isArray(types) ? [types] : types.split('|')) {
|
||||||
|
if (type === 'file') {
|
||||||
|
test =
|
||||||
|
value && !value.startsWith('http')
|
||||||
|
? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension })
|
||||||
|
: false;
|
||||||
|
} else test = await fmt.is(value, type, { extension });
|
||||||
|
if (test) break;
|
||||||
|
}
|
||||||
|
if (!test) {
|
||||||
|
if (optional && (await fmt.is(value, 'undefined'))) return true;
|
||||||
|
if (error) registry._errors.push({ source: mod._dir, message: error });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
let conditions = [
|
||||||
|
check('name', mod.name, 'string'),
|
||||||
|
check('id', mod.id, 'uuid'),
|
||||||
|
check('version', mod.version, 'semver'),
|
||||||
|
check('environments', mod.environments, 'array', { optional: true }).then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
if (!mod.environments) {
|
||||||
|
mod.environments = registry.supportedEnvs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mod.environments.map((tag) =>
|
||||||
|
check('environments.env', tag, registry.supportedEnvs)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
check('description', mod.description, 'string'),
|
||||||
|
check('preview', mod.preview, 'file|url', { optional: true }),
|
||||||
|
check('tags', mod.tags, 'array').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
const containsCategory = mod.tags.filter((tag) =>
|
||||||
|
['core', 'extension', 'theme'].includes(tag)
|
||||||
|
).length;
|
||||||
|
if (!containsCategory) {
|
||||||
|
registry._errors.push({
|
||||||
|
source: mod._dir,
|
||||||
|
message: `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): ${JSON.stringify(
|
||||||
|
mod.tags
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(mod.tags.includes('theme') &&
|
||||||
|
!(mod.tags.includes('light') || mod.tags.includes('dark'))) ||
|
||||||
|
(mod.tags.includes('light') && mod.tags.includes('dark'))
|
||||||
|
) {
|
||||||
|
registry._errors.push({
|
||||||
|
source: mod._dir,
|
||||||
|
message: `invalid tags (themes must be either 'light' or 'dark', not neither or both): ${JSON.stringify(
|
||||||
|
mod.tags
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mod.tags.map((tag) => check('tags.tag', tag, 'string'));
|
||||||
|
}),
|
||||||
|
check('authors', mod.authors, 'array').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return mod.authors.map((author) => [
|
||||||
|
check('authors.author.name', author.name, 'string'),
|
||||||
|
check('authors.author.email', author.email, 'email'),
|
||||||
|
check('authors.author.homepage', author.homepage, 'url'),
|
||||||
|
check('authors.author.avatar', author.avatar, 'url'),
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
check('css', mod.css, 'object').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
const tests = [];
|
||||||
|
for (let dest of ['frame', 'client', 'menu']) {
|
||||||
|
if (!mod.css[dest]) continue;
|
||||||
|
let test = check(`css.${dest}`, mod.css[dest], 'array');
|
||||||
|
test = test.then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return mod.css[dest].map((file) =>
|
||||||
|
check(`css.${dest}.file`, file, 'file', { extension: '.css' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
tests.push(test);
|
||||||
|
}
|
||||||
|
return tests;
|
||||||
|
}),
|
||||||
|
check('js', mod.js, 'object').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
const tests = [];
|
||||||
|
if (mod.js.client) {
|
||||||
|
let test = check('js.client', mod.js.client, 'array');
|
||||||
|
test = test.then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return mod.js.client.map((file) =>
|
||||||
|
check('js.client.file', file, 'file', { extension: '.mjs' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
tests.push(test);
|
||||||
|
}
|
||||||
|
if (mod.js.electron) {
|
||||||
|
let test = check('js.electron', mod.js.electron, 'array');
|
||||||
|
test = test.then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return mod.js.electron.map((file) =>
|
||||||
|
check('js.electron.file', file, 'object').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return [
|
||||||
|
check('js.electron.file.source', file.source, 'file', {
|
||||||
|
extension: '.mjs',
|
||||||
|
}),
|
||||||
|
// referencing the file within the electron app
|
||||||
|
// existence can't be validated, so only format is
|
||||||
|
check('js.electron.file.target', file.target, 'string', {
|
||||||
|
extension: '.js',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
tests.push(test);
|
||||||
|
}
|
||||||
|
return tests;
|
||||||
|
}),
|
||||||
|
check('options', mod.options, 'array').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return mod.options.map((option) =>
|
||||||
|
check('options.option.type', option.type, registry.optionTypes).then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
const tests = [
|
||||||
|
check('options.option.key', option.key, 'alphanumeric'),
|
||||||
|
check('options.option.label', option.label, 'string'),
|
||||||
|
check('options.option.tooltip', option.tooltip, 'string', {
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
check('options.option.environments', option.environments, 'array', {
|
||||||
|
optional: true,
|
||||||
|
}).then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
if (!option.environments) {
|
||||||
|
option.environments = registry.supportedEnvs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return option.environments.map((environment) =>
|
||||||
|
check('options.option.environments.env', environment, registry.supportedEnvs)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
switch (option.type) {
|
||||||
|
case 'toggle':
|
||||||
|
tests.push(check('options.option.value', option.value, 'boolean'));
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
tests.push(
|
||||||
|
check('options.option.values', option.values, 'array').then((passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return option.values.map((value) =>
|
||||||
|
check('options.option.values.value', value, 'string')
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
case 'hotkey':
|
||||||
|
tests.push(check('options.option.value', option.value, 'string'));
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
case 'color':
|
||||||
|
tests.push(check('options.option.value', option.value, option.type));
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
tests.push(
|
||||||
|
check('options.option.extensions', option.extensions, 'array').then(
|
||||||
|
(passed) => {
|
||||||
|
if (!passed) return false;
|
||||||
|
return option.extensions.map((value) =>
|
||||||
|
check('options.option.extensions.extension', value, 'string')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return tests;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
do {
|
||||||
|
conditions = await Promise.all(conditions.flat(Infinity));
|
||||||
|
} while (conditions.some((condition) => Array.isArray(condition)));
|
||||||
|
return conditions.every((passed) => passed);
|
||||||
|
}
|
@ -11,238 +11,57 @@
|
|||||||
* @module notion-enhancer/api/registry
|
* @module notion-enhancer/api/registry
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as env from './env.mjs';
|
import { env, fs, storage } from './_.mjs';
|
||||||
import { getJSON } from './fs.mjs';
|
import { validate } from './registry-validation.mjs';
|
||||||
import * as storage from './storage.mjs';
|
|
||||||
import { is } from './validation.mjs';
|
|
||||||
|
|
||||||
const _cache = [],
|
export const _cache = [],
|
||||||
_errors = [];
|
_errors = [];
|
||||||
|
|
||||||
/** mod ids whitelisted as part of the enhancer's core, permanently enabled */
|
/**
|
||||||
|
* mod ids whitelisted as part of the enhancer's core, permanently enabled
|
||||||
|
* @constant
|
||||||
|
* @type {array<string>}
|
||||||
|
*/
|
||||||
export const core = [
|
export const core = [
|
||||||
'a6621988-551d-495a-97d8-3c568bca2e9e',
|
'a6621988-551d-495a-97d8-3c568bca2e9e',
|
||||||
'0f0bf8b6-eae6-4273-b307-8fc43f2ee082',
|
'0f0bf8b6-eae6-4273-b307-8fc43f2ee082',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** all available configuration types */
|
/**
|
||||||
export const optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey'];
|
* all environments/platforms currently supported by the enhancer
|
||||||
|
* @constant
|
||||||
/** the name of the active configuration profile */
|
* @type {array<string>}
|
||||||
export const profileName = await storage.get(['currentprofile'], 'default');
|
*/
|
||||||
|
export const supportedEnvs = ['linux', 'win32', 'darwin', 'extension'];
|
||||||
/** the root database for the current profile */
|
|
||||||
export const profileDB = storage.db(['profiles', profileName]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* internally used to validate mod.json files and provide helpful errors
|
* all available configuration types
|
||||||
* @private
|
* @constant
|
||||||
* @param {object} mod - a mod's mod.json in object form
|
* @type {array<string>}
|
||||||
* @returns {boolean} whether or not the mod has passed validation
|
|
||||||
*/
|
*/
|
||||||
async function validate(mod) {
|
export const optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey'];
|
||||||
const check = async (
|
|
||||||
key,
|
/**
|
||||||
value,
|
* the name of the active configuration profile
|
||||||
types,
|
* @returns {string}
|
||||||
{
|
*/
|
||||||
extension = '',
|
export const profileName = async () => storage.get(['currentprofile'], 'default');
|
||||||
error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify(
|
|
||||||
value
|
/**
|
||||||
)}`,
|
* the root database for the current profile
|
||||||
optional = false,
|
* @returns {object} the get/set functions for the profile's storage
|
||||||
} = {}
|
*/
|
||||||
) => {
|
export const profileDB = async () => storage.db(['profiles', await profileName()]);
|
||||||
let test;
|
|
||||||
for (const type of types.split('|')) {
|
/** a notification displayed when the menu is opened for the first time */
|
||||||
if (type === 'file') {
|
export const welcomeNotification = {
|
||||||
test =
|
id: '84e2d49b-c3dc-44b4-a154-cf589676bfa0',
|
||||||
value && !value.startsWith('http')
|
color: 'purple',
|
||||||
? await is(`repo/${mod._dir}/${value}`, type, { extension })
|
icon: 'message-circle',
|
||||||
: false;
|
message: 'Welcome! Come chat with us on Discord.',
|
||||||
} else test = await is(value, type, { extension });
|
link: 'https://discord.gg/sFWPXtA',
|
||||||
if (test) break;
|
version: env.version,
|
||||||
}
|
};
|
||||||
if (!test) {
|
|
||||||
if (optional && (await is(value, 'undefined'))) return true;
|
|
||||||
if (error) _errors.push({ source: mod._dir, message: error });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
let conditions = [
|
|
||||||
check('name', mod.name, 'string'),
|
|
||||||
check('id', mod.id, 'uuid'),
|
|
||||||
check('version', mod.version, 'semver'),
|
|
||||||
check('environments', mod.environments, 'array', { optional: true }).then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
if (!mod.environments) {
|
|
||||||
mod.environments = env.supported;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return mod.environments.map((tag) => check('environments.env', tag, 'env'));
|
|
||||||
}),
|
|
||||||
check('description', mod.description, 'string'),
|
|
||||||
check('preview', mod.preview, 'file|url', { optional: true }),
|
|
||||||
check('tags', mod.tags, 'array').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
const containsCategory = mod.tags.filter((tag) =>
|
|
||||||
['core', 'extension', 'theme'].includes(tag)
|
|
||||||
).length;
|
|
||||||
if (!containsCategory) {
|
|
||||||
_errors.push({
|
|
||||||
source: mod._dir,
|
|
||||||
message: `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): ${JSON.stringify(
|
|
||||||
mod.tags
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(mod.tags.includes('theme') &&
|
|
||||||
!(mod.tags.includes('light') || mod.tags.includes('dark'))) ||
|
|
||||||
(mod.tags.includes('light') && mod.tags.includes('dark'))
|
|
||||||
) {
|
|
||||||
_errors.push({
|
|
||||||
source: mod._dir,
|
|
||||||
message: `invalid tags (themes must be either 'light' or 'dark', not neither or both): ${JSON.stringify(
|
|
||||||
mod.tags
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return mod.tags.map((tag) => check('tags.tag', tag, 'string'));
|
|
||||||
}),
|
|
||||||
check('authors', mod.authors, 'array').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return mod.authors.map((author) => [
|
|
||||||
check('authors.author.name', author.name, 'string'),
|
|
||||||
check('authors.author.email', author.email, 'email'),
|
|
||||||
check('authors.author.homepage', author.homepage, 'url'),
|
|
||||||
check('authors.author.avatar', author.avatar, 'url'),
|
|
||||||
]);
|
|
||||||
}),
|
|
||||||
check('css', mod.css, 'object').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
const tests = [];
|
|
||||||
for (let dest of ['frame', 'client', 'menu']) {
|
|
||||||
if (!mod.css[dest]) continue;
|
|
||||||
let test = check(`css.${dest}`, mod.css[dest], 'array');
|
|
||||||
test = test.then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return mod.css[dest].map((file) =>
|
|
||||||
check(`css.${dest}.file`, file, 'file', { extension: '.css' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
tests.push(test);
|
|
||||||
}
|
|
||||||
return tests;
|
|
||||||
}),
|
|
||||||
check('js', mod.js, 'object').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
const tests = [];
|
|
||||||
if (mod.js.client) {
|
|
||||||
let test = check('js.client', mod.js.client, 'array');
|
|
||||||
test = test.then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return mod.js.client.map((file) =>
|
|
||||||
check('js.client.file', file, 'file', { extension: '.mjs' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
tests.push(test);
|
|
||||||
}
|
|
||||||
if (mod.js.electron) {
|
|
||||||
let test = check('js.electron', mod.js.electron, 'array');
|
|
||||||
test = test.then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return mod.js.electron.map((file) =>
|
|
||||||
check('js.electron.file', file, 'object').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return [
|
|
||||||
check('js.electron.file.source', file.source, 'file', {
|
|
||||||
extension: '.mjs',
|
|
||||||
}),
|
|
||||||
// referencing the file within the electron app
|
|
||||||
// existence can't be validated, so only format is
|
|
||||||
check('js.electron.file.target', file.target, 'string', {
|
|
||||||
extension: '.js',
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
tests.push(test);
|
|
||||||
}
|
|
||||||
return tests;
|
|
||||||
}),
|
|
||||||
check('options', mod.options, 'array').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return mod.options.map((option) =>
|
|
||||||
check('options.option.type', option.type, 'optionType').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
const tests = [
|
|
||||||
check('options.option.key', option.key, 'alphanumeric'),
|
|
||||||
check('options.option.label', option.label, 'string'),
|
|
||||||
check('options.option.tooltip', option.tooltip, 'string', {
|
|
||||||
optional: true,
|
|
||||||
}),
|
|
||||||
check('options.option.environments', option.environments, 'array', {
|
|
||||||
optional: true,
|
|
||||||
}).then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
if (!option.environments) {
|
|
||||||
option.environments = env.supported;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return option.environments.map((env) =>
|
|
||||||
check('options.option.environments.env', env, 'env')
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
switch (option.type) {
|
|
||||||
case 'toggle':
|
|
||||||
tests.push(check('options.option.value', option.value, 'boolean'));
|
|
||||||
break;
|
|
||||||
case 'select':
|
|
||||||
tests.push(
|
|
||||||
check('options.option.values', option.values, 'array').then((passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return option.values.map((value) =>
|
|
||||||
check('options.option.values.value', value, 'string')
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
case 'hotkey':
|
|
||||||
tests.push(check('options.option.value', option.value, 'string'));
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
case 'color':
|
|
||||||
tests.push(check('options.option.value', option.value, option.type));
|
|
||||||
break;
|
|
||||||
case 'file':
|
|
||||||
tests.push(
|
|
||||||
check('options.option.extensions', option.extensions, 'array').then(
|
|
||||||
(passed) => {
|
|
||||||
if (!passed) return false;
|
|
||||||
return option.extensions.map((value) =>
|
|
||||||
check('options.option.extensions.extension', value, 'string')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tests;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
do {
|
|
||||||
conditions = await Promise.all(conditions.flat(Infinity));
|
|
||||||
} while (conditions.some((condition) => Array.isArray(condition)));
|
|
||||||
return conditions.every((passed) => passed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list all available mods in the repo
|
* list all available mods in the repo
|
||||||
@ -251,12 +70,13 @@ async function validate(mod) {
|
|||||||
*/
|
*/
|
||||||
export const list = async (filter = (mod) => true) => {
|
export const list = async (filter = (mod) => true) => {
|
||||||
if (!_cache.length) {
|
if (!_cache.length) {
|
||||||
for (const dir of await getJSON('repo/registry.json')) {
|
for (const dir of await fs.getJSON('repo/registry.json')) {
|
||||||
try {
|
try {
|
||||||
const mod = await getJSON(`repo/${dir}/mod.json`);
|
const mod = await fs.getJSON(`repo/${dir}/mod.json`);
|
||||||
mod._dir = dir;
|
mod._dir = dir;
|
||||||
if (await validate(mod)) _cache.push(mod);
|
if (await validate(mod)) _cache.push(mod);
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
_errors.push({ source: dir, message: 'invalid mod.json' });
|
_errors.push({ source: dir, message: 'invalid mod.json' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,7 +115,7 @@ export const enabled = async (id) => {
|
|||||||
const mod = await get(id);
|
const mod = await get(id);
|
||||||
if (!mod.environments.includes(env.name)) return false;
|
if (!mod.environments.includes(env.name)) return false;
|
||||||
if (core.includes(id)) return true;
|
if (core.includes(id)) return true;
|
||||||
return await profileDB.get(['_mods', id], false);
|
return (await profileDB()).get(['_mods', id], false);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -328,6 +148,7 @@ export const optionDefault = async (id, key) => {
|
|||||||
* @returns {object} an object with the wrapped get/set functions
|
* @returns {object} an object with the wrapped get/set functions
|
||||||
*/
|
*/
|
||||||
export const db = async (id) => {
|
export const db = async (id) => {
|
||||||
|
const db = await profileDB();
|
||||||
return storage.db(
|
return storage.db(
|
||||||
[id],
|
[id],
|
||||||
async (path, fallback = undefined) => {
|
async (path, fallback = undefined) => {
|
||||||
@ -335,8 +156,8 @@ export const db = async (id) => {
|
|||||||
// profiles -> profile -> mod -> option
|
// profiles -> profile -> mod -> option
|
||||||
fallback = (await optionDefault(id, path[1])) ?? fallback;
|
fallback = (await optionDefault(id, path[1])) ?? fallback;
|
||||||
}
|
}
|
||||||
return profileDB.get(path, fallback);
|
return db.get(path, fallback);
|
||||||
},
|
},
|
||||||
profileDB.set
|
db.set
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* notion-enhancer: api
|
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pattern and type validators
|
|
||||||
* @module notion-enhancer/api/validation
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { supported } from './env.mjs';
|
|
||||||
import { optionTypes } from './registry.mjs';
|
|
||||||
import { isFile } from './fs.mjs';
|
|
||||||
|
|
||||||
const patterns = {
|
|
||||||
alphanumeric: /^[\w\.-]+$/,
|
|
||||||
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
||||||
semver:
|
|
||||||
/^(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,
|
|
||||||
email:
|
|
||||||
/^(([^<>()\[\]\\.,;:\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,
|
|
||||||
url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,
|
|
||||||
color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i,
|
|
||||||
};
|
|
||||||
function test(str, pattern) {
|
|
||||||
const match = str.match(pattern);
|
|
||||||
return !!(match && match.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check is string is alphanumeric-only (letters, numbers, underscores, dots, dashes)
|
|
||||||
* @param {string} str - the string to test
|
|
||||||
* @returns {boolean} whether or not the test passed successfully
|
|
||||||
*/
|
|
||||||
export const alphanumeric = (str) => test(str, patterns.alphanumeric);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check for a valid uuid (8-4-4-4-12 hexadecimal digits)
|
|
||||||
* @param {string} str - the string to test
|
|
||||||
* @returns {boolean} whether or not the test passed successfully
|
|
||||||
*/
|
|
||||||
export const uuid = (str) => test(str, patterns.uuid);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check for a valid semver (MAJOR.MINOR.PATCH)
|
|
||||||
* @param {string} str - the string to test
|
|
||||||
* @returns {boolean} whether or not the test passed successfully
|
|
||||||
*/
|
|
||||||
export const semver = (str) => test(str, patterns.semver);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check for a valid email (e.g. someone@somewhere.domain)
|
|
||||||
* @param {string} str - the string to test
|
|
||||||
* @returns {boolean} whether or not the test passed successfully
|
|
||||||
*/
|
|
||||||
export const email = (str) => test(str, patterns.email);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check for a valid url (e.g. https://example.com/path)
|
|
||||||
* @param {string} str - the string to test
|
|
||||||
* @returns {boolean} whether or not the test passed successfully
|
|
||||||
*/
|
|
||||||
export const url = (str) => test(str, patterns.url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check for a valid color (https://regexr.com/39cgj)
|
|
||||||
* @param {string} str - the string to test
|
|
||||||
* @returns {boolean} whether or not the test passed successfully
|
|
||||||
*/
|
|
||||||
export const color = (str) => test(str, patterns.color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* test the type of a value. unifies builtin, regex, and environment/api checks.
|
|
||||||
* @param {*} value - the value to check
|
|
||||||
* @param {string} type - the type the value should be
|
|
||||||
* @returns {boolean} whether or not the value matches the type
|
|
||||||
*/
|
|
||||||
export const is = async (value, type, { extension = '' } = {}) => {
|
|
||||||
extension = !value || !value.endsWith || value.endsWith(extension);
|
|
||||||
switch (type) {
|
|
||||||
case 'array':
|
|
||||||
return Array.isArray(value);
|
|
||||||
case 'object':
|
|
||||||
return value && typeof value === 'object' && !Array.isArray(value);
|
|
||||||
case 'undefined':
|
|
||||||
case 'boolean':
|
|
||||||
case 'number':
|
|
||||||
return typeof value === type && extension;
|
|
||||||
case 'string':
|
|
||||||
return typeof value === type && value.length && extension;
|
|
||||||
case 'alphanumeric':
|
|
||||||
case 'uuid':
|
|
||||||
case 'semver':
|
|
||||||
case 'email':
|
|
||||||
case 'url':
|
|
||||||
case 'color':
|
|
||||||
return typeof value === 'string' && test(value, patterns[type]) && extension;
|
|
||||||
case 'file':
|
|
||||||
return typeof value === 'string' && value && (await isFile(value)) && extension;
|
|
||||||
case 'env':
|
|
||||||
return supported.includes(value);
|
|
||||||
case 'optionType':
|
|
||||||
return optionTypes.includes(value);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
@ -11,8 +11,7 @@
|
|||||||
* @module notion-enhancer/api/web
|
* @module notion-enhancer/api/web
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { localPath } from './fs.mjs';
|
import { fs, fmt } from './_.mjs';
|
||||||
import { md } from './fmt.mjs';
|
|
||||||
|
|
||||||
const _hotkeyEventListeners = [],
|
const _hotkeyEventListeners = [],
|
||||||
_documentObserverListeners = [],
|
_documentObserverListeners = [],
|
||||||
@ -139,7 +138,7 @@ export const loadStylesheet = (path) => {
|
|||||||
document.head,
|
document.head,
|
||||||
html`<link
|
html`<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="${path.startsWith('https://') ? path : localPath(path)}"
|
href="${path.startsWith('https://') ? path : fs.localPath(path)}"
|
||||||
/>`
|
/>`
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
@ -158,7 +157,7 @@ export const icon = (name, attrs = {}) => {
|
|||||||
).trim();
|
).trim();
|
||||||
return `<svg ${Object.entries(attrs)
|
return `<svg ${Object.entries(attrs)
|
||||||
.map(([key, val]) => `${escape(key)}="${escape(val)}"`)
|
.map(([key, val]) => `${escape(key)}="${escape(val)}"`)
|
||||||
.join(' ')}><use xlink:href="${localPath('dep/feather-sprite.svg')}#${name}" /></svg>`;
|
.join(' ')}><use xlink:href="${fs.localPath('dep/feather-sprite.svg')}#${name}" /></svg>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,7 +192,7 @@ export const tooltip = ($ref, text) => {
|
|||||||
render(document.head, _$tooltipStylesheet);
|
render(document.head, _$tooltipStylesheet);
|
||||||
render(document.body, _$tooltip);
|
render(document.body, _$tooltip);
|
||||||
}
|
}
|
||||||
text = md.render(text);
|
text = fmt.md.render(text);
|
||||||
$ref.addEventListener('mouseover', (event) => {
|
$ref.addEventListener('mouseover', (event) => {
|
||||||
_$tooltip.innerHTML = text;
|
_$tooltip.innerHTML = text;
|
||||||
_$tooltip.style.display = 'block';
|
_$tooltip.style.display = 'block';
|
||||||
|
24
extension/env.mjs
Normal file
24
extension/env.mjs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
* @module notion-enhancer/api/env
|
||||||
|
*/
|
||||||
|
|
||||||
|
const focusMenu = () => chrome.runtime.sendMessage({ action: 'focusMenu' }),
|
||||||
|
focusNotion = () => chrome.runtime.sendMessage({ action: 'focusNotion' }),
|
||||||
|
reload = () => chrome.runtime.sendMessage({ action: 'reload' });
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'extension',
|
||||||
|
version: chrome.runtime.getManifest().version,
|
||||||
|
focusMenu,
|
||||||
|
focusNotion,
|
||||||
|
reload,
|
||||||
|
};
|
@ -33,7 +33,7 @@ export default async function (api, db) {
|
|||||||
const notifications = {
|
const notifications = {
|
||||||
cache: await storage.get(['notifications'], []),
|
cache: await storage.get(['notifications'], []),
|
||||||
provider: [
|
provider: [
|
||||||
env.welcomeNotification,
|
registry.welcomeNotification,
|
||||||
...(await fs.getJSON('https://notion-enhancer.github.io/notifications.json')),
|
...(await fs.getJSON('https://notion-enhancer.github.io/notifications.json')),
|
||||||
],
|
],
|
||||||
count: (await registry.errors()).length,
|
count: (await registry.errors()).length,
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { fmt, registry, web } from '../../api/_.mjs';
|
import { fmt, web } from '../../api/_.mjs';
|
||||||
|
|
||||||
import { notifications } from './notifications.mjs';
|
import { notifications } from './notifications.mjs';
|
||||||
|
import { profileDB } from './menu.mjs';
|
||||||
|
|
||||||
export const components = {
|
export const components = {
|
||||||
preview: (url) => web.html`<img
|
preview: (url) => web.html`<img
|
||||||
@ -56,7 +56,7 @@ export const components = {
|
|||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
toggle: async (mod, opt) => {
|
toggle: async (mod, opt) => {
|
||||||
const checked = await registry.profileDB.get([mod.id, opt.key], opt.value),
|
const checked = await profileDB.get([mod.id, opt.key], opt.value),
|
||||||
$toggle = components.toggle(opt.label, checked),
|
$toggle = components.toggle(opt.label, checked),
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = $toggle.children[0],
|
$label = $toggle.children[0],
|
||||||
@ -66,13 +66,13 @@ export const options = {
|
|||||||
web.tooltip($tooltip, opt.tooltip);
|
web.tooltip($tooltip, opt.tooltip);
|
||||||
}
|
}
|
||||||
$input.addEventListener('change', async (event) => {
|
$input.addEventListener('change', async (event) => {
|
||||||
await registry.profileDB.set([mod.id, opt.key], $input.checked);
|
await profileDB.set([mod.id, opt.key], $input.checked);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
return $toggle;
|
return $toggle;
|
||||||
},
|
},
|
||||||
select: async (mod, opt) => {
|
select: async (mod, opt) => {
|
||||||
const value = await registry.profileDB.get([mod.id, opt.key], opt.values[0]),
|
const value = await profileDB.get([mod.id, opt.key], opt.values[0]),
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = web.render(
|
$label = web.render(
|
||||||
web.html`<label class="input-label"></label>`,
|
web.html`<label class="input-label"></label>`,
|
||||||
@ -91,13 +91,13 @@ export const options = {
|
|||||||
$icon = web.html`${web.icon('chevron-down', { class: 'input-icon' })}`;
|
$icon = web.html`${web.icon('chevron-down', { class: 'input-icon' })}`;
|
||||||
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
||||||
$select.addEventListener('change', async (event) => {
|
$select.addEventListener('change', async (event) => {
|
||||||
await registry.profileDB.set([mod.id, opt.key], $select.value);
|
await profileDB.set([mod.id, opt.key], $select.value);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
return web.render($label, $select, $icon);
|
return web.render($label, $select, $icon);
|
||||||
},
|
},
|
||||||
text: async (mod, opt) => {
|
text: async (mod, opt) => {
|
||||||
const value = await registry.profileDB.get([mod.id, opt.key], opt.value),
|
const value = await profileDB.get([mod.id, opt.key], opt.value),
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = web.render(
|
$label = web.render(
|
||||||
web.html`<label class="input-label"></label>`,
|
web.html`<label class="input-label"></label>`,
|
||||||
@ -107,13 +107,13 @@ export const options = {
|
|||||||
$icon = web.html`${web.icon('type', { class: 'input-icon' })}`;
|
$icon = web.html`${web.icon('type', { class: 'input-icon' })}`;
|
||||||
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
||||||
$input.addEventListener('change', async (event) => {
|
$input.addEventListener('change', async (event) => {
|
||||||
await registry.profileDB.set([mod.id, opt.key], $input.value);
|
await profileDB.set([mod.id, opt.key], $input.value);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
return web.render($label, $input, $icon);
|
return web.render($label, $input, $icon);
|
||||||
},
|
},
|
||||||
number: async (mod, opt) => {
|
number: async (mod, opt) => {
|
||||||
const value = await registry.profileDB.get([mod.id, opt.key], opt.value),
|
const value = await profileDB.get([mod.id, opt.key], opt.value),
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = web.render(
|
$label = web.render(
|
||||||
web.html`<label class="input-label"></label>`,
|
web.html`<label class="input-label"></label>`,
|
||||||
@ -123,13 +123,13 @@ export const options = {
|
|||||||
$icon = web.html`${web.icon('hash', { class: 'input-icon' })}`;
|
$icon = web.html`${web.icon('hash', { class: 'input-icon' })}`;
|
||||||
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
||||||
$input.addEventListener('change', async (event) => {
|
$input.addEventListener('change', async (event) => {
|
||||||
await registry.profileDB.set([mod.id, opt.key], $input.value);
|
await profileDB.set([mod.id, opt.key], $input.value);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
return web.render($label, $input, $icon);
|
return web.render($label, $input, $icon);
|
||||||
},
|
},
|
||||||
color: async (mod, opt) => {
|
color: async (mod, opt) => {
|
||||||
const value = await registry.profileDB.get([mod.id, opt.key], opt.value),
|
const value = await profileDB.get([mod.id, opt.key], opt.value),
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = web.render(
|
$label = web.render(
|
||||||
web.html`<label class="input-label"></label>`,
|
web.html`<label class="input-label"></label>`,
|
||||||
@ -155,14 +155,14 @@ export const options = {
|
|||||||
});
|
});
|
||||||
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
if (opt.tooltip) web.tooltip($tooltip, opt.tooltip);
|
||||||
$input.addEventListener('change', async (event) => {
|
$input.addEventListener('change', async (event) => {
|
||||||
await registry.profileDB.set([mod.id, opt.key], $input.value);
|
await profileDB.set([mod.id, opt.key], $input.value);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
paint();
|
paint();
|
||||||
return web.render($label, $input, $icon);
|
return web.render($label, $input, $icon);
|
||||||
},
|
},
|
||||||
file: async (mod, opt) => {
|
file: async (mod, opt) => {
|
||||||
const { filename } = (await registry.profileDB.get([mod.id, opt.key], {})) || {},
|
const { filename } = (await profileDB.get([mod.id, opt.key], {})) || {},
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = web.render(
|
$label = web.render(
|
||||||
web.html`<label class="input-label"></label>`,
|
web.html`<label class="input-label"></label>`,
|
||||||
@ -181,7 +181,7 @@ export const options = {
|
|||||||
reader = new FileReader();
|
reader = new FileReader();
|
||||||
reader.onload = async (progress) => {
|
reader.onload = async (progress) => {
|
||||||
$filename.innerText = file.name;
|
$filename.innerText = file.name;
|
||||||
await registry.profileDB.set([mod.id, opt.key], {
|
await profileDB.set([mod.id, opt.key], {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
content: progress.currentTarget.result,
|
content: progress.currentTarget.result,
|
||||||
});
|
});
|
||||||
@ -191,7 +191,7 @@ export const options = {
|
|||||||
});
|
});
|
||||||
$latest.addEventListener('click', (event) => {
|
$latest.addEventListener('click', (event) => {
|
||||||
$filename.innerText = 'none';
|
$filename.innerText = 'none';
|
||||||
registry.profileDB.set([mod.id, opt.key], {});
|
profileDB.set([mod.id, opt.key], {});
|
||||||
});
|
});
|
||||||
return web.render(
|
return web.render(
|
||||||
web.html`<div></div>`,
|
web.html`<div></div>`,
|
||||||
@ -200,7 +200,7 @@ export const options = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
hotkey: async (mod, opt) => {
|
hotkey: async (mod, opt) => {
|
||||||
const value = await registry.profileDB.get([mod.id, opt.key], opt.value),
|
const value = await profileDB.get([mod.id, opt.key], opt.value),
|
||||||
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
$tooltip = web.html`${web.icon('info', { class: 'input-tooltip' })}`,
|
||||||
$label = web.render(
|
$label = web.render(
|
||||||
web.html`<label class="input-label"></label>`,
|
web.html`<label class="input-label"></label>`,
|
||||||
@ -229,7 +229,7 @@ export const options = {
|
|||||||
pressed.push(key);
|
pressed.push(key);
|
||||||
}
|
}
|
||||||
$input.value = pressed.join('+');
|
$input.value = pressed.join('+');
|
||||||
await registry.profileDB.set([mod.id, opt.key], $input.value);
|
await profileDB.set([mod.id, opt.key], $input.value);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
return web.render($label, $input, $icon);
|
return web.render($label, $input, $icon);
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { env, fs, storage, registry, web } from '../../api/_.mjs';
|
import { env, fs, storage, registry, web } from '../../api/_.mjs';
|
||||||
const db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e');
|
|
||||||
|
export const db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e'),
|
||||||
|
profileName = await registry.profileName(),
|
||||||
|
profileDB = await registry.profileDB();
|
||||||
|
|
||||||
import './styles.mjs';
|
import './styles.mjs';
|
||||||
import { notifications } from './notifications.mjs';
|
import { notifications } from './notifications.mjs';
|
||||||
@ -35,12 +38,12 @@ window.addEventListener('beforeunload', (event) => {
|
|||||||
|
|
||||||
const $main = web.html`<main class="main"></main>`,
|
const $main = web.html`<main class="main"></main>`,
|
||||||
$sidebar = web.html`<article class="sidebar"></article>`,
|
$sidebar = web.html`<article class="sidebar"></article>`,
|
||||||
$profile = web.html`<button class="profile-trigger">
|
|
||||||
Profile: ${web.escape(registry.profileName)}
|
|
||||||
</button>`,
|
|
||||||
$options = web.html`<div class="options-container">
|
$options = web.html`<div class="options-container">
|
||||||
<p class="options-placeholder">Select a mod to view and configure its options.</p>
|
<p class="options-placeholder">Select a mod to view and configure its options.</p>
|
||||||
</div>`;
|
</div>`,
|
||||||
|
$profile = web.html`<button class="profile-trigger">
|
||||||
|
Profile: ${web.escape(profileName)}
|
||||||
|
</button>`;
|
||||||
|
|
||||||
let _$profileConfig;
|
let _$profileConfig;
|
||||||
$profile.addEventListener('click', async (event) => {
|
$profile.addEventListener('click', async (event) => {
|
||||||
@ -51,14 +54,14 @@ $profile.addEventListener('click', async (event) => {
|
|||||||
const profileNames = [
|
const profileNames = [
|
||||||
...new Set([
|
...new Set([
|
||||||
...Object.keys(await storage.get(['profiles'], { default: {} })),
|
...Object.keys(await storage.get(['profiles'], { default: {} })),
|
||||||
registry.profileName,
|
profileName,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
$options = profileNames.map(
|
$options = profileNames.map(
|
||||||
(profile) => web.raw`<option
|
(profile) => web.raw`<option
|
||||||
class="select-option"
|
class="select-option"
|
||||||
value="${web.escape(profile)}"
|
value="${web.escape(profile)}"
|
||||||
${profile === registry.profileName ? 'selected' : ''}
|
${profile === profileName ? 'selected' : ''}
|
||||||
>${web.escape(profile)}</option>`
|
>${web.escape(profile)}</option>`
|
||||||
),
|
),
|
||||||
$select = web.html`<select class="input">
|
$select = web.html`<select class="input">
|
||||||
@ -68,7 +71,7 @@ $profile.addEventListener('click', async (event) => {
|
|||||||
$edit = web.html`<input
|
$edit = web.html`<input
|
||||||
type="text"
|
type="text"
|
||||||
class="input"
|
class="input"
|
||||||
value="${web.escape(registry.profileName)}"
|
value="${web.escape(profileName)}"
|
||||||
pattern="/^[A-Za-z0-9_-]+$/"
|
pattern="/^[A-Za-z0-9_-]+$/"
|
||||||
>`,
|
>`,
|
||||||
$export = web.html`<button class="profile-export">
|
$export = web.html`<button class="profile-export">
|
||||||
@ -214,13 +217,13 @@ const _$modListCache = {},
|
|||||||
mod.id !== id
|
mod.id !== id
|
||||||
);
|
);
|
||||||
for (const mod of mods) {
|
for (const mod of mods) {
|
||||||
registry.profileDB.set(['_mods', mod.id], false);
|
profileDB.set(['_mods', mod.id], false);
|
||||||
document.querySelector(
|
document.querySelector(
|
||||||
`[data-id="${web.escape(mod.id)}"] .toggle-check`
|
`[data-id="${web.escape(mod.id)}"] .toggle-check`
|
||||||
).checked = false;
|
).checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
registry.profileDB.set(['_mods', mod.id], event.target.checked);
|
profileDB.set(['_mods', mod.id], event.target.checked);
|
||||||
notifications.onChange();
|
notifications.onChange();
|
||||||
});
|
});
|
||||||
$mod.addEventListener('click', async (event) => {
|
$mod.addEventListener('click', async (event) => {
|
||||||
|
@ -15,7 +15,7 @@ export const notifications = {
|
|||||||
$container: web.html`<div class="notifications-container"></div>`,
|
$container: web.html`<div class="notifications-container"></div>`,
|
||||||
cache: await storage.get(['notifications'], []),
|
cache: await storage.get(['notifications'], []),
|
||||||
provider: [
|
provider: [
|
||||||
env.welcomeNotification,
|
registry.welcomeNotification,
|
||||||
...(await fs.getJSON('https://notion-enhancer.github.io/notifications.json')),
|
...(await fs.getJSON('https://notion-enhancer.github.io/notifications.json')),
|
||||||
],
|
],
|
||||||
add({ icon, message, id = undefined, color = undefined, link = undefined }) {
|
add({ icon, message, id = undefined, color = undefined, link = undefined }) {
|
||||||
|
Loading…
Reference in New Issue
Block a user