mirror of
				https://github.com/notion-enhancer/notion-enhancer.git
				synced 2025-10-27 12:18:09 +11:00 
			
		
		
		
	use esbuild to compile .mjs to .cjs
This commit is contained in:
		
							parent
							
								
									24bc8bda66
								
							
						
					
					
						commit
						b770eb64f0
					
				| @ -1,185 +0,0 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| /** | ||||
|  * helpers for formatting or parsing text | ||||
|  * @module notion-enhancer/api/fmt | ||||
|  */ | ||||
| 
 | ||||
| import { web, fs, components } from '../index.mjs'; | ||||
| import '../../dep/prism.min.js'; | ||||
| import '../../dep/markdown-it.min.js'; | ||||
| 
 | ||||
| /** syntax highlighting using https://prismjs.com/ */ | ||||
| export const prism = Prism; | ||||
| Prism.manual = true; | ||||
| Prism.hooks.add('complete', async (event) => { | ||||
|   event.element.parentElement.removeAttribute('tabindex'); | ||||
|   event.element.parentElement.parentElement | ||||
|     .querySelector('.copy-to-clipboard-button') | ||||
|     .prepend(web.html`${await components.feather('clipboard')}`); | ||||
| }); | ||||
| 
 | ||||
| /** markdown -> html using https://github.com/markdown-it/markdown-it/ */ | ||||
| export const md = new markdownit({ | ||||
|   linkify: true, | ||||
|   highlight: (str, lang) => | ||||
|     web.html`<pre class="language-${lang || 'plaintext'} match-braces"><code>${web.escape( | ||||
|       str | ||||
|     )}</code></pre>`, | ||||
| }); | ||||
| md.renderer.rules.code_block = (tokens, idx, options, env, slf) => { | ||||
|   const attrIdx = tokens[idx].attrIndex('class'); | ||||
|   if (attrIdx === -1) { | ||||
|     tokens[idx].attrPush(['class', 'match-braces language-plaintext']); | ||||
|   } else tokens[idx].attrs[attrIdx][1] = 'match-braces language-plaintext'; | ||||
|   return web.html`<pre${slf.renderAttrs(tokens[idx])}><code>${web.escape( | ||||
|     tokens[idx].content | ||||
|   )}</code></pre>\n`; | ||||
| }; | ||||
| md.core.ruler.push( | ||||
|   'heading_ids', | ||||
|   function (md, state) { | ||||
|     const slugs = new Set(); | ||||
|     state.tokens.forEach(function (token, i) { | ||||
|       if (token.type === 'heading_open') { | ||||
|         const text = md.renderer.render(state.tokens[i + 1].children, md.options), | ||||
|           slug = slugger(text, slugs); | ||||
|         slugs.add(slug); | ||||
|         const attrIdx = token.attrIndex('id'); | ||||
|         if (attrIdx === -1) { | ||||
|           token.attrPush(['id', slug]); | ||||
|         } else token.attrs[attrIdx][1] = slug; | ||||
|       } | ||||
|     }); | ||||
|   }.bind(null, md) | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), | ||||
|  * e.g. for use as an anchor id | ||||
|  * @param {string} heading - the original heading to be slugified | ||||
|  * @param {Set<string>} [slugs] - a list of pre-generated slugs to avoid duplicates | ||||
|  * @returns {string} the generated slug | ||||
|  */ | ||||
| export const slugger = (heading, slugs = new Set()) => { | ||||
|   heading = heading | ||||
|     .replace(/\s/g, '-') | ||||
|     .replace(/[^A-Za-z0-9-_]/g, '') | ||||
|     .toLowerCase(); | ||||
|   let i = 0, | ||||
|     slug = heading; | ||||
|   while (slugs.has(slug)) { | ||||
|     i++; | ||||
|     slug = `${heading}-${i}`; | ||||
|   } | ||||
|   return slug; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * generate a reasonably random uuidv4 string. uses crypto implementation if available | ||||
|  * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid)
 | ||||
