From 1f717b98ca213b7730297b5a2a53eaf46a23b08e Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 17 Feb 2024 00:27:35 +1100 Subject: [PATCH] chore: simplify api imports --- src/{common/markup.mjs => api/interface.mjs} | 204 +++++++++++++------ src/{common => api}/notion.js | 0 src/{common => api}/registry.js | 2 +- src/api/state.js | 70 +++++++ src/{common => api}/system.js | 9 +- src/common/events.js | 140 ------------- src/core/menu/islands/View.mjs | 4 +- src/init.js | 6 +- src/load.mjs | 9 +- 9 files changed, 224 insertions(+), 220 deletions(-) rename src/{common/markup.mjs => api/interface.mjs} (75%) rename src/{common => api}/notion.js (100%) rename src/{common => api}/registry.js (97%) create mode 100644 src/api/state.js rename src/{common => api}/system.js (96%) delete mode 100644 src/common/events.js diff --git a/src/common/markup.mjs b/src/api/interface.mjs similarity index 75% rename from src/common/markup.mjs rename to src/api/interface.mjs index 9c5ac5f..6ecfa8d 100644 --- a/src/common/markup.mjs +++ b/src/api/interface.mjs @@ -1,6 +1,6 @@ /** * notion-enhancer - * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (c) 2024 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -13,6 +13,7 @@ import { expandVariantGroup, } from "../vendor/@unocss-core.mjs"; import { presetUno } from "../vendor/@unocss-preset-uno.mjs"; +import "../assets/icons.svg.js"; // prettier-ignore // https://developer.mozilla.org/en-US/docs/Web/SVG/Element @@ -21,71 +22,87 @@ import { presetUno } from "../vendor/@unocss-preset-uno.mjs"; const svgElements = ["animate","animateMotion","animateTransform","circle","clipPath","defs","desc","discard","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","foreignObject","g","hatch","hatchpath","image","line","linearGradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialGradient","rect","script","set","stop","style","svg","switch","symbol","text","textPath","title","tspan","use","view"], htmlAttributes = ["accept","accept-charset","accesskey","action","align","allow","alt","async","autocapitalize","autocomplete","autofocus","autoplay","background","bgcolor","border","buffered","capture","challenge","charset","checked","cite","class","code","codebase","color","cols","colspan","content","contenteditable","contextmenu","controls","coords","crossorigin","csp","data","data-*","datetime","decoding","default","defer","dir","dirname","disabled","download","draggable","enctype","enterkeyhint","for","form","formaction","formenctype","formmethod","formnovalidate","formtarget","headers","height","hidden","high","href","hreflang","http-equiv","icon","id","importance","integrity","inputmode","ismap","itemprop","keytype","kind","label","lang","loading","list","loop","low","max","maxlength","minlength","media","method","min","multiple","muted","name","novalidate","open","optimum","pattern","ping","placeholder","playsinline","poster","preload","radiogroup","readonly","referrerpolicy","rel","required","reversed","role","rows","rowspan","sandbox","scope","selected","shape","size","sizes","slot","span","spellcheck","src","srcdoc","srclang","srcset","start","step","style","tabindex","target","title","translate","type","usemap","value","width","wrap","accent-height","accumulate","additive","alignment-baseline","alphabetic","amplitude","arabic-form","ascent","attributeName","attributeType","azimuth","baseFrequency","baseline-shift","baseProfile","bbox","begin","bias","by","calcMode","cap-height","clip","clipPathUnits","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","contentScriptType","contentStyleType","cursor","cx","cy","d","decelerate","descent","diffuseConstant","direction","display","divisor","dominant-baseline","dur","dx","dy","edgeMode","elevation","enable-background","end","exponent","fill","fill-opacity","fill-rule","filter","filterRes","filterUnits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","format","from","fr","fx","fy","g1","g2","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","glyphRef","gradientTransform","gradientUnits","hanging","horiz-adv-x","horiz-origin-x","ideographic","image-rendering","in","in2","intercept","k","k1","k2","k3","k4","kernelMatrix","kernelUnitLength","kerning","keyPoints","keySplines","keyTimes","lengthAdjust","letter-spacing","lighting-color","limitingConeAngle","local","marker-end","marker-mid","marker-start","markerHeight","markerUnits","markerWidth","mask","maskContentUnits","maskUnits","mathematical","mode","numOctaves","offset","opacity","operator","order","orient","orientation","origin","overflow","overline-position","overline-thickness","panose-1","paint-order","path","pathLength","patternContentUnits","patternTransform","patternUnits","pointer-events","points","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","r","radius","referrerPolicy","refX","refY","rendering-intent","repeatCount","repeatDur","requiredExtensions","requiredFeatures","restart","result","rotate","rx","ry","scale","seed","shape-rendering","slope","spacing","specularConstant","specularExponent","speed","spreadMethod","startOffset","stdDeviation","stemh","stemv","stitchTiles","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","string","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","surfaceScale","systemLanguage","tableValues","targetX","targetY","text-anchor","text-decoration","text-rendering","textLength","to","transform","transform-origin","u1","u2","underline-position","underline-thickness","unicode","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","values","vector-effect","version","vert-adv-y","vert-origin-x","vert-origin-y","viewBox","viewTarget","visibility","widths","word-spacing","writing-mode","x","x-height","x1","x2","xChannelSelector","xlink:actuate","xlink:arcrole","xlink:href","xlink:role","xlink:show","xlink:title","xlink:type","xml:base","xml:lang","xml:space","y","y1","y2","yChannelSelector","z","zoomAndPan"]; -const kebabToPascalCase = (string) => - string[0].toUpperCase() + - string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1), - hToString = (type, props, ...children) => - `<${type}${Object.entries(props) - .map(([attr, value]) => ` ${attr}="${value}"`) - .join("")}>${children - .map((child) => (Array.isArray(child) ? hToString(...child) : child)) - .join("")}`, - svgToUri = (svg) => { - // https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 - const xlmns = ~svg.indexOf("xmlns") - ? "/g, "%3E") - .replace(/\s+/g, " ") - .trim()}")`; - }; - -const iconPattern = /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, - presetIcons = ([, icon, mode]) => { - let svg; - if (!["bg", "mask"].includes(mode)) mode = undefined; - if (icon === "notion-enhancer") { - const { iconColour, iconMonochrome } = globalThis.__enhancerApi; - svg = mode === "mask" ? iconMonochrome : iconColour; - } else { - icon = kebabToPascalCase(icon); - if (!lucide[icon]) return; - const [type, props, children] = lucide[icon]; - svg = hToString(type, props, ...children); +// accelerators approximately match electron accelerators. +// logic used when recording hotkeys in menu matches logic used +// when triggering hotkeys => detection should be reliable. +// default hotkeys using "alt" may trigger an altcode or +// accented character on some keyboard layouts (not recommended). +let keyListeners = []; +const modifierAliases = [ + ["metaKey", ["meta", "os", "win", "cmd", "command"]], + ["ctrlKey", ["ctrl", "control"]], + ["shiftKey", ["shift"]], + ["altKey", ["alt"]], + ], + addKeyListener = (accelerator, callback, waitForKeyup = false) => { + if (typeof accelerator === "string") accelerator = accelerator.split("+"); + accelerator = accelerator.map((key) => key.toLowerCase()); + keyListeners.push([accelerator, callback, waitForKeyup]); + }, + removeKeyListener = (callback) => { + keyListeners = keyListeners.filter(([, c]) => c !== callback); + }, + handleKeypress = (event, keyListeners) => { + for (const [accelerator, callback] of keyListeners) { + const acceleratorModifiers = [], + combinationTriggered = + accelerator.every((key) => { + for (const [modifier, aliases] of modifierAliases) { + if (aliases.includes(key)) { + acceleratorModifiers.push(modifier); + return true; + } + } + if (key === "space") key = " "; + if (key === "plus") key = "equal"; + if (key === "minus") key = "-"; + if (key === "\\") key = "backslash"; + if (key === ",") key = "comma"; + if (key === ".") key = "period"; + const keyPressed = [ + event.key.toLowerCase(), + event.code.toLowerCase(), + ].includes(key); + return keyPressed; + }) && + modifierAliases.every(([modifier]) => { + // required && used -> matches accelerator + // !required && !used -> matches accelerator + // (required && !used) || (!required && used) -> no match + // differentiates e.g.ctrl + x from ctrl + shift + x + return acceleratorModifiers.includes(modifier) === event[modifier]; + }); + if (combinationTriggered) callback(event); } - mode ??= svg.includes("currentColor") ? "mask" : "bg"; - return { - // https://antfu.me/posts/icons-in-pure-css - display: "inline-block", - height: "1em", - width: "1em", - ...(mode === "mask" - ? { - mask: `${svgToUri(svg)} no-repeat`, - "mask-size": "100% 100%", - "background-color": "currentColor", - } - : { - background: `${svgToUri(svg)} no-repeat`, - "background-size": "100% 100%", - "background-color": "transparent", - }), - }; + }, + onKeyup = (event) => { + const keyupListeners = keyListeners // + .filter(([, , waitForKeyup]) => waitForKeyup); + handleKeypress(event, keyupListeners); + }, + onKeydown = (event) => { + const keydownListeners = keyListeners // + .filter(([, , waitForKeyup]) => !waitForKeyup); + handleKeypress(event, keydownListeners); }; +document.removeEventListener("keyup", onKeyup); +document.removeEventListener("keydown", onKeydown); +document.addEventListener("keyup", onKeyup); +document.addEventListener("keydown", onKeydown); +// mutation listeners observe updates to the dom. +// by default, the criteria for matching a selector +// is very broad. custom opts can be passed when +// adding a listener to reduce handler calls let documentObserver, observerDefaults = { + // whether to observe attribute updates attributes: true, + // whether to observe innerText updates characterData: true, + // whether to observe added/removed nodes childList: true, + // whether to observe parent/descendant nodes subtree: true, }, mutationListeners = []; @@ -109,7 +126,6 @@ const _mutations = [], matchesParent = opts.subtree && target.matches(`${selector} *`), matchesChild = opts.subtree && target.querySelector(selector), matchesAdded = - // was matching element added? opts.childList && [...(mutation.addedNodes || [])].some((node) => { if (!(node instanceof HTMLElement)) node = node.parentElement; @@ -136,12 +152,21 @@ const _mutations = [], document.addEventListener("readystatechange", attachObserver); attachObserver(); -// combines instance-provided element props -// with a template of element props such that -// island/component/template props handlers -// and styles can be preserved and extended -// rather than overwritten -const extendProps = (props, extend) => { +const kebabToPascalCase = (string) => + string[0].toUpperCase() + + string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1), + hToString = (type, props, ...children) => + `<${type}${Object.entries(props) + .map(([attr, value]) => ` ${attr}="${value}"`) + .join("")}>${children + .map((child) => (Array.isArray(child) ? hToString(...child) : child)) + .join("")}`, + // combines instance-provided element props + // with a template of element props such that + // island/component/template props handlers + // and styles can be preserved and extended + // rather than overwritten + extendProps = (props, extend) => { for (const key in extend) { const { [key]: value } = props; if (typeof extend[key] === "function") { @@ -194,6 +219,53 @@ const extendProps = (props, extend) => { }, html = htm.bind(h); +const iconPattern = /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, + svgToUri = (svg) => { + // https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 + const xlmns = ~svg.indexOf("xmlns") + ? "/g, "%3E") + .replace(/\s+/g, " ") + .trim()}")`; + }, + // prefer custom preset over @unocss/preset-icons: + // limits icons to single set, avoids loading over + // cdn (otherwise could cause issues when submitting + // to the chrome webstore). also makes custom icon + // handling straightforward + presetIcons = ([, icon, mode]) => { + let svg, + mask = mode === "mask"; + if (icon === "notion-enhancer") { + const { iconColour, iconMonochrome } = globalThis.__enhancerApi; + svg = mask ? iconMonochrome : iconColour; + } else { + icon = kebabToPascalCase(icon); + if (!lucide[icon]) return; + const [type, props, children] = lucide[icon]; + svg = hToString(type, props, ...children); + } + mask ||= mode !== "bg" && svg.includes("currentColor"); + return { + // https://antfu.me/posts/icons-in-pure-css + display: "inline-block", + height: "1em", + width: "1em", + [mask ? "mask" : "background"]: `${svgToUri(svg)} no-repeat`, + [mask ? "mask-size" : "background-size"]: "100% 100%", + "background-color": mask ? "currentColor" : "transparent", + }; + }; + let _renderedTokens = -1; const _tokens = new Set(), _stylesheet = html``, @@ -229,6 +301,8 @@ renderStylesheet(); Object.assign((globalThis.__enhancerApi ??= {}), { html, extendProps, + addKeyListener, + removeKeyListener, addMutationListener, removeMutationListener, }); diff --git a/src/common/notion.js b/src/api/notion.js similarity index 100% rename from src/common/notion.js rename to src/api/notion.js diff --git a/src/common/registry.js b/src/api/registry.js similarity index 97% rename from src/common/registry.js rename to src/api/registry.js index eea05e1..23cc3cf 100644 --- a/src/common/registry.js +++ b/src/api/registry.js @@ -1,6 +1,6 @@ /** * notion-enhancer - * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (c) 2024 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/src/api/state.js b/src/api/state.js new file mode 100644 index 0000000..1d59487 --- /dev/null +++ b/src/api/state.js @@ -0,0 +1,70 @@ +/** + * notion-enhancer + * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +"use strict"; + +// batch event callbacks to avoid over-handling +// and any conflicts / perf.issues that may +// otherwise result. initial call is immediate, +// following calls are delayed. a wait time of +// ~200ms is recommended (the avg. human visual +// reaction time is ~180-200ms) +const sleep = async (ms) => { + return new Promise((res, rej) => setTimeout(res, ms)); + }, + debounce = (callback, ms = 200) => { + let delay, update; + const next = () => + sleep(ms).then(() => { + if (!update) return (delay = undefined); + update(), (update = undefined); + delay = next(); + }); + return (...args) => { + if (delay) update = callback.bind(this, ...args); + return delay || ((delay = next()), callback(...args)); + }; + }; + +// provides basic key/value reactivity: +// this is shared between all active mods, +// i.e. mods can read and update other mods' +// reactive states. this enables interop +// between a mod's component islands and +// supports inter-mod communication if so +// required. caution should be used in +// naming keys to avoid conflicts +const _state = {}, + _subscribers = [], + setState = (state) => { + Object.assign(_state, state); + const updates = Object.keys(state); + _subscribers + .filter(([keys]) => updates.some((key) => keys.includes(key))) + .forEach(([keys, callback]) => callback(keys.map((key) => _state[key]))); + }, + // useState(["keyA", "keyB"]) => returns [valueA, valueB] + // useState(["keyA", "keyB"], callback) => registers callback + // to be triggered after each update to either keyA or keyB, + // with [valueA, valueB] passed to the callback's first arg + useState = (keys, callback) => { + const state = keys.map((key) => _state[key]); + if (callback) { + callback = debounce(callback); + _subscribers.push([keys, callback]); + callback(state); + } + return state; + }, + dumpState = () => _state; + +Object.assign((globalThis.__enhancerApi ??= {}), { + sleep, + debounce, + setState, + useState, + dumpState, +}); diff --git a/src/common/system.js b/src/api/system.js similarity index 96% rename from src/common/system.js rename to src/api/system.js index f8cbe1b..62e1bc8 100644 --- a/src/common/system.js +++ b/src/api/system.js @@ -1,6 +1,6 @@ /** * notion-enhancer - * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (c) 2024 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -8,9 +8,10 @@ const IS_ELECTRON = typeof module !== "undefined", IS_RENDERER = IS_ELECTRON && process.type === "renderer", - whenReady = new Promise((res, rej) => { + API_LOADED = new Promise((res, rej) => { (globalThis.__enhancerApi ??= {}).__isReady = res; - }); + }), + whenReady = (callback) => API_LOADED.then(callback); // expected values: 'linux', 'win32', 'darwin' (== macos), 'firefox' // and 'chromium' (inc. chromium-based browsers like edge and brave) @@ -159,5 +160,5 @@ Object.assign((globalThis.__enhancerApi ??= {}), { readJson, initDatabase, reloadApp, - whenReady: (callback) => whenReady.then(callback), + whenReady, }); diff --git a/src/common/events.js b/src/common/events.js deleted file mode 100644 index 1707fc8..0000000 --- a/src/common/events.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * notion-enhancer - * (c) 2023 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -"use strict"; - -// batch event callbacks to avoid over-handling -// and any conflicts / perf.issues that may -// otherwise result. initial call is immediate, -// following calls are delayed. a wait time of -// ~200ms is recommended (the avg. human visual -// reaction time is ~180-200ms) -const sleep = async (ms) => { - return new Promise((res, rej) => setTimeout(res, ms)); - }, - debounce = (callback, ms = 200) => { - let delay, update; - const next = () => - sleep(ms).then(() => { - if (!update) return (delay = undefined); - update(), (update = undefined); - delay = next(); - }); - return (...args) => { - if (delay) update = callback.bind(this, ...args); - return delay || ((delay = next()), callback(...args)); - }; - }; - -// provides basic key/value reactivity: -// this is shared between all active mods, -// i.e. mods can read and update other mods' -// reactive states. this enables interop -// between a mod's component islands and -// supports inter-mod communication if so -// required. caution should be used in -// naming keys to avoid conflicts -const _state = {}, - _subscribers = [], - setState = (state) => { - Object.assign(_state, state); - const updates = Object.keys(state); - _subscribers - .filter(([keys]) => updates.some((key) => keys.includes(key))) - .forEach(([keys, callback]) => callback(keys.map((key) => _state[key]))); - }, - // useState(["keyA", "keyB"]) => returns [valueA, valueB] - // useState(["keyA", "keyB"], callback) => registers callback - // to be triggered after each update to either keyA or keyB, - // with [valueA, valueB] passed to the callback's first arg - useState = (keys, callback) => { - const state = keys.map((key) => _state[key]); - if (callback) { - callback = debounce(callback); - _subscribers.push([keys, callback]); - callback(state); - } - return state; - }, - dumpState = () => _state; - -let keyListeners = []; -// accelerators approximately match electron accelerators. -// logic used when recording hotkeys in menu matches logic used -// when triggering hotkeys => detection should be reliable. -// default hotkeys using "alt" may trigger an altcode or -// accented character on some keyboard layouts (not recommended). -const modifierAliases = [ - ["metaKey", ["meta", "os", "win", "cmd", "command"]], - ["ctrlKey", ["ctrl", "control"]], - ["shiftKey", ["shift"]], - ["altKey", ["alt"]], - ], - addKeyListener = (accelerator, callback, waitForKeyup = false) => { - if (typeof accelerator === "string") accelerator = accelerator.split("+"); - accelerator = accelerator.map((key) => key.toLowerCase()); - keyListeners.push([accelerator, callback, waitForKeyup]); - }, - removeKeyListener = (callback) => { - keyListeners = keyListeners.filter(([, c]) => c !== callback); - }, - handleKeypress = (event, keyListeners) => { - for (const [accelerator, callback] of keyListeners) { - const acceleratorModifiers = [], - combinationTriggered = - accelerator.every((key) => { - for (const [modifier, aliases] of modifierAliases) { - if (aliases.includes(key)) { - acceleratorModifiers.push(modifier); - return true; - } - } - if (key === "space") key = " "; - if (key === "plus") key = "equal"; - if (key === "minus") key = "-"; - if (key === "\\") key = "backslash"; - if (key === ",") key = "comma"; - if (key === ".") key = "period"; - const keyPressed = [ - event.key.toLowerCase(), - event.code.toLowerCase(), - ].includes(key); - return keyPressed; - }) && - modifierAliases.every(([modifier]) => { - // required && used -> matches accelerator - // !required && !used -> matches accelerator - // (required && !used) || (!required && used) -> no match - // differentiates e.g.ctrl + x from ctrl + shift + x - return acceleratorModifiers.includes(modifier) === event[modifier]; - }); - if (combinationTriggered) callback(event); - } - }, - onKeyup = (event) => { - const keyupListeners = keyListeners // - .filter(([, , waitForKeyup]) => waitForKeyup); - handleKeypress(event, keyupListeners); - }, - onKeydown = (event) => { - const keydownListeners = keyListeners // - .filter(([, , waitForKeyup]) => !waitForKeyup); - handleKeypress(event, keydownListeners); - }; -document.removeEventListener("keyup", onKeyup); -document.removeEventListener("keydown", onKeydown); -document.addEventListener("keyup", onKeyup); -document.addEventListener("keydown", onKeydown); - -Object.assign((globalThis.__enhancerApi ??= {}), { - sleep, - debounce, - setState, - useState, - dumpState, - addKeyListener, - removeKeyListener, -}); diff --git a/src/core/menu/islands/View.mjs b/src/core/menu/islands/View.mjs index 6cb91aa..2d46c1f 100644 --- a/src/core/menu/islands/View.mjs +++ b/src/core/menu/islands/View.mjs @@ -11,8 +11,8 @@ function View({ id }, ...children) { // set padding on last child to maintain pad on overflow $view = html`
${children}
`; diff --git a/src/init.js b/src/init.js index 4c7c544..31da384 100644 --- a/src/init.js +++ b/src/init.js @@ -14,8 +14,8 @@ const isElectron = () => { }; if (isElectron()) { - require("./common/system.js"); - require("./common/registry.js"); + require("./api/system.js"); + require("./api/registry.js"); const { enhancerUrl } = globalThis.__enhancerApi, { getMods, isEnabled, modDatabase } = globalThis.__enhancerApi; @@ -49,6 +49,6 @@ if (isElectron()) { } }; } else { - import(chrome.runtime.getURL("/common/system.js")) // + import(chrome.runtime.getURL("/api/system.js")) // .then(() => import(chrome.runtime.getURL("/load.mjs"))); } diff --git a/src/load.mjs b/src/load.mjs index 650d4bc..0ddc583 100644 --- a/src/load.mjs +++ b/src/load.mjs @@ -39,12 +39,11 @@ export default (async () => { // in both situations, modules that attach to // the dom must be re-imported + await Promise.all([ - // i.e. if (not_menu) or (is_menu && not_electron), then import - !(!IS_MENU || !IS_ELECTRON) || import(enhancerUrl("assets/icons.svg.js")), - !(!IS_MENU || !IS_ELECTRON) || import(enhancerUrl("common/registry.js")), - import(enhancerUrl("common/scaffold.mjs")), - import(enhancerUrl("common/events.js")), + IS_ELECTRON || import(enhancerUrl("common/registry.js")), + (IS_ELECTRON && IS_MENU) || import(enhancerUrl("api/state.js")), + import(enhancerUrl("api/interface.mjs")), ]); globalThis.__enhancerApi.__isReady(globalThis.__enhancerApi);