mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-11 15:59:03 +00:00
chore: simplify api imports
This commit is contained in:
parent
f0e2570448
commit
1f717b98ca
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer
|
* notion-enhancer
|
||||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
expandVariantGroup,
|
expandVariantGroup,
|
||||||
} from "../vendor/@unocss-core.mjs";
|
} from "../vendor/@unocss-core.mjs";
|
||||||
import { presetUno } from "../vendor/@unocss-preset-uno.mjs";
|
import { presetUno } from "../vendor/@unocss-preset-uno.mjs";
|
||||||
|
import "../assets/icons.svg.js";
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element
|
// 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"],
|
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"];
|
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) =>
|
// accelerators approximately match electron accelerators.
|
||||||
string[0].toUpperCase() +
|
// logic used when recording hotkeys in menu matches logic used
|
||||||
string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1),
|
// when triggering hotkeys => detection should be reliable.
|
||||||
hToString = (type, props, ...children) =>
|
// default hotkeys using "alt" may trigger an altcode or
|
||||||
`<${type}${Object.entries(props)
|
// accented character on some keyboard layouts (not recommended).
|
||||||
.map(([attr, value]) => ` ${attr}="${value}"`)
|
let keyListeners = [];
|
||||||
.join("")}>${children
|
const modifierAliases = [
|
||||||
.map((child) => (Array.isArray(child) ? hToString(...child) : child))
|
["metaKey", ["meta", "os", "win", "cmd", "command"]],
|
||||||
.join("")}</${type}>`,
|
["ctrlKey", ["ctrl", "control"]],
|
||||||
svgToUri = (svg) => {
|
["shiftKey", ["shift"]],
|
||||||
// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
|
["altKey", ["alt"]],
|
||||||
const xlmns = ~svg.indexOf("xmlns")
|
],
|
||||||
? "<svg"
|
addKeyListener = (accelerator, callback, waitForKeyup = false) => {
|
||||||
: '<svg xmlns="http://www.w3.org/2000/svg"';
|
if (typeof accelerator === "string") accelerator = accelerator.split("+");
|
||||||
return `url("data:image/svg+xml;utf8,${svg
|
accelerator = accelerator.map((key) => key.toLowerCase());
|
||||||
.replace("<svg", xlmns)
|
keyListeners.push([accelerator, callback, waitForKeyup]);
|
||||||
.replace(/"/g, "'")
|
},
|
||||||
.replace(/%/g, "%25")
|
removeKeyListener = (callback) => {
|
||||||
.replace(/#/g, "%23")
|
keyListeners = keyListeners.filter(([, c]) => c !== callback);
|
||||||
.replace(/{/g, "%7B")
|
},
|
||||||
.replace(/}/g, "%7D")
|
handleKeypress = (event, keyListeners) => {
|
||||||
.replace(/</g, "%3C")
|
for (const [accelerator, callback] of keyListeners) {
|
||||||
.replace(/>/g, "%3E")
|
const acceleratorModifiers = [],
|
||||||
.replace(/\s+/g, " ")
|
combinationTriggered =
|
||||||
.trim()}")`;
|
accelerator.every((key) => {
|
||||||
};
|
for (const [modifier, aliases] of modifierAliases) {
|
||||||
|
if (aliases.includes(key)) {
|
||||||
const iconPattern = /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/,
|
acceleratorModifiers.push(modifier);
|
||||||
presetIcons = ([, icon, mode]) => {
|
return true;
|
||||||
let svg;
|
}
|
||||||
if (!["bg", "mask"].includes(mode)) mode = undefined;
|
}
|
||||||
if (icon === "notion-enhancer") {
|
if (key === "space") key = " ";
|
||||||
const { iconColour, iconMonochrome } = globalThis.__enhancerApi;
|
if (key === "plus") key = "equal";
|
||||||
svg = mode === "mask" ? iconMonochrome : iconColour;
|
if (key === "minus") key = "-";
|
||||||
} else {
|
if (key === "\\") key = "backslash";
|
||||||
icon = kebabToPascalCase(icon);
|
if (key === ",") key = "comma";
|
||||||
if (!lucide[icon]) return;
|
if (key === ".") key = "period";
|
||||||
const [type, props, children] = lucide[icon];
|
const keyPressed = [
|
||||||
svg = hToString(type, props, ...children);
|
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 {
|
onKeyup = (event) => {
|
||||||
// https://antfu.me/posts/icons-in-pure-css
|
const keyupListeners = keyListeners //
|
||||||
display: "inline-block",
|
.filter(([, , waitForKeyup]) => waitForKeyup);
|
||||||
height: "1em",
|
handleKeypress(event, keyupListeners);
|
||||||
width: "1em",
|
},
|
||||||
...(mode === "mask"
|
onKeydown = (event) => {
|
||||||
? {
|
const keydownListeners = keyListeners //
|
||||||
mask: `${svgToUri(svg)} no-repeat`,
|
.filter(([, , waitForKeyup]) => !waitForKeyup);
|
||||||
"mask-size": "100% 100%",
|
handleKeypress(event, keydownListeners);
|
||||||
"background-color": "currentColor",
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
background: `${svgToUri(svg)} no-repeat`,
|
|
||||||
"background-size": "100% 100%",
|
|
||||||
"background-color": "transparent",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
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,
|
let documentObserver,
|
||||||
observerDefaults = {
|
observerDefaults = {
|
||||||
|
// whether to observe attribute updates
|
||||||
attributes: true,
|
attributes: true,
|
||||||
|
// whether to observe innerText updates
|
||||||
characterData: true,
|
characterData: true,
|
||||||
|
// whether to observe added/removed nodes
|
||||||
childList: true,
|
childList: true,
|
||||||
|
// whether to observe parent/descendant nodes
|
||||||
subtree: true,
|
subtree: true,
|
||||||
},
|
},
|
||||||
mutationListeners = [];
|
mutationListeners = [];
|
||||||
@ -109,7 +126,6 @@ const _mutations = [],
|
|||||||
matchesParent = opts.subtree && target.matches(`${selector} *`),
|
matchesParent = opts.subtree && target.matches(`${selector} *`),
|
||||||
matchesChild = opts.subtree && target.querySelector(selector),
|
matchesChild = opts.subtree && target.querySelector(selector),
|
||||||
matchesAdded =
|
matchesAdded =
|
||||||
// was matching element added?
|
|
||||||
opts.childList &&
|
opts.childList &&
|
||||||
[...(mutation.addedNodes || [])].some((node) => {
|
[...(mutation.addedNodes || [])].some((node) => {
|
||||||
if (!(node instanceof HTMLElement)) node = node.parentElement;
|
if (!(node instanceof HTMLElement)) node = node.parentElement;
|
||||||
@ -136,12 +152,21 @@ const _mutations = [],
|
|||||||
document.addEventListener("readystatechange", attachObserver);
|
document.addEventListener("readystatechange", attachObserver);
|
||||||
attachObserver();
|
attachObserver();
|
||||||
|
|
||||||
// combines instance-provided element props
|
const kebabToPascalCase = (string) =>
|
||||||
// with a template of element props such that
|
string[0].toUpperCase() +
|
||||||
// island/component/template props handlers
|
string.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase()).slice(1),
|
||||||
// and styles can be preserved and extended
|
hToString = (type, props, ...children) =>
|
||||||
// rather than overwritten
|
`<${type}${Object.entries(props)
|
||||||
const extendProps = (props, extend) => {
|
.map(([attr, value]) => ` ${attr}="${value}"`)
|
||||||
|
.join("")}>${children
|
||||||
|
.map((child) => (Array.isArray(child) ? hToString(...child) : child))
|
||||||
|
.join("")}</${type}>`,
|
||||||
|
// 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) {
|
for (const key in extend) {
|
||||||
const { [key]: value } = props;
|
const { [key]: value } = props;
|
||||||
if (typeof extend[key] === "function") {
|
if (typeof extend[key] === "function") {
|
||||||
@ -194,6 +219,53 @@ const extendProps = (props, extend) => {
|
|||||||
},
|
},
|
||||||
html = htm.bind(h);
|
html = htm.bind(h);
|
||||||
|
|
||||||
|
const iconPattern = /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/,
|
||||||
|
svgToUri = (svg) => {
|
||||||
|
// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
|
||||||
|
const xlmns = ~svg.indexOf("xmlns")
|
||||||
|
? "<svg"
|
||||||
|
: '<svg xmlns="http://www.w3.org/2000/svg"';
|
||||||
|
return `url("data:image/svg+xml;utf8,${svg
|
||||||
|
.replace("<svg", xlmns)
|
||||||
|
.replace(/"/g, "'")
|
||||||
|
.replace(/%/g, "%25")
|
||||||
|
.replace(/#/g, "%23")
|
||||||
|
.replace(/{/g, "%7B")
|
||||||
|
.replace(/}/g, "%7D")
|
||||||
|
.replace(/</g, "%3C")
|
||||||
|
.replace(/>/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;
|
let _renderedTokens = -1;
|
||||||
const _tokens = new Set(),
|
const _tokens = new Set(),
|
||||||
_stylesheet = html`<style id="__unocss"></style>`,
|
_stylesheet = html`<style id="__unocss"></style>`,
|
||||||
@ -229,6 +301,8 @@ renderStylesheet();
|
|||||||
Object.assign((globalThis.__enhancerApi ??= {}), {
|
Object.assign((globalThis.__enhancerApi ??= {}), {
|
||||||
html,
|
html,
|
||||||
extendProps,
|
extendProps,
|
||||||
|
addKeyListener,
|
||||||
|
removeKeyListener,
|
||||||
addMutationListener,
|
addMutationListener,
|
||||||
removeMutationListener,
|
removeMutationListener,
|
||||||
});
|
});
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer
|
* notion-enhancer
|
||||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
70
src/api/state.js
Normal file
70
src/api/state.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* notion-enhancer
|
||||||
|
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (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,
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer
|
* notion-enhancer
|
||||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -8,9 +8,10 @@
|
|||||||
|
|
||||||
const IS_ELECTRON = typeof module !== "undefined",
|
const IS_ELECTRON = typeof module !== "undefined",
|
||||||
IS_RENDERER = IS_ELECTRON && process.type === "renderer",
|
IS_RENDERER = IS_ELECTRON && process.type === "renderer",
|
||||||
whenReady = new Promise((res, rej) => {
|
API_LOADED = new Promise((res, rej) => {
|
||||||
(globalThis.__enhancerApi ??= {}).__isReady = res;
|
(globalThis.__enhancerApi ??= {}).__isReady = res;
|
||||||
});
|
}),
|
||||||
|
whenReady = (callback) => API_LOADED.then(callback);
|
||||||
|
|
||||||
// expected values: 'linux', 'win32', 'darwin' (== macos), 'firefox'
|
// expected values: 'linux', 'win32', 'darwin' (== macos), 'firefox'
|
||||||
// and 'chromium' (inc. chromium-based browsers like edge and brave)
|
// and 'chromium' (inc. chromium-based browsers like edge and brave)
|
||||||
@ -159,5 +160,5 @@ Object.assign((globalThis.__enhancerApi ??= {}), {
|
|||||||
readJson,
|
readJson,
|
||||||
initDatabase,
|
initDatabase,
|
||||||
reloadApp,
|
reloadApp,
|
||||||
whenReady: (callback) => whenReady.then(callback),
|
whenReady,
|
||||||
});
|
});
|
@ -1,140 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer
|
|
||||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (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,
|
|
||||||
});
|
|
@ -11,8 +11,8 @@ function View({ id }, ...children) {
|
|||||||
// set padding on last child to maintain pad on overflow
|
// set padding on last child to maintain pad on overflow
|
||||||
$view = html`<article
|
$view = html`<article
|
||||||
id=${id}
|
id=${id}
|
||||||
class="notion-enhancer--menu-view h-full w-full min-w-[580px]
|
class="notion-enhancer--menu-view min-h-full w-full
|
||||||
absolute px-[60px] pt-[36px] important:[&>*]:last:pb-[36px]"
|
absolute px-[60px] py-[36px] min-w-[580px]"
|
||||||
>
|
>
|
||||||
${children}
|
${children}
|
||||||
</article>`;
|
</article>`;
|
||||||
|
@ -14,8 +14,8 @@ const isElectron = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
require("./common/system.js");
|
require("./api/system.js");
|
||||||
require("./common/registry.js");
|
require("./api/registry.js");
|
||||||
const { enhancerUrl } = globalThis.__enhancerApi,
|
const { enhancerUrl } = globalThis.__enhancerApi,
|
||||||
{ getMods, isEnabled, modDatabase } = globalThis.__enhancerApi;
|
{ getMods, isEnabled, modDatabase } = globalThis.__enhancerApi;
|
||||||
|
|
||||||
@ -49,6 +49,6 @@ if (isElectron()) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
import(chrome.runtime.getURL("/common/system.js")) //
|
import(chrome.runtime.getURL("/api/system.js")) //
|
||||||
.then(() => import(chrome.runtime.getURL("/load.mjs")));
|
.then(() => import(chrome.runtime.getURL("/load.mjs")));
|
||||||
}
|
}
|
||||||
|
@ -39,12 +39,11 @@ export default (async () => {
|
|||||||
|
|
||||||
// in both situations, modules that attach to
|
// in both situations, modules that attach to
|
||||||
// the dom must be re-imported
|
// the dom must be re-imported
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// i.e. if (not_menu) or (is_menu && not_electron), then import
|
IS_ELECTRON || import(enhancerUrl("common/registry.js")),
|
||||||
!(!IS_MENU || !IS_ELECTRON) || import(enhancerUrl("assets/icons.svg.js")),
|
(IS_ELECTRON && IS_MENU) || import(enhancerUrl("api/state.js")),
|
||||||
!(!IS_MENU || !IS_ELECTRON) || import(enhancerUrl("common/registry.js")),
|
import(enhancerUrl("api/interface.mjs")),
|
||||||
import(enhancerUrl("common/scaffold.mjs")),
|
|
||||||
import(enhancerUrl("common/events.js")),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
globalThis.__enhancerApi.__isReady(globalThis.__enhancerApi);
|
globalThis.__enhancerApi.__isReady(globalThis.__enhancerApi);
|
||||||
|
Loading…
Reference in New Issue
Block a user