From ac5daf5b73f083116fc63fbccb7b810ec0099cde Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 11 Jan 2023 17:29:57 +1100 Subject: [PATCH] feat(menu): reactive view navigation --- src/api/events.js | 3 +- src/api/interface.js | 215 +++++++++++++++++------------------ src/core/client.mjs | 36 ++++-- src/core/menu/components.mjs | 104 +++++++---------- src/core/menu/index.html | 1 + src/core/menu/menu.mjs | 124 ++++++++++---------- src/core/menu/state.mjs | 21 ++++ src/init.js | 4 +- src/load.mjs | 4 +- 9 files changed, 266 insertions(+), 246 deletions(-) create mode 100644 src/core/menu/state.mjs diff --git a/src/api/events.js b/src/api/events.js index cd76ba3..cb8141a 100644 --- a/src/api/events.js +++ b/src/api/events.js @@ -1,6 +1,6 @@ /** * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) + * (c) 2023 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -38,6 +38,7 @@ const mutationQueue = [], mutationQueue.push(...mutations); }); documentObserver.observe(document.body, { + attributes: true, childList: true, subtree: true, }); diff --git a/src/api/interface.js b/src/api/interface.js index 28a1ae8..0e44bde 100644 --- a/src/api/interface.js +++ b/src/api/interface.js @@ -25,8 +25,8 @@ const kebabToPascalCase = (string) => .map((child) => (Array.isArray(child) ? hToString(...child) : child)) .join("")}`; -// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 const encodeSvg = (svg) => + // https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 svg .replace( " .replace(//g, "%3E") .replace(/\s+/g, " "), - 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", - ]; - + presetIcons = ([, icon, mode]) => { + let svg; + // manually register i-notion-enhancer: renders the colour + // version by default, renders the monochrome version when + // mask mode is requested via i-notion-enhancer?mask + if (icon === "notion-enhancer") { + svg = mode === "mask" ? iconMonochrome : iconColour; + } else { + icon = kebabToPascalCase(icon); + if (!globalThis.lucide[icon]) return; + const [type, props, children] = globalThis.lucide[icon]; + svg = hToString(type, props, ...children); + } + // https://antfu.me/posts/icons-in-pure-css + const dataUri = `url("data:image/svg+xml;utf8,${encodeSvg(svg)}")`; + if (mode === "auto") mode = undefined; + mode ??= svg.includes("currentColor") ? "mask" : "bg"; + return { + display: "inline-block", + height: "1em", + width: "1em", + ...(mode === "mask" + ? { + mask: `${dataUri} no-repeat`, + "mask-size": "100% 100%", + "background-color": "currentColor", + color: "inherit", + } + : { + background: `${dataUri} no-repeat`, + "background-size": "100% 100%", + "background-color": "transparent", + }), + }; + }; twind.install({ - rules: [ - [ - /^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, - ([, icon, mode]) => { - let svg; - // manually register i-notion-enhancer: renders the colour - // version by default, renders the monochrome version when - // mask mode is requested via i-notion-enhancer?mask - if (icon === "notion-enhancer") { - svg = mode === "mask" ? iconMonochrome : iconColour; - } else { - icon = kebabToPascalCase(icon); - if (!globalThis.lucide[icon]) return; - const [type, props, children] = globalThis.lucide[icon]; - svg = hToString(type, props, ...children); - } - // https://antfu.me/posts/icons-in-pure-css - const dataUri = `url("data:image/svg+xml;utf8,${encodeSvg(svg)}")`; - if (mode === "auto") mode = undefined; - mode ??= svg.includes("currentColor") ? "mask" : "bg"; - return { - display: "inline-block", - height: "1em", - width: "1em", - ...(mode === "mask" - ? { - mask: `${dataUri} no-repeat`, - "mask-size": "100% 100%", - "background-color": "currentColor", - color: "inherit", - } - : { - background: `${dataUri} no-repeat`, - "background-size": "100% 100%", - "background-color": "transparent", - }), - }; - }, - ], - ], + rules: [[/^i-((?:\w|-)+)(?:\?(mask|bg|auto))?$/, presetIcons]], variants: [["open", "&[open]"]], }); +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", +]; // html`
` const h = (type, props, ...children) => { children = children.flat(Infinity); @@ -169,7 +164,7 @@ const h = (type, props, ...children) => { elem.setAttribute(prop, props[prop]); } else elem[prop] = props[prop]; } - for (const child of children) elem.append(child); + elem.append(...children); return elem; }, html = htm.bind(h); diff --git a/src/core/client.mjs b/src/core/client.mjs index 1af6e77..062cc89 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -45,20 +45,24 @@ export default async (api, db) => { // menu - let $menuModal, $menuFrame; - const setTheme = () => { - if (platform !== "browser") $menuFrame.contentWindow.__enhancerApi = api; + let $menuModal, $menuFrame, _notionTheme; + const updateTheme = (force = false) => { + const darkMode = document.body.classList.contains("dark"), + notionTheme = darkMode ? "dark" : "light"; + if (notionTheme !== _notionTheme || force) { + _notionTheme = notionTheme; const msg = { namespace: "notion-enhancer", - mode: document.body.classList.contains("dark") ? "dark" : "light", + mode: notionTheme, }; - $menuFrame.contentWindow.postMessage(msg, "*"); - }, - openMenu = () => { - if ($menuFrame) setTheme(); + $menuFrame?.contentWindow.postMessage(msg, "*"); + } + }; + + const openMenu = () => { $menuModal?.setAttribute("open", true); }, - closeMenu = () => $menuModal.removeAttribute("open"); + closeMenu = () => $menuModal?.removeAttribute("open"); $menuFrame = html``; $menuModal = html`
{ + if ($menuModal?.hasAttribute("open")) updateTheme(); + }); onMessage("notion-enhancer", (message) => { if (message === "open-menu") openMenu(); }); diff --git a/src/core/menu/components.mjs b/src/core/menu/components.mjs index d3abb56..d70614b 100644 --- a/src/core/menu/components.mjs +++ b/src/core/menu/components.mjs @@ -4,6 +4,8 @@ * (https://notion-enhancer.github.io/) under the MIT license */ +import { setState, useState } from "./state.mjs"; + const Sidebar = ({}, ...children) => { const { html } = globalThis.__enhancerApi; return html`