feat(menu): text, number, hotkey & color input types

- replaced jscolor with coloris
This commit is contained in:
dragonwocky 2023-01-12 15:24:01 +11:00
parent f85b3c1359
commit 8fe05b2888
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
10 changed files with 260 additions and 88 deletions

View File

@ -13,8 +13,10 @@ const dependencies = {
"htm.min.js": "https://unpkg.com/htm@3.1.1/mini/index.js",
"twind.min.js": "https://unpkg.com/@twind/cdn@1.0.7/cdn.global.js",
"lucide.min.js": "https://unpkg.com/lucide@0.104.0/dist/umd/lucide.min.js",
"jscolor.min.js":
"https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.5.1/jscolor.min.js",
"coloris.min.js":
"https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.js",
"coloris.min.css":
"https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.css",
};
const output = fileURLToPath(new URL("../src/vendor", import.meta.url)),

View File

@ -77,7 +77,11 @@ const modifierAliases = [
return true;
}
}
if (key === "plus") key = "+";
if (key === "space") key = " ";
if (key === "plus") key = "equal";
if (key === "minus") key = "-";
if (key === ",") key = "comma";
if (key === ".") key = "period";
const keyPressed = [
event.key.toLowerCase(),
event.code.toLowerCase(),

View File

@ -543,7 +543,7 @@ const h = (type, props, ...children) => {
? document.createElementNS("http://www.w3.org/2000/svg", type)
: document.createElement(type);
for (const prop in props ?? {}) {
if (htmlAttributes.includes(prop)) {
if (htmlAttributes.includes(prop) || prop.startsWith("data-")) {
elem.setAttribute(prop, props[prop]);
} else elem[prop] = props[prop];
}

View File

@ -6,7 +6,7 @@
import { setState, useState } from "./state.mjs";
const Sidebar = ({}, ...children) => {
function Sidebar({}, ...children) {
const { html } = globalThis.__enhancerApi;
return html`<aside
class="notion-enhancer--menu-sidebar min-w-[224.14px] max-w-[250px]
@ -14,20 +14,20 @@ const Sidebar = ({}, ...children) => {
>
${children}
</aside>`;
};
}
const SidebarSection = ({}, ...children) => {
function SidebarSection({}, ...children) {
const { html } = globalThis.__enhancerApi;
return html`<div
return html`<h2
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>`;
};
</h2>`;
}
const SidebarButton = ({ icon, ...props }, ...children) => {
function SidebarButton({ icon, ...props }, ...children) {
const { html } = globalThis.__enhancerApi,
iconSize =
icon === "notion-enhancer"
@ -52,9 +52,9 @@ const SidebarButton = ({ icon, ...props }, ...children) => {
});
}
return $el;
};
}
const View = ({ id }, ...children) => {
function View({ id }, ...children) {
const { html } = globalThis.__enhancerApi,
$el = html`<article
id=${id}
@ -68,23 +68,215 @@ const View = ({ id }, ...children) => {
$el.style.display = active ? "" : "none";
});
return $el;
};
}
const TextInput = ({}, ...children) => {};
function Option({ mod, type, ...props }) {
const { html } = globalThis.__enhancerApi,
camelToSentenceCase = (string) =>
string[0].toUpperCase() +
string.replace(/[A-Z]/g, (match) => ` ${match.toLowerCase()}`).slice(1);
const NumberInput = ({}, ...children) => {};
const label = props.label ?? camelToSentenceCase(props.key),
description = props.description;
if (type === "heading") {
return html`<h3
class="notion-enhancer--menu-heading font-semibold
mb-[16px] mt-[48px] first:mt-0 pb-[12px] text-[16px]
border-b border-b-[color:var(--theme--fg-border)]"
>
${label}
</h3>`;
}
const HotkeyInput = ({}, ...children) => {};
let $input;
switch (type) {
case "text":
$input = html`<${TextInput} value=${props.value} />`;
break;
case "number":
$input = html`<${NumberInput} value=${props.value} />`;
break;
case "hotkey":
$input = html`<${HotkeyInput} value=${props.value} />`;
break;
case "color":
$input = html`<${ColorInput} value=${props.value} />`;
break;
case "file":
$input = html`<${FileInput} extensions=${props.extensions} />`;
break;
case "select":
$input = html`<${Select} values=${props.values} />`;
break;
case "toggle":
$input = html`<${Toggle} />`;
}
return html`<${type === "toggle" ? "label" : "div"}
class="notion-enhancer--menu-option flex items-center justify-between
mb-[18px] ${type === "toggle" ? "cursor-pointer" : ""}"
><div class="flex flex-col ${type === "text" ? "w-full" : "mr-[10%]"}">
<h4 class="text-[14px] mb-[2px] mt-0">${label}</h4>
${type === "text" ? $input : ""}
<p
class="text-[12px] leading-[16px]
text-[color:var(--theme--fg-secondary)]"
innerHTML=${description}
></p>
</div>
${type === "text" ? "" : $input}
<//>`;
}
const ColorInput = ({}, ...children) => {};
function TextInput({ value, ...props }) {
const { html } = globalThis.__enhancerApi;
return html`<label
class="notion-enhancer--menu-text-input
relative block w-full mt-[4px] mb-[8px]"
>
<input
type="text"
class="appearance-none text-[14px] leading-[1.2] rounded-[4px] pb-px
h-[28px] w-full pl-[8px] pr-[30px] bg-[color:var(--theme--bg-hover)]"
value=${value}
...${props}
/>
<i
class="i-text-cursor pointer-events-none
absolute right-[8px] top-[6px] w-[16px] h-[16px]
text-[color:var(--theme--fg-secondary)]"
></i>
</label>`;
}
const FileInput = ({ extensions, ...props }, ..._children) => {
function NumberInput({ value, ...props }) {
const { html } = globalThis.__enhancerApi;
return html`<label
class="notion-enhancer--menu-number-input
relative shrink-0 w-[192px]"
>
<input
type="number"
class="appearance-none text-[14px] leading-[1.2] rounded-[4px] pb-px
h-[28px] w-full pl-[8px] pr-[32px] bg-[color:var(--theme--bg-hover)]"
value=${value}
...${props}
/>
<i
class="i-hash pointer-events-none
absolute right-[8px] top-[6px] w-[16px] h-[16px]
text-[color:var(--theme--fg-secondary)]"
></i>
</label>`;
}
function HotkeyInput({ value, onkeydown, ...props }) {
const { html } = globalThis.__enhancerApi,
updateHotkey = (event) => {
event.preventDefault();
const keys = [];
for (const modifier of ["metaKey", "ctrlKey", "altKey", "shiftKey"]) {
if (!event[modifier]) continue;
const alias = modifier[0].toUpperCase() + modifier.slice(1, -3);
keys.push(alias);
}
if (!keys.length && ["Backspace", "Delete"].includes(event.key)) {
event.target.value = "";
} else if (event.key) {
let key = event.key;
if (key === " ") key = "Space";
if (["+", "="].includes(key)) key = "Plus";
if (key === "-") key = "Minus";
if (event.code === "Comma") key = ",";
if (event.code === "Period") key = ".";
if (key === "Control") key = "Ctrl";
// avoid e.g. Shift+Shift, force inclusion of non-modifier
if (keys.includes(event.key)) return;
keys.push(key.length === 1 ? key.toUpperCase() : key);
event.target.value = keys.join("+");
}
};
props.onkeydown = (event) => {
updateHotkey(event);
onkeydown?.(event);
};
return html`<label
class="notion-enhancer--menu-hotkey-input
relative shrink-0 w-[192px]"
>
<input
type="text"
class="appearance-none text-[14px] leading-[1.2] rounded-[4px] pb-px
h-[28px] w-full pl-[8px] pr-[32px] bg-[color:var(--theme--bg-hover)]"
value=${value}
...${props}
/>
<i
class="i-command pointer-events-none
absolute right-[8px] top-[6px] w-[16px] h-[16px]
text-[color:var(--theme--fg-secondary)]"
></i>
</label>`;
}
function ColorInput({ value, ...props }) {
const { html } = globalThis.__enhancerApi,
updateContrast = ($input, $icon) => {
$input.style.background = $input.value;
const [r, g, b, a = 1] = $input.value
.replace(/^rgba?\(/, "")
.replace(/\)$/, "")
.split(",")
.map((n) => parseFloat(n));
if (a > 0.5) {
// pick a contrasting foreground for an rgb background
// using the percieved brightness constants from http://alienryderflex.com/hsp.html
const brightness = 0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b);
$input.style.color = Math.sqrt(brightness) > 165.75 ? "#000" : "#fff";
} else $input.style.color = "#000";
$icon.style.color = $input.style.color;
};
const $input = html`<input
type="text"
class="appearance-none text-[14px] leading-[1.2]
h-[28px] w-full pl-[8px] pr-[32px] pb-px"
style="background: ${value}"
value=${value}
data-coloris
...${props}
/>`,
$icon = html`<i
class="i-pipette pointer-events-none absolute opacity-70
right-[8px] top-[6px] w-[16px] h-[16px] text-current"
></i>`,
{ oninput } = $input;
$input.oninput = (event) => {
oninput?.(event);
updateContrast($input, $icon);
};
updateContrast($input, $icon);
Coloris({ format: "rgb" });
return html`<label
class="notion-enhancer--menu-color-input shrink-0
relative overflow-hidden rounded-[4px] w-[192px] bg-(
[image:repeating-linear-gradient(45deg,#aaa_25%,transparent_25%,transparent_75%,#aaa_75%,#aaa),repeating-linear-gradient(45deg,#aaa_25%,#fff_25%,#fff_75%,#aaa_75%,#aaa)]
[position:0_0,4px_4px]
[size:8px_8px]
)"
>
${$input}${$icon}
</label>`;
}
function FileInput({ extensions, ...props }) {
// todo: show uploaded file name, clear prev upload
const { html } = globalThis.__enhancerApi;
return html`<label
tabindex="0"
class="flex items-center h-[28px] max-w-[256px]
leading-[1.2] px-[8px] rounded-[4px] cursor-pointer select-none
class="notion-enhancer--menu-file-input flex shrink-0 items-center
h-[28px] leading-[1.2] px-[8px] rounded-[4px] cursor-pointer select-none
text-[14px] text-[color:var(--theme--fg-secondary)] transition duration-[20ms]
bg-[color:var(--theme--bg-secondary)] hover:bg-[color:var(--theme--bg-hover)]"
>
@ -99,13 +291,13 @@ const FileInput = ({ extensions, ...props }, ..._children) => {
<i class="i-file-up w-[16px] h-[16px] mr-[8px]"></i>
<span>Upload a file</span>
</label>`;
};
}
const Select = ({ values, onchange, ...props }, ..._children) => {
function Select({ values, onchange, ...props }) {
const { html } = globalThis.__enhancerApi,
updateWidth = ($select) => {
const $tmp = html`<span
class="text-[14px] pl-[8px] pr-[32px]
class="text-[14px] pl-[8px] pr-[28px]
absolute top-[-9999px] left-[-9999px]"
>${$select.value}</span
>`;
@ -121,8 +313,8 @@ const Select = ({ values, onchange, ...props }, ..._children) => {
};
const $select = html`<select
class="appearance-none bg-transparent rounded-[4px] max-w-[256px]
text-[14px] leading-[1.2] h-[28px] pl-[8px] pr-[32px] cursor-pointer
class="appearance-none bg-transparent rounded-[4px] cursor-pointer
text-[14px] leading-[1.2] pl-[8px] pr-[28px] h-[28px] max-w-[256px]
transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
...${props}
>
@ -142,13 +334,13 @@ const Select = ({ values, onchange, ...props }, ..._children) => {
${$select}
<i
class="i-chevron-down pointer-events-none
absolute right-[8px] top-[6px] w-[16px] h-[16px]
absolute right-[6px] top-[6px] w-[16px] h-[16px]
text-[color:var(--theme--fg-secondary)]"
></i>
</div>`;
};
}
const Toggle = (props, ..._children) => {
function Toggle(props) {
const { html } = globalThis.__enhancerApi;
return html`<div class="notion-enhancer--menu-toggle shrink-0">
<input
@ -174,67 +366,19 @@ const Toggle = (props, ..._children) => {
></div>
</div>
</div>`;
};
const Option = ({ mod, type, ...props }, ..._children) => {
const { html } = globalThis.__enhancerApi,
camelToSentenceCase = (string) =>
string[0].toUpperCase() +
string.replace(/[A-Z]/g, (match) => ` ${match.toLowerCase()}`).slice(1);
const label = props.label ?? camelToSentenceCase(props.key),
description = props.description;
if (type === "heading") {
return html`<h2
class="notion-enhancer--menu-heading font-semibold
mb-[16px] mt-[48px] first:mt-0 pb-[12px] text-[16px]
border-b border-b-[color:var(--theme--fg-border)]"
>
${label}
</h2>`;
}
let $input;
switch (type) {
// case "text":
// break;
// case "number":
// break;
// case "hotkey":
// break;
// case "color":
// break;
case "file":
$input = html`<${FileInput} extensions=${props.extensions} />`;
break;
case "select":
$input = html`<${Select} values=${props.values} />`;
break;
case "toggle":
$input = html`<${Toggle} />`;
}
return html`<${type === "toggle" ? "label" : "div"}
class="notion-enhancer--menu-option flex items-center justify-between
mb-[18px] ${type === "toggle" ? "cursor-pointer" : ""}"
><div class="flex flex-col mr-[10%]">
<h3 class="text-[14px] mb-[2px] mt-0">${label}</h3>
<p
class="text-[12px] leading-[16px]
text-[color:var(--theme--fg-secondary)]"
innerHTML=${description}
></p>
</div>
${$input}
<//>`;
};
}
export {
Sidebar,
SidebarSection,
SidebarButton,
View,
Option,
TextInput,
NumberInput,
HotkeyInput,
ColorInput,
FileInput,
Select,
Toggle,
Option,
};

View File

@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>notion-enhancer menu</title>
<link rel="stylesheet" href="./menu.css" />
<link rel="stylesheet" href="../../vendor/coloris.min.css" />
<script src="../../vendor/coloris.min.js" type="module"></script>
<script src="./menu.mjs" type="module" defer></script>
</head>
<body

View File

@ -18,13 +18,13 @@
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
background: var(--theme--scrollbar-track) !important;
background: var(--theme--scrollbar-track);
}
::-webkit-scrollbar-thumb {
background: var(--theme--scrollbar-thumb) !important;
background: var(--theme--scrollbar-thumb);
}
::-webkit-scrollbar-thumb:hover {
background: var(--theme--scrollbar-thumb_hover) !important;
background: var(--theme--scrollbar-thumb_hover);
}
.notion-enhancer--menu-option a {
@ -34,3 +34,17 @@
.notion-enhancer--menu-option a:hover {
color: var(--theme--accent-secondary);
}
/* coloris theming */
.clr-picker {
background-color: var(--theme--bg-secondary) !important;
}
.clr-color {
background-color: var(--theme--bg-hover) !important;
border-color: var(--theme--fg-border) !important;
color: var(--theme--fg-primary) !important;
}
.clr-preview:after,
.clr-preview:before {
border-color: var(--theme--fg-border) !important;
}

View File

@ -20,7 +20,7 @@
"type": "hotkey",
"key": "openMenuHotkey",
"description": "Opens the notion-enhancer menu from within Notion.",
"value": "Ctrl+Shift+Comma"
"value": "Ctrl+Shift+,"
},
{
"type": "heading",

1
src/vendor/coloris.min.css vendored Normal file

File diff suppressed because one or more lines are too long

6
src/vendor/coloris.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long