/* * notion-enhancer core: menu * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ 'use strict'; const _id = 'a6621988-551d-495a-97d8-3c568bca2e9e'; import { env, storage, web, fmt, fs, registry } from '../../helpers.js'; for (let mod of await registry.get()) { for (let sheet of mod.css?.menu || []) { web.loadStyleset(`repo/${mod._dir}/${sheet}`); } } document .querySelector('img[data-view-target="notion"]') .addEventListener('click', env.focusNotion); web.hotkeyListener(['Ctrl', 'Alt', 'E'], env.focusNotion); const tooltips = { $list: document.querySelector('.tooltip--list'), _generate($target, text) { const $tooltip = web.createElement(web.html`
${fmt.md.render(text)}
`); this.$list.appendChild($tooltip); $target.addEventListener('mouseover', (event) => { $tooltip.style.display = 'block'; }); $target.addEventListener('mousemove', (event) => { $tooltip.style.top = event.clientY - $tooltip.clientHeight + 'px'; $tooltip.style.left = event.clientX < window.innerWidth / 2 ? event.clientX + 20 + 'px' : ''; }); $target.addEventListener('mouseout', (event) => { $tooltip.style.display = ''; }); }, }; const components = {}; components.card = { preview: ({ preview = '' }) => web.createElement(web.html``), name: ({ name, id, version }) => web.createElement(web.html``), tags: ({ tags = [] }) => web.createElement(web.html``), description: ({ description }) => web.createElement( web.html`

${fmt.md.renderInline( description )}

` ), authors: ({ authors }) => web.createElement(web.html``), expand: async ({ id }) => web.createElement( web.html`

settings & documentation

` ), async _generate(mod) { const card = web.createElement(web.html`
`), body = web.createElement(web.html`
`); card.append(this.preview(mod)); body.append(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 = { async toggle(id, { key, label, description }) { const state = await storage.get(id, key), opt = web.createElement(web.html``); opt.addEventListener('change', (event) => storage.set(id, key, event.target.checked)); tooltips._generate(opt, description); return opt; }, async select(id, { key, label, description, values }) { const state = await storage.get(id, key), opt = web.createElement(web.html``); opt.addEventListener('change', (event) => storage.set(id, key, event.target.value)); tooltips._generate(opt, description); return opt; }, async text(id, { key, label, description }) { const state = await storage.get(id, key), opt = web.createElement(web.html``); opt.querySelector('textarea').addEventListener('input', (event) => { event.target.style.removeProperty('--txt--scroll-height'); event.target.style.setProperty('--txt--scroll-height', event.target.scrollHeight + 'px'); }); opt.addEventListener('change', (event) => storage.set(id, key, event.target.value)); tooltips._generate(opt, description); return opt; }, async number(id, { key, label, description }) { const state = await storage.get(id, key), opt = web.createElement(web.html``); opt.addEventListener('change', (event) => storage.set(id, key, event.target.value)); tooltips._generate(opt, description); return opt; }, async file(id, { key, label, description, extensions }) { const state = await storage.get(id, key), opt = web.createElement(web.html``); opt.addEventListener('change', (event) => { const file = event.target.files[0], reader = new FileReader(); opt.querySelector('.library--file_path').innerText = file.name; storage.set(id, key, file.name); reader.onload = (progress) => { storage.set(id, `_file.${key}`, progress.currentTarget.result); }; reader.readAsText(file); }); opt.addEventListener('click', (event) => { document.documentElement.scrollTop = 0; }); tooltips._generate( opt, `${description}\n\n**warning:** browser extensions do not have true filesystem access, so the content of the file is saved on selection. after editing it, the file will need to be re-selected.` ); 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`
`), inputs = await Promise.all(mod.options.map((opt) => this[opt.type](mod.id, opt))); inputs.forEach((opt) => options.append(opt)); card.append(options); } return card; }, }; components.documentation = { buttons: async ({ _dir }) => web.createElement(web.html`

back to library view source code

`), readme: async (mod) => { const readme = web.createElement(web.html`
${ (await fs.isFile(`repo/${mod._dir}/README.md`)) ? fmt.md.render(await fs.getText(`repo/${mod._dir}/README.md`)) : '' }
`); fmt.Prism.highlightAllUnder(readme); return readme; }, }; const views = { $container: document.querySelector('[data-container]'), _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 = ''; this.$container.dataset.container = ''; document .querySelector('[data-view-target][data-view-active]') ?.removeAttribute('data-view-active'); res(); }, 200); }); }, async _load() { await this._reset(); 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(); } 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); svg.dataset.icon = icon.dataset.icon; icon.replaceWith(svg); }) ); }, async mod(mod) { this.$container.dataset.container = 'mod'; document.querySelector('header [data-view-target="library"]').dataset.active = true; this.$container.append(await components.documentation.buttons(mod)); this.$container.append(await components.options._generate(mod)); this.$container.append(await components.documentation.readme(mod)); }, async library() { this.$container.dataset.container = 'library'; document.querySelector('header [data-view-target="library"]').dataset.active = true; for (let mod of await registry.get()) this.$container.append(await components.card._generate(mod)); }, }; 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 = { _generate({ heading, message = '', type = 'information' }, onDismiss = () => {}) { let svg = '', className = 'notification'; switch (type) { case 'celebration': svg = web.html``; className += ' celebration'; break; case 'information': svg = web.html``; className += ' information'; break; case 'warning': svg = web.html``; className += ' warning'; break; } const $notif = web.createElement(web.html``); $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 $notif; }, async load() { 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) ); const $list = document.querySelector('.notification--list'); for (let notification of notifications.waiting) { if ( notification.heading && notification.appears_on && (notification.appears_on.versions.includes('*') || notification.appears_on.versions.includes(env.version)) && notification.appears_on.extension ) { $list.append( this._generate(notification, async () => { const dismissed = await storage.get(_id, 'notifications', []); storage.set(_id, 'notifications', [...new Set([...dismissed, notification.id])]); }) ); } } }, }; notifications.load(); 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); // });