add new colour picker option

This commit is contained in:
dragonwocky 2021-05-13 10:59:08 +10:00
parent 6f3c1691c4
commit 99c166a1ac
7 changed files with 118 additions and 41 deletions

View File

@ -394,6 +394,9 @@ web.addTooltip = ($element, text) => {
* @namespace fmt * @namespace fmt
*/ */
export const fmt = {}; export const fmt = {};
import './dep/jscolor.min.js';
/** color picker with alpha channel using https://jscolor.com/ */
fmt.JSColor = JSColor;
import './dep/prism.js'; import './dep/prism.js';
/** syntax highlighting using https://prismjs.com/ */ /** syntax highlighting using https://prismjs.com/ */
fmt.Prism = Prism; fmt.Prism = Prism;
@ -525,6 +528,18 @@ regexers.url = (str, err = () => {}) => {
err(`invalid url ${str}`); err(`invalid url ${str}`);
return env.ERROR; return env.ERROR;
}; };
/**
* check for a valid color (https://regexr.com/39cgj)
* @param {string} str - the string to test
* @param {function} err - a callback to execute if the test fails
* @returns {boolean | env.ERROR} true or the env.ERROR constant
*/
regexers.color = (str, err = () => {}) => {
const match = str.match(/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i);
if (match && match.length) return true;
err(`invalid color ${str}`);
return env.ERROR;
};
/** /**
* an api for interacting with the enhancer's repository of mods * an api for interacting with the enhancer's repository of mods
@ -704,6 +719,13 @@ registry.validate = async (mod, err, check) => {
check('option.value', option.value, typeof option.value === 'number') check('option.value', option.value, typeof option.value === 'number')
); );
break; break;
case 'color':
conditions.push(
check('option.value', option.value, typeof option.value === 'string').then(
(color) => (color === env.ERROR ? env.ERROR : regexers.color(color, err))
)
);
break;
case 'file': case 'file':
conditions.push( conditions.push(
check( check(
@ -778,17 +800,14 @@ registry.defaults = async (id) => {
for (const opt of mod.options) { for (const opt of mod.options) {
switch (opt.type) { switch (opt.type) {
case 'toggle': case 'toggle':
case 'text':
case 'number':
case 'color':
defaults[opt.key] = opt.value; defaults[opt.key] = opt.value;
break; break;
case 'select': case 'select':
defaults[opt.key] = opt.values[0]; defaults[opt.key] = opt.values[0];
break; break;
case 'text':
defaults[opt.key] = opt.value;
break;
case 'number':
defaults[opt.key] = opt.value;
break;
case 'file': case 'file':
defaults[opt.key] = undefined; defaults[opt.key] = undefined;
break; break;

1
extension/dep/jscolor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -16,5 +16,10 @@ import(chrome.runtime.getURL('api.js')).then(({ web, registry }) => {
import(chrome.runtime.getURL(`repo/${mod._dir}/${script}`)); import(chrome.runtime.getURL(`repo/${mod._dir}/${script}`));
} }
} }
const errors = await registry.errors();
if (errors.length) {
console.log('notion-enhancer errors:');
console.table(errors);
}
}); });
}); });

View File

@ -14,7 +14,6 @@
- documentation e.g. \_file - documentation e.g. \_file
- complete/bugfix theming variables - complete/bugfix theming variables
- color pickers
#### app-specific #### app-specific

View File

@ -25,7 +25,6 @@ web.whenReady([sidebarSelector]).then(async () => {
list: await fs.getJSON('https://notion-enhancer.github.io/notifications.json'), list: await fs.getJSON('https://notion-enhancer.github.io/notifications.json'),
dismissed: await storage.get(_id, 'notifications', []), dismissed: await storage.get(_id, 'notifications', []),
}; };
console.log($enhancerSidebarElement);
notifications.waiting = notifications.list.filter( notifications.waiting = notifications.list.filter(
({ id }) => !notifications.dismissed.includes(id) ({ id }) => !notifications.dismissed.includes(id)
); );

View File

@ -250,6 +250,7 @@ main article img {
.library--select_label, .library--select_label,
.library--text_label, .library--text_label,
.library--number_label, .library--number_label,
.library--color_label,
.library--file_label { .library--file_label {
margin: 0.6rem 0; margin: 0.6rem 0;
display: block; display: block;
@ -260,6 +261,7 @@ main article img {
.library--select_label *, .library--select_label *,
.library--text_label *, .library--text_label *,
.library--number_label *, .library--number_label *,
.library--color_label *,
.library--file_label * { .library--file_label * {
appearance: none; appearance: none;
font-family: var(--theme--font_sans); font-family: var(--theme--font_sans);
@ -282,6 +284,7 @@ label [data-icon='fa/solid/question-circle'] {
.library--select_label > p, .library--select_label > p,
.library--text_label > p, .library--text_label > p,
.library--number_label > p, .library--number_label > p,
.library--color_label > p,
.library--file_label > p { .library--file_label > p {
margin: 0.6rem 0; margin: 0.6rem 0;
} }
@ -320,10 +323,11 @@ label [data-icon='fa/solid/question-circle'] {
} }
.library--toggle_label:focus-within .library--toggle, .library--toggle_label:focus-within .library--toggle,
.library--file_label:focus-within .library--file, .library--file_label:focus-within .library--file,
.library--color_label:focus-within .library--color,
.library--select_label .library--select:focus-within { .library--select_label .library--select:focus-within {
outline: solid thin; outline: -webkit-focus-ring-color auto 1px;
} }
.library--toggle_label input[type='checkbox'], .library--toggle_label input,
.library--file_label input { .library--file_label input {
position: absolute; position: absolute;
width: 1px; width: 1px;
@ -343,6 +347,7 @@ label [data-icon='fa/solid/question-circle'] {
.library--text_label textarea, .library--text_label textarea,
.library--number_label input, .library--number_label input,
.library--select_label .library--select, .library--select_label .library--select,
.library--color_label .library--color,
.library--file_label .library--file { .library--file_label .library--file {
width: 100%; width: 100%;
padding: 6px 8px; padding: 6px 8px;
@ -351,35 +356,44 @@ label [data-icon='fa/solid/question-circle'] {
border: none; border: none;
box-shadow: var(--theme--input-border) 0px 0px 0px 1px inset; box-shadow: var(--theme--input-border) 0px 0px 0px 1px inset;
} }
.library--select_label .library--select select { .library--select_label .library--select select,
.library--color_label .library--color input {
outline: none; outline: none;
cursor: pointer; cursor: pointer;
} }
.library--color_label .library--color input {
border-radius: 0;
}
.library--select_label .library--select select option { .library--select_label .library--select select option {
background: var(--theme--tag_select); background: var(--theme--tag_select);
} }
.library--select_label .library--select, .library--select_label .library--select,
.library--color_label .library--color,
.library--file_label .library--file { .library--file_label .library--file {
padding: 0; padding: 0;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
} }
.library--select_label .library--select > :first-child, .library--select_label .library--select > :first-child,
.library--color_label .library--color > :first-child,
.library--file_label .library--file > :first-child { .library--file_label .library--file > :first-child {
display: flex; display: flex;
padding: 6px 8px; padding: 6px 8px;
background: var(--theme--input-border); background: var(--theme--input-border);
} }
.library--select_label .library--select > :first-child svg, .library--select_label .library--select > :first-child svg,
.library--color_label .library--color > :first-child svg,
.library--file_label .library--file > :first-child svg { .library--file_label .library--file > :first-child svg {
width: 0.9em; width: 0.9em;
margin: auto 0; margin: auto 0;
} }
.library--select_label .library--select > :first-child svg *, .library--select_label .library--select > :first-child svg *,
.library--color_label .library--color > :first-child svg *,
.library--file_label .library--file > :first-child svg * { .library--file_label .library--file > :first-child svg * {
color: var(--theme--input_icon); color: var(--theme--input_icon);
} }
.library--select_label .library--select > :last-child, .library--select_label .library--select > :last-child,
.library--color_label .library--color > :last-child,
.library--file_label .library--file > :last-child { .library--file_label .library--file > :last-child {
margin: auto 0; margin: auto 0;
padding: 6px 8px; padding: 6px 8px;

View File

@ -7,7 +7,7 @@
'use strict'; 'use strict';
const _id = 'a6621988-551d-495a-97d8-3c568bca2e9e'; const _id = 'a6621988-551d-495a-97d8-3c568bca2e9e';
import { env, storage, web, fmt, fs, registry } from '../../api.js'; import { env, storage, web, fmt, fs, registry, regexers } from '../../api.js';
for (const mod of await registry.get((mod) => registry.isEnabled(mod.id))) { for (const mod of await registry.get((mod) => registry.isEnabled(mod.id))) {
for (const sheet of mod.css?.menu || []) { for (const sheet of mod.css?.menu || []) {
@ -232,6 +232,46 @@ components.options = {
if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip); if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip);
return opt; return opt;
}, },
async color(id, { key, label, tooltip }) {
const state = await storage.get(id, key),
opt = web.createElement(web.html`
<label for="color--${web.escapeHtml(`${id}.${key}`)}" class="library--color_label">
<p class="library--color_title">
<span data-tooltip>${web.escapeHtml(label)}
<i data-icon="fa/solid/question-circle"></i>
</span>
<p class="library--color">
<span><i data-icon="fa/solid/eye-dropper"></i></span>
<input type="text" id="color--${web.escapeHtml(`${id}.${key}`)}"/>
</p>
</label>`);
const $fill = opt.querySelector('input'),
paintInput = () => {
$fill.style.background = picker.toBackground();
$fill.style.color = picker.isLight() ? '#000' : '#fff';
},
picker = new fmt.JSColor($fill, {
value: state,
previewSize: 0,
borderRadius: 3,
borderColor: 'var(--theme--divider)',
controlBorderColor: 'var(--theme--divider)',
backgroundColor: 'var(--theme--page)',
onInput() {
paintInput();
},
onChange() {
paintInput();
storage.set(id, key, this.toRGBAString());
},
});
paintInput();
opt.addEventListener('click', (event) => {
picker.show();
});
if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip);
return opt;
},
async file(id, { key, label, tooltip, extensions }) { async file(id, { key, label, tooltip, extensions }) {
const state = await storage.get(id, key), const state = await storage.get(id, key),
opt = web.createElement(web.html` opt = web.createElement(web.html`
@ -295,15 +335,13 @@ const actionButtons = {
_reloadTriggered: false, _reloadTriggered: false,
async reload($fragment = document) { async reload($fragment = document) {
let $reload = $fragment.querySelector('[data-reload]'); let $reload = $fragment.querySelector('[data-reload]');
if (!$reload) { if (!$reload && this._reloadTriggered) {
$reload = web.createElement(web.html` $reload = web.createElement(web.html`
<button class="action--alert" data-reload> <button class="action--alert" data-reload>
<span><i data-icon="fa/solid/redo"></i></span> <span><i data-icon="fa/solid/redo"></i></span>
<span>reload tabs to apply changes</span> <span>reload tabs to apply changes</span>
</button>`); </button>`);
$reload.addEventListener('click', env.reloadTabs); $reload.addEventListener('click', env.reloadTabs);
}
if (this._reloadTriggered) {
$fragment.querySelector('.action--buttons').append($reload); $fragment.querySelector('.action--buttons').append($reload);
await new Promise((res, rej) => requestAnimationFrame(res)); await new Promise((res, rej) => requestAnimationFrame(res));
$reload.dataset.triggered = true; $reload.dataset.triggered = true;
@ -311,19 +349,19 @@ const actionButtons = {
}, },
async clearFilters($fragment = document) { async clearFilters($fragment = document) {
let $clearFilters = $fragment.querySelector('[data-clear-filters]'); let $clearFilters = $fragment.querySelector('[data-clear-filters]');
if (!$clearFilters) {
$clearFilters = web.createElement(web.html`
<a class="action--alert" href="?view=library" data-clear-filters>
<span><i data-icon="fa/solid/times"></i></span>
<span>clear filters</span>
</a>`);
}
const search = router.getSearch(); const search = router.getSearch();
if (search.get('tag') || search.has('enabled') || search.has('disabled')) { if (search.get('tag') || search.has('enabled') || search.has('disabled')) {
$fragment.querySelector('.action--buttons').append($clearFilters); if (!$clearFilters) {
await new Promise((res, rej) => requestAnimationFrame(res)); $clearFilters = web.createElement(web.html`
$clearFilters.dataset.triggered = true; <a class="action--alert" href="?view=library" data-clear-filters>
} else $clearFilters.remove(); <span><i data-icon="fa/solid/times"></i></span>
<span>clear filters</span>
</a>`);
$fragment.querySelector('.action--buttons').append($clearFilters);
await new Promise((res, rej) => requestAnimationFrame(res));
$clearFilters.dataset.triggered = true;
}
} else if ($clearFilters) $clearFilters.remove();
}, },
}; };
storage.addChangeListener(async (event) => { storage.addChangeListener(async (event) => {
@ -395,22 +433,24 @@ router.addView(
.querySelector(`.action--buttons > [href="?view=library&${filter}"]`) .querySelector(`.action--buttons > [href="?view=library&${filter}"]`)
.classList[active ? 'add' : 'remove']('action--active'); .classList[active ? 'add' : 'remove']('action--active');
} }
for (const card of document.querySelectorAll('main > .library--card')) { const visible = new Set();
const { tags } = (await registry.get()).find((mod) => mod.id === card.dataset.mod), for (const mod of await registry.get()) {
isEnabled = await registry.isEnabled(card.dataset.mod); const isEnabled = await registry.isEnabled(mod.id),
if ( filterConditions =
(search.has('tag') ? tags.includes(search.get('tag')) : true) && (search.has('tag') ? mod.tags.includes(search.get('tag')) : true) &&
(search.has('enabled') && search.has('disabled') (search.has('enabled') && search.has('disabled')
? true ? true
: search.has('enabled') : search.has('enabled')
? isEnabled ? isEnabled
: search.has('disabled') : search.has('disabled')
? !isEnabled ? !isEnabled
: true) : true);
) { if (filterConditions) visible.add(mod.id);
card.style.display = '';
} else card.style.display = 'none';
} }
for (const card of document.querySelectorAll('main > .library--card'))
card.style.display = 'none';
for (const card of document.querySelectorAll('main > .library--card'))
if (visible.has(card.dataset.mod)) card.style.display = '';
actionButtons.clearFilters(); actionButtons.clearFilters();
} }
); );