feat(menu): notion-styled menu sidebar

This commit is contained in:
dragonwocky 2023-01-10 22:48:12 +11:00
parent bb7f044d3a
commit 70cd128a46
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
6 changed files with 269 additions and 48 deletions

View File

@ -1,6 +1,6 @@
/** /**
* notion-enhancer * notion-enhancer
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2023 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
*/ */
@ -22,25 +22,93 @@ const kebabToPascalCase = (string) =>
`<${type}${Object.entries(props) `<${type}${Object.entries(props)
.map(([attr, value]) => ` ${attr}="${value}"`) .map(([attr, value]) => ` ${attr}="${value}"`)
.join("")}>${children .join("")}>${children
.flat(Infinity) .map((child) => (Array.isArray(child) ? hToString(...child) : child))
.map(([tag, attrs, children]) => hToString(tag, attrs, children))
.join("")}</${type}>`; .join("")}</${type}>`;
// https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 // https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
const encodeSvg = (svg) => const encodeSvg = (svg) =>
svg svg
.replace( .replace(
"<svg", "<svg",
~svg.indexOf("xmlns") ? "<svg" : '<svg xmlns="http://www.w3.org/2000/svg"' ~svg.indexOf("xmlns")
) ? "<svg"
.replace(/"/g, "'") : '<svg xmlns="http://www.w3.org/2000/svg"'
.replace(/%/g, "%25") )
.replace(/#/g, "%23") .replace(/"/g, "'")
.replace(/{/g, "%7B") .replace(/%/g, "%25")
.replace(/}/g, "%7D") .replace(/#/g, "%23")
.replace(/</g, "%3C") .replace(/{/g, "%7B")
.replace(/>/g, "%3E") .replace(/}/g, "%7D")
.replace(/\s+/g, " "); .replace(/</g, "%3C")
.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({ twind.install({
rules: [ rules: [
@ -56,39 +124,47 @@ twind.install({
} else { } else {
icon = kebabToPascalCase(icon); icon = kebabToPascalCase(icon);
if (!globalThis.lucide[icon]) return; 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 // https://antfu.me/posts/icons-in-pure-css
const dataUri = `url("data:image/svg+xml;utf8,${encodeSvg(svg)}")`; const dataUri = `url("data:image/svg+xml;utf8,${encodeSvg(svg)}")`;
if (mode === "auto") mode = undefined; if (mode === "auto") mode = undefined;
mode ??= svg.includes("currentColor") ? "mask" : "bg"; mode ??= svg.includes("currentColor") ? "mask" : "bg";
return mode === "mask" return {
? { display: "inline-block",
mask: `${dataUri} no-repeat`, height: "1em",
"mask-size": "100% 100%", width: "1em",
"background-color": "currentColor", ...(mode === "mask"
color: "inherit", ? {
height: "1em", mask: `${dataUri} no-repeat`,
width: "1em", "mask-size": "100% 100%",
} "background-color": "currentColor",
: { color: "inherit",
background: `${dataUri} no-repeat`, }
"background-size": "100% 100%", : {
"background-color": "transparent", background: `${dataUri} no-repeat`,
height: "1em", "background-size": "100% 100%",
width: "1em", "background-color": "transparent",
}; }),
};
}, },
], ],
], ],
variants: [["open", "&[open]"]], variants: [["open", "&[open]"]],
}); });
// construct elements via tagged tagged // html`<div class=${className}></div>`
// e.g. html`<div class=${className}></div>`
const h = (type, props, ...children) => { const h = (type, props, ...children) => {
const elem = document.createElement(type); children = children.flat(Infinity);
for (const prop in props) { // 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])) { if (["string", "number", "boolean"].includes(typeof props[prop])) {
elem.setAttribute(prop, props[prop]); elem.setAttribute(prop, props[prop]);
} else elem[prop] = props[prop]; } else elem[prop] = props[prop];

View File

@ -1,6 +1,6 @@
/** /**
* notion-enhancer * notion-enhancer
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2023 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
*/ */

View File

@ -0,0 +1,95 @@
/**
* notion-enhancer
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
const Sidebar = ({}, ...children) => {
const { html } = globalThis.__enhancerApi;
return html`<aside
class="notion-enhancer--menu-sidebar h-full w-[250px]
overflow-y-auto bg-[color:var(--theme--bg-secondary)]"
>
${children}
</aside>`;
};
const SidebarSection = ({}, ...children) => {
const { html } = globalThis.__enhancerApi;
return html`<div
class="text-([11px] [color:var(--theme--fg-secondary)])
py-[5px] px-[15px] mb-px mt-[18px] first:mt-[10px]
uppercase font-medium tracking-[0.03em] leading-none"
>
${children}
</div>`;
};
const SidebarButton = ({ icon, ...props }, ...children) => {
const { html } = globalThis.__enhancerApi;
return html`<a
tabindex="0"
role="button"
class="flex select-none cursor-pointer
items-center py-[5px] px-[15px] text-[14px] last:mb-[12px]
transition hover:bg-[color:var(--theme--bg-hover)]"
...${props}
>
<i
class="i-${icon} ${icon === "notion-enhancer"
? "w-[18px] h-[18px] ml-px mr-[9px]"
: "w-[20px] h-[20px] mr-[8px]"}"
></i>
<span class="leading-[20px]">${children}</span>
</a>`;
};
export { Sidebar, SidebarSection, SidebarButton };
// <div
// class="notion-focusable"
// role="button"
// tabindex="0"
// style="
// display: flex;
// align-items: center;
// justify-content: space-between;
// padding: 5px 15px;
// "
// >
// <div style="display: flex; align-items: center">
// <div
// style="
// width: 20px;
// height: 20px;
// margin-right: 8px;
// color: rgba(255, 255, 255, 0.81);
// fill: rgba(255, 255, 255, 0.81);
// "
// >
// <svg
// viewBox="0 0 20 20"
// class="settingsIntegration"
// style="
// width: 20px;
// height: 20px;
// display: block;
// fill: inherit;
// flex-shrink: 0;
// backface-visibility: hidden;
// "
// >
// <path d="M4.633 9.42h3.154c1.093 0 1.632-.532 1.632-1.656V4.655C9.42 3.532 8.88 3 7.787 3H4.633C3.532 3 3 3.532 3 4.655v3.109c0 1.124.532 1.655 1.633 1.655zm7.58 0h3.162C16.468 9.42 17 8.887 17 7.763V4.655C17 3.532 16.468 3 15.374 3h-3.16c-1.094 0-1.633.532-1.633 1.655v3.109c0 1.124.539 1.655 1.633 1.655zm-7.58-1.251c-.262 0-.382-.135-.382-.405V4.648c0-.27.12-.405.382-.405h3.146c.262 0 .39.135.39.405v3.116c0 .27-.128.405-.39.405H4.633zm7.588 0c-.262 0-.39-.135-.39-.405V4.648c0-.27.128-.405.39-.405h3.146c.262 0 .39.135.39.405v3.116c0 .27-.128.405-.39.405h-3.146zM4.633 17h3.154c1.093 0 1.632-.532 1.632-1.655v-3.109c0-1.124-.539-1.655-1.632-1.655H4.633C3.532 10.58 3 11.112 3 12.236v3.109C3 16.468 3.532 17 4.633 17zm7.58 0h3.162C16.468 17 17 16.468 17 15.345v-3.109c0-1.124-.532-1.655-1.626-1.655h-3.16c-1.094 0-1.633.531-1.633 1.655v3.109c0 1.123.539 1.655 1.633 1.655zm-7.58-1.25c-.262 0-.382-.128-.382-.398v-3.116c0-.277.12-.405.382-.405h3.146c.262 0 .39.128.39.405v3.116c0 .27-.128.397-.39.397H4.633zm7.588 0c-.262 0-.39-.128-.39-.398v-3.116c0-.277.128-.405.39-.405h3.146c.262 0 .39.128.39.405v3.116c0 .27-.128.397-.39.397h-3.146z"></path>
// </svg>
// </div>
// <div
// style="
// font-size: 14px;
// line-height: 20px;
// color: rgba(255, 255, 255, 0.81);
// "
// >
// Connections
// </div>
// </div>
// </div>;

View File

@ -8,6 +8,6 @@
<script src="./menu.mjs" type="module" defer></script> <script src="./menu.mjs" type="module" defer></script>
</head> </head>
<body <body
class="w-screen h-screen text-[color:var(--theme--fg-primary)] font-[family:var(--theme--font-sans)]" class="flex flex-row w-screen h-screen text-[color:var(--theme--fg-primary)] font-[family:var(--theme--font-sans)]"
></body> ></body>
</html> </html>

View File

@ -1,10 +1,13 @@
/** /**
* notion-enhancer * notion-enhancer
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2023 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
*/ */
let stylesLoaded = false; import { Sidebar, SidebarSection, SidebarButton } from "./components.mjs";
let stylesLoaded = false,
sidebarPopulated = false;
const importApi = async () => { const importApi = async () => {
// chrome extensions run in an isolated execution context // chrome extensions run in an isolated execution context
// but extension:// pages can access chrome apis // but extension:// pages can access chrome apis
@ -12,17 +15,15 @@ const importApi = async () => {
if (typeof globalThis.__enhancerApi === "undefined") { if (typeof globalThis.__enhancerApi === "undefined") {
await import("../../api/browser.js"); 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) // not running in an isolated execution context and b)
// the notion:// protocol csp bypass allows scripts to // the notion:// protocol csp bypass allows scripts to
// set iframe globals via $iframe.contentWindow // set iframe globals via $iframe.contentWindow
}, },
importStyles = async () => { importStyles = async () => {
if (!stylesLoaded) { if (stylesLoaded) return false;
// clientStyles + twind/htm/etc. stylesLoaded = true;
await import("../../load.mjs"); await import("../../load.mjs");
stylesLoaded = true;
}
}, },
updateTheme = (mode) => { updateTheme = (mode) => {
if (mode === "dark") { if (mode === "dark") {
@ -30,6 +31,54 @@ const importApi = async () => {
} else if (mode === "light") { } else if (mode === "light") {
document.body.classList.remove("dark"); 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) => { window.addEventListener("message", async (event) => {
@ -37,4 +86,5 @@ window.addEventListener("message", async (event) => {
updateTheme(event.data?.mode); updateTheme(event.data?.mode);
await importApi(); await importApi();
await importStyles(); await importStyles();
populateSidebar();
}); });

View File

@ -56,7 +56,7 @@
"value": false "value": false
} }
], ],
"clientStyles": [], "clientStyles": ["variables.css"],
"clientScripts": ["client.mjs"], "clientScripts": ["client.mjs"],
"electronScripts": [] "electronScripts": []
} }