refactor: move state.mjs into markup.mjs for use in notion interface

This commit is contained in:
dragonwocky 2023-08-06 23:45:56 +10:00
parent bb5853e866
commit 45e0be7d62
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
21 changed files with 284 additions and 248 deletions

View File

@ -8,6 +8,7 @@ import { checkForUpdate } from "./updateCheck.mjs";
import { sendTelemetryPing } from "./sendTelemetry.mjs";
import { Modal, Frame } from "./islands/Modal.mjs";
import { MenuButton } from "./islands/MenuButton.mjs";
import { TopbarButton } from "./islands/TopbarButton.mjs";
import { Panel } from "./islands/Panel.mjs";
const shouldLoadThemeOverrides = async (db) => {
@ -118,10 +119,11 @@ const insertMenu = async (db) => {
});
};
const insertPanel = async (db) => {
const insertPanel = async (api, db) => {
const notionFrame = ".notion-frame",
{ html, addKeyListener, addMutationListener } = globalThis.__enhancerApi,
togglePanelHotkey = await db.get("togglePanelHotkey");
notionTopbarBtn = ".notion-topbar-more-button",
togglePanelHotkey = await db.get("togglePanelHotkey"),
{ html } = api;
const $panel = html`<${Panel}
_getWidth=${() => db.get("sidePanelWidth")}
@ -129,28 +131,45 @@ const insertPanel = async (db) => {
_getOpen=${() => db.get("sidePanelOpen")}
_setOpen=${(open) => db.set("sidePanelOpen", open)}
/>`,
togglePanel = () => {
if ($panel.hasAttribute("open")) $panel.close();
else $panel.open();
};
const $panelTopbarBtn = html`<${TopbarButton}
aria-label="Open side panel"
icon="panel-right"
onclick=${togglePanel}
/>`,
appendToDom = () => {
const $frame = document.querySelector(notionFrame);
if (!$frame) return;
if (!$frame.contains($panel)) $frame.append($panel);
if (!$frame.style.flexDirection !== "row")
$frame.style.flexDirection = "row";
if (!document.contains($panelTopbarBtn)) {
const $notionTopbarBtn = document.querySelector(notionTopbarBtn);
$notionTopbarBtn?.before($panelTopbarBtn);
}
};
addMutationListener(notionFrame, appendToDom);
api.addMutationListener(`${notionFrame}, ${notionTopbarBtn}`, appendToDom);
api.useState(["panelOpen"], ([panelOpen]) => {
if (panelOpen) $panelTopbarBtn.setAttribute("data-active", true);
else $panelTopbarBtn.removeAttribute("data-active");
});
appendToDom();
addKeyListener(togglePanelHotkey, (event) => {
api.addKeyListener(togglePanelHotkey, (event) => {
event.preventDefault();
event.stopPropagation();
if ($panel.hasAttribute("open")) $panel.close();
else $panel.open();
togglePanel();
});
};
export default async (api, db) =>
Promise.all([
insertMenu(db),
insertPanel(db),
insertPanel(api, db),
insertCustomStyles(db),
loadThemeOverrides(db),
sendTelemetryPing(),

View File

@ -4,17 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
// <div class="absolute top-0">
// <svg
// role="graphics-symbol"
// viewBox="0 0 16 16"
// class="doubleChevronRight"
// style="width: 16px; height: 16px; display: block; fill: rgba(255, 255, 255, 0.443); flex-shrink: 0;"
// >
// <path d="M2.25781 14.1211C2.47656 14.1211 2.66797 14.0391 2.81836 13.8887L8.14355 8.67969C8.32812 8.49512 8.41699 8.29688 8.41699 8.06445C8.41699 7.8252 8.32812 7.62012 8.14355 7.44922L2.81836 2.24023C2.66797 2.08984 2.4834 2.00781 2.25781 2.00781C1.81348 2.00781 1.46484 2.35645 1.46484 2.80078C1.46484 3.0127 1.55371 3.21777 1.7041 3.375L6.50977 8.05762L1.7041 12.7539C1.55371 12.9043 1.46484 13.1094 1.46484 13.3281C1.46484 13.7725 1.81348 14.1211 2.25781 14.1211ZM8.36914 14.1211C8.58789 14.1211 8.77246 14.0391 8.92285 13.8887L14.2549 8.67969C14.4395 8.49512 14.5283 8.29688 14.5283 8.06445C14.5283 7.8252 14.4326 7.62012 14.2549 7.44922L8.92285 2.24023C8.77246 2.08984 8.58789 2.00781 8.36914 2.00781C7.9248 2.00781 7.56934 2.35645 7.56934 2.80078C7.56934 3.0127 7.66504 3.21777 7.81543 3.375L12.6211 8.05762L7.81543 12.7539C7.66504 12.9043 7.56934 13.1094 7.56934 13.3281C7.56934 13.7725 7.9248 14.1211 8.36914 14.1211Z"></path>
// </svg>
// </div>;
import { Tooltip } from "./Tooltip.mjs";
function PanelView(props) {
@ -36,9 +25,8 @@ function Panel({
maxWidth = 640,
...props
}) {
const { html, extendProps } = globalThis.__enhancerApi,
{ addMutationListener, removeMutationListener } = globalThis.__enhancerApi;
extendProps(props, {
const { html, ...api } = globalThis.__enhancerApi;
api.extendProps(props, {
class: `notion-enhancer--side-panel order-2 shrink-0
transition-[width] open:w-[var(--side\\_panel--width)]
w-0 border-l-1 border-[color:var(--theme--fg-border)]
@ -65,8 +53,9 @@ function Panel({
d="M2.25781 14.1211C2.47656 14.1211 2.66797 14.0391 2.81836 13.8887L8.14355 8.67969C8.32812 8.49512 8.41699 8.29688 8.41699 8.06445C8.41699 7.8252 8.32812 7.62012 8.14355 7.44922L2.81836 2.24023C2.66797 2.08984 2.4834 2.00781 2.25781 2.00781C1.81348 2.00781 1.46484 2.35645 1.46484 2.80078C1.46484 3.0127 1.55371 3.21777 1.7041 3.375L6.50977 8.05762L1.7041 12.7539C1.55371 12.9043 1.46484 13.1094 1.46484 13.3281C1.46484 13.7725 1.81348 14.1211 2.25781 14.1211ZM8.36914 14.1211C8.58789 14.1211 8.77246 14.0391 8.92285 13.8887L14.2549 8.67969C14.4395 8.49512 14.5283 8.29688 14.5283 8.06445C14.5283 7.8252 14.4326 7.62012 14.2549 7.44922L8.92285 2.24023C8.77246 2.08984 8.58789 2.00781 8.36914 2.00781C7.9248 2.00781 7.56934 2.35645 7.56934 2.80078C7.56934 3.0127 7.66504 3.21777 7.81543 3.375L12.6211 8.05762L7.81543 12.7539C7.66504 12.9043 7.56934 13.1094 7.56934 13.3281C7.56934 13.7725 7.9248 14.1211 8.36914 14.1211Z"
></path>
</svg>
</div>`,
$panel = html`<aside ...${props}>
</div>`;
const $panel = html`<aside ...${props}>
${$resizeHandle}
<div
class="flex justify-between items-center
@ -139,33 +128,9 @@ function Panel({
options = { duration: 150, easing: "cubic-bezier(0.4, 0, 0.2, 1)" };
$notionHelp.style.setProperty("right", destination);
$notionHelp.animate(keyframes, options);
removeMutationListener(repositionHelp);
api.removeMutationListener(repositionHelp);
};
addMutationListener(notionHelp, repositionHelp);
const notionTopbarMore = ".notion-topbar-more-button",
$topbarPanelButton = html`<button
aria-label="Open side panel"
class="user-select-none h-[28px] w-[33px] duration-[20ms]
transition inline-flex items-center justify-center mr-[2px]
rounded-[3px] hover:bg-[color:var(--theme--bg-hover)]
[data-active]:bg-[color:var(--theme--bg-hover)]"
>
<i
class="i-panel-right w-[20px] h-[20px] fill-[color:var(--theme--fg-secondary)]"
/>
</button>`,
appendToDom = () => {
if (document.contains($topbarPanelButton)) return;
const $notionTopbarMore = document.querySelector(notionTopbarMore);
$notionTopbarMore?.before($topbarPanelButton);
};
$topbarPanelButton.addEventListener("click", () => {
if ($panel.hasAttribute("open")) $panel.close();
else $panel.open();
});
addMutationListener(notionTopbarMore, appendToDom);
appendToDom();
api.addMutationListener(notionHelp, repositionHelp);
$panel.resize = async (width) => {
$tooltip.hide();
@ -181,7 +146,7 @@ function Panel({
$panel.open = () => {
$panel.setAttribute("open", true);
$panel.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0));
$topbarPanelButton.setAttribute("data-active", true);
api.setState({ panelOpen: true });
$panel.onopen?.();
_setOpen(true);
$panel.resize();
@ -191,8 +156,8 @@ function Panel({
$panel.onbeforeclose?.();
$panel.removeAttribute("open");
$panel.style.pointerEvents = "auto";
$topbarPanelButton.removeAttribute("data-active");
$panel.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1));
api.setState({ panelOpen: false });
repositionHelp();
_setOpen(false);
setTimeout(() => {

View File

@ -0,0 +1,26 @@
/**
* notion-enhancer
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
function TopbarButton({ icon, ...props }, ...children) {
const { html, extendProps } = globalThis.__enhancerApi;
extendProps(props, {
tabindex: 0,
role: "button",
class: `notion-enhancer--topbar-button
user-select-none h-[28px] w-[33px] duration-[20ms]
transition inline-flex items-center justify-center mr-[2px]
rounded-[3px] hover:bg-[color:var(--theme--bg-hover)]
[data-active]:bg-[color:var(--theme--bg-hover)]`,
});
return html`<button ...${props}>
<i
class="i-${icon} w-[20px] h-[20px]
fill-[color:var(--theme--fg-secondary)]"
/>
</button>`;
}
export { TopbarButton };

View File

@ -7,7 +7,6 @@
import { Popup } from "./Popup.mjs";
import { Button } from "./Button.mjs";
import { Description } from "./Description.mjs";
import { useState } from "../state.mjs";
const updateGuide =
"https://notion-enhancer.github.io/getting-started/updating/",
@ -69,7 +68,7 @@ function Circle(rect) {
}
function Banner({ updateAvailable, isDevelopmentBuild }) {
const { html, version, initDatabase } = globalThis.__enhancerApi,
const { html, version, initDatabase, useState } = globalThis.__enhancerApi,
$version = html`<button
class="text-[12px] py-[2px] px-[6px] mt-[2px]
font-medium leading-tight tracking-wide rounded-[3px]

View File

@ -4,11 +4,10 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
function Checkbox({ _get, _set, _requireReload = true, ...props }) {
let _initialValue;
const { html, extendProps } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$input = html`<input
type="checkbox"
class="hidden checked:sibling:(px-px

View File

@ -4,11 +4,11 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
import { Button } from "./Button.mjs";
function Footer({ categories }) {
const { html, reloadApp } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$reload = html`<${Button}
class="ml-auto"
variant="primary"

View File

@ -4,8 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
const updateHotkey = (event) => {
const keys = [];
for (const modifier of ["metaKey", "ctrlKey", "altKey", "shiftKey"]) {
@ -76,7 +74,8 @@ function Input({
...props
}) {
let $filename, $clear;
const { html, extendProps } = globalThis.__enhancerApi;
const { html, extendProps } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi;
Coloris({ format: "rgb" });
type ??= "text";

View File

@ -4,7 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState } from "../state.mjs";
import { Description } from "./Description.mjs";
import { Input } from "./Input.mjs";
import { Mod } from "./Mod.mjs";
@ -38,7 +37,7 @@ function Search({ items, itemType }) {
}
function List({ id, mods, description }) {
const { html, isEnabled, setEnabled } = globalThis.__enhancerApi,
const { html, isEnabled, setEnabled, setState } = globalThis.__enhancerApi,
$mods = mods.map((mod) => {
const _get = () => isEnabled(mod.id),
_set = async (enabled) => {

View File

@ -4,7 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState } from "../state.mjs";
import { Description } from "./Description.mjs";
import { Toggle } from "./Toggle.mjs";
@ -21,7 +20,7 @@ function Mod({
_set,
_src,
}) {
const { html, enhancerUrl } = globalThis.__enhancerApi,
const { html, enhancerUrl, setState } = globalThis.__enhancerApi,
toggleId = Math.random().toString(36).slice(2, 5);
return html`<label

View File

@ -9,13 +9,13 @@ import { Description } from "./Description.mjs";
import { Checkbox } from "./Checkbox.mjs";
import { Button } from "./Button.mjs";
import { Tile } from "./Tile.mjs";
import { setState, useState } from "../state.mjs";
const privacyPolicy = "https://notion-enhancer.github.io/about/privacy-policy/",
tsAndCs = "https://notion-enhancer.github.io/about/terms-and-conditions/";
function Onboarding() {
const { html, version, initDatabase } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$submitAgreement = html`<${Button}
icon="arrow-right"
class="ml-auto"

View File

@ -4,7 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState } from "../state.mjs";
import { Heading } from "./Heading.mjs";
import { Description } from "./Description.mjs";
import { Input } from "./Input.mjs";
@ -78,7 +77,7 @@ function Option({ _get, _set, ...opt }) {
}
function Options({ mod }) {
const { html, modDatabase } = globalThis.__enhancerApi;
const { html, modDatabase, setState } = globalThis.__enhancerApi;
return filterOptionsForRender(mod.options).map((opt) => {
opt.label ??= camelToSentenceCase(opt.key);
if (opt.type === "heading") return html`<${Heading}>${opt.label}<//>`;

View File

@ -4,10 +4,9 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
function Popup({ trigger, ...props }, ...children) {
const { html, extendProps } = globalThis.__enhancerApi;
const { html, extendProps } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi;
extendProps(props, {
class: `notion-enhancer--menu-popup
group absolute top-0 left-0 w-full h-full

View File

@ -4,7 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
import { Heading } from "./Heading.mjs";
import { Description } from "./Description.mjs";
import { Checkbox } from "./Checkbox.mjs";
@ -14,6 +13,7 @@ import { Popup } from "./Popup.mjs";
function Profile({ id }) {
const { html, getProfile, initDatabase } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
profile = initDatabase([id]),
db = initDatabase();
@ -180,6 +180,7 @@ function Profile({ id }) {
function Profiles() {
const { html, initDatabase } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$input = html`<${Input} icon="file-cog" />`,
$list = html`<ul></ul>`;

View File

@ -4,11 +4,11 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
import { Popup } from "./Popup.mjs";
function Option({ value, _get, _set }) {
const { html } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$selected = html`<i class="ml-auto i-check w-[16px] h-[16px]"></i>`,
$option = html`<div
tabindex="0"
@ -36,6 +36,7 @@ function Option({ value, _get, _set }) {
function Select({ values, _get, _set, _requireReload = true, ...props }) {
let _initialValue;
const { html } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
// dir="rtl" overflows to the left during transition
$select = html`<div
dir="rtl"

View File

@ -4,11 +4,11 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
import { Description } from "./Description.mjs";
function SidebarHeading({}, ...children) {
const { html } = globalThis.__enhancerApi;
const { html } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi;
return html`<h2
class="flex items-center font-semibold leading-none
text-([12px] [color:var(--theme--fg-secondary)])
@ -20,6 +20,7 @@ function SidebarHeading({}, ...children) {
function SidebarButton({ id, icon, ...props }, ...children) {
const { html, extendProps } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$btn = html`<${props["href"] ? "a" : "button"}
class="flex items-center select-none text-[14px]
min-h-[27px] px-[12px] my-px last:mb-[12px] w-full
@ -53,6 +54,7 @@ function SidebarButton({ id, icon, ...props }, ...children) {
function Sidebar({ items, categories }) {
const { html, version } = globalThis.__enhancerApi,
{ initDatabase, isEnabled } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$agreeToUnlock = html`<span
class="pt-[2px] pb-[5px] px-[15px] text-[12px]
inline-block text-[color:var(--theme--fg-red)]"

View File

@ -5,12 +5,12 @@
*/
import { collectTelemetryData } from "../../sendTelemetry.mjs";
import { useState, setState } from "../state.mjs";
import { Option } from "./Options.mjs";
const privacyPolicy = "https://notion-enhancer.github.io/about/privacy-policy/";
function Telemetry() {
const { html, initDatabase } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
_get = async () => {
// defaults to true, must be explicitly set to false to disable
return (await initDatabase().get("telemetryEnabled")) ?? true;

View File

@ -4,11 +4,10 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
function Toggle({ _get, _set, _requireReload = true, ...props }) {
let _initialValue;
const { html, extendProps } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
$input = html`<input
type="checkbox"
class="hidden checked:sibling:children:(

View File

@ -4,10 +4,9 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "../state.mjs";
function View({ id }, ...children) {
const { html } = globalThis.__enhancerApi,
{ setState, useState } = globalThis.__enhancerApi,
// set padding on last child to maintain pad on overflow
$view = html`<article
id=${id}

View File

@ -4,7 +4,6 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
import { setState, useState } from "./state.mjs";
import { checkForUpdate, isDevelopmentBuild } from "../updateCheck.mjs";
import { Sidebar } from "./islands/Sidebar.mjs";
import { Footer } from "./islands/Footer.mjs";
@ -17,6 +16,11 @@ import { Mod } from "./islands/Mod.mjs";
import { Options } from "./islands/Options.mjs";
import { Profiles } from "./islands/Profiles.mjs";
let _apiImported = false,
_renderStarted = false,
_stateHookedInto = false,
_hotkeyRegistered = false;
const categories = [
{
icon: "palette",
@ -85,15 +89,14 @@ const categories = [
})),
];
const render = async () => {
const { html, getMods } = globalThis.__enhancerApi,
{ isEnabled, setEnabled } = globalThis.__enhancerApi,
[icon, renderStarted] = useState(["icon", "renderStarted"]);
if (!html || !getMods || !icon || renderStarted) return;
const renderMenu = async () => {
const { html, ...api } = globalThis.__enhancerApi,
[theme, icon] = api.useState(["theme", "icon"]);
if (!theme || !icon || _renderStarted) return;
if (icon === "Monochrome") sidebar[1].icon += "?mask";
setState({ renderStarted: true });
_renderStarted = true;
const mods = await getMods();
const mods = await api.getMods();
for (let i = 0; i < categories.length; i++) {
const { id } = categories[i];
categories[i].mods = mods.filter(({ _src }) => _src.startsWith(`${id}/`));
@ -104,10 +107,10 @@ const render = async () => {
for (let i = 0; i < mods.length; i++) {
const options = mods[i].options?.filter((opt) => opt.type !== "heading");
if (mods[i]._src === "core" || !options?.length) continue;
const _get = () => isEnabled(mods[i].id),
const _get = () => api.isEnabled(mods[i].id),
_set = async (enabled) => {
await setEnabled(mods[i].id, enabled);
setState({ rerender: true });
await api.setEnabled(mods[i].id, enabled);
api.setState({ rerender: true });
};
mods[i].view = html`<${View} id=${mods[i].id}>
<!-- passing an empty options array hides the settings button -->
@ -145,58 +148,67 @@ const render = async () => {
<${Footer} categories=${categories} />
</main>
`;
useState(["footerOpen"], ([footerOpen]) => {
api.useState(["footerOpen"], ([footerOpen]) => {
$main.style.height = footerOpen ? "100%" : "calc(100% + 33px)";
});
useState(["transitionInProgress"], ([transitionInProgress]) => {
api.useState(["transitionInProgress"], ([transitionInProgress]) => {
$main.children[0].style.overflow = transitionInProgress ? "hidden" : "";
});
const $skeleton = document.querySelector("#skeleton");
$skeleton.replaceWith($sidebar, $main);
};
window.addEventListener("focus", () => {
setState({ focus: true, rerender: true });
});
window.addEventListener("message", (event) => {
if (event.data?.channel !== "notion-enhancer") return;
const [hotkey, theme, icon] = useState(["hotkey", "theme", "icon"]);
setState({
rerender: true,
hotkey: event.data?.hotkey ?? hotkey,
theme: event.data?.theme ?? theme,
icon: event.data?.icon ?? icon,
});
});
useState(["hotkey"], ([hotkey]) => {
const { addKeyListener } = globalThis.__enhancerApi ?? {},
[hotkeyRegistered] = useState(["hotkeyRegistered"]),
[renderStarted] = useState(["renderStarted"]);
if (!hotkey || !addKeyListener || hotkeyRegistered || !renderStarted) return;
setState({ hotkeyRegistered: true });
addKeyListener(hotkey, (event) => {
},
registerHotkey = ([hotkey]) => {
const api = globalThis.__enhancerApi;
if (!hotkey || _hotkeyRegistered) return;
_hotkeyRegistered = true;
api.addKeyListener(hotkey, (event) => {
event.preventDefault();
const msg = { channel: "notion-enhancer", action: "open-menu" };
parent?.postMessage(msg, "*");
});
addKeyListener("Escape", () => {
const [popupOpen] = useState(["popupOpen"]);
api.addKeyListener("Escape", () => {
const [popupOpen] = api.useState(["popupOpen"]);
if (!popupOpen) {
const msg = { channel: "notion-enhancer", action: "close-menu" };
parent?.postMessage(msg, "*");
} else setState({ rerender: true });
} else api.setState({ rerender: true });
});
});
useState(["theme"], ([theme]) => {
},
updateTheme = ([theme]) => {
if (theme === "dark") document.body.classList.add("dark");
if (theme === "light") document.body.classList.remove("dark");
});
};
useState(["rerender"], async () => {
const [theme, icon] = useState(["theme", "icon"]);
if (!theme || !icon) return;
if (typeof globalThis.__enhancerApi === "undefined")
await import("../../shared/system.js");
(await import("../../load.mjs")).default.then(render);
const importApi = async () => {
if (_apiImported) return;
_apiImported = true;
const api = globalThis.__enhancerApi;
if (typeof api === "undefined") await import("../../shared/system.js");
await import("../../load.mjs").then((i) => i.default);
},
hookIntoState = () => {
if (_stateHookedInto) return;
_stateHookedInto = true;
const api = globalThis.__enhancerApi;
api.useState(["rerender"], renderMenu);
api.useState(["hotkey"], registerHotkey);
api.useState(["theme"], updateTheme);
};
window.addEventListener("focus", async () => {
await importApi().then(hookIntoState);
const api = globalThis.__enhancerApi;
api.setState({ focus: true, rerender: true });
});
window.addEventListener("message", async (event) => {
if (event.data?.channel !== "notion-enhancer") return;
await importApi().then(hookIntoState);
const api = globalThis.__enhancerApi;
api.setState({
rerender: true,
hotkey: event.data?.hotkey ?? api.useState(["hotkey"]),
theme: event.data?.theme ?? api.useState(["theme"]),
icon: event.data?.icon ?? api.useState(["icon"]),
});
});

View File

@ -1,24 +0,0 @@
/**
* notion-enhancer
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
const _state = {},
_subscribers = [];
const 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 = (keys, callback) => {
const state = keys.map((key) => _state[key]);
if (callback) _subscribers.push([keys, callback]);
callback?.(state);
return state;
};
export { setState, useState };

View File

@ -6,6 +6,8 @@
"use strict";
// jump to
const { twind, htm, lucide } = globalThis,
{ readFile, iconColour, iconMonochrome } = globalThis.__enhancerApi;
@ -71,6 +73,14 @@ const encodeSvg = (svg) =>
}),
};
};
// at-runtime utility class evaluation w/ twind:
// - feature parity w/ tailwind v3
// - useful for building self-contained components
// (mods can extend interfaces w/out needing to
// import additional stylesheets)
// - integrated with lucide to render icons w/out
// complex markup, e.g. `<i class="i-bookmark" />`
twind.install({
darkMode: "class",
rules: [[/^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, presetIcons]],
@ -87,14 +97,6 @@ twind.install({
["\\[.+]", (match) => "&" + match.input],
["([a-z-]+):", ({ 1: $1 }) => "&::" + $1],
],
theme: {
extend: {
backgroundImage: {
"brand-gradient":
"linear-gradient(225deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 100%)",
},
},
},
});
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element
@ -535,7 +537,11 @@ const svgElements = [
"zoomAndPan",
];
// html`<div class=${className}></div>`
// enables use of the jsx-like htm syntax
// for building components and interfaces
// with tagged templates. instantiates dom
// elements directly, does not use a vdom.
// e.g. html`<div class=${className}></div>`
const h = (type, props, ...children) => {
children = children.flat(Infinity);
// html`<${Component} attr="value">Click Me<//>`
@ -557,9 +563,12 @@ const h = (type, props, ...children) => {
elem.append(...children);
return elem;
},
html = htm.bind(h);
const extendProps = (props, extend) => {
// 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]: userProvided } = props;
if (typeof extend[key] === "function") {
@ -574,7 +583,41 @@ const extendProps = (props, extend) => {
} else props[key] = extend[key] ?? userProvided;
}
return props;
},
html = htm.bind(h);
// 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) _subscribers.push([keys, callback]);
callback?.(state);
return state;
};
globalThis.__enhancerApi ??= {};
Object.assign(globalThis.__enhancerApi, { html, extendProps });
Object.assign(globalThis.__enhancerApi, {
html,
extendProps,
setState,
useState,
});