mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-10 15:39:01 +00:00
feat(menu): notion-styled menu sidebar
This commit is contained in:
parent
bb7f044d3a
commit
70cd128a46
@ -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];
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
95
src/core/menu/components.mjs
Normal file
95
src/core/menu/components.mjs
Normal 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>;
|
@ -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>
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
"value": false
|
"value": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"clientStyles": [],
|
"clientStyles": ["variables.css"],
|
||||||
"clientScripts": ["client.mjs"],
|
"clientScripts": ["client.mjs"],
|
||||||
"electronScripts": []
|
"electronScripts": []
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user