add menu filters + unspaghetti menu code

This commit is contained in:
dragonwocky 2021-05-11 23:29:12 +10:00
parent 4e3f921ee3
commit a55482d62d
13 changed files with 521 additions and 494 deletions

View 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

View File

@ -5,12 +5,13 @@
- improved: split the core mod into the theming & menu mods. - improved: split the core mod into the theming & menu mods.
- improved: new larger menu layout, with individual mod pages. - improved: new larger menu layout, with individual mod pages.
- improved: merged bracketed-links into tweaks. - 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: integrated scrollbar tweak (notion now includes by default).
- removed: js insert. css insert moved to tweaks mod. - removed: js insert. css insert moved to tweaks mod.
- ported: tweaks, bypass-preview.
#### todo #### todo
- tag sort
- documentation e.g. \_file - documentation e.g. \_file
- complete/bugfix theming variables - complete/bugfix theming variables
- color pickers - color pickers

View File

@ -4,6 +4,8 @@
* (https://notion-enhancer.github.io/) under the MIT license * (https://notion-enhancer.github.io/) under the MIT license
*/ */
'use strict';
import { web } from '../../api.js'; import { web } from '../../api.js';
web.whenReady().then(async () => { web.whenReady().then(async () => {
@ -23,7 +25,7 @@ function getCurrentPage() {
return { type: 'page', id: location.pathname.split(/(-|\/)/g).reverse()[0] }; return { type: 'page', id: location.pathname.split(/(-|\/)/g).reverse()[0] };
} }
let lastPage = getCurrentPage(); let lastPage = getCurrentPage();
web.observeDocument((event) => { web.addDocumentObserver((event) => {
const currentPage = getCurrentPage(); const currentPage = getCurrentPage();
if (currentPage.id !== lastPage.id || currentPage.type !== lastPage.type) { if (currentPage.id !== lastPage.id || currentPage.type !== lastPage.type) {
const openAsPage = document.querySelector( const openAsPage = document.querySelector(

View File

@ -1,7 +1,7 @@
{ {
"name": "bypass-preview", "name": "bypass-preview",
"id": "cb6fd684-f113-4a7a-9423-8f0f0cff069f", "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", "version": "0.2.0",
"tags": ["extension", "automation"], "tags": ["extension", "automation"],
"authors": [ "authors": [

View File

@ -1 +1,3 @@
# menu # menu
[theming mod link test](?view=mod&id=0f0bf8b6-eae6-4273-b307-8fc43f2ee082)

View File

@ -25,6 +25,7 @@ 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)
); );
@ -48,4 +49,4 @@ web.whenReady([sidebarSelector]).then(async () => {
setTheme(); setTheme();
document.querySelector(sidebarSelector).appendChild($enhancerSidebarElement); 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);

View File

@ -11,6 +11,7 @@
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
font-size: inherit; font-size: inherit;
font-family: inherit; font-family: inherit;
fill: currentColor;
} }
html { html {
@ -43,7 +44,7 @@ header > * {
margin: 0 1.25rem 0.1em 0; margin: 0 1.25rem 0.1em 0;
font-size: var(--theme--font_heading1-size); font-size: var(--theme--font_heading1-size);
} }
header h1 a:not([data-view-active]) { header h1 a {
text-decoration: none; text-decoration: none;
} }
header h1 img { header h1 img {
@ -80,7 +81,7 @@ main {
main { main {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
[data-view='mod'] main > .documentation--buttons { main > .action--buttons {
grid-column: span 2; grid-column: span 2;
} }
[data-view='mod'] main .library--card, [data-view='mod'] main .library--card,
@ -97,7 +98,7 @@ main {
main { main {
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} }
[data-view='mod'] main > .documentation--buttons { main > .action--buttons {
grid-column: span 3; grid-column: span 3;
} }
[data-view='mod'] main > .documentation--body { [data-view='mod'] main > .documentation--body {
@ -108,7 +109,7 @@ main {
main { main {
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
} }
[data-view='mod'] main > .documentation--buttons { main > .action--buttons {
grid-column: span 4; grid-column: span 4;
} }
[data-view='mod'] main > .documentation--body { [data-view='mod'] main > .documentation--body {
@ -119,7 +120,7 @@ main {
main { main {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
} }
[data-view='mod'] main > .documentation--buttons { main > .action--buttons {
grid-column: span 5; grid-column: span 5;
} }
[data-view='mod'] main > .documentation--body { [data-view='mod'] main > .documentation--body {
@ -136,7 +137,7 @@ main article img {
max-width: 100%; max-width: 100%;
} }
.documentation--buttons, .action--buttons,
.library--expand { .library--expand {
margin: 0; margin: 0;
display: flex; display: flex;
@ -146,14 +147,14 @@ main article img {
.library--expand a { .library--expand a {
margin-left: auto; margin-left: auto;
} }
.documentation--buttons a, .action--buttons a,
.library--expand a { .library--expand a {
border-radius: 3px; border-radius: 3px;
padding: 0.35rem 0.45rem; padding: 0.35rem 0.45rem;
text-decoration: none; text-decoration: none;
display: flex; display: flex;
} }
.documentation--buttons .documentation--reload { .action--buttons .action--alert {
cursor: pointer; cursor: pointer;
border-radius: 3px; border-radius: 3px;
padding: 0.35rem 0.45rem; padding: 0.35rem 0.45rem;
@ -164,31 +165,32 @@ main article img {
opacity: 0; opacity: 0;
transition: opacity 200ms ease-in-out; transition: opacity 200ms ease-in-out;
} }
.documentation--buttons .documentation--reload[data-triggered] { .action--buttons .action--alert[data-triggered] {
pointer-events: all; pointer-events: all;
opacity: 1; opacity: 1;
} }
.documentation--buttons .documentation--reload[data-triggered]:hover { .action--buttons .action--alert[data-triggered]:hover {
background: none; background: none;
color: var(--theme--block_grey-text); color: var(--theme--block_grey-text);
box-shadow: var(--theme--block_grey) 0px 0px 0px 1px inset; box-shadow: var(--theme--block_grey) 0px 0px 0px 1px inset;
} }
.documentation--buttons span, .action--buttons span,
.library--expand span { .library--expand span {
color: var(--theme--text_property); color: var(--theme--text_property);
} }
.documentation--buttons a:hover, .action--buttons a:hover,
.action--buttons a.action--active,
.library--expand a:hover { .library--expand a:hover {
background: var(--theme--button-hover); background: var(--theme--button-hover);
} }
.documentation--buttons svg, .action--buttons svg,
.library--expand svg { .library--expand svg {
width: 1em; width: 1em;
height: 1em; height: 1em;
padding-top: 2px; padding-top: 2px;
margin-right: 0.3rem; margin-right: 0.3rem;
} }
.documentation--buttons svg *, .action--buttons svg *,
.library--expand svg * { .library--expand svg * {
fill: var(--theme--text_property); fill: var(--theme--text_property);
} }
@ -269,7 +271,7 @@ label p > span:not([class]),
label > span:not([class]) { label > span:not([class]) {
font-size: 1rem; font-size: 1rem;
} }
label [data-icon='fa/question-circle'] { label [data-icon='fa/solid/question-circle'] {
height: var(--theme--font_ui_small-size); height: var(--theme--font_ui_small-size);
width: var(--theme--font_ui_small-size); width: var(--theme--font_ui_small-size);
margin-left: 0.25em; margin-left: 0.25em;
@ -306,7 +308,7 @@ label [data-icon='fa/question-circle'] {
height: 0.8rem; height: 0.8rem;
width: 0.8rem; width: 0.8rem;
left: 0.325rem; left: 0.325rem;
top: 0.225rem; top: 0.2rem;
position: absolute; position: absolute;
border-radius: 100%; border-radius: 100%;
background: var(--theme--toggle_dot); background: var(--theme--toggle_dot);
@ -419,22 +421,7 @@ label [data-icon='fa/question-circle'] {
overflow-x: auto; overflow-x: auto;
} }
.tooltip { .notifications {
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 {
position: absolute; position: absolute;
bottom: 1.5rem; bottom: 1.5rem;
right: 1.5rem; right: 1.5rem;
@ -459,7 +446,7 @@ label [data-icon='fa/question-circle'] {
transform-origin: 100% 100%; transform-origin: 100% 100%;
opacity: 0; opacity: 0;
} }
.notification svg { .notification :not(.notification--dismiss) > svg {
height: 1.5rem; height: 1.5rem;
width: 1.5rem; width: 1.5rem;
margin-top: 0.25rem; margin-top: 0.25rem;
@ -478,27 +465,20 @@ label [data-icon='fa/question-circle'] {
right: 0.75rem; right: 0.75rem;
background: none; background: none;
border: none; border: none;
padding: 0.25rem 0.35rem; padding: 0.15rem 0 0.15rem 0.5rem;
font-size: var(--theme--font_body-size); width: var(--theme--font_body-size);
color: currentColor; color: currentColor;
cursor: pointer; cursor: pointer;
transition: opacity 200ms ease-in-out; transition: opacity 200ms ease-in-out;
opacity: 0; opacity: 0;
} }
.notification .notification--dismiss svg {
width: 100%;
}
.notification:hover .notification--dismiss, .notification:hover .notification--dismiss,
.notification:focus-within .notification--dismiss { .notification:focus-within .notification--dismiss {
opacity: 1; 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 { ::-webkit-scrollbar {
background: transparent; background: transparent;

View File

@ -8,21 +8,23 @@
<body data-view> <body data-view>
<header> <header>
<h1> <h1>
<img data-view-target="notion" alt="" width="24" src="../../icons/colour.svg" /> <img data-notion alt="" width="24" src="../../icons/colour.svg" />
<a href="?view=library" data-view-target="library">library</a> <a href="?view=library">library</a>
</h1> </h1>
<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>
<h1> <h1>
<span data-icon="fa/code"></span <i data-icon="fa/solid/code"></i>
><a href="https://github.com/notion-enhancer/extension">source code</a> <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>
<h1><i data-icon="fa/discord"></i><a href="https://discord.gg/sFWPXtA">support</a></h1>
</header> </header>
<main></main> <main></main>
<section class="tooltip"></section>
<footer class="notification--list"></footer>
<script src="./menu.js" type="module"></script> <script src="./menu.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -9,112 +9,124 @@
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 } 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 || []) { for (const sheet of mod.css?.menu || []) {
web.loadStyleset(`repo/${mod._dir}/${sheet}`); 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 document.querySelector('img[data-notion]').addEventListener('click', env.focusNotion);
.querySelector('img[data-view-target="notion"]') web.addHotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.focusNotion);
.addEventListener('click', env.focusNotion); web.addDocumentObserver(web.loadIcons);
web.hotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.focusNotion);
const tooltips = { const notifications = {
$el: document.querySelector('.tooltip'), $el: web.createElement(web.html`<footer class="notifications"></footer>`),
add($parent, selector, text) { push({ heading, message, icon, color }, onDismiss = () => {}) {
text = fmt.md.render(text); const $notif = web.createElement(web.html`
$parent.addEventListener('mouseover', (event) => { <div role="alert" class="notification" style="
if (event.target.matches(selector) || event.target.matches(`${selector} *`)) { background: var(--theme--block_${web.escapeHtml(color)});
this.$el.innerHTML = text; color: var(--theme--block_${web.escapeHtml(color)}-text);
this.$el.style.display = 'block'; ">
} <div><i data-icon="${web.escapeHtml(icon)}"></i></div>
}); <div>
$parent.addEventListener('mousemove', (event) => { <h3>${web.escapeHtml(heading)}</h3>
this.$el.style.top = event.clientY - this.$el.clientHeight + 'px'; <p>${fmt.md.renderInline(message)}</p>
this.$el.style.left = </div>
event.clientX < window.innerWidth / 2 ? event.clientX + 20 + 'px' : ''; <button class="notification--dismiss"><i data-icon="fa/solid/times"></i></button>
}); </div>`);
$parent.addEventListener('mouseout', (event) => { this.$el.append($notif);
if (event.target.matches(selector) || event.target.matches(`${selector} *`)) { setTimeout(() => {
this.$el.style.display = ''; $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 = {}; const components = {};
components.card = { components.card = async (mod) => {
preview: ({ preview = '' }) => const $card = web.createElement(web.html`
preview <article class="library--card" data-mod='${mod.id}'>
? web.createElement(web.html`<img ${
alt="" mod.preview
class="library--preview" ? web.html`<img
src="${web.escapeHtml(preview)}" alt=""
/>`) class="library--preview"
: '', src="${web.escapeHtml(mod.preview)}"
async name({ name, id, version, tags }) { />`
if (registry.CORE.includes(id)) : ''
return web.createElement(web.html`<div class="library--title"><h2> }
<span> <div>
${web.escapeHtml(name)} <label
<span class="library--version">v${web.escapeHtml(version)}</span> for="enable--${web.escapeHtml(mod.id)}"
</span>
</h2></div>`);
const $el = web.createElement(web.html`<label
for="enable--${web.escapeHtml(id)}"
class="library--title library--toggle_label" class="library--title library--toggle_label"
> >
<input type="checkbox" id="enable--${web.escapeHtml(id)}" <input type="checkbox" id="enable--${web.escapeHtml(mod.id)}"
${(await storage.get('_enabled', id, false)) ? 'checked' : ''}/> ${(await registry.isEnabled(mod.id)) ? 'checked' : ''}/>
<h2> <h2>
<span> <span>
${web.escapeHtml(name)} ${web.escapeHtml(mod.name)}
<span class="library--version">v${web.escapeHtml(version)}</span> <span class="library--version">v${web.escapeHtml(mod.version)}</span>
</span> </span>
<span class="library--toggle"></span> ${
</h2> registry.CORE.includes(mod.id) ? '' : web.html`<span class="library--toggle"></span>`
</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);
} }
} </h2>
}); </label>
return $el; <ul class="library--tags">
}, ${mod.tags.map((tag) => web.html`<li>#${web.escapeHtml(tag)}</li>`).join('')}
tags: ({ tags = [] }) => </ul>
web.createElement(web.html`<ul class="library--tags"> <p class="library--description markdown">${fmt.md.renderInline(mod.description)}</p>
${tags.map((tag) => web.html`<li>#${web.escapeHtml(tag)}</li>`).join('')} <ul class="library--authors">
</ul>`), ${mod.authors
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
.map( .map(
(author) => (author) =>
web.html`<li> web.html`
<li>
<a href="${web.escapeHtml(author.url)}"> <a href="${web.escapeHtml(author.url)}">
<img alt="" src="${web.escapeHtml(author.icon)}" /> <img alt="" src="${web.escapeHtml(author.icon)}" />
<span>${web.escapeHtml(author.name)}</span> <span>${web.escapeHtml(author.name)}</span>
@ -122,83 +134,77 @@ components.card = {
</li>` </li>`
) )
.join('')} .join('')}
</ul>`), </ul>
expand: async ({ id }) => <p class="library--expand">
web.createElement( <a href="?view=mod&id=${web.escapeHtml(mod.id)}">
web.html`<p class="library--expand"> <span><i data-icon="fa/solid/long-arrow-alt-right"></i></span>
<a href="?view=mod&id=${web.escapeHtml(id)}"> <span>settings & documentation</span>
<span><i data-icon="fa/long-arrow-alt-right"></i></span> </a>
<span>settings & documentation</span> </p>
</a> </div>
</p>` </article>`);
), $card.querySelector('.library--title input').addEventListener('change', async (event) => {
async _generate(mod) { storage.set('_mods', mod.id, event.target.checked);
const card = web.createElement(web.html`<article class="library--card"></article>`), });
body = web.createElement(web.html`<div></div>`); return $card;
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;
},
}; };
components.options = { components.options = {
async toggle(id, { key, label, tooltip }) { async toggle(id, { key, label, tooltip }) {
const state = await storage.get(id, key), const state = await storage.get(id, key),
opt = web.createElement(web.html`<label opt = web.createElement(web.html`
for="toggle--${web.escapeHtml(`${id}.${key}`)}" <label
class="library--toggle_label" for="toggle--${web.escapeHtml(`${id}.${key}`)}"
class="library--toggle_label"
> >
<input type="checkbox" id="toggle--${web.escapeHtml(`${id}.${key}`)}" <input type="checkbox" id="toggle--${web.escapeHtml(`${id}.${key}`)}"
${state ? 'checked' : ''}/> ${state ? 'checked' : ''}/>
<p> <p>
<span data-tooltip>${web.escapeHtml(label)} <span data-tooltip>${web.escapeHtml(label)}
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span> ${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span>
<span class="library--toggle"></span> <span class="library--toggle"></span>
</p> </p>
</label>`); </label>`);
opt.addEventListener('change', (event) => storage.set(id, key, event.target.checked)); 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; return opt;
}, },
async select(id, { key, label, tooltip, values }) { async select(id, { key, label, tooltip, values }) {
const state = await storage.get(id, key), 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}`)}" for="select--${web.escapeHtml(`${id}.${key}`)}"
class="library--select_label" class="library--select_label"
> >
<p><span data-tooltip>${web.escapeHtml(label)} <p><span data-tooltip>${web.escapeHtml(label)}
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span></p> ${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p>
<p class="library--select"> <p class="library--select">
<span><i data-icon="fa/caret-down"></i></span> <span><i data-icon="fa/solid/caret-down"></i></span>
<select id="select--${web.escapeHtml(`${id}.${key}`)}"> <select id="select--${web.escapeHtml(`${id}.${key}`)}">
${values.map( ${values.map(
(value) => (value) =>
web.html`<option value="${web.escapeHtml(value)}" web.html`<option value="${web.escapeHtml(value)}"
${value === state ? 'selected' : ''}> ${value === state ? 'selected' : ''}>
${web.escapeHtml(value)}</option>` ${web.escapeHtml(value)}</option>`
)} )}
</select> </select>
</p> </p>
</label>`); </label>`);
opt.addEventListener('change', (event) => storage.set(id, key, event.target.value)); 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; return opt;
}, },
async text(id, { key, label, tooltip }) { async text(id, { key, label, tooltip }) {
const state = await storage.get(id, key), const state = await storage.get(id, key),
opt = web.createElement(web.html`<label opt = web.createElement(web.html`
for="text--${web.escapeHtml(`${id}.${key}`)}" <label
class="library--text_label" for="text--${web.escapeHtml(`${id}.${key}`)}"
class="library--text_label"
> >
<p><span data-tooltip>${web.escapeHtml(label)} <p><span data-tooltip>${web.escapeHtml(label)}
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span></p> ${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p>
<textarea id="text--${web.escapeHtml(`${id}.${key}`)}" <textarea id="text--${web.escapeHtml(`${id}.${key}`)}"
rows="1">${web.escapeHtml(state)}</textarea> rows="1">${web.escapeHtml(state)}</textarea>
</label>`); </label>`);
opt.querySelector('textarea').addEventListener('input', (event) => { opt.querySelector('textarea').addEventListener('input', (event) => {
event.target.style.removeProperty('--txt--scroll-height'); event.target.style.removeProperty('--txt--scroll-height');
event.target.style.setProperty( event.target.style.setProperty(
@ -207,52 +213,54 @@ components.options = {
); );
}); });
opt.addEventListener('change', (event) => storage.set(id, key, event.target.value)); 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; return opt;
}, },
async number(id, { key, label, tooltip }) { async number(id, { key, label, tooltip }) {
const state = await storage.get(id, key), const state = await storage.get(id, key),
opt = web.createElement(web.html`<label opt = web.createElement(web.html`
for="number--${web.escapeHtml(`${id}.${key}`)}" <label
class="library--number_label" for="number--${web.escapeHtml(`${id}.${key}`)}"
class="library--number_label"
> >
<p><span data-tooltip>${web.escapeHtml(label)} <p><span data-tooltip>${web.escapeHtml(label)}
${tooltip ? web.html`<i data-icon="fa/question-circle"></i>` : ''}</span></p> ${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p>
<input id="number--${web.escapeHtml(`${id}.${key}`)}" <input id="number--${web.escapeHtml(`${id}.${key}`)}"
type="number" value="${web.escapeHtml(state.toString())}"/> type="number" value="${web.escapeHtml(state.toString())}"/>
</label>`); </label>`);
opt.addEventListener('change', (event) => storage.set(id, key, event.target.value)); 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; 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`<label opt = web.createElement(web.html`
for="file--${web.escapeHtml(`${id}.${key}`)}" <label
class="library--file_label" for="file--${web.escapeHtml(`${id}.${key}`)}"
class="library--file_label"
> >
<input <input
type="file" type="file"
id="file--${web.escapeHtml(`${id}.${key}`)}" id="file--${web.escapeHtml(`${id}.${key}`)}"
${web.escapeHtml( ${web.escapeHtml(
extensions && extensions.length extensions && extensions.length
? ` accept=${web.escapeHtml(extensions.join(','))}` ? ` accept=${web.escapeHtml(extensions.join(','))}`
: '' : ''
)} )}
/> />
<p class="library--file_title"><span data-tooltip>${web.escapeHtml(label)} <p class="library--file_title"><span data-tooltip>${web.escapeHtml(label)}
<i data-icon="fa/question-circle"></i></span> <i data-icon="fa/solid/question-circle"></i></span>
<span class="library--file_remove"><i data-icon="fa/minus"></i></span></p> <span class="library--file_remove"><i data-icon="fa/solid/minus"></i></span></p>
<p class="library--file"> <p class="library--file">
<span><i data-icon="fa/file"></i></span> <span><i data-icon="fa/solid/file"></i></span>
<span class="library--file_path">${web.escapeHtml(state || 'choose file...')}</span> <span class="library--file_path">${web.escapeHtml(state || 'choose file...')}</span>
</p> </p>
</label>`); </label>`);
opt.addEventListener('change', (event) => { opt.addEventListener('change', (event) => {
const file = event.target.files[0], const file = event.target.files[0],
reader = new FileReader(); reader = new FileReader();
opt.querySelector('.library--file_path').innerText = file.name; opt.querySelector('.library--file_path').innerText = file.name;
reader.onload = (progress) => { reader.onload = (progress) => {
storage.set(id, key, file.name); storage.set(id, key, file.name);
storage.set(id, `_file.${key}`, progress.currentTarget.result); storage.set(id, `_file.${key}`, progress.currentTarget.result);
}; };
@ -272,281 +280,190 @@ components.options = {
opt.addEventListener('click', (event) => { opt.addEventListener('click', (event) => {
document.documentElement.scrollTop = 0; document.documentElement.scrollTop = 0;
}); });
tooltips.add( web.addTooltip(
opt, opt.querySelector('[data-tooltip]'),
'[data-tooltip]', `${tooltip ? `${tooltip}\n\n` : ''}**warning:** ${
`${ 'browser extensions do not have true filesystem access, ' +
tooltip ? `${tooltip}\n\n` : '' 'so file content is only saved on selection. re-select files to apply edits.'
}**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; 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, _reloadTriggered: false,
buttons({ _dir }) { async reload($fragment = document) {
const $el = web.createElement(web.html`<p class="documentation--buttons"> let $reload = $fragment.querySelector('[data-reload]');
<a href="?view=library"> if (!$reload) {
<span><i data-icon="fa/long-arrow-alt-left"></i></span> $reload = web.createElement(web.html`
<span>back to library</span> <button class="action--alert" data-reload>
</a> <span><i data-icon="fa/solid/redo"></i></span>
<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>
<span>reload tabs to apply changes</span> <span>reload tabs to apply changes</span>
</button> </button>`);
</p>`); $reload.addEventListener('click', env.reloadTabs);
storage.onChange(() => { }
const $reload = $el.querySelector('.documentation--reload'); if (this._reloadTriggered) {
if (document.body.contains($el) && !$reload.dataset.triggered) { $fragment.querySelector('.action--buttons').append($reload);
$reload.dataset.triggered = true; await new Promise((res, rej) => requestAnimationFrame(res));
this._reloadTriggered = true; $reload.dataset.triggered = true;
} }
});
$el.querySelector('.documentation--reload').addEventListener('click', env.reloadTabs);
return $el;
}, },
readme: async (mod) => { async clearFilters($fragment = document) {
const readme = web.createElement(web.html`<article class="documentation--body markdown"> let $clearFilters = $fragment.querySelector('[data-clear-filters]');
${ if (!$clearFilters) {
(await fs.isFile(`repo/${mod._dir}/README.md`)) $clearFilters = web.createElement(web.html`
? fmt.md.render(await fs.getText(`repo/${mod._dir}/README.md`)) <a class="action--alert" href="?view=library" data-clear-filters>
: '' <span><i data-icon="fa/solid/times"></i></span>
} <span>clear filters</span>
</article>`); </a>`);
fmt.Prism.highlightAllUnder(readme); }
return readme; 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 = { storage.addChangeListener(async (event) => {
$container: document.querySelector('main'), actionButtons._reloadTriggered = true;
_router(event) { actionButtons.reload();
event.preventDefault(); router.load();
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();
const search = new Map( if (event.namespace === '_mods' && event.new === true) {
location.search const enabledTheme = (await registry.get()).find((mod) => mod.id === event.key);
.slice(1) if (
.split('&') enabledTheme.tags.includes('theme') &&
.map((query) => query.split('=')) (await storage.get(_id, 'themes.autoresolve', true))
); ) {
switch (search.get('view')) { for (const theme of await registry.get(
case 'mod': (mod) =>
const mod = (await registry.get()).find((mod) => mod.id === search.get('id')); mod.tags.includes('theme') &&
if (mod) { mod.id !== enabledTheme.id &&
await this.mod(mod); ((mod.tags.includes('dark') && enabledTheme.tags.includes('dark')) ||
break; (mod.tags.includes('light') && enabledTheme.tags.includes('light')))
} )) {
case 'library': if (document.body.dataset.view === 'library') {
await this.library(); const $toggle = document.getElementById(`enable--${theme.id}`);
break; if ($toggle.checked) $toggle.click();
default: } else storage.set('_mods', theme.id, false);
window.history.replaceState( }
{ search: '?view=library', hash: '' },
null,
'?view=library'
);
return this._load();
} }
}
});
setTimeout(() => { router.addView(
document.getElementById(location.hash.slice(1))?.scrollIntoView(true); 'library',
document.documentElement.scrollTop = 0; async () => {
}, 50); const $fragment = web.createFragment(web.html`
document <p class="action--buttons">
.querySelectorAll('img') <a href="?view=library&tag=theme">
.forEach((img) => (img.onerror = (event) => event.target.remove())); <span><i data-icon="fa/solid/palette"></i></span>
document <span>themes</span>
.querySelectorAll('a[href^="?"]') </a>
.forEach((a) => a.addEventListener('click', this._router)); <a href="?view=library&tag=extension">
document <span><i data-icon="fa/solid/plus"></i></span>
.querySelectorAll('a[href^="#"]') <span>extensions</span>
.forEach((a) => a.addEventListener('click', this._navigator)); </a>
document.querySelectorAll('[data-icon]').forEach((icon) => <a href="?view=library&enabled">
fs.getText(`icons/${icon.dataset.icon}.svg`).then((svg) => { <span><i data-icon="fa/solid/toggle-on"></i></span>
svg = web.createElement(svg); <span>enabled</span>
for (const attr of icon.attributes) { </a>
svg.setAttribute(attr.name, attr.value); <a href="?view=library&disabled">
} <span><i data-icon="fa/solid/toggle-off"></i></span>
icon.replaceWith(svg); <span>disabled</span>
}) </a>
); </p>`);
},
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;
for (const mod of await registry.get( for (const mod of await registry.get(
(mod) => !mod.environments || mod.environments.includes(env.name) (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;
}, },
}; async (search = router.getSearch()) => {
views._router = views._router.bind(views); for (const [filter, active] of [
views._navigator = views._navigator.bind(views); ['tag=theme', search.get('tag') === 'theme'],
views._load(); ['tag=extension', search.get('tag') === 'extension'],
window.addEventListener('popstate', (event) => { ['enabled', search.has('enabled')],
if (event.state) views._load(); ['disabled', search.has('disabled')],
}); ]) {
document
const notifications = { .querySelector(`.action--buttons > [href="?view=library&${filter}"]`)
$list: document.querySelector('.notification--list'), .classList[active ? 'add' : 'remove']('action--active');
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;
} }
const $notif = web.createElement(web.html`<div role="alert" class="${className}"> for (const card of document.querySelectorAll('main > .library--card')) {
<div>${svg}</div> const { tags } = (await registry.get()).find((mod) => mod.id === card.dataset.mod),
<div> isEnabled = await registry.isEnabled(card.dataset.mod);
<h3>${web.escapeHtml(heading)}</h3>
<p>${fmt.md.renderInline(message)}</p>
</div>
<button class="notification--dismiss">&times;</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) {
if ( if (
notification.heading && (search.has('tag') ? tags.includes(search.get('tag')) : true) &&
notification.appears_on && (search.has('enabled') && search.has('disabled')
(notification.appears_on.versions.includes('*') || ? true
notification.appears_on.versions.includes(env.version)) && : search.has('enabled')
notification.appears_on.extension ? isEnabled
: search.has('disabled')
? !isEnabled
: true)
) { ) {
this.push(notification, async () => { card.style.display = '';
const dismissed = await storage.get(_id, 'notifications', []); } else card.style.display = 'none';
storage.set('_notifications', 'external', [
...new Set([...dismissed, notification.id]),
]);
});
}
} }
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()) { if (document.querySelector('[data-mod]').dataset.mod !== router.getSearch().get('id'))
notifications.push({ router.load(true);
heading: `error: ${error.source}`, }
message: error.message, );
type: 'warning',
});
}
notifications.fetch();
async function theme() { router.setDefaultView('library');
document.documentElement.className = `notion-${ router.load();
(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);
// });

View File

@ -13,8 +13,9 @@
} }
], ],
"css": { "css": {
"client": ["client.css"], "frame": ["tooltips.css"],
"menu": ["menu.css", "markdown.css"] "client": ["client.css", "tooltips.css"],
"menu": ["menu.css", "markdown.css", "tooltips.css"]
}, },
"js": { "js": {
"client": ["client.js"] "client": ["client.js"]

View File

@ -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);
});
});

View File

@ -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;
}

View File

@ -14,9 +14,8 @@ web.whenReady().then(async () => {
if (cssInsert) { if (cssInsert) {
document.body.append( document.body.append(
web.createElement( web.createElement(
web.html`<style id="${await storage.get(_id, 'insert.css')}@${_id}"> web.html`
${cssInsert} <style id="${await storage.get(_id, 'insert.css')}@${_id}">${cssInsert}</style>`
</style>`
) )
); );
} }