mirror of
				https://github.com/notion-enhancer/notion-enhancer.git
				synced 2025-10-31 14:18:08 +11: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}`); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| document | ||||
|   .querySelector('img[data-view-target="notion"]') | ||||
|   .addEventListener('click', env.focusNotion); | ||||
| web.hotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.focusNotion); | ||||
| 
 | ||||
| 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 = ''; | ||||
| async function loadTheme() { | ||||
|   document.documentElement.className = `notion-${ | ||||
|     (await storage.get(_id, 'theme')) || 'dark' | ||||
|   }-theme`;
 | ||||
| } | ||||
| window.addEventListener('focus', loadTheme); | ||||
| loadTheme(); | ||||
| 
 | ||||
| document.querySelector('img[data-notion]').addEventListener('click', env.focusNotion); | ||||
| web.addHotkeyListener(await storage.get(_id, 'hotkey.focustoggle'), env.focusNotion); | ||||
| web.addDocumentObserver(web.loadIcons); | ||||
| 
 | ||||
| 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
 | ||||
| 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(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)}" | ||||
|           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> | ||||
|         ${ | ||||
|           registry.CORE.includes(mod.id) ? '' : web.html`<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); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|     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 | ||||
|     </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,33 +134,25 @@ 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> | ||||
|     </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>` | ||||
|     ), | ||||
|   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; | ||||
|   }, | ||||
|     </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
 | ||||
|       opt = web.createElement(web.html` | ||||
|       <label | ||||
|         for="toggle--${web.escapeHtml(`${id}.${key}`)}" | ||||
|         class="library--toggle_label" | ||||
|       > | ||||
| @ -156,24 +160,25 @@ components.options = { | ||||
|         ${state ? 'checked' : ''}/> | ||||
|         <p> | ||||
|           <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> | ||||
|         </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> | ||||
|         ${tooltip ? web.html`<i data-icon="fa/solid/question-circle"></i>` : ''}</span></p> | ||||
|         <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}`)}"> | ||||
|             ${values.map( | ||||
|               (value) => | ||||
| @ -185,17 +190,18 @@ components.options = { | ||||
|         </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
 | ||||
|       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> | ||||
|         ${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>`); | ||||
| @ -207,27 +213,29 @@ 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
 | ||||
|       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> | ||||
|         ${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
 | ||||
|       opt = web.createElement(web.html` | ||||
|       <label | ||||
|         for="file--${web.escapeHtml(`${id}.${key}`)}" | ||||
|         class="library--file_label" | ||||
|       > | ||||
| @ -241,10 +249,10 @@ components.options = { | ||||
|           )} | ||||
|         /> | ||||
|         <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> | ||||
|         <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/file"></i></span> | ||||
|           <span><i data-icon="fa/solid/file"></i></span> | ||||
|           <span class="library--file_path">${web.escapeHtml(state || 'choose file...')}</span> | ||||
|         </p> | ||||
|       </label>`); | ||||
| @ -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); | ||||
| }; | ||||
| 
 | ||||
| const actionButtons = { | ||||
|   _reloadTriggered: false, | ||||
|   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>`); | ||||
|       $reload.addEventListener('click', env.reloadTabs); | ||||
|     } | ||||
|     return card; | ||||
|     if (this._reloadTriggered) { | ||||
|       $fragment.querySelector('.action--buttons').append($reload); | ||||
|       await new Promise((res, rej) => requestAnimationFrame(res)); | ||||
|       $reload.dataset.triggered = true; | ||||
|     } | ||||
|   }, | ||||
|   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(); | ||||
|   }, | ||||
| }; | ||||
| components.documentation = { | ||||
|   _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> | ||||
|         <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; | ||||
| storage.addChangeListener(async (event) => { | ||||
|   actionButtons._reloadTriggered = true; | ||||
|   actionButtons.reload(); | ||||
|   router.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); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }); | ||||
|     $el.querySelector('.documentation--reload').addEventListener('click', env.reloadTabs); | ||||
|     return $el; | ||||
| 
 | ||||
| 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) | ||||
|     )) { | ||||
|       $fragment.append(await components.card(mod)); | ||||
|     } | ||||
|     actionButtons.reload($fragment); | ||||
|     actionButtons.clearFilters($fragment); | ||||
|     return $fragment; | ||||
|   }, | ||||
|   readme: async (mod) => { | ||||
|     const readme = web.createElement(web.html`<article class="documentation--body markdown">
 | ||||
|   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'); | ||||
|     } | ||||
|     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 ( | ||||
|         (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) | ||||
|       ) { | ||||
|         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(readme); | ||||
|     return readme; | ||||
|   }, | ||||
| }; | ||||
| 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 | ||||
|       </article>`) | ||||
|     ); | ||||
|       this._load(); | ||||
|     fmt.Prism.highlightAllUnder($fragment); | ||||
|     actionButtons.reload($fragment); | ||||
|     return $fragment; | ||||
|   }, | ||||
|   () => { | ||||
|     if (document.querySelector('[data-mod]').dataset.mod !== router.getSearch().get('id')) | ||||
|       router.load(true); | ||||
|   } | ||||
|   }, | ||||
|   _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( | ||||
|       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); | ||||
|         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; | ||||
|     for (const mod of await registry.get( | ||||
|       (mod) => !mod.environments || mod.environments.includes(env.name) | ||||
|     )) { | ||||
|       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 = { | ||||
|   $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; | ||||
|     } | ||||
|     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) { | ||||
|       if ( | ||||
|         notification.heading && | ||||
|         notification.appears_on && | ||||
|         (notification.appears_on.versions.includes('*') || | ||||
|           notification.appears_on.versions.includes(env.version)) && | ||||
|         notification.appears_on.extension | ||||
|       ) { | ||||
|         this.push(notification, async () => { | ||||
|           const dismissed = await storage.get(_id, 'notifications', []); | ||||
|           storage.set('_notifications', 'external', [ | ||||
|             ...new Set([...dismissed, notification.id]), | ||||
|           ]); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
| for (const error of await registry.errors()) { | ||||
|   notifications.push({ | ||||
|     heading: `error: ${error.source}`, | ||||
|     message: error.message, | ||||
|     type: 'warning', | ||||
|   }); | ||||
| } | ||||
| notifications.fetch(); | ||||
| 
 | ||||
| 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