From 70cd128a468f5cf42e908f413c64a3c3e5110a65 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 10 Jan 2023 22:48:12 +1100 Subject: [PATCH] feat(menu): notion-styled menu sidebar --- src/api/interface.js | 150 ++++++++++++++++++++++++++--------- src/core/client.mjs | 2 +- src/core/menu/components.mjs | 95 ++++++++++++++++++++++ src/core/menu/index.html | 2 +- src/core/menu/menu.mjs | 66 +++++++++++++-- src/core/mod.json | 2 +- 6 files changed, 269 insertions(+), 48 deletions(-) create mode 100644 src/core/menu/components.mjs diff --git a/src/api/interface.js b/src/api/interface.js index a302ced..28a1ae8 100644 --- a/src/api/interface.js +++ b/src/api/interface.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 */ @@ -22,25 +22,93 @@ const kebabToPascalCase = (string) => `<${type}${Object.entries(props) .map(([attr, value]) => ` ${attr}="${value}"`) .join("")}>${children - .flat(Infinity) - .map(([tag, attrs, children]) => hToString(tag, attrs, children)) + .map((child) => (Array.isArray(child) ? hToString(...child) : child)) .join("")}`; // https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 const encodeSvg = (svg) => - svg - .replace( - "/g, "%3E") - .replace(/\s+/g, " "); + svg + .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", + ]; twind.install({ rules: [ @@ -56,39 +124,47 @@ twind.install({ } else { icon = kebabToPascalCase(icon); if (!globalThis.lucide[icon]) return; - svg = hToString(...globalThis.lucide[icon]); + 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 mode === "mask" - ? { - mask: `${dataUri} no-repeat`, - "mask-size": "100% 100%", - "background-color": "currentColor", - color: "inherit", - height: "1em", - width: "1em", - } - : { - background: `${dataUri} no-repeat`, - "background-size": "100% 100%", - "background-color": "transparent", - height: "1em", - width: "1em", - }; + 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", + }), + }; }, ], ], variants: [["open", "&[open]"]], }); -// construct elements via tagged tagged -// e.g. html`
` +// html`
` const h = (type, props, ...children) => { - const elem = document.createElement(type); - for (const prop in props) { + children = children.flat(Infinity); + // html`<${Component} attr="value">Click Me` + if (typeof type === "function") { + return type(props ?? {}, ...children); + } + const elem = svgElements.includes(type) + ? document.createElementNS("http://www.w3.org/2000/svg", type) + : document.createElement(type); + for (const prop in props ?? {}) { if (["string", "number", "boolean"].includes(typeof props[prop])) { elem.setAttribute(prop, props[prop]); } else elem[prop] = props[prop]; diff --git a/src/core/client.mjs b/src/core/client.mjs index bd4ffe6..1af6e77 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -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 */ diff --git a/src/core/menu/components.mjs b/src/core/menu/components.mjs new file mode 100644 index 0000000..d3abb56 --- /dev/null +++ b/src/core/menu/components.mjs @@ -0,0 +1,95 @@ +/** + * notion-enhancer + * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +const Sidebar = ({}, ...children) => { + const { html } = globalThis.__enhancerApi; + return html``; +}; + +const SidebarSection = ({}, ...children) => { + const { html } = globalThis.__enhancerApi; + return html`
+ ${children} +
`; +}; + +const SidebarButton = ({ icon, ...props }, ...children) => { + const { html } = globalThis.__enhancerApi; + return html` + + ${children} + `; +}; + +export { Sidebar, SidebarSection, SidebarButton }; + +//
+//
+//
+// +// +// +//
+//
+// Connections +//
+//
+//
; diff --git a/src/core/menu/index.html b/src/core/menu/index.html index 56b5199..a15ccc7 100644 --- a/src/core/menu/index.html +++ b/src/core/menu/index.html @@ -8,6 +8,6 @@ diff --git a/src/core/menu/menu.mjs b/src/core/menu/menu.mjs index 2922e35..0837f4a 100644 --- a/src/core/menu/menu.mjs +++ b/src/core/menu/menu.mjs @@ -1,10 +1,13 @@ /** * notion-enhancer - * (c) 2022 dragonwocky (https://dragonwocky.me/) + * (c) 2023 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ -let stylesLoaded = false; +import { Sidebar, SidebarSection, SidebarButton } from "./components.mjs"; + +let stylesLoaded = false, + sidebarPopulated = false; const importApi = async () => { // chrome extensions run in an isolated execution context // but extension:// pages can access chrome apis @@ -12,17 +15,15 @@ const importApi = async () => { if (typeof globalThis.__enhancerApi === "undefined") { await import("../../api/browser.js"); } - // in electron this is not necessary, as a) scripts are + // in electron this isn't necessary, as a) scripts are // not running in an isolated execution context and b) // the notion:// protocol csp bypass allows scripts to // set iframe globals via $iframe.contentWindow }, importStyles = async () => { - if (!stylesLoaded) { - // clientStyles + twind/htm/etc. - await import("../../load.mjs"); - stylesLoaded = true; - } + if (stylesLoaded) return false; + stylesLoaded = true; + await import("../../load.mjs"); }, updateTheme = (mode) => { if (mode === "dark") { @@ -30,6 +31,54 @@ const importApi = async () => { } else if (mode === "light") { document.body.classList.remove("dark"); } + }, + populateSidebar = () => { + const { html } = globalThis.__enhancerApi; + if (!html || sidebarPopulated) return; + sidebarPopulated = true; + document.body.append(html`<${Sidebar}> + ${[ + "notion-enhancer", + { icon: "notion-enhancer", title: "Welcome", onClick() {} }, + { + icon: "message-circle", + title: "Community", + href: "https://discord.gg/sFWPXtA", + }, + { + icon: "clock", + title: "Changelog", + href: "https://notion-enhancer.github.io/about/changelog/", + }, + { + icon: "book", + title: "Documentation", + href: "https://notion-enhancer.github.io/", + }, + { + icon: "github", + title: "Source Code", + href: "https://github.com/notion-enhancer", + }, + { + icon: "coffee", + title: "Sponsor", + href: "https://github.com/sponsors/dragonwocky", + }, + "Settings", + { icon: "sliders-horizontal", title: "Core", onClick() {} }, + { icon: "palette", title: "Themes", onClick() {} }, + { icon: "zap", title: "Extensions", onClick() {} }, + { icon: "plug", title: "Integrations", onClick() {} }, + ].map((item) => { + if (typeof item === "string") { + return html`<${SidebarSection}>${item}`; + } else { + const { title, ...props } = item; + return html`<${SidebarButton} ...${props}>${title}`; + } + })} + `); }; window.addEventListener("message", async (event) => { @@ -37,4 +86,5 @@ window.addEventListener("message", async (event) => { updateTheme(event.data?.mode); await importApi(); await importStyles(); + populateSidebar(); }); diff --git a/src/core/mod.json b/src/core/mod.json index d3fd098..11c6ef1 100644 --- a/src/core/mod.json +++ b/src/core/mod.json @@ -56,7 +56,7 @@ "value": false } ], - "clientStyles": [], + "clientStyles": ["variables.css"], "clientScripts": ["client.mjs"], "electronScripts": [] }