|  * @returns {string} a uuidv4 | ||||
|  */ | ||||
| export const uuidv4 = () => { | ||||
|   if (crypto?.randomUUID) return crypto.randomUUID(); | ||||
|   return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => | ||||
|     (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * log-based shading of an rgb color, from | ||||
|  * https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
 | ||||
|  * @param {number} p - a decimal amount to shade the color. | ||||
|  * 1 = white, 0 = the original color, -1 = black | ||||
|  * @param {string} c - the rgb color | ||||
|  * @returns {string} the shaded color | ||||
|  */ | ||||
| export const rgbLogShade = (p, c) => { | ||||
|   var i = parseInt, | ||||
|     r = Math.round, | ||||
|     [a, b, c, d] = c.split(','), | ||||
|     P = p < 0, | ||||
|     t = P ? 0 : p * 255 ** 2, | ||||
|     P = P ? 1 + p : 1 - p; | ||||
|   return ( | ||||
|     'rgb' + | ||||
|     (d ? 'a(' : '(') + | ||||
|     r((P * i(a[3] == 'a' ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + | ||||
|     ',' + | ||||
|     r((P * i(b) ** 2 + t) ** 0.5) + | ||||
|     ',' + | ||||
|     r((P * i(c) ** 2 + t) ** 0.5) + | ||||
|     (d ? ',' + d : ')') | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * pick a contrasting color e.g. for text on a variable color background | ||||
|  * using the hsp (perceived brightness) constants from http://alienryderflex.com/hsp.html
 | ||||
|  * @param {number} r - red (0-255) | ||||
|  * @param {number} g - green (0-255) | ||||
|  * @param {number} b - blue (0-255) | ||||
|  * @returns {string} the contrasting rgb color, white or black | ||||
|  */ | ||||
| export const rgbContrast = (r, g, b) => { | ||||
|   return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 | ||||
|     ? 'rgb(0,0,0)' | ||||
|     : 'rgb(255,255,255)'; | ||||
| }; | ||||
| 
 | ||||
| const patterns = { | ||||
|   alphanumeric: /^[\w\.-]+$/, | ||||
|   uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, | ||||
|   semver: | ||||
|     /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, | ||||
|   email: | ||||
|     /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, | ||||
|   url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, | ||||
|   color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i, | ||||
| }; | ||||
| function test(str, pattern) { | ||||
|   const match = str.match(pattern); | ||||
|   return !!(match && match.length); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * test the type of a value. unifies builtin, regex, and environment/api checks | ||||
|  * @param {*} value - the value to check | ||||
|  * @param {string|array<values>} type - the type the value should be or a list of allowed values | ||||
|  * @returns {boolean} whether or not the value matches the type | ||||
|  */ | ||||
| export const is = async (value, type, { extension = '' } = {}) => { | ||||
|   extension = !value || !value.endsWith || value.endsWith(extension); | ||||
|   if (Array.isArray(type)) { | ||||
|     return type.includes(value); | ||||
|   } | ||||
|   switch (type) { | ||||
|     case 'array': | ||||
|       return Array.isArray(value); | ||||
|     case 'object': | ||||
|       return value && typeof value === 'object' && !Array.isArray(value); | ||||
|     case 'undefined': | ||||
|     case 'boolean': | ||||
|     case 'number': | ||||
|       return typeof value === type && extension; | ||||
|     case 'string': | ||||
|       return typeof value === type && extension; | ||||
|     case 'alphanumeric': | ||||
|     case 'uuid': | ||||
|     case 'semver': | ||||
|     case 'email': | ||||
|     case 'url': | ||||
|     case 'color': | ||||
|       return typeof value === 'string' && test(value, patterns[type]) && extension; | ||||
|     case 'file': | ||||
|       return typeof value === 'string' && value && (await fs.isFile(value)) && extension; | ||||
|   } | ||||
|   return false; | ||||
| }; | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) | ||||
|  * (c) 2021 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill) | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (c) 2021 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
| @ -12,11 +12,9 @@ | ||||
|  * @module notion-enhancer/api/components/corner-action | ||||
|  */ | ||||
| 
 | ||||
| import { web } from '../../index.mjs'; | ||||
| import { web } from '../index.mjs'; | ||||
| 
 | ||||
| web.loadStylesheet('api/client/components/corner-action.css'); | ||||
| 
 | ||||
| const $cornerButtonsContainer = web.html`<div id="enhancer--corner-actions"></div>`; | ||||
| let $stylesheet, $cornerButtonsContainer; | ||||
| 
 | ||||
| /** | ||||
|  * adds a button to notion's bottom right corner | ||||
| @ -25,6 +23,11 @@ const $cornerButtonsContainer = web.html`<div id="enhancer--corner-actions"></di | ||||
|  * @returns {Element} the appended corner action element | ||||
|  */ | ||||
| export const addCornerAction = async (icon, listener) => { | ||||
|   if (!$stylesheet) { | ||||
|     $stylesheet = web.loadStylesheet('api/components/corner-action.css'); | ||||
|     $cornerButtonsContainer = web.html`<div id="enhancer--corner-actions"></div>`; | ||||
|   } | ||||
| 
 | ||||
|   await web.whenReady(['.notion-help-button']); | ||||
|   const $helpButton = document.querySelector('.notion-help-button'), | ||||
|     $onboardingButton = document.querySelector('.onboarding-checklist-button'); | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,7 +11,7 @@ | ||||
|  * @module notion-enhancer/api/components/feather | ||||
|  */ | ||||
| 
 | ||||
| import { fs, web } from '../../index.mjs'; | ||||
| import { fs, web } from '../index.mjs'; | ||||
| 
 | ||||
| let _$iconSheet; | ||||
| 
 | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -23,7 +23,7 @@ | ||||
|  * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped | ||||
|  * to, used to position and size the tooltip correctly (default: 1) | ||||
|  */ | ||||
| export { tooltip } from './tooltip.mjs'; | ||||
| export { addTooltip } from './tooltip.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * generate an icon from the feather icons set | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) | ||||
|  * (c) 2021 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill) | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (c) 2021 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
| @ -12,37 +12,43 @@ | ||||
|  * @module notion-enhancer/api/components/side-panel | ||||
|  */ | ||||
| 
 | ||||
| import { fmt, web, components, registry } from '../../index.mjs'; | ||||
| 
 | ||||
| web.loadStylesheet('api/client/components/panel.css'); | ||||
| import { web, components, registry } from '../index.mjs'; | ||||
| 
 | ||||
| const _views = [], | ||||
|   svgExpand = web.raw`<svg viewBox="-1 -1 9 11">
 | ||||
|   <path d="M 3.5 0L 3.98809 -0.569442L 3.5 -0.987808L 3.01191 -0.569442L 3.5 0ZM 3.5 9L 3.01191 | ||||
|     9.56944L 3.5 9.98781L 3.98809 9.56944L 3.5 9ZM 0.488094 3.56944L 3.98809 0.569442L 3.01191 | ||||
|     -0.569442L -0.488094 2.43056L 0.488094 3.56944ZM 3.01191 0.569442L 6.51191 3.56944L 7.48809 | ||||
|     2.43056L 3.98809 -0.569442L 3.01191 0.569442ZM -0.488094 6.56944L 3.01191 9.56944L 3.98809 | ||||
|     8.43056L 0.488094 5.43056L -0.488094 6.56944ZM 3.98809 9.56944L 7.48809 6.56944L 6.51191 | ||||
|     5.43056L 3.01191 8.43056L 3.98809 9.56944Z"></path> | ||||
| </svg>`; | ||||
|     <path d="M 3.5 0L 3.98809 -0.569442L 3.5 -0.987808L 3.01191 -0.569442L 3.5 0ZM 3.5 9L 3.01191 | ||||
|       9.56944L 3.5 9.98781L 3.98809 9.56944L 3.5 9ZM 0.488094 3.56944L 3.98809 0.569442L 3.01191 | ||||
|       -0.569442L -0.488094 2.43056L 0.488094 3.56944ZM 3.01191 0.569442L 6.51191 3.56944L 7.48809 | ||||
|       2.43056L 3.98809 -0.569442L 3.01191 0.569442ZM -0.488094 6.56944L 3.01191 9.56944L 3.98809 | ||||
|       8.43056L 0.488094 5.43056L -0.488094 6.56944ZM 3.98809 9.56944L 7.48809 6.56944L 6.51191 | ||||
|       5.43056L 3.01191 8.43056L 3.98809 9.56944Z"></path> | ||||
|   </svg>`; | ||||
| 
 | ||||
| let db, | ||||
| let $stylesheet, | ||||
|   db, | ||||
|   // open + close
 | ||||
|   $notionFrame, | ||||
|   $notionRightSidebar, | ||||
|   $panel, | ||||
|   $hoverTrigger, | ||||
|   // resize
 | ||||
|   $resizeHandle, | ||||
|   dragStartX, | ||||
|   dragStartWidth, | ||||
|   dragEventsFired, | ||||
|   panelWidth, | ||||
|   // render content
 | ||||
|   $notionApp, | ||||
|   $pinnedToggle; | ||||
|   $pinnedToggle, | ||||
|   $panelTitle, | ||||
|   $header, | ||||
|   $panelContent, | ||||
|   $switcher, | ||||
|   $switcherTrigger, | ||||
|   $switcherOverlayContainer; | ||||
| 
 | ||||
| // open + close
 | ||||
| const $panel = web.html`<div id="enhancer--panel"></div>`, | ||||
|   $hoverTrigger = web.html`<div id="enhancer--panel-hover-trigger"></div>`, | ||||
|   panelPinnedAttr = 'data-enhancer-panel-pinned', | ||||
| const panelPinnedAttr = 'data-enhancer-panel-pinned', | ||||
|   isPinned = () => $panel.hasAttribute(panelPinnedAttr), | ||||
|   togglePanel = () => { | ||||
|     const $elems = [$notionFrame, $notionRightSidebar, $hoverTrigger, $panel].filter( | ||||
| @ -57,7 +63,6 @@ const $panel = web.html`<div id="enhancer--panel"></div>`, | ||||
|     db.set(['panel.pinned'], isPinned()); | ||||
|   }, | ||||
|   // resize
 | ||||
|   $resizeHandle = web.html`<div id="enhancer--panel-resize"><div></div></div>`, | ||||
|   updateWidth = async () => { | ||||
|     document.documentElement.style.setProperty('--component--panel-width', panelWidth + 'px'); | ||||
|     db.set(['panel.width'], panelWidth); | ||||
| @ -91,14 +96,6 @@ const $panel = web.html`<div id="enhancer--panel"></div>`, | ||||
|     document.body.addEventListener('mouseup', resizeEnd); | ||||
|   }, | ||||
|   // render content
 | ||||
|   $panelTitle = web.html`<div id="enhancer--panel-header-title"></div>`, | ||||
|   $header = web.render(web.html`<div id="enhancer--panel-header"></div>`, $panelTitle), | ||||
|   $panelContent = web.html`<div id="enhancer--panel-content"></div>`, | ||||
|   $switcher = web.html`<div id="enhancer--panel-switcher"></div>`, | ||||
|   $switcherTrigger = web.html`<div id="enhancer--panel-header-switcher" tabindex="0">
 | ||||
|     ${svgExpand} | ||||
|   </div>`, | ||||
|   $switcherOverlayContainer = web.html`<div id="enhancer--panel-switcher-overlay-container"></div>`, | ||||
|   isSwitcherOpen = () => document.body.contains($switcher), | ||||
|   openSwitcher = () => { | ||||
|     if (!isPinned()) return togglePanel(); | ||||
| @ -186,6 +183,18 @@ async function createPanel() { | ||||
|   await web.whenReady(['.notion-frame']); | ||||
|   $notionFrame = document.querySelector('.notion-frame'); | ||||
| 
 | ||||
|   $panel = web.html`<div id="enhancer--panel"></div>`; | ||||
|   $hoverTrigger = web.html`<div id="enhancer--panel-hover-trigger"></div>`; | ||||
|   $resizeHandle = web.html`<div id="enhancer--panel-resize"><div></div></div>`; | ||||
|   $panelTitle = web.html`<div id="enhancer--panel-header-title"></div>`; | ||||
|   $header = web.render(web.html`<div id="enhancer--panel-header"></div>`, $panelTitle); | ||||
|   $panelContent = web.html`<div id="enhancer--panel-content"></div>`; | ||||
|   $switcher = web.html`<div id="enhancer--panel-switcher"></div>`; | ||||
|   $switcherTrigger = web.html`<div id="enhancer--panel-header-switcher" tabindex="0">
 | ||||
|     ${svgExpand} | ||||
|   </div>`; | ||||
|   $switcherOverlayContainer = web.html`<div id="enhancer--panel-switcher-overlay-container"></div>`; | ||||
| 
 | ||||
|   const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]', | ||||
|     detectRightSidebar = () => { | ||||
|       if (!document.contains($notionRightSidebar)) { | ||||
| @ -256,6 +265,10 @@ export const addPanelView = async ({ | ||||
|   onFocus = () => {}, | ||||
|   onBlur = () => {}, | ||||
| }) => { | ||||
|   if (!$stylesheet) { | ||||
|     $stylesheet = web.loadStylesheet('api/components/panel.css'); | ||||
|   } | ||||
| 
 | ||||
|   if (!db) db = await registry.db('36a2ffc9-27ff-480e-84a7-c7700a7d232d'); | ||||
|   if (!$pinnedToggle) { | ||||
|     $pinnedToggle = web.html`<div id="enhancer--panel-header-toggle" tabindex="0"><div>
 | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license | ||||
|  */ | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: components | ||||
| /** | ||||
|  * notion-enhancer: components | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,11 +11,9 @@ | ||||
|  * @module notion-enhancer/api/components/tooltip | ||||
|  */ | ||||
| 
 | ||||
| import { fmt, web } from '../../index.mjs'; | ||||
| import { fs, web } from '../index.mjs'; | ||||
| 
 | ||||
| const _$tooltip = web.html`<div id="enhancer--tooltip"></div>`; | ||||
| web.loadStylesheet('api/client/components/tooltip.css'); | ||||
| web.render(document.body, _$tooltip); | ||||
| let $stylesheet, _$tooltip; | ||||
| 
 | ||||
| const countLines = ($el) => | ||||
|     [...$el.getClientRects()].reduce( | ||||
| @ -71,16 +69,25 @@ const countLines = ($el) => | ||||
|  * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped | ||||
|  * to, used to position and size the tooltip correctly (default: 1) | ||||
|  */ | ||||
| export const tooltip = ( | ||||
| export const addTooltip = async ( | ||||
|   $ref, | ||||
|   $content, | ||||
|   { delay = 100, offsetDirection = 'bottom', maxLines = 1 } = {} | ||||
| ) => { | ||||
|   if (!$stylesheet) { | ||||
|     $stylesheet = web.loadStylesheet('api/components/tooltip.css'); | ||||
|     _$tooltip = web.html`<div id="enhancer--tooltip"></div>`; | ||||
|     web.render(document.body, _$tooltip); | ||||
|   } | ||||
| 
 | ||||
|   if (!globalThis.markdownit) await import(fs.localPath('dep/markdown-it.min.js')); | ||||
|   const md = markdownit({ linkify: true }); | ||||
| 
 | ||||
|   if (!($content instanceof Element)) | ||||
|     $content = web.html`<div style="display:inline">
 | ||||
|       ${$content | ||||
|         .split('\n') | ||||
|         .map((text) => fmt.md.renderInline(text)) | ||||
|         .map((text) => md.renderInline(text)) | ||||
|         .join('<br>')} | ||||
|     </div>`; | ||||
| 
 | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -15,34 +15,65 @@ | ||||
|  * access to the electron BrowserWindow instance for the current window | ||||
|  * see https://www.electronjs.org/docs/latest/api/browser-window
 | ||||
|  * @type {BrowserWindow} | ||||
|  * | ||||
|  * @env win32 | ||||
|  * @env linux | ||||
|  * @env darwin | ||||
|  * @runtime client | ||||
|  * @runtime menu | ||||
|  */ | ||||
| export const browser = window.__enhancerElectronApi?.browser; | ||||
| export const browser = globalThis.__enhancerElectronApi?.browser; | ||||
| 
 | ||||
| /** | ||||
|  * access to the electron webFrame instance for the current page | ||||
|  * see https://www.electronjs.org/docs/latest/api/web-frame
 | ||||
|  * @type {webFrame} | ||||
|  * | ||||
|  * @env win32 | ||||
|  * @env linux | ||||
|  * @env darwin | ||||
|  * @runtime client | ||||
|  * @runtime menu | ||||
|  */ | ||||
| export const webFrame = window.__enhancerElectronApi?.webFrame; | ||||
| export const webFrame = globalThis.__enhancerElectronApi?.webFrame; | ||||
| 
 | ||||
| /** | ||||
|  * send a message to the main electron process | ||||
|  * @param {string} channel - the message identifier | ||||
|  * @param {any} data - the data to pass along with the message | ||||
|  * | ||||
|  * @env win32 | ||||
|  * @env linux | ||||
|  * @env darwin | ||||
|  * @runtime client | ||||
|  * @runtime menu | ||||
|  */ | ||||
| export const sendMessage = window.__enhancerElectronApi?.sendMessage; | ||||
| export const sendMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessage; | ||||
| 
 | ||||
| /** | ||||
|  * send a message to the webview's parent renderer process | ||||
|  * @param {string} channel - the message identifier | ||||
|  * @param {any} data - the data to pass along with the message | ||||
|  * | ||||
|  * @env win32 | ||||
|  * @env linux | ||||
|  * @env darwin | ||||
|  * @runtime client | ||||
|  * @runtime menu | ||||
|  */ | ||||
| export const sendMessageToHost = window.__enhancerElectronApi?.sendMessageToHost; | ||||
| export const sendMessageToHost = | ||||
|   globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessageToHost; | ||||
| 
 | ||||
| /** | ||||
|  * receive a message from either the main process or | ||||
|  * the webview's parent renderer process | ||||
|  * @param {string} channel - the message identifier to listen for | ||||
|  * @param {function} listener - the message handler, passed the args (event, data) | ||||
|  * | ||||
|  * @env win32 | ||||
|  * @env linux | ||||
|  * @env darwin | ||||
|  * @runtime client | ||||
|  * @runtime menu | ||||
|  */ | ||||
| export const onMessage = window.__enhancerElectronApi?.onMessage; | ||||
| export const onMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.onMessage; | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,7 +11,7 @@ | ||||
|  * @module notion-enhancer/api/env | ||||
|  */ | ||||
| 
 | ||||
| import * as env from '../../env/env.mjs'; | ||||
| import * as env from '../env/env.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * the environment/platform name code is currently being executed in | ||||
| @ -44,3 +44,14 @@ export const focusNotion = env.focusNotion; | ||||
|  * @type {function} | ||||
|  */ | ||||
| export const reload = env.reload; | ||||
| 
 | ||||
| /** | ||||
|  * require() notion app files | ||||
|  * @param {string} path - within notion/resources/app/ e.g. main/createWindow.js | ||||
|  * | ||||
|  * @env win32 | ||||
|  * @env linux | ||||
|  * @env darwin | ||||
|  * @runtime electron | ||||
|  */ | ||||
| export const notionRequire = env.notionRequire; | ||||
| @ -1,17 +1,18 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| module.exports = {}; | ||||
| 
 | ||||
| /** | ||||
|  * helpers for formatting or parsing text | ||||
|  * @module notion-enhancer/api/fmt | ||||
|  */ | ||||
| 
 | ||||
| import { fs } from './index.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), | ||||
|  * e.g. for use as an anchor id | ||||
| @ -19,7 +20,7 @@ module.exports = {}; | ||||
|  * @param {Set<string>} [slugs] - a list of pre-generated slugs to avoid duplicates | ||||
|  * @returns {string} the generated slug | ||||
|  */ | ||||
| module.exports.slugger = (heading, slugs = new Set()) => { | ||||
| export const slugger = (heading, slugs = new Set()) => { | ||||
|   heading = heading | ||||
|     .replace(/\s/g, '-') | ||||
|     .replace(/[^A-Za-z0-9-_]/g, '') | ||||
| @ -38,7 +39,7 @@ module.exports.slugger = (heading, slugs = new Set()) => { | ||||
|  * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid)
 | ||||
|  * @returns {string} a uuidv4 | ||||
|  */ | ||||
| module.exports.uuidv4 = () => { | ||||
| export const uuidv4 = () => { | ||||
|   if (crypto?.randomUUID) return crypto.randomUUID(); | ||||
|   return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => | ||||
|     (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) | ||||
| @ -53,7 +54,7 @@ module.exports.uuidv4 = () => { | ||||
|  * @param {string} c - the rgb color | ||||
|  * @returns {string} the shaded color | ||||
|  */ | ||||
| module.exports.rgbLogShade = (p, c) => { | ||||
| export const rgbLogShade = (p, c) => { | ||||
|   var i = parseInt, | ||||
|     r = Math.round, | ||||
|     [a, b, c, d] = c.split(','), | ||||
| @ -80,7 +81,7 @@ module.exports.rgbLogShade = (p, c) => { | ||||
|  * @param {number} b - blue (0-255) | ||||
|  * @returns {string} the contrasting rgb color, white or black | ||||
|  */ | ||||
| module.exports.rgbContrast = (r, g, b) => { | ||||
| export const rgbContrast = (r, g, b) => { | ||||
|   return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 | ||||
|     ? 'rgb(0,0,0)' | ||||
|     : 'rgb(255,255,255)'; | ||||
| @ -107,7 +108,7 @@ function test(str, pattern) { | ||||
|  * @param {string|array<values>} type - the type the value should be or a list of allowed values | ||||
|  * @returns {boolean} whether or not the value matches the type | ||||
|  */ | ||||
| module.exports.is = async (value, type, { extension = '' } = {}) => { | ||||
| export const is = async (value, type, { extension = '' } = {}) => { | ||||
|   extension = !value || !value.endsWith || value.endsWith(extension); | ||||
|   if (Array.isArray(type)) { | ||||
|     return type.includes(value); | ||||
| @ -130,10 +131,8 @@ module.exports.is = async (value, type, { extension = '' } = {}) => { | ||||
|     case 'url': | ||||
|     case 'color': | ||||
|       return typeof value === 'string' && test(value, patterns[type]) && extension; | ||||
|     case 'file': { | ||||
|       const { isFile } = require('notion-enhancer/api/node/fs.cjs'); | ||||
|       return typeof value === 'string' && value && (await isFile(value)) && extension; | ||||
|     } | ||||
|     case 'file': | ||||
|       return typeof value === 'string' && value && (await fs.isFile(value)) && extension; | ||||
|   } | ||||
|   return false; | ||||
| }; | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,7 +11,7 @@ | ||||
|  * @module notion-enhancer/api/fs | ||||
|  */ | ||||
| 
 | ||||
| import * as fs from '../../env/fs.mjs'; | ||||
| import * as fs from '../env/fs.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * transform a path relative to the enhancer root directory into an absolute path | ||||
							
								
								
									
										1062
									
								
								api/index.cjs
									
									
									
									
									
								
							
							
						
						
									
										1062
									
								
								api/index.cjs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -8,23 +8,26 @@ | ||||
| 
 | ||||
| /** @module notion-enhancer/api */ | ||||
| 
 | ||||
| // compiles to .cjs for use in electron:
 | ||||
| // npx -y esbuild insert/api/index.mjs --bundle --format=cjs --outfile=insert/api/index.cjs
 | ||||
| 
 | ||||
| /** environment-specific methods and constants */ | ||||
| export * as env from './client/env.mjs'; | ||||
| export * as env from './env.mjs'; | ||||
| /** environment-specific file reading */ | ||||
| export * as fs from './client/fs.mjs'; | ||||
| export * as fs from './fs.mjs'; | ||||
| /** environment-specific data persistence */ | ||||
| export * as storage from './client/storage.mjs'; | ||||
| export * as storage from './storage.mjs'; | ||||
| 
 | ||||
| /** access to electron renderer apis */ | ||||
| export * as electron from './client/electron.mjs'; | ||||
| export * as electron from './electron.mjs'; | ||||
| 
 | ||||
| /** a basic wrapper around notion's unofficial api */ | ||||
| export * as notion from './client/notion.mjs'; | ||||
| // export * as notion from './notion.mjs';
 | ||||
| /** helpers for formatting, validating and parsing values */ | ||||
| export * as fmt from './client/fmt.mjs'; | ||||
| export * as fmt from './fmt.mjs'; | ||||
| /** interactions with the enhancer's repository of mods */ | ||||
| export * as registry from './client/registry.mjs'; | ||||
| export * as registry from './registry.mjs'; | ||||
| /** helpers for manipulation of a webpage */ | ||||
| export * as web from './client/web.mjs'; | ||||
| export * as web from './web.mjs'; | ||||
| /** shared notion-style elements */ | ||||
| export * as components from './client/components/index.mjs'; | ||||
| export * as components from './components/index.mjs'; | ||||
|  | ||||
| @ -1,53 +0,0 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| module.exports = {}; | ||||
| 
 | ||||
| /** | ||||
|  * environment-specific methods and constants | ||||
|  * @module notion-enhancer/api/env | ||||
|  */ | ||||
| 
 | ||||
| const env = require('notion-enhancer/env/env.cjs'); | ||||
| 
 | ||||
| /** | ||||
|  * the environment/platform name code is currently being executed in | ||||
|  * @constant | ||||
|  * @type {string} | ||||
|  */ | ||||
| module.exports.name = env.name; | ||||
| 
 | ||||
| /** | ||||
|  * the current version of the enhancer | ||||
|  * @constant | ||||
|  * @type {string} | ||||
|  */ | ||||
| module.exports.version = env.version; | ||||
| 
 | ||||
| /** | ||||
|  * open the enhancer's menu | ||||
|  * @type {function} | ||||
|  */ | ||||
| module.exports.focusMenu = env.focusMenu; | ||||
| 
 | ||||
| /** | ||||
|  * focus an active notion tab | ||||
|  * @type {function} | ||||
|  */ | ||||
| module.exports.focusNotion = env.focusNotion; | ||||
| 
 | ||||
| /** | ||||
|  * reload all notion and enhancer menu tabs to apply changes | ||||
|  * @type {function} | ||||
|  */ | ||||
| module.exports.reload = env.reload; | ||||
| 
 | ||||
| /** | ||||
|  * require() notion app files | ||||
|  * @param {string} path - path from the root of notion/resources/app/ e.g. main/createWindow.js | ||||
|  */ | ||||
| module.exports.notionRequire = env.notionRequire; | ||||
| @ -1,49 +0,0 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| module.exports = {}; | ||||
| 
 | ||||
| /** | ||||
|  * environment-specific file reading | ||||
|  * @module notion-enhancer/api/fs | ||||
|  */ | ||||
| 
 | ||||
| const fs = require('notion-enhancer/env/fs.cjs'); | ||||
| 
 | ||||
| /** | ||||
|  * transform a path relative to the enhancer root directory into an absolute path | ||||
|  * @type {function} | ||||
|  * @param {string} path - a url or within-the-enhancer filepath | ||||
|  * @returns {string} an absolute filepath | ||||
|  */ | ||||
| module.exports.localPath = fs.localPath; | ||||
| 
 | ||||
| /** | ||||
|  * fetch and parse a json file's contents | ||||
|  * @type {function} | ||||
|  * @param {string} path - a url or within-the-enhancer filepath | ||||
|  * @param {object} [opts] - the second argument of a fetch() request | ||||
|  * @returns {object} the json value of the requested file as a js object | ||||
|  */ | ||||
| module.exports.getJSON = fs.getJSON; | ||||
| 
 | ||||
| /** | ||||
|  * fetch a text file's contents | ||||
|  * @type {function} | ||||
|  * @param {string} path - a url or within-the-enhancer filepath | ||||
|  * @param {object} [opts] - the second argument of a fetch() request | ||||
|  * @returns {string} the text content of the requested file | ||||
|  */ | ||||
| module.exports.getText = fs.getText; | ||||
| 
 | ||||
| /** | ||||
|  * check if a file exists | ||||
|  * @type {function} | ||||
|  * @param {string} path - a url or within-the-enhancer filepath | ||||
|  * @returns {boolean} whether or not the file exists | ||||
|  */ | ||||
| module.exports.isFile = fs.isFile; | ||||
| @ -1,223 +0,0 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| const check = async ( | ||||
|   mod, | ||||
|   key, | ||||
|   value, | ||||
|   types, | ||||
|   { | ||||
|     extension = '', | ||||
|     error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify( | ||||
|       value | ||||
|     )}`,
 | ||||
|     optional = false, | ||||
|   } = {} | ||||
| ) => { | ||||
|   const { is } = require('notion-enhancer/api/node/fmt.cjs'); | ||||
|   let test; | ||||
|   for (const type of Array.isArray(types) ? [types] : types.split('|')) { | ||||
|     if (type === 'file') { | ||||
|       test = | ||||
|         value && !value.startsWith('http') | ||||
|           ? await is(`repo/${mod._dir}/${value}`, type, { extension }) | ||||
|           : false; | ||||
|     } else test = await is(value, type, { extension }); | ||||
|     if (test) break; | ||||
|   } | ||||
|   if (!test) { | ||||
|     if (optional && (await is(value, 'undefined'))) return true; | ||||
|     if (error) mod._err(error); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| }; | ||||
| 
 | ||||
| const validateEnvironments = async (mod) => { | ||||
|     const { supportedEnvs } = require('notion-enhancer/api/node/registry.cjs'); | ||||
|     mod.environments = mod.environments ?? supportedEnvs; | ||||
|     const isArray = await check(mod, 'environments', mod.environments, 'array'); | ||||
|     if (!isArray) return false; | ||||
|     return mod.environments.map((tag) => check(mod, 'environments.env', tag, supportedEnvs)); | ||||
|   }, | ||||
|   validateTags = async (mod) => { | ||||
|     const isArray = await check(mod, 'tags', mod.tags, 'array'); | ||||
|     if (!isArray) return false; | ||||
|     const categoryTags = ['core', 'extension', 'theme', 'integration'], | ||||
|       containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; | ||||
|     if (!containsCategory) { | ||||
|       mod._err( | ||||
|         `invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'):
 | ||||
|         ${JSON.stringify(mod.tags)}` | ||||
|       ); | ||||
|       return false; | ||||
|     } | ||||
|     const isTheme = mod.tags.includes('theme'), | ||||
|       hasThemeMode = mod.tags.includes('light') || mod.tags.includes('dark'), | ||||
|       isBothThemeModes = mod.tags.includes('light') && mod.tags.includes('dark'); | ||||
|     if (isTheme && (!hasThemeMode || isBothThemeModes)) { | ||||
|       mod._err( | ||||
|         `invalid tags (themes must be either 'light' or 'dark', not neither or both):
 | ||||
|         ${JSON.stringify(mod.tags)}` | ||||
|       ); | ||||
|       return false; | ||||
|     } | ||||
|     return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); | ||||
|   }, | ||||
|   validateAuthors = async (mod) => { | ||||
|     const isArray = await check(mod, 'authors', mod.authors, 'array'); | ||||
|     if (!isArray) return false; | ||||
|     return mod.authors.map((author) => [ | ||||
|       check(mod, 'authors.author.name', author.name, 'string'), | ||||
|       check(mod, 'authors.author.email', author.email, 'email', { optional: true }), | ||||
|       check(mod, 'authors.author.homepage', author.homepage, 'url'), | ||||
|       check(mod, 'authors.author.avatar', author.avatar, 'url'), | ||||
|     ]); | ||||
|   }, | ||||
|   validateCSS = async (mod) => { | ||||
|     const isArray = await check(mod, 'css', mod.css, 'object'); | ||||
|     if (!isArray) return false; | ||||
|     const tests = []; | ||||
|     for (let dest of ['frame', 'client', 'menu']) { | ||||
|       if (!mod.css[dest]) continue; | ||||
|       let test = await check(mod, `css.${dest}`, mod.css[dest], 'array'); | ||||
|       if (test) { | ||||
|         test = mod.css[dest].map((file) => | ||||
|           check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) | ||||
|         ); | ||||
|       } | ||||
|       tests.push(test); | ||||
|     } | ||||
|     return tests; | ||||
|   }, | ||||
|   validateJS = async (mod) => { | ||||
|     const isArray = await check(mod, 'js', mod.js, 'object'); | ||||
|     if (!isArray) return false; | ||||
|     const tests = []; | ||||
|     for (let dest of ['frame', 'client', 'menu']) { | ||||
|       if (!mod.js[dest]) continue; | ||||
|       let test = await check(mod, `js.${dest}`, mod.js[dest], 'array'); | ||||
|       if (test) { | ||||
|         test = mod.js[dest].map((file) => | ||||
|           check(mod, `js.${dest}.file`, file, 'file', { extension: '.mjs' }) | ||||
|         ); | ||||
|       } | ||||
|       tests.push(test); | ||||
|     } | ||||
|     if (mod.js.electron) { | ||||
|       const isArray = await check(mod, 'js.electron', mod.js.electron, 'array'); | ||||
|       if (isArray) { | ||||
|         for (const file of mod.js.electron) { | ||||
|           const isObject = await check(mod, 'js.electron.file', file, 'object'); | ||||
|           if (!isObject) { | ||||
|             tests.push(false); | ||||
|             continue; | ||||
|           } | ||||
|           tests.push([ | ||||
|             check(mod, 'js.electron.file.source', file.source, 'file', { | ||||
|               extension: '.cjs', | ||||
|             }), | ||||
|             // referencing the file within the electron app
 | ||||
|             // existence can't be validated, so only format is
 | ||||
|             check(mod, 'js.electron.file.target', file.target, 'string', { | ||||
|               extension: '.js', | ||||
|             }), | ||||
|           ]); | ||||
|         } | ||||
|       } else tests.push(false); | ||||
|     } | ||||
|     return tests; | ||||
|   }, | ||||
|   validateOptions = async (mod) => { | ||||
|     const { supportedEnvs, optionTypes } = require('notion-enhancer/api/node/registry.cjs'), | ||||
|       isArray = await check(mod, 'options', mod.options, 'array'); | ||||
|     if (!isArray) return false; | ||||
|     const tests = []; | ||||
|     for (const option of mod.options) { | ||||
|       const key = 'options.option', | ||||
|         optTypeValid = await check(mod, `${key}.type`, option.type, optionTypes); | ||||
|       if (!optTypeValid) { | ||||
|         tests.push(false); | ||||
|         continue; | ||||
|       } | ||||
|       option.environments = option.environments ?? supportedEnvs; | ||||
|       tests.push([ | ||||
|         check(mod, `${key}.key`, option.key, 'alphanumeric'), | ||||
|         check(mod, `${key}.label`, option.label, 'string'), | ||||
|         check(mod, `${key}.tooltip`, option.tooltip, 'string', { | ||||
|           optional: true, | ||||
|         }), | ||||
|         check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => { | ||||
|           if (!isArray) return false; | ||||
|           return option.environments.map((environment) => | ||||
|             check(mod, `${key}.environments.env`, environment, supportedEnvs) | ||||
|           ); | ||||
|         }), | ||||
|       ]); | ||||
|       switch (option.type) { | ||||
|         case 'toggle': | ||||
|           tests.push(check(mod, `${key}.value`, option.value, 'boolean')); | ||||
|           break; | ||||
|         case 'select': { | ||||
|           let test = await check(mod, `${key}.values`, option.values, 'array'); | ||||
|           if (test) { | ||||
|             test = option.values.map((value) => | ||||
|               check(mod, `${key}.values.value`, value, 'string') | ||||
|             ); | ||||
|           } | ||||
|           tests.push(test); | ||||
|           break; | ||||
|         } | ||||
|         case 'text': | ||||
|         case 'hotkey': | ||||
|           tests.push(check(mod, `${key}.value`, option.value, 'string')); | ||||
|           break; | ||||
|         case 'number': | ||||
|         case 'color': | ||||
|           tests.push(check(mod, `${key}.value`, option.value, option.type)); | ||||
|           break; | ||||
|         case 'file': { | ||||
|           let test = await check(mod, `${key}.extensions`, option.extensions, 'array'); | ||||
|           if (test) { | ||||
|             test = option.extensions.map((ext) => | ||||
|               check(mod, `${key}.extensions.extension`, ext, 'string') | ||||
|             ); | ||||
|           } | ||||
|           tests.push(test); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return tests; | ||||
|   }; | ||||
| 
 | ||||
| /** | ||||
|  * internally used to validate mod.json files and provide helpful errors | ||||
|  * @private | ||||
|  * @param {object} mod - a mod's mod.json in object form | ||||
|  * @returns {boolean} whether or not the mod has passed validation | ||||
|  */ | ||||
| module.exports.validate = async function (mod) { | ||||
|   let conditions = [ | ||||
|     check(mod, 'name', mod.name, 'string'), | ||||
|     check(mod, 'id', mod.id, 'uuid'), | ||||
|     check(mod, 'version', mod.version, 'semver'), | ||||
|     validateEnvironments(mod), | ||||
|     check(mod, 'description', mod.description, 'string'), | ||||
|     check(mod, 'preview', mod.preview, 'file|url', { optional: true }), | ||||
|     validateTags(mod), | ||||
|     validateAuthors(mod), | ||||
|     validateCSS(mod), | ||||
|     validateJS(mod), | ||||
|     validateOptions(mod), | ||||
|   ]; | ||||
|   do { | ||||
|     conditions = await Promise.all(conditions.flat(Infinity)); | ||||
|   } while (conditions.some((condition) => Array.isArray(condition))); | ||||
|   return conditions.every((passed) => passed); | ||||
| }; | ||||
| @ -1,166 +0,0 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| /** | ||||
|  * interactions with the enhancer's repository of mods | ||||
|  * @module notion-enhancer/api/registry | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * mod ids whitelisted as part of the enhancer's core, permanently enabled | ||||
|  * @constant | ||||
|  * @type {array<string>} | ||||
|  */ | ||||
| module.exports.core = [ | ||||
|   'a6621988-551d-495a-97d8-3c568bca2e9e', | ||||
|   '0f0bf8b6-eae6-4273-b307-8fc43f2ee082', | ||||
|   '36a2ffc9-27ff-480e-84a7-c7700a7d232d', | ||||
| ]; | ||||
| 
 | ||||
| /** | ||||
|  * all environments/platforms currently supported by the enhancer | ||||
|  * @constant | ||||
|  * @type {array<string>} | ||||
|  */ | ||||
| module.exports.supportedEnvs = ['linux', 'win32', 'darwin', 'extension']; | ||||
| 
 | ||||
| /** | ||||
|  * all available configuration types | ||||
|  * @constant | ||||
|  * @type {array<string>} | ||||
|  */ | ||||
| module.exports.optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey']; | ||||
| 
 | ||||
| /** | ||||
|  * the name of the active configuration profile | ||||
|  * @returns {string} | ||||
|  */ | ||||
| module.exports.profileName = async () => { | ||||
|   const storage = require('notion-enhancer/api/node/storage.cjs'); | ||||
|   return storage.get(['currentprofile'], 'default'); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * the root database for the current profile | ||||
|  * @returns {object} the get/set functions for the profile's storage | ||||
|  */ | ||||
| module.exports.profileDB = async () => { | ||||
|   const storage = require('notion-enhancer/api/node/storage.cjs'); | ||||
|   return storage.db(['profiles', await module.exports.profileName()]); | ||||
| }; | ||||
| 
 | ||||
| let _list, | ||||
|   _errors = []; | ||||
| /** | ||||
|  * list all available mods in the repo | ||||
|  * @param {function} filter - a function to filter out mods | ||||
|  * @returns {array} a validated list of mod.json objects | ||||
|  */ | ||||
| module.exports.list = async (filter = (mod) => true) => { | ||||
|   if (!_list) { | ||||
|     const { validate } = require('notion-enhancer/api/node/registry-validation.cjs'), | ||||
|       { getJSON } = require('notion-enhancer/api/node/fs.cjs'); | ||||
|     _list = new Promise(async (res, rej) => { | ||||
|       const passed = []; | ||||
|       for (const dir of await getJSON('repo/registry.json')) { | ||||
|         try { | ||||
|           const mod = { | ||||
|             ...(await getJSON(`repo/${dir}/mod.json`)), | ||||
|             _dir: dir, | ||||
|             _err: (message) => _errors.push({ source: dir, message }), | ||||
|           }; | ||||
|           if (await validate(mod)) passed.push(mod); | ||||
|         } catch { | ||||
|           _errors.push({ source: dir, message: 'invalid mod.json' }); | ||||
|         } | ||||
|       } | ||||
|       res(passed); | ||||
|     }); | ||||
|   } | ||||
|   const filtered = []; | ||||
|   for (const mod of await _list) if (await filter(mod)) filtered.push(mod); | ||||
|   return filtered; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * list validation errors encountered when loading the repo | ||||
|  * @returns {array<object>} error objects with an error message and a source directory | ||||
|  */ | ||||
| module.exports.errors = async () => { | ||||
|   await module.exports.list(); | ||||
|   return _errors; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * get a single mod from the repo | ||||
|  * @param {string} id - the uuid of the mod | ||||
|  * @returns {object} the mod's mod.json | ||||
|  */ | ||||
| module.exports.get = async (id) => { | ||||
|   return (await module.exports.list((mod) => mod.id === id))[0]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * checks if a mod is enabled: affected by the core whitelist, | ||||
|  * environment and menu configuration | ||||
|  * @param {string} id - the uuid of the mod | ||||
|  * @returns {boolean} whether or not the mod is enabled | ||||
|  */ | ||||
| module.exports.enabled = async (id) => { | ||||
|   const env = require('notion-enhancer/api/node/env.cjs'), | ||||
|     mod = await module.exports.get(id); | ||||
|   if (!mod.environments.includes(env.name)) return false; | ||||
|   if (module.exports.core.includes(id)) return true; | ||||
|   return (await module.exports.profileDB()).get(['_mods', id], false); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * get a default value of a mod's option according to its mod.json | ||||
|  * @param {string} id - the uuid of the mod | ||||
|  * @param {string} key - the key of the option | ||||
|  * @returns {string|number|boolean|undefined} the option's default value | ||||
|  */ | ||||
| module.exports.optionDefault = async (id, key) => { | ||||
|   const mod = await module.exports.get(id), | ||||
|     opt = mod.options.find((opt) => opt.key === key); | ||||
|   if (!opt) return undefined; | ||||
|   switch (opt.type) { | ||||
|     case 'toggle': | ||||
|     case 'text': | ||||
|     case 'number': | ||||
|     case 'color': | ||||
|     case 'hotkey': | ||||
|       return opt.value; | ||||
|     case 'select': | ||||
|       return opt.values[0]; | ||||
|     case 'file': | ||||
|       return undefined; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * access the storage partition of a mod in the current profile | ||||
|  * @param {string} id - the uuid of the mod | ||||
|  * @returns {object} an object with the wrapped get/set functions | ||||
|  */ | ||||
| module.exports.db = async (id) => { | ||||
|   const storage = require('notion-enhancer/api/node/storage.cjs'), | ||||
|     db = await module.exports.profileDB(); | ||||
|   return storage.db( | ||||
|     [id], | ||||
|     async (path, fallback = undefined) => { | ||||
|       if (typeof path === 'string') path = [path]; | ||||
|       if (path.length === 2) { | ||||
|         // profiles -> profile -> mod -> option
 | ||||
|         fallback = (await module.exports.optionDefault(id, path[1])) ?? fallback; | ||||
|       } | ||||
|       return db.get(path, fallback); | ||||
|     }, | ||||
|     db.set | ||||
|   ); | ||||
| }; | ||||
| @ -1,66 +0,0 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| module.exports = {}; | ||||
| 
 | ||||
| /** | ||||
|  * environment-specific data persistence | ||||
|  * @module notion-enhancer/api/storage | ||||
|  */ | ||||
| 
 | ||||
| const storage = require('notion-enhancer/env/storage.cjs'); | ||||
| 
 | ||||
| /** | ||||
|  * get persisted data | ||||
|  * @type {function} | ||||
|  * @param {array<string>} path - the path of keys to the value being fetched | ||||
|  * @param {*} [fallback] - a default value if the path is not matched | ||||
|  * @returns {Promise} value ?? fallback | ||||
|  */ | ||||
| module.exports.get = storage.get; | ||||
| 
 | ||||
| /** | ||||
|  * persist data | ||||
|  * @type {function} | ||||
|  * @param {array<string>} path - the path of keys to the value being set | ||||
|  * @param {*} value - the data to save | ||||
|  * @returns {Promise} resolves when data has been saved | ||||
|  */ | ||||
| module.exports.set = storage.set; | ||||
| 
 | ||||
| /** | ||||
|  * create a wrapper for accessing a partition of the storage | ||||
|  * @type {function} | ||||
|  * @param {array<string>} namespace - the path of keys to prefix all storage requests with | ||||
|  * @param {function} [get] - the storage get function to be wrapped | ||||
|  * @param {function} [set] - the storage set function to be wrapped | ||||
|  * @returns {object} an object with the wrapped get/set functions | ||||
|  */ | ||||
| module.exports.db = storage.db; | ||||
| 
 | ||||
| /** | ||||
|  * add an event listener for changes in storage | ||||
|  * @type {function} | ||||
|  * @param {onStorageChangeCallback} callback - called whenever a change in | ||||
|  * storage is initiated from the current process | ||||
|  */ | ||||
| module.exports.addChangeListener = storage.addChangeListener; | ||||
| 
 | ||||
| /** | ||||
|  * remove a listener added with storage.addChangeListener | ||||
|  * @type {function} | ||||
|  * @param {onStorageChangeCallback} callback | ||||
|  */ | ||||
| module.exports.removeChangeListener = storage.removeChangeListener; | ||||
| 
 | ||||
| /** | ||||
|  * @callback onStorageChangeCallback | ||||
|  * @param {object} event | ||||
|  * @param {string} event.path- the path of keys to the changed value | ||||
|  * @param {string} [event.new] - the new value being persisted to the store | ||||
|  * @param {string} [event.old] - the previous value associated with the key | ||||
|  */ | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,7 +11,7 @@ | ||||
|  * @module notion-enhancer/api/notion | ||||
|  */ | ||||
| 
 | ||||
| import { web, fs, fmt } from '../index.mjs'; | ||||
| import { web, fs, fmt } from './index.mjs'; | ||||
| 
 | ||||
| const standardiseUUID = (uuid) => { | ||||
|   if (uuid?.length === 32 && !uuid.includes('-')) { | ||||
| @ -1,12 +1,12 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| import { fmt, registry } from '../index.mjs'; | ||||
| import { fmt, registry } from './index.mjs'; | ||||
| 
 | ||||
| const check = async ( | ||||
|   mod, | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,7 +11,7 @@ | ||||
|  * @module notion-enhancer/api/registry | ||||
|  */ | ||||
| 
 | ||||
| import { env, fs, storage } from '../index.mjs'; | ||||
| import { env, fs, storage } from './index.mjs'; | ||||
| import { validate } from './registry-validation.mjs'; | ||||
| 
 | ||||
| /** | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,7 +11,7 @@ | ||||
|  * @module notion-enhancer/api/storage | ||||
|  */ | ||||
| 
 | ||||
| import * as storage from '../../env/storage.mjs'; | ||||
| import * as storage from '../env/storage.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * get persisted data | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * notion-enhancer core: api | ||||
| /** | ||||
|  * notion-enhancer: api | ||||
|  * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||
|  * (https://notion-enhancer.github.io/) under the MIT license
 | ||||
|  */ | ||||
| @ -11,17 +11,14 @@ | ||||
|  * @module notion-enhancer/api/web | ||||
|  */ | ||||
| 
 | ||||
| import { fs } from '../index.mjs'; | ||||
| import '../../dep/jscolor.min.js'; | ||||
| import { fs } from './index.mjs'; | ||||
| 
 | ||||
| let _hotkeyEventListeners = [], | ||||
| let _hotkeyListenersActivated = false, | ||||
|   _hotkeyEventListeners = [], | ||||
|   _documentObserver, | ||||
|   _documentObserverListeners = [], | ||||
|   _documentObserverEvents = []; | ||||
| 
 | ||||
| /** color picker with alpha channel using https://jscolor.com/ */ | ||||
| export const jscolor = JSColor; | ||||
| 
 | ||||
| /** | ||||
|  * wait until a page is loaded and ready for modification | ||||
|  * @param {array} [selectors] - wait for the existence of elements that match these css selectors | ||||
| @ -133,14 +130,12 @@ export const empty = ($container) => { | ||||
|  * @param {string} path - a url or within-the-enhancer filepath | ||||
|  */ | ||||
| export const loadStylesheet = (path) => { | ||||
|   render( | ||||
|     document.head, | ||||
|     html`<link
 | ||||
|       rel="stylesheet" | ||||
|       href="${path.startsWith('https://') ? path : fs.localPath(path)}" | ||||
|     />` | ||||
|   ); | ||||
|   return true; | ||||
|   const $stylesheet = html`<link
 | ||||
|     rel="stylesheet" | ||||
|     href="${path.startsWith('https://') ? path : fs.localPath(path)}" | ||||
|   />`; | ||||
|   render(document.head, $stylesheet); | ||||
|   return $stylesheet; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
| @ -193,16 +188,6 @@ const triggerHotkeyListener = (event, hotkey) => { | ||||
|   }); | ||||
|   if (pressed) hotkey.callback(event); | ||||
| }; | ||||
| document.addEventListener('keyup', (event) => { | ||||
|   for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => !keydown)) { | ||||
|     triggerHotkeyListener(event, hotkey); | ||||
|   } | ||||
| }); | ||||
| document.addEventListener('keydown', (event) => { | ||||
|   for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => keydown)) { | ||||
|     triggerHotkeyListener(event, hotkey); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * register a hotkey listener to the page | ||||
| @ -224,6 +209,20 @@ export const addHotkeyListener = ( | ||||
| ) => { | ||||
|   if (typeof keys === 'string') keys = keys.split('+'); | ||||
|   _hotkeyEventListeners.push({ keys, callback, listenInInput, keydown }); | ||||
| 
 | ||||
|   if (!_hotkeyListenersActivated) { | ||||
|     _hotkeyListenersActivated = true; | ||||
|     document.addEventListener('keyup', (event) => { | ||||
|       for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => !keydown)) { | ||||
|         triggerHotkeyListener(event, hotkey); | ||||
|       } | ||||
|     }); | ||||
|     document.addEventListener('keydown', (event) => { | ||||
|       for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => keydown)) { | ||||
|         triggerHotkeyListener(event, hotkey); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| /** | ||||
|  * remove a listener added with web.addHotkeyListener | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user