/* * notion-enhancer core: menu * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ 'use strict'; // css-in-js for better component generation import { tw, apply, setup } from '../../dep/twind.mjs'; import { content } from '../../dep/twind-content.mjs'; const pseudoContent = content('""'); const mapColorVariables = (color) => ({ 'text': `var(--theme--text_${color})`, 'highlight': `var(--theme--highlight_${color})`, 'highlight-text': `var(--theme--highlight_${color}-text)`, 'block': `var(--theme--block_${color})`, 'block-text': `var(--theme--block_${color}-text)`, 'tag': `var(--theme--tag_${color})`, 'tag-text': `var(--theme--tag_${color}-text)`, 'callout': `var(--theme--callout_${color})`, 'callout-text': `var(--theme--callout_${color}-text)`, }); setup({ preflight: { html: apply`w-full h-full`, body: apply`w-full h-full bg-notion-bg font-sans text-foreground`, }, theme: { fontFamily: { sans: ['var(--theme--font_sans)'], mono: ['var(--theme--font_mono)'], }, colors: { 'notion': { 'bg': 'var(--theme--bg)', 'secondary': 'var(--theme--bg_secondary)', 'popup': 'var(--theme--bg_popup)', 'divider': 'var(--theme--ui_divider)', 'input': 'var(--theme--ui_input)', }, 'icon': 'var(--theme--icon)', 'icon-ui': 'var(--theme--icon_ui)', 'foreground': 'var(--theme--text)', 'foreground-ui': 'var(--theme--text_ui)', 'interactive': 'var(--theme--ui_interactive)', 'interactive-hover': 'var(--theme--ui_interactive-hover)', 'tag': 'var(--theme--tag_default)', 'tag-text': 'var(--theme--tag_default-text)', 'toggle': { 'on': 'var(--theme--ui_toggle-on)', 'off': 'var(--theme--ui_toggle-off)', 'feature': 'var(--theme--ui_toggle-feature)', }, 'accent': { 'blue': 'var(--theme--accent_blue)', 'blue-hover': 'var(--theme--accent_blue-hover)', 'blue-active': 'var(--theme--accent_blue-active)', 'blue-text': 'var(--theme--accent_blue-text)', 'red': 'var(--theme--accent_red)', 'red-hover': 'var(--theme--accent_red-hover)', 'red-text': 'var(--theme--accent_red-text)', }, 'grey': mapColorVariables('grey'), 'brown': mapColorVariables('brown'), 'orange': mapColorVariables('orange'), 'yellow': mapColorVariables('yellow'), 'green': mapColorVariables('green'), 'blue': mapColorVariables('blue'), 'purple': mapColorVariables('purple'), 'pink': mapColorVariables('pink'), 'red': mapColorVariables('red'), }, extend: { width: { 'full-96': 'calc(100% - 24rem)', }, maxHeight: { 'full-16': 'calc(100% - 4rem)', 'full-32': 'calc(100% - 8rem)', 'full-48': 'calc(100% - 12rem)', }, }, }, }); // initialisation and external interactions import * as api from '../../api/_.mjs'; import { render } from '../../api/web.mjs'; const { env, fmt, fs, registry, storage, web } = api, db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e'); web.addHotkeyListener(await db.get(['hotkey']), env.focusNotion); for (const mod of await registry.list((mod) => registry.enabled(mod.id))) { for (const sheet of mod.css?.menu || []) { web.loadStylesheet(`repo/${mod._dir}/${sheet}`); } } const loadTheme = async () => { document.documentElement.className = (await db.get(['theme'], 'light')) === 'dark' ? 'dark' : ''; }; document.addEventListener('visibilitychange', loadTheme); loadTheme(); const notifications = { $container: web.html`
`, cache: await db.get(['notifications'], []), provider: [ env.welcomeNotification, ...(await fs.getJSON('https://notion-enhancer.github.io/notifications.json')), ], add({ icon, message, id = undefined, color = undefined, link = undefined }) { const style = tw`p-2 ${ color ? `bg-${color}-tag text-${color}-tag-text border border-${color}-text hover:bg-${color}-text` : 'bg-tag text-tag-text hover:bg-interactive-hover border border-notion-divider' } flex items-center rounded-full mt-3 shadow-md cursor-pointer`, $notification = web.render( link ? web.html`` : web.html``, web.html` ${message} `, web.html`${web.icon(icon, { class: tw`fill-current opacity-75 h-4 w-4 mx-2` })}` ), resolve = async () => { if (id !== undefined) { notifications.cache.push(id); await db.set(['notifications'], notifications.cache); } $notification.remove(); }; $notification.addEventListener('click', resolve); $notification.addEventListener('keyup', (event) => { if (['Enter', ' '].includes(event.key)) resolve(); }); web.render(notifications.$container, $notification); return $notification; }, _changes: false, changes() { if (this._changes) return; this._changes = true; const $notification = this.add({ icon: 'refresh-cw', message: 'Reload to apply changes.', }); $notification.addEventListener('click', env.reload); }, }; render(document.body, notifications.$container); for (const notification of notifications.provider) { if ( !notifications.cache.includes(notification.id) && notification.version === env.version && (!notification.environments || notification.environments.includes(env.name)) ) { notifications.add(notification); } } const errors = await registry.errors(); if (errors.length) { console.log('[notion-enhancer] registry errors:'); console.table(errors); notifications.add({ icon: 'alert-circle', message: 'Failed to load mods (check console).', color: 'red', }); } // mod config const $container = web.html`
`, $nav = web.html``, $main = web.html`
abc
`, // $footer = web.html``, $sidebar = web.html`
`; const notionNavStyle = tw`flex items-center font-semibold text-xl cursor-pointer select-none mr-4 ml-4 sm:mb-4 md:w-full lg:(w-auto ml-0 mb-0)`, $notion = web.html`

${(await fs.getText('icon/colour.svg')).replace( /width="\d+" height="\d+"/, `class="${tw`h-12 w-12 mr-5 sm:(h-6 w-6 mr-3)`}"` )} notion-enhancer

`; $notion.children[0].addEventListener('click', env.focusNotion); const navItemStyle = tw`ml-4 px-3 py-2 rounded-md text-sm font-medium bg-interactive hover:bg-interactive-hover`, selectedNavItemStyle = tw`ml-4 px-3 py-2 rounded-md text-sm font-medium ring-1 ring-notion-divider bg-notion-secondary`; const $coreNavItem = web.html`core`, $extensionsNavItem = web.html`extensions`, $themesNavItem = web.html`themes`, $supportNavItem = web.html`support`; web.render( document.body, web.render( $container, web.render( web.html`
`, web.render( $nav, $notion, $coreNavItem, $extensionsNavItem, $themesNavItem, $supportNavItem ), $main // $footer ), $sidebar ) ); const components = { preview: (url) => web.html``, title: (title) => { const style = tw`mb-2 text-xl font-semibold tracking-tight flex items-center`; return web.html`

${web.escape(title)}

`; }, version: (version) => { const style = tw`mt-px ml-3 p-1 font-normal text-xs leading-none bg-tag text-tag-text rounded`; return web.html`v${web.escape(version)}`; }, tags: (tags) => { if (!tags.length) return ''; return web.render( web.html`

`, tags.map((tag) => `#${web.escape(tag)}`).join(' ') ); }, description: (description) => { return web.html`

${fmt.md.renderInline(description)}

`; }, authors: (authors) => { const author = (author) => web.html` ${web.escape(author.name)}'s avatar ${web.escape(author.name)} `; return web.render( web.html`

`, ...authors.map(author) ); }, toggle: ( checked, { customLabelStyle = '', customCheckStyle = '', customBoxStyle = '', customFeatureStyle = '', } ) => { const checkStyle = tw`appearance-none checked:sibling:(bg-toggle-on after::translate-x-4) ${customCheckStyle}`, boxStyle = tw`w-9 h-5 p-0.5 flex items-center bg-toggle-off rounded-full duration-300 ease-in-out ${customBoxStyle}`, featureStyle = tw`after::(${pseudoContent} w-4 h-4 bg-toggle-feature rounded-full duration-300) ${customFeatureStyle}`, $label = web.html``, $input = web.html``; $label.addEventListener('keyup', (event) => { if (['Enter', ' '].includes(event.key)) { $input.checked = !$input.checked; } }); return web.render( $label, $input, web.html`` ); }, }; components.mod = async (mod) => { const $toggle = components.toggle(await registry.enabled(mod.id), { customLabelStyle: 'flex w-full mt-auto', customCheckStyle: 'ml-auto', }); $toggle.addEventListener('change', (event) => { registry.profile.set(['_mods', mod.id], event.target.checked); notifications.changes(); }); const style = tw`relative h-full w-full flex flex-col overflow-hidden rounded-lg shadow-lg bg-notion-secondary border border-notion-divider`; return web.render( web.html`
`, web.render( web.html`
`, mod.preview ? components.preview( mod.preview.startsWith('http') ? mod.preview : fs.localPath(`repo/${mod._dir}/${mod.preview}`) ) : '', web.render( web.html`
`, web.render(components.title(mod.name), components.version(mod.version)), components.tags(mod.tags), components.description(mod.description), components.authors(mod.authors), mod.environments.includes(env.name) && !registry.core.includes(mod.id) ? $toggle : '' ) ) ); }; components.modList = async (category) => { const $search = web.html``, $list = web.html`
`, mods = await registry.list( (mod) => mod.environments.includes(env.name) && mod.tags.includes(category) ); web.addHotkeyListener(['/'], () => $search.focus()); $search.addEventListener('input', (event) => { const query = $search.value.toLowerCase(), hiddenStyle = tw`hidden`; for (const $mod of $list.children) { const matches = !query || $mod.innerText.toLowerCase().includes(query); $mod.classList[matches ? 'remove' : 'add'](hiddenStyle); } }); for (const mod of mods) { mod.tags = mod.tags.filter((tag) => tag !== category); web.render($list, await components.mod(mod)); mod.tags.unshift(category); } return web.render( web.html`
`, web.render(web.html`
`, $search), $list ); }; import * as router from './router.mjs'; router.addView('core', async () => { $extensionsNavItem.className = navItemStyle; $themesNavItem.className = navItemStyle; $coreNavItem.className = selectedNavItemStyle; web.empty($main); return web.render($main, await components.modList('core')); }); router.addView('extensions', async () => { $coreNavItem.className = navItemStyle; $themesNavItem.className = navItemStyle; $extensionsNavItem.className = selectedNavItemStyle; web.empty($main); return web.render($main, await components.modList('extension')); }); router.addView('themes', async () => { $coreNavItem.className = navItemStyle; $extensionsNavItem.className = navItemStyle; $themesNavItem.className = selectedNavItemStyle; web.empty($main); return web.render($main, await components.modList('theme')); }); router.loadView('extensions', $main);