mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-06 21:49:03 +00:00
add menu filters + unspaghetti menu code
This commit is contained in:
parent
4e3f921ee3
commit
a55482d62d
2
extension/icons/monstr/party.svg
Normal file
2
extension/icons/monstr/party.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<!-- https://iconmonstr.com/party-5-svg/ -->
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path fill="currentColor" d="M4.823 21.933l2.734-1.171-3.241-8.847-1.561 4.372 2.068 5.646zm-2.594-4.174l-2.229 6.241 3.903-1.672-1.674-4.569zm6.248 2.609l2.934-1.258-3.482-9.141-2.215-1.969-.872 2.443 3.635 9.925zm7.523-3.224l-6.453-5.736 2.785 7.308 3.668-1.572zm-.826-5.003l2.201-1.445c.23-.152.295-.462.143-.693-.152-.232-.463-.295-.692-.143l-2.201 1.445c-.231.151-.295.461-.144.692.096.147.256.226.418.226.095 0 .19-.026.275-.082m-2.993-4.312l1.112-2.388c.117-.25.008-.548-.242-.664-.251-.116-.548-.009-.665.242l-1.111 2.388c-.117.25-.008.547.242.664l.211.047c.189 0 .368-.107.453-.289m-2.627.709c1.539-2.963 1.644-5.73.314-8.222-.09-.169-.263-.265-.442-.265-.37 0-.621.398-.44.736 1.166 2.184 1.058 4.637-.32 7.29-.127.245-.031.547.214.674.073.038.152.057.23.057.18 0 .355-.099.444-.27m6.505 6.095c2.017-1.434 4.463-1.64 7.272-.613.327.119.672-.123.672-.47 0-.203-.125-.395-.328-.47-3.136-1.147-5.894-.9-8.196.738-.224.16-.277.472-.117.698.098.136.251.209.407.209.101 0 .202-.03.29-.092m3.757-6.757l-1.697.014.938 1.415-.511 1.618 1.635-.455 1.381.986.073-1.696 1.365-1.009-1.591-.592-.538-1.61-1.055 1.329zm-7.307 3.624c.276-.009.492-.24.483-.517-.056-1.627.36-1.937 1.377-2.051 1.689-.191 1.785-1.312 1.842-1.982.053-.637.071-.851.773-.903.63-.046 1.331-.16 1.76-.659.461-.538.466-1.358.402-2.164-.021-.276-.266-.478-.537-.459-.275.021-.481.262-.459.537.062.787.011 1.23-.165 1.434-.149.174-.48.271-1.074.314-1.553.114-1.644 1.179-1.697 1.816-.057.668-.082.973-.956 1.071-2.075.234-2.315 1.619-2.266 3.08.01.27.231.483.5.483h.017m7.842-8.675c0 1.006.818 1.824 1.825 1.824 1.006 0 1.824-.818 1.824-1.824 0-1.007-.818-1.825-1.824-1.825-1.007 0-1.825.818-1.825 1.825m-6.623-2.841c1.104 0 2 .897 2 2 0 1.104-.896 2-2 2-1.103 0-2-.896-2-2 0-1.103.897-2 2-2"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -5,12 +5,13 @@
|
||||
- improved: split the core mod into the theming & menu mods.
|
||||
- improved: new larger menu layout, with individual mod pages.
|
||||
- improved: merged bracketed-links into tweaks.
|
||||
- improved: replaced confusing all-tag filters with themes/extensions/enabled/disabled filters.
|
||||
- removed: integrated scrollbar tweak (notion now includes by default).
|
||||
- removed: js insert. css insert moved to tweaks mod.
|
||||
- ported: tweaks, bypass-preview.
|
||||
|
||||
#### todo
|
||||
|
||||
- tag sort
|
||||
- documentation e.g. \_file
|
||||
- complete/bugfix theming variables
|
||||
- color pickers
|
||||
|
@ -4,6 +4,8 @@
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { web } from '../../api.js';
|
||||
|
||||
web.whenReady().then(async () => {
|
||||
@ -23,7 +25,7 @@ function getCurrentPage() {
|
||||
return { type: 'page', id: location.pathname.split(/(-|\/)/g).reverse()[0] };
|
||||
}
|
||||
let lastPage = getCurrentPage();
|
||||
web.observeDocument((event) => {
|
||||
web.addDocumentObserver((event) => {
|
||||
const currentPage = getCurrentPage();
|
||||
if (currentPage.id !== lastPage.id || currentPage.type !== lastPage.type) {
|
||||
const openAsPage = document.querySelector(
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bypass-preview",
|
||||
"id": "cb6fd684-f113-4a7a-9423-8f0f0cff069f",
|
||||
"description": "go straight to the normal full view when opening a page..",
|
||||
"description": "go straight to the normal full view when opening a page.",
|
||||
"version": "0.2.0",
|
||||
"tags": ["extension", "automation"],
|
||||
"authors": [
|
||||
|
@ -1 +1,3 @@
|
||||
# menu
|
||||
|
||||
[theming mod link test](?view=mod&id=0f0bf8b6-eae6-4273-b307-8fc43f2ee082)
|
||||
|
@ -25,6 +25,7 @@ web.whenReady([sidebarSelector]).then(async () => {
|
||||
list: await fs.getJSON('https://notion-enhancer.github.io/notifications.json'),
|
||||
dismissed: await storage.get(_id, 'notifications', []),
|
||||
};
|
||||
console.log($enhancerSidebarElement);
|
||||
notifications.waiting = notifications.list.filter(
|
||||
({ id }) => !notifications.dismissed.includes(id)
|
||||
);
|
||||
@ -48,4 +49,4 @@ web.whenReady([sidebarSelector]).then(async () => {
|
||||
setTheme();
|
||||
document.querySelector(sidebarSelector).appendChild($enhancerSidebarElement);
|
||||
});
|
||||
web.hotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.openEnhancerMenu);
|
||||
web.addHotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.openEnhancerMenu);
|
||||
|
@ -11,6 +11,7 @@
|
||||
-webkit-text-size-adjust: 100%;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
html {
|
||||
@ -43,7 +44,7 @@ header > * {
|
||||
margin: 0 1.25rem 0.1em 0;
|
||||
font-size: var(--theme--font_heading1-size);
|
||||
}
|
||||
header h1 a:not([data-view-active]) {
|
||||
header h1 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
header h1 img {
|
||||
@ -80,7 +81,7 @@ main {
|
||||
main {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--buttons {
|
||||
main > .action--buttons {
|
||||
grid-column: span 2;
|
||||
}
|
||||
[data-view='mod'] main .library--card,
|
||||
@ -97,7 +98,7 @@ main {
|
||||
main {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--buttons {
|
||||
main > .action--buttons {
|
||||
grid-column: span 3;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--body {
|
||||
@ -108,7 +109,7 @@ main {
|
||||
main {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--buttons {
|
||||
main > .action--buttons {
|
||||
grid-column: span 4;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--body {
|
||||
@ -119,7 +120,7 @@ main {
|
||||
main {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--buttons {
|
||||
main > .action--buttons {
|
||||
grid-column: span 5;
|
||||
}
|
||||
[data-view='mod'] main > .documentation--body {
|
||||
@ -136,7 +137,7 @@ main article img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.documentation--buttons,
|
||||
.action--buttons,
|
||||
.library--expand {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
@ -146,14 +147,14 @@ main article img {
|
||||
.library--expand a {
|
||||
margin-left: auto;
|
||||
}
|
||||
.documentation--buttons a,
|
||||
.action--buttons a,
|
||||
.library--expand a {
|
||||
border-radius: 3px;
|
||||
padding: 0.35rem 0.45rem;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
}
|
||||
.documentation--buttons .documentation--reload {
|
||||
.action--buttons .action--alert {
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
padding: 0.35rem 0.45rem;
|
||||
@ -164,31 +165,32 @@ main article img {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
}
|
||||
.documentation--buttons .documentation--reload[data-triggered] {
|
||||
.action--buttons .action--alert[data-triggered] {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
.documentation--buttons .documentation--reload[data-triggered]:hover {
|
||||
.action--buttons .action--alert[data-triggered]:hover {
|
||||
background: none;
|
||||
color: var(--theme--block_grey-text);
|
||||
box-shadow: var(--theme--block_grey) 0px 0px 0px 1px inset;
|
||||
}
|
||||
.documentation--buttons span,
|
||||
.action--buttons span,
|
||||
.library--expand span {
|
||||
color: var(--theme--text_property);
|
||||
}
|
||||
.documentation--buttons a:hover,
|
||||
.action--buttons a:hover,
|
||||
.action--buttons a.action--active,
|
||||
.library--expand a:hover {
|
||||
background: var(--theme--button-hover);
|
||||
}
|
||||
.documentation--buttons svg,
|
||||
.action--buttons svg,
|
||||
.library--expand svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
padding-top: 2px;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
.documentation--buttons svg *,
|
||||
.action--buttons svg *,
|
||||
.library--expand svg * {
|
||||
fill: var(--theme--text_property);
|
||||
}
|
||||
@ -269,7 +271,7 @@ label p > span:not([class]),
|
||||
label > span:not([class]) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
label [data-icon='fa/question-circle'] {
|
||||
label [data-icon='fa/solid/question-circle'] {
|
||||
height: var(--theme--font_ui_small-size);
|
||||
width: var(--theme--font_ui_small-size);
|
||||
margin-left: 0.25em;
|
||||
@ -306,7 +308,7 @@ label [data-icon='fa/question-circle'] {
|
||||
height: 0.8rem;
|
||||
width: 0.8rem;
|
||||
left: 0.325rem;
|
||||
top: 0.225rem;
|
||||
top: 0.2rem;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background: var(--theme--toggle_dot);
|
||||
@ -419,22 +421,7 @@ label [data-icon='fa/question-circle'] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
background: var(--theme--tooltip);
|
||||
color: var(--theme--tooltip-text);
|
||||
font-size: var(--theme--font_ui_small-size);
|
||||
padding: 0.15rem 0.4rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||||
border-radius: 3px;
|
||||
max-width: 20rem;
|
||||
display: none;
|
||||
}
|
||||
.tooltip p {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.notification--list {
|
||||
.notifications {
|
||||
position: absolute;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
@ -459,7 +446,7 @@ label [data-icon='fa/question-circle'] {
|
||||
transform-origin: 100% 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
.notification svg {
|
||||
.notification :not(.notification--dismiss) > svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
margin-top: 0.25rem;
|
||||
@ -478,27 +465,20 @@ label [data-icon='fa/question-circle'] {
|
||||
right: 0.75rem;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.25rem 0.35rem;
|
||||
font-size: var(--theme--font_body-size);
|
||||
padding: 0.15rem 0 0.15rem 0.5rem;
|
||||
width: var(--theme--font_body-size);
|
||||
color: currentColor;
|
||||
cursor: pointer;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
.notification .notification--dismiss svg {
|
||||
width: 100%;
|
||||
}
|
||||
.notification:hover .notification--dismiss,
|
||||
.notification:focus-within .notification--dismiss {
|
||||
opacity: 1;
|
||||
}
|
||||
.notification.celebration,
|
||||
.notification.information {
|
||||
background: var(--theme--block_blue);
|
||||
color: var(--theme--block_blue-text);
|
||||
}
|
||||
.notification.warning,
|
||||
.notification.danger {
|
||||
background: var(--theme--block_red);
|
||||
color: var(--theme--block_red-text);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
|
@ -8,21 +8,23 @@
|
||||
<body data-view>
|
||||
<header>
|
||||
<h1>
|
||||
<img data-view-target="notion" alt="" width="24" src="../../icons/colour.svg" />
|
||||
<a href="?view=library" data-view-target="library">library</a>
|
||||
<img data-notion alt="" width="24" src="../../icons/colour.svg" />
|
||||
<a href="?view=library">library</a>
|
||||
</h1>
|
||||
<h1>
|
||||
<i data-icon="fa/info"></i><a href="https://notion-enhancer.github.io/">website</a>
|
||||
<i data-icon="fa/solid/info"></i>
|
||||
<a href="https://notion-enhancer.github.io/">website</a>
|
||||
</h1>
|
||||
<h1>
|
||||
<span data-icon="fa/code"></span
|
||||
><a href="https://github.com/notion-enhancer/extension">source code</a>
|
||||
<i data-icon="fa/solid/code"></i>
|
||||
<a href="https://github.com/notion-enhancer/extension">source code</a>
|
||||
</h1>
|
||||
<h1>
|
||||
<i data-icon="fa/brands/discord"></i>
|
||||
<a href="https://discord.gg/sFWPXtA">support</a>
|
||||
</h1>
|
||||
<h1><i data-icon="fa/discord"></i><a href="https://discord.gg/sFWPXtA">support</a></h1>
|
||||
</header>
|
||||
<main></main>
|
||||
<section class="tooltip"></section>
|
||||
<footer class="notification--list"></footer>
|
||||
<script src="./menu.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -9,112 +9,124 @@
|
||||
const _id = 'a6621988-551d-495a-97d8-3c568bca2e9e';
|
||||
import { env, storage, web, fmt, fs, registry } from '../../api.js';
|
||||
|
||||
for (const mod of await registry.get((mod) => registry.enabled(mod.id))) {
|
||||
for (const mod of await registry.get((mod) => registry.isEnabled(mod.id))) {
|
||||
for (const sheet of mod.css?.menu || []) {
|
||||
web.loadStyleset(`repo/${mod._dir}/${sheet}`);
|
||||
}
|
||||
}
|
||||
async function loadTheme() {
|
||||
document.documentElement.className = `notion-${
|
||||
(await storage.get(_id, 'theme')) || 'dark'
|
||||
}-theme`;
|
||||
}
|
||||
window.addEventListener('focus', loadTheme);
|
||||
loadTheme();
|
||||
|
||||
document
|
||||
.querySelector('img[data-view-target="notion"]')
|
||||
.addEventListener('click', env.focusNotion);
|
||||
web.hotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.focusNotion);
|
||||
document.querySelector('img[data-notion]').addEventListener('click', env.focusNotion);
|
||||
web.addHotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.focusNotion);
|
||||
web.addDocumentObserver(web.loadIcons);
|
||||
|
||||
const tooltips = {
|
||||
$el: document.querySelector('.tooltip'),
|
||||
add($parent, selector, text) {
|
||||
text = fmt.md.render(text);
|
||||
$parent.addEventListener('mouseover', (event) => {
|
||||
if (event.target.matches(selector) || event.target.matches(`${selector} *`)) {
|
||||
this.$el.innerHTML = text;
|
||||
this.$el.style.display = 'block';
|
||||
}
|
||||
});
|
||||
$parent.addEventListener('mousemove', (event) => {
|
||||
this.$el.style.top = event.clientY - this.$el.clientHeight + 'px';
|
||||
this.$el.style.left =
|
||||
event.clientX < window.innerWidth / 2 ? event.clientX + 20 + 'px' : '';
|
||||
});
|
||||
$parent.addEventListener('mouseout', (event) => {
|
||||
if (event.target.matches(selector) || event.target.matches(`${selector} *`)) {
|
||||
this.$el.style.display = '';
|
||||
}
|
||||
const notifications = {
|
||||
$el: web.createElement(web.html`<footer class="notifications"></footer>`),
|
||||
push({ heading, message, icon, color }, onDismiss = () => {}) {
|
||||
const $notif = web.createElement(web.html`
|
||||
<div role="alert" class="notification" style="
|
||||
background: var(--theme--block_${web.escapeHtml(color)});
|
||||
color: var(--theme--block_${web.escapeHtml(color)}-text);
|
||||
">
|
||||
<div><i data-icon="${web.escapeHtml(icon)}"></i></div>
|
||||
<div>
|
||||
<h3>${web.escapeHtml(heading)}</h3>
|
||||
<p>${fmt.md.renderInline(message)}</p>
|
||||
</div>
|
||||
<button class="notification--dismiss"><i data-icon="fa/solid/times"></i></button>
|
||||
</div>`);
|
||||
this.$el.append($notif);
|
||||
setTimeout(() => {
|
||||
$notif.style.opacity = 1;
|
||||
}, 100);
|
||||
$notif.querySelector('.notification--dismiss').addEventListener('click', (event) => {
|
||||
$notif.style.opacity = 0;
|
||||
$notif.style.transform = 'scaleY(0)';
|
||||
$notif.style.marginTop = `-${
|
||||
$notif.offsetHeight / parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||
}rem`;
|
||||
setTimeout(() => $notif.remove(), 400);
|
||||
onDismiss();
|
||||
});
|
||||
return $notif;
|
||||
},
|
||||
};
|
||||
document.body.append(notifications.$el);
|
||||
for (const error of await registry.errors()) {
|
||||
notifications.push({
|
||||
heading: `error: ${error.source}`,
|
||||
message: error.message,
|
||||
color: 'red',
|
||||
icon: 'fa/solid/exclamation-triangle',
|
||||
});
|
||||
}
|
||||
for (const notification of await (async () => {
|
||||
const dismissed = await storage.get('_notifications', 'dismissed', []);
|
||||
return (await fs.getJSON('https://notion-enhancer.github.io/notifications.json'))
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.filter(({ id }) => !dismissed.includes(id));
|
||||
})()) {
|
||||
if (
|
||||
(!notification.versions || notification.versions.includes(env.version)) &&
|
||||
(!notification.environments || notification.environments.includes(env.name))
|
||||
) {
|
||||
notifications.push(notification, async () => {
|
||||
const dismissed = await storage.get('_notifications', 'dismissed', []);
|
||||
storage.set('_notifications', 'dismissed', [
|
||||
...new Set([...dismissed, notification.id]),
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
import * as router from './router.js';
|
||||
|
||||
const components = {};
|
||||
components.card = {
|
||||
preview: ({ preview = '' }) =>
|
||||
preview
|
||||
? web.createElement(web.html`<img
|
||||
alt=""
|
||||
class="library--preview"
|
||||
src="${web.escapeHtml(preview)}"
|
||||
/>`)
|
||||
: '',
|
||||
async name({ name, id, version, tags }) {
|
||||
if (registry.CORE.includes(id))
|
||||
return web.createElement(web.html`<div class="library--title"><h2>
|
||||
<span>
|
||||
${web.escapeHtml(name)}
|
||||
<span class="library--version">v${web.escapeHtml(version)}</span>
|
||||
</span>
|
||||
</h2></div>`);
|
||||
const $el = web.createElement(web.html`<label
|
||||
for="enable--${web.escapeHtml(id)}"
|
||||
components.card = async (mod) => {
|
||||
const $card = web.createElement(web.html`
|
||||
<article class="library--card" data-mod='${mod.id}'>
|
||||
${
|
||||
mod.preview
|
||||
? web.html`<img
|
||||
alt=""
|
||||
class="library--preview"
|
||||
src="${web.escapeHtml(mod.preview)}"
|
||||
/>`
|
||||
: ''
|
||||
}
|
||||
<div>
|
||||
<label
|
||||
for="enable--${web.escapeHtml(mod.id)}"
|
||||
class="library--title library--toggle_label"
|
||||
>
|
||||
<input type="checkbox" id="enable--${web.escapeHtml(id)}"
|
||||
${(await storage.get('_enabled', id, false)) ? 'checked' : ''}/>
|
||||
>
|
||||
<input type="checkbox" id="enable--${web.escapeHtml(mod.id)}"
|
||||
${(await registry.isEnabled(mod.id)) ? 'checked' : ''}/>
|
||||
<h2>
|
||||
<span>
|
||||
${web.escapeHtml(name)}
|
||||
<span class="library--version">v${web.escapeHtml(version)}</span>
|
||||
${web.escapeHtml(mod.name)}
|
||||
<span class="library--version">v${web.escapeHtml(mod.version)}</span>
|
||||
</span>
|
||||
<span class="library--toggle"></span>
|
||||
</h2>
|
||||
</label>`);
|
||||
$el.addEventListener('change', async (event) => {
|
||||
storage.set('_enabled', id, event.target.checked);
|
||||
if (
|
||||
event.target.checked &&
|
||||
tags.includes('theme') &&
|
||||
(await storage.get(_id, 'themes.autoresolve', true))
|
||||
) {
|
||||
const themes = await registry.get(
|
||||
(mod) =>
|
||||
mod.tags.includes('theme') &&
|
||||
mod.id !== id &&
|
||||
((mod.tags.includes('dark') && tags.includes('dark')) ||
|
||||
(mod.tags.includes('light') && tags.includes('light')))
|
||||
);
|
||||
for (const theme of themes) {
|
||||
if (document.body.dataset.view === 'library') {
|
||||
const $toggle = document.getElementById(`enable--${theme.id}`);
|
||||
if ($toggle.checked) $toggle.click();
|
||||
} else storage.set('_enabled', theme.id, false);
|
||||
${
|
||||
registry.CORE.includes(mod.id) ? '' : web.html`<span class="library--toggle"></span>`
|
||||
}
|
||||
}
|
||||
});
|
||||
return $el;
|
||||
},
|
||||
tags: ({ tags = [] }) =>
|
||||
web.createElement(web.html`<ul class="library--tags">
|
||||
${tags.map((tag) => web.html`<li>#${web.escapeHtml(tag)}</li>`).join('')}
|
||||
</ul>`),
|
||||
description: ({ description }) =>
|
||||
web.createElement(
|
||||
web.html`<p class="library--description markdown">${fmt.md.renderInline(
|
||||
description
|
||||
)}</p>`
|
||||
),
|
||||
authors: ({ authors }) =>
|
||||
web.createElement(web.html`<ul class="library--authors">
|
||||
${authors
|
||||
</h2>
|
||||
</label>
|
||||
<ul class="library--tags">
|
||||
${mod.tags.map((tag) => web.html`<li>#${web.escapeHtml(tag)}</li>`).join('')}
|
||||
</ul>
|
||||
<p class="library--description markdown">${fmt.md.renderInline(mod.description)}</p>
|
||||
<ul class="library--authors">
|
||||
${mod.authors
|
||||
.map(
|
||||
(author) =>
|
||||
web.html`<li>
|
||||
web.html`
|
||||
<li>
|
||||
<a href="${web.escapeHtml(author.url)}">
|
||||
<img alt="" src="${web.escapeHtml(author.icon)}" />
|
||||
<span>${web.escapeHtml(author.name)}</span>
|
||||
@ -122,83 +134,77 @@ components.card = {
|
||||
</li>`
|
||||
)
|
||||
.join('')}
|
||||
</ul>`),
|
||||
expand: async ({ id }) =>
|
||||
web.createElement(
|
||||
web.html`<p class="library--expand">
|
||||
<a href="?view=mod&id=${web.escapeHtml(id)}">
|
||||
<span><i data-icon="fa/long-arrow-alt-right"></i></span>
|
||||
<span>settings & documentation</span>
|
||||
</a>
|
||||
</p>`
|
||||
),
|
||||
async _generate(mod) {
|
||||
const card = web.createElement(web.html`<article class="library--card"></article>`),
|
||||
body = web.createElement(web.html`<div></div>`);
|
||||
card.append(this.preview(mod));
|
||||
body.append(await this.name(mod));
|
||||
body.append(this.tags(mod));
|
||||
body.append(this.description(mod));
|
||||
body.append(this.authors(mod));
|
||||
body.append(await this.expand(mod));
|
||||
card.append(body);
|
||||
return card;
|
||||
},
|
||||
</ul>
|
||||
<p class="library--expand">
|
||||
<a href="?view=mod&id=${web.escapeHtml(mod.id)}">
|
||||
<span><i data-icon="fa/solid/long-arrow-alt-right"></i></span>
|
||||
<span>settings & documentation</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</article>`);
|
||||
$card.querySelector('.library--title input').addEventListener('change', async (event) => {
|
||||
storage.set('_mods', mod.id, event.target.checked);
|
||||
});
|
||||
return $card;
|
||||
};
|
||||
components.options = {
|
||||
async toggle(id, { key, label, tooltip }) {
|
||||
const state = await storage.get(id, key),
|
||||
opt = web.createElement(web.html`<label
|
||||
for="toggle--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--toggle_label"
|
||||
opt = web.createElement(web.html`
|
||||
<label
|
||||
for="toggle--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--toggle_label"
|
||||
>
|
||||
<input type="checkbox" id="toggle--${web.escapeHtml(`${id}.${key}`)}"
|
||||
${state ? 'checked' : ''}/>
|
||||
<p>
|
||||
<span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span>
|
||||
<span class="library--toggle"></span>
|
||||
</p>
|
||||
<input type="checkbox" id="toggle--${web.escapeHtml(`${id}.${key}`)}"
|
||||
${state ? 'checked' : ''}/>
|
||||
<p>
|
||||
<span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span>
|
||||
<span class="library--toggle"></span>
|
||||
</p>
|
||||
</label>`);
|
||||
opt.addEventListener('change', (event) => storage.set(id, key, event.target.checked));
|
||||
if (tooltip) tooltips.add(opt, '[data-tooltip]', tooltip);
|
||||
if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip);
|
||||
return opt;
|
||||
},
|
||||
async select(id, { key, label, tooltip, values }) {
|
||||
const state = await storage.get(id, key),
|
||||
opt = web.createElement(web.html`<label
|
||||
opt = web.createElement(web.html`
|
||||
<label
|
||||
for="select--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--select_label"
|
||||
>
|
||||
<p><span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span></p>
|
||||
<p class="library--select">
|
||||
<span><i data-icon="fa/caret-down"></i></span>
|
||||
<select id="select--${web.escapeHtml(`${id}.${key}`)}">
|
||||
${values.map(
|
||||
(value) =>
|
||||
web.html`<option value="${web.escapeHtml(value)}"
|
||||
${value === state ? 'selected' : ''}>
|
||||
${web.escapeHtml(value)}</option>`
|
||||
)}
|
||||
</select>
|
||||
</p>
|
||||
</label>`);
|
||||
<p><span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p>
|
||||
<p class="library--select">
|
||||
<span><i data-icon="fa/solid/caret-down"></i></span>
|
||||
<select id="select--${web.escapeHtml(`${id}.${key}`)}">
|
||||
${values.map(
|
||||
(value) =>
|
||||
web.html`<option value="${web.escapeHtml(value)}"
|
||||
${value === state ? 'selected' : ''}>
|
||||
${web.escapeHtml(value)}</option>`
|
||||
)}
|
||||
</select>
|
||||
</p>
|
||||
</label>`);
|
||||
opt.addEventListener('change', (event) => storage.set(id, key, event.target.value));
|
||||
if (tooltip) tooltips.add(opt, '[data-tooltip]', tooltip);
|
||||
if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip);
|
||||
return opt;
|
||||
},
|
||||
async text(id, { key, label, tooltip }) {
|
||||
const state = await storage.get(id, key),
|
||||
opt = web.createElement(web.html`<label
|
||||
for="text--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--text_label"
|
||||
opt = web.createElement(web.html`
|
||||
<label
|
||||
for="text--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--text_label"
|
||||
>
|
||||
<p><span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span></p>
|
||||
<textarea id="text--${web.escapeHtml(`${id}.${key}`)}"
|
||||
rows="1">${web.escapeHtml(state)}</textarea>
|
||||
</label>`);
|
||||
<p><span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p>
|
||||
<textarea id="text--${web.escapeHtml(`${id}.${key}`)}"
|
||||
rows="1">${web.escapeHtml(state)}</textarea>
|
||||
</label>`);
|
||||
opt.querySelector('textarea').addEventListener('input', (event) => {
|
||||
event.target.style.removeProperty('--txt--scroll-height');
|
||||
event.target.style.setProperty(
|
||||
@ -207,52 +213,54 @@ components.options = {
|
||||
);
|
||||
});
|
||||
opt.addEventListener('change', (event) => storage.set(id, key, event.target.value));
|
||||
if (tooltip) tooltips.add(opt, '[data-tooltip]', tooltip);
|
||||
if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip);
|
||||
return opt;
|
||||
},
|
||||
async number(id, { key, label, tooltip }) {
|
||||
const state = await storage.get(id, key),
|
||||
opt = web.createElement(web.html`<label
|
||||
for="number--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--number_label"
|
||||
opt = web.createElement(web.html`
|
||||
<label
|
||||
for="number--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--number_label"
|
||||
>
|
||||
<p><span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span></p>
|
||||
<input id="number--${web.escapeHtml(`${id}.${key}`)}"
|
||||
type="number" value="${web.escapeHtml(state.toString())}"/>
|
||||
</label>`);
|
||||
<p><span data-tooltip>${web.escapeHtml(label)}
|
||||
${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p>
|
||||
<input id="number--${web.escapeHtml(`${id}.${key}`)}"
|
||||
type="number" value="${web.escapeHtml(state.toString())}"/>
|
||||
</label>`);
|
||||
opt.addEventListener('change', (event) => storage.set(id, key, event.target.value));
|
||||
if (tooltip) tooltips.add(opt, '[data-tooltip]', tooltip);
|
||||
if (tooltip) web.addTooltip(opt.querySelector('[data-tooltip]'), tooltip);
|
||||
return opt;
|
||||
},
|
||||
async file(id, { key, label, tooltip, extensions }) {
|
||||
const state = await storage.get(id, key),
|
||||
opt = web.createElement(web.html`<label
|
||||
for="file--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--file_label"
|
||||
opt = web.createElement(web.html`
|
||||
<label
|
||||
for="file--${web.escapeHtml(`${id}.${key}`)}"
|
||||
class="library--file_label"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
id="file--${web.escapeHtml(`${id}.${key}`)}"
|
||||
${web.escapeHtml(
|
||||
extensions && extensions.length
|
||||
? ` accept=${web.escapeHtml(extensions.join(','))}`
|
||||
: ''
|
||||
)}
|
||||
/>
|
||||
<p class="library--file_title"><span data-tooltip>${web.escapeHtml(label)}
|
||||
<i data-icon="fa/question-circle"></i></span>
|
||||
<span class="library--file_remove"><i data-icon="fa/minus"></i></span></p>
|
||||
<p class="library--file">
|
||||
<span><i data-icon="fa/file"></i></span>
|
||||
<span class="library--file_path">${web.escapeHtml(state || 'choose file...')}</span>
|
||||
</p>
|
||||
</label>`);
|
||||
<input
|
||||
type="file"
|
||||
id="file--${web.escapeHtml(`${id}.${key}`)}"
|
||||
${web.escapeHtml(
|
||||
extensions && extensions.length
|
||||
? ` accept=${web.escapeHtml(extensions.join(','))}`
|
||||
: ''
|
||||
)}
|
||||
/>
|
||||
<p class="library--file_title"><span data-tooltip>${web.escapeHtml(label)}
|
||||
<i data-icon="fa/solid/question-circle"></i></span>
|
||||
<span class="library--file_remove"><i data-icon="fa/solid/minus"></i></span></p>
|
||||
<p class="library--file">
|
||||
<span><i data-icon="fa/solid/file"></i></span>
|
||||
<span class="library--file_path">${web.escapeHtml(state || 'choose file...')}</span>
|
||||
</p>
|
||||
</label>`);
|
||||
opt.addEventListener('change', (event) => {
|
||||
const file = event.target.files[0],
|
||||
reader = new FileReader();
|
||||
opt.querySelector('.library--file_path').innerText = file.name;
|
||||
reader.onload = (progress) => {
|
||||
opt.querySelector('.library--file_path').innerText = file.name;
|
||||
reader.onload = (progress) => {
|
||||
storage.set(id, key, file.name);
|
||||
storage.set(id, `_file.${key}`, progress.currentTarget.result);
|
||||
};
|
||||
@ -272,281 +280,190 @@ components.options = {
|
||||
opt.addEventListener('click', (event) => {
|
||||
document.documentElement.scrollTop = 0;
|
||||
});
|
||||
tooltips.add(
|
||||
opt,
|
||||
'[data-tooltip]',
|
||||
`${
|
||||
tooltip ? `${tooltip}\n\n` : ''
|
||||
}**warning:** browser extensions do not have true filesystem access,
|
||||
so file content is only saved on selection. re-select files to apply edits.`
|
||||
web.addTooltip(
|
||||
opt.querySelector('[data-tooltip]'),
|
||||
`${tooltip ? `${tooltip}\n\n` : ''}**warning:** ${
|
||||
'browser extensions do not have true filesystem access, ' +
|
||||
'so file content is only saved on selection. re-select files to apply edits.'
|
||||
}`
|
||||
);
|
||||
return opt;
|
||||
},
|
||||
async _generate(mod) {
|
||||
const card = await components.card._generate(mod);
|
||||
card.querySelector('.library--expand').remove();
|
||||
if (mod.options && mod.options.length) {
|
||||
const options = web.createElement(web.html`<div class="library--options"></div>`),
|
||||
inputs = await Promise.all(
|
||||
mod.options
|
||||
.filter((opt) => !opt.environments || opt.environments.includes(env.name))
|
||||
.map((opt) => this[opt.type](mod.id, opt))
|
||||
);
|
||||
inputs.forEach((opt) => options.append(opt));
|
||||
card.append(options);
|
||||
}
|
||||
return card;
|
||||
},
|
||||
};
|
||||
components.documentation = {
|
||||
|
||||
const actionButtons = {
|
||||
_reloadTriggered: false,
|
||||
buttons({ _dir }) {
|
||||
const $el = web.createElement(web.html`<p class="documentation--buttons">
|
||||
<a href="?view=library">
|
||||
<span><i data-icon="fa/long-arrow-alt-left"></i></span>
|
||||
<span>back to library</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/notion-enhancer/extension/tree/main/repo/${encodeURIComponent(
|
||||
_dir
|
||||
)}"
|
||||
>
|
||||
<span><i data-icon="fa/code"></i></span>
|
||||
<span>view source code</span>
|
||||
</a>
|
||||
<button class="documentation--reload"${this._reloadTriggered ? ' data-triggered' : ''}>
|
||||
<span><i data-icon="fa/redo"></i></span>
|
||||
async reload($fragment = document) {
|
||||
let $reload = $fragment.querySelector('[data-reload]');
|
||||
if (!$reload) {
|
||||
$reload = web.createElement(web.html`
|
||||
<button class="action--alert" data-reload>
|
||||
<span><i data-icon="fa/solid/redo"></i></span>
|
||||
<span>reload tabs to apply changes</span>
|
||||
</button>
|
||||
</p>`);
|
||||
storage.onChange(() => {
|
||||
const $reload = $el.querySelector('.documentation--reload');
|
||||
if (document.body.contains($el) && !$reload.dataset.triggered) {
|
||||
$reload.dataset.triggered = true;
|
||||
this._reloadTriggered = true;
|
||||
}
|
||||
});
|
||||
$el.querySelector('.documentation--reload').addEventListener('click', env.reloadTabs);
|
||||
return $el;
|
||||
</button>`);
|
||||
$reload.addEventListener('click', env.reloadTabs);
|
||||
}
|
||||
if (this._reloadTriggered) {
|
||||
$fragment.querySelector('.action--buttons').append($reload);
|
||||
await new Promise((res, rej) => requestAnimationFrame(res));
|
||||
$reload.dataset.triggered = true;
|
||||
}
|
||||
},
|
||||
readme: async (mod) => {
|
||||
const readme = web.createElement(web.html`<article class="documentation--body markdown">
|
||||
${
|
||||
(await fs.isFile(`repo/${mod._dir}/README.md`))
|
||||
? fmt.md.render(await fs.getText(`repo/${mod._dir}/README.md`))
|
||||
: ''
|
||||
}
|
||||
</article>`);
|
||||
fmt.Prism.highlightAllUnder(readme);
|
||||
return readme;
|
||||
async clearFilters($fragment = document) {
|
||||
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();
|
||||
if (search.get('tag') || search.has('enabled') || search.has('disabled')) {
|
||||
$fragment.querySelector('.action--buttons').append($clearFilters);
|
||||
await new Promise((res, rej) => requestAnimationFrame(res));
|
||||
$clearFilters.dataset.triggered = true;
|
||||
} else $clearFilters.remove();
|
||||
},
|
||||
};
|
||||
const views = {
|
||||
$container: document.querySelector('main'),
|
||||
_router(event) {
|
||||
event.preventDefault();
|
||||
let anchor,
|
||||
i = 0;
|
||||
do {
|
||||
anchor = event.path[i];
|
||||
i++;
|
||||
} while (anchor.nodeName !== 'A');
|
||||
if (location.search !== anchor.getAttribute('href')) {
|
||||
window.history.pushState(
|
||||
{ search: anchor.getAttribute('href'), hash: '' },
|
||||
'',
|
||||
anchor.href
|
||||
);
|
||||
this._load();
|
||||
}
|
||||
},
|
||||
_navigator(event) {
|
||||
event.preventDefault();
|
||||
const hash = event.target.getAttribute('href').slice(1);
|
||||
document.getElementById(hash).scrollIntoView(true);
|
||||
document.documentElement.scrollTop = 0;
|
||||
history.replaceState({ search: location.search, hash }, null, `#${hash}`);
|
||||
},
|
||||
_reset() {
|
||||
document
|
||||
.querySelectorAll('a[href^="?"]')
|
||||
.forEach((a) => a.removeEventListener('click', this._router));
|
||||
document
|
||||
.querySelectorAll('a[href^="#"]')
|
||||
.forEach((a) => a.removeEventListener('click', this._navigator));
|
||||
this.$container.style.opacity = 0;
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => {
|
||||
this.$container.innerHTML = '';
|
||||
this.$container.style.opacity = '';
|
||||
document.body.dataset.view = '';
|
||||
document
|
||||
.querySelector('[data-view-target][data-view-active]')
|
||||
?.removeAttribute('data-view-active');
|
||||
res();
|
||||
}, 200);
|
||||
});
|
||||
},
|
||||
async _load() {
|
||||
await this._reset();
|
||||
storage.addChangeListener(async (event) => {
|
||||
actionButtons._reloadTriggered = true;
|
||||
actionButtons.reload();
|
||||
router.load();
|
||||
|
||||
const search = new Map(
|
||||
location.search
|
||||
.slice(1)
|
||||
.split('&')
|
||||
.map((query) => query.split('='))
|
||||
);
|
||||
switch (search.get('view')) {
|
||||
case 'mod':
|
||||
const mod = (await registry.get()).find((mod) => mod.id === search.get('id'));
|
||||
if (mod) {
|
||||
await this.mod(mod);
|
||||
break;
|
||||
}
|
||||
case 'library':
|
||||
await this.library();
|
||||
break;
|
||||
default:
|
||||
window.history.replaceState(
|
||||
{ search: '?view=library', hash: '' },
|
||||
null,
|
||||
'?view=library'
|
||||
);
|
||||
return this._load();
|
||||
if (event.namespace === '_mods' && event.new === true) {
|
||||
const enabledTheme = (await registry.get()).find((mod) => mod.id === event.key);
|
||||
if (
|
||||
enabledTheme.tags.includes('theme') &&
|
||||
(await storage.get(_id, 'themes.autoresolve', true))
|
||||
) {
|
||||
for (const theme of await registry.get(
|
||||
(mod) =>
|
||||
mod.tags.includes('theme') &&
|
||||
mod.id !== enabledTheme.id &&
|
||||
((mod.tags.includes('dark') && enabledTheme.tags.includes('dark')) ||
|
||||
(mod.tags.includes('light') && enabledTheme.tags.includes('light')))
|
||||
)) {
|
||||
if (document.body.dataset.view === 'library') {
|
||||
const $toggle = document.getElementById(`enable--${theme.id}`);
|
||||
if ($toggle.checked) $toggle.click();
|
||||
} else storage.set('_mods', theme.id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById(location.hash.slice(1))?.scrollIntoView(true);
|
||||
document.documentElement.scrollTop = 0;
|
||||
}, 50);
|
||||
document
|
||||
.querySelectorAll('img')
|
||||
.forEach((img) => (img.onerror = (event) => event.target.remove()));
|
||||
document
|
||||
.querySelectorAll('a[href^="?"]')
|
||||
.forEach((a) => a.addEventListener('click', this._router));
|
||||
document
|
||||
.querySelectorAll('a[href^="#"]')
|
||||
.forEach((a) => a.addEventListener('click', this._navigator));
|
||||
document.querySelectorAll('[data-icon]').forEach((icon) =>
|
||||
fs.getText(`icons/${icon.dataset.icon}.svg`).then((svg) => {
|
||||
svg = web.createElement(svg);
|
||||
for (const attr of icon.attributes) {
|
||||
svg.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
icon.replaceWith(svg);
|
||||
})
|
||||
);
|
||||
},
|
||||
async mod(mod) {
|
||||
document.body.dataset.view = 'mod';
|
||||
document.querySelector('header [data-view-target="library"]').dataset.active = true;
|
||||
this.$container.append(components.documentation.buttons(mod));
|
||||
this.$container.append(await components.options._generate(mod));
|
||||
this.$container.append(await components.documentation.readme(mod));
|
||||
},
|
||||
async library() {
|
||||
document.body.dataset.view = 'library';
|
||||
document.querySelector('header [data-view-target="library"]').dataset.active = true;
|
||||
router.addView(
|
||||
'library',
|
||||
async () => {
|
||||
const $fragment = web.createFragment(web.html`
|
||||
<p class="action--buttons">
|
||||
<a href="?view=library&tag=theme">
|
||||
<span><i data-icon="fa/solid/palette"></i></span>
|
||||
<span>themes</span>
|
||||
</a>
|
||||
<a href="?view=library&tag=extension">
|
||||
<span><i data-icon="fa/solid/plus"></i></span>
|
||||
<span>extensions</span>
|
||||
</a>
|
||||
<a href="?view=library&enabled">
|
||||
<span><i data-icon="fa/solid/toggle-on"></i></span>
|
||||
<span>enabled</span>
|
||||
</a>
|
||||
<a href="?view=library&disabled">
|
||||
<span><i data-icon="fa/solid/toggle-off"></i></span>
|
||||
<span>disabled</span>
|
||||
</a>
|
||||
</p>`);
|
||||
for (const mod of await registry.get(
|
||||
(mod) => !mod.environments || mod.environments.includes(env.name)
|
||||
)) {
|
||||
this.$container.append(await components.card._generate(mod));
|
||||
$fragment.append(await components.card(mod));
|
||||
}
|
||||
actionButtons.reload($fragment);
|
||||
actionButtons.clearFilters($fragment);
|
||||
return $fragment;
|
||||
},
|
||||
};
|
||||
views._router = views._router.bind(views);
|
||||
views._navigator = views._navigator.bind(views);
|
||||
views._load();
|
||||
window.addEventListener('popstate', (event) => {
|
||||
if (event.state) views._load();
|
||||
});
|
||||
|
||||
const notifications = {
|
||||
$list: document.querySelector('.notification--list'),
|
||||
push({ heading, message = '', type = 'information' }, onDismiss = () => {}) {
|
||||
let svg = '',
|
||||
className = 'notification';
|
||||
switch (type) {
|
||||
case 'celebration':
|
||||
svg = web.html`<i data-icon="monster/party"></i>`;
|
||||
className += ' celebration';
|
||||
break;
|
||||
case 'information':
|
||||
svg = web.html`<i data-icon="fa/info"></i>`;
|
||||
className += ' information';
|
||||
break;
|
||||
case 'warning':
|
||||
svg = web.html`<i data-icon="fa/exclamation-triangle"></i>`;
|
||||
className += ' warning';
|
||||
break;
|
||||
async (search = router.getSearch()) => {
|
||||
for (const [filter, active] of [
|
||||
['tag=theme', search.get('tag') === 'theme'],
|
||||
['tag=extension', search.get('tag') === 'extension'],
|
||||
['enabled', search.has('enabled')],
|
||||
['disabled', search.has('disabled')],
|
||||
]) {
|
||||
document
|
||||
.querySelector(`.action--buttons > [href="?view=library&${filter}"]`)
|
||||
.classList[active ? 'add' : 'remove']('action--active');
|
||||
}
|
||||
const $notif = web.createElement(web.html`<div role="alert" class="${className}">
|
||||
<div>${svg}</div>
|
||||
<div>
|
||||
<h3>${web.escapeHtml(heading)}</h3>
|
||||
<p>${fmt.md.renderInline(message)}</p>
|
||||
</div>
|
||||
<button class="notification--dismiss">×</button>
|
||||
</div>`);
|
||||
$notif.querySelector('.notification--dismiss').addEventListener('click', (event) => {
|
||||
$notif.style.opacity = 0;
|
||||
$notif.style.transform = 'scaleY(0)';
|
||||
$notif.style.marginTop = `-${
|
||||
$notif.offsetHeight / parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||
}rem`;
|
||||
setTimeout(() => $notif.remove(), 400);
|
||||
onDismiss();
|
||||
});
|
||||
setTimeout(() => {
|
||||
$notif.style.opacity = 1;
|
||||
}, 100);
|
||||
return this.$list.append($notif);
|
||||
},
|
||||
async fetch() {
|
||||
const notifications = {
|
||||
list: await fs.getJSON('https://notion-enhancer.github.io/notifications.json'),
|
||||
dismissed: await storage.get(_id, 'notifications', []),
|
||||
};
|
||||
notifications.list = notifications.list.sort((a, b) => b.id - a.id);
|
||||
notifications.waiting = notifications.list.filter(
|
||||
({ id }) => !notifications.dismissed.includes(id)
|
||||
);
|
||||
for (const notification of notifications.waiting) {
|
||||
for (const card of document.querySelectorAll('main > .library--card')) {
|
||||
const { tags } = (await registry.get()).find((mod) => mod.id === card.dataset.mod),
|
||||
isEnabled = await registry.isEnabled(card.dataset.mod);
|
||||
if (
|
||||
notification.heading &&
|
||||
notification.appears_on &&
|
||||
(notification.appears_on.versions.includes('*') ||
|
||||
notification.appears_on.versions.includes(env.version)) &&
|
||||
notification.appears_on.extension
|
||||
(search.has('tag') ? tags.includes(search.get('tag')) : true) &&
|
||||
(search.has('enabled') && search.has('disabled')
|
||||
? true
|
||||
: search.has('enabled')
|
||||
? isEnabled
|
||||
: search.has('disabled')
|
||||
? !isEnabled
|
||||
: true)
|
||||
) {
|
||||
this.push(notification, async () => {
|
||||
const dismissed = await storage.get(_id, 'notifications', []);
|
||||
storage.set('_notifications', 'external', [
|
||||
...new Set([...dismissed, notification.id]),
|
||||
]);
|
||||
});
|
||||
}
|
||||
card.style.display = '';
|
||||
} else card.style.display = 'none';
|
||||
}
|
||||
actionButtons.clearFilters();
|
||||
}
|
||||
);
|
||||
|
||||
router.addView(
|
||||
'mod',
|
||||
async () => {
|
||||
const mod = (await registry.get()).find((mod) => mod.id === router.getSearch().get('id'));
|
||||
if (!mod) return false;
|
||||
const $fragment = web.createFragment(web.html`
|
||||
<p class="action--buttons">
|
||||
<a href="?view=library">
|
||||
<span><i data-icon="fa/solid/long-arrow-alt-left"></i></span>
|
||||
<span>back to library</span>
|
||||
</a>
|
||||
<a href="https://github.com/notion-enhancer/extension/tree/main/repo/${encodeURIComponent(
|
||||
mod._dir
|
||||
)}">
|
||||
<span><i data-icon="fa/solid/code"></i></span>
|
||||
<span>view source code</span>
|
||||
</a>
|
||||
</p>`);
|
||||
const $card = await components.card(mod);
|
||||
$card.querySelector('.library--expand').remove();
|
||||
if (mod.options && mod.options.length) {
|
||||
const options = web.createElement(web.html`<div class="library--options"></div>`);
|
||||
mod.options
|
||||
.filter((opt) => !opt.environments || opt.environments.includes(env.name))
|
||||
.forEach(async (opt) =>
|
||||
options.append(await components.options[opt.type](mod.id, opt))
|
||||
);
|
||||
$card.append(options);
|
||||
}
|
||||
$fragment.append(
|
||||
$card,
|
||||
web.createElement(web.html`
|
||||
<article class="documentation--body markdown">
|
||||
${
|
||||
(await fs.isFile(`repo/${mod._dir}/README.md`))
|
||||
? fmt.md.render(await fs.getText(`repo/${mod._dir}/README.md`))
|
||||
: ''
|
||||
}
|
||||
</article>`)
|
||||
);
|
||||
fmt.Prism.highlightAllUnder($fragment);
|
||||
actionButtons.reload($fragment);
|
||||
return $fragment;
|
||||
},
|
||||
};
|
||||
for (const error of await registry.errors()) {
|
||||
notifications.push({
|
||||
heading: `error: ${error.source}`,
|
||||
message: error.message,
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
notifications.fetch();
|
||||
() => {
|
||||
if (document.querySelector('[data-mod]').dataset.mod !== router.getSearch().get('id'))
|
||||
router.load(true);
|
||||
}
|
||||
);
|
||||
|
||||
async function theme() {
|
||||
document.documentElement.className = `notion-${
|
||||
(await storage.get(_id, 'theme')) || 'dark'
|
||||
}-theme`;
|
||||
}
|
||||
window.addEventListener('focus', theme);
|
||||
theme();
|
||||
|
||||
// registry.errors().then((err) => {
|
||||
// document.querySelector('[data-section="alerts"]').innerHTML = JSON.stringify(err);
|
||||
// });
|
||||
router.setDefaultView('library');
|
||||
router.load();
|
||||
|
@ -13,8 +13,9 @@
|
||||
}
|
||||
],
|
||||
"css": {
|
||||
"client": ["client.css"],
|
||||
"menu": ["menu.css", "markdown.css"]
|
||||
"frame": ["tooltips.css"],
|
||||
"client": ["client.css", "tooltips.css"],
|
||||
"menu": ["menu.css", "markdown.css", "tooltips.css"]
|
||||
},
|
||||
"js": {
|
||||
"client": ["client.js"]
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* notion-enhancer core: menu
|
||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { web } from '../../api.js';
|
||||
|
||||
export const getSearch = () =>
|
||||
new Map(
|
||||
location.search
|
||||
.slice(1)
|
||||
.split('&')
|
||||
.map((query) => query.split('='))
|
||||
);
|
||||
|
||||
let defaultView = '';
|
||||
const views = new Map(),
|
||||
filters = new Map();
|
||||
|
||||
export function setDefaultView(name) {
|
||||
defaultView = name;
|
||||
}
|
||||
export function addView(name, loader, filter = () => {}) {
|
||||
views.set(name, loader);
|
||||
filters.set(name, filter);
|
||||
}
|
||||
export function removeView(name) {
|
||||
views.delete(name);
|
||||
filters.delete(name);
|
||||
}
|
||||
|
||||
function router(event) {
|
||||
event.preventDefault();
|
||||
const anchor = event.path.find((anchor) => anchor.nodeName === 'A');
|
||||
if (location.search !== anchor.getAttribute('href')) {
|
||||
window.history.pushState(
|
||||
{ search: anchor.getAttribute('href'), hash: '' },
|
||||
'',
|
||||
anchor.href
|
||||
);
|
||||
load();
|
||||
}
|
||||
}
|
||||
function navigator(event) {
|
||||
event.preventDefault();
|
||||
const anchor = event.path.find((anchor) => anchor.nodeName === 'A'),
|
||||
hash = anchor.getAttribute('href').slice(1);
|
||||
document.getElementById(hash).scrollIntoView(true);
|
||||
document.documentElement.scrollTop = 0;
|
||||
history.replaceState({ search: location.search, hash }, null, `#${hash}`);
|
||||
}
|
||||
|
||||
export async function load(force = false) {
|
||||
const $container = document.querySelector('main'),
|
||||
search = getSearch(),
|
||||
fallbackView = () =>
|
||||
window.history.replaceState(
|
||||
{ search: `?view=${defaultView}`, hash: '' },
|
||||
null,
|
||||
`?view=${defaultView}`
|
||||
);
|
||||
if (force || !search.get('view') || document.body.dataset.view !== search.get('view')) {
|
||||
if (views.get(search.get('view'))) {
|
||||
const $body = await (views.get(search.get('view')) || (() => void 0))();
|
||||
if ($body) {
|
||||
$container.style.opacity = 0;
|
||||
await new Promise((res, rej) =>
|
||||
setTimeout(() => {
|
||||
document.body.dataset.view = search.get('view');
|
||||
$container.innerHTML = '';
|
||||
$container.append($body);
|
||||
requestAnimationFrame(() => {
|
||||
$container.style.opacity = '';
|
||||
setTimeout(res, 200);
|
||||
});
|
||||
}, 200)
|
||||
);
|
||||
} else return fallbackView();
|
||||
} else return fallbackView();
|
||||
}
|
||||
if (filters.get(search.get('view'))) filters.get(search.get('view'))(search);
|
||||
}
|
||||
window.addEventListener('popstate', (event) => {
|
||||
if (event.state) load();
|
||||
document.getElementById(location.hash.slice(1))?.scrollIntoView(true);
|
||||
document.documentElement.scrollTop = 0;
|
||||
});
|
||||
web.addDocumentObserver((mutation) => {
|
||||
mutation.target.querySelectorAll('a[href^="?"]').forEach((a) => {
|
||||
a.removeEventListener('click', router);
|
||||
a.addEventListener('click', router);
|
||||
});
|
||||
mutation.target.querySelectorAll('a[href^="#"]').forEach((a) => {
|
||||
a.removeEventListener('click', navigator);
|
||||
a.addEventListener('click', navigator);
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* notion-enhancer core: tooltips
|
||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
.enhancer--tooltip {
|
||||
position: absolute;
|
||||
background: var(--theme--tooltip);
|
||||
color: var(--theme--tooltip-text);
|
||||
font-size: var(--theme--font_ui_small-size);
|
||||
padding: 0.15rem 0.4rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||||
border-radius: 3px;
|
||||
max-width: 20rem;
|
||||
display: none;
|
||||
}
|
||||
.enhancer--tooltip p {
|
||||
margin: 0.25rem 0;
|
||||
}
|
@ -14,9 +14,8 @@ web.whenReady().then(async () => {
|
||||
if (cssInsert) {
|
||||
document.body.append(
|
||||
web.createElement(
|
||||
web.html`<style id="${await storage.get(_id, 'insert.css')}@${_id}">
|
||||
${cssInsert}
|
||||
</style>`
|
||||
web.html`
|
||||
<style id="${await storage.get(_id, 'insert.css')}@${_id}">${cssInsert}</style>`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user