mirror of
				https://github.com/notion-enhancer/notion-enhancer.git
				synced 2025-10-31 22:28:08 +11:00 
			
		
		
		
	
		
			
				
	
	
		
			615 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			615 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * notion-enhancer
 | |
|  * (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | |
|  * (https://notion-enhancer.github.io/) under the MIT license
 | |
|  */
 | |
| 
 | |
| import { setState, useState, getState } from "./state.mjs";
 | |
| 
 | |
| function Sidebar({}, ...children) {
 | |
|   const { html } = globalThis.__enhancerApi;
 | |
|   return html`<aside
 | |
|     class="notion-enhancer--menu-sidebar min-w-[224.14px] max-w-[250px]
 | |
|     h-full overflow-y-auto bg-[color:var(--theme--bg-secondary)]"
 | |
|   >
 | |
|     ${children}
 | |
|   </aside>`;
 | |
| }
 | |
| 
 | |
| function SidebarSection({}, ...children) {
 | |
|   const { html } = globalThis.__enhancerApi;
 | |
|   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}
 | |
|   </h2>`;
 | |
| }
 | |
| 
 | |
| function SidebarButton({ icon, ...props }, ...children) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     iconSize = icon.startsWith("notion-enhancer")
 | |
|       ? "w-[17px] h-[17px] ml-[1.5px] mr-[9.5px]"
 | |
|       : "w-[18px] h-[18px] ml-px mr-[9px]",
 | |
|     $el = html`<${props.href ? "a" : "button"}
 | |
|       class="flex select-none cursor-pointer w-full
 | |
|       items-center py-[5px] px-[15px] text-[14px] last:mb-[12px]
 | |
|       transition hover:bg-[color:var(--theme--bg-hover)]"
 | |
|       ...${props}
 | |
|     >
 | |
|       <i class="i-${icon} ${iconSize}"></i>
 | |
|       <span class="leading-[20px]">${children}</span>
 | |
|     <//>`;
 | |
|   if (!props.href) {
 | |
|     const id = $el.innerText;
 | |
|     $el.onclick ??= () => setState({ transition: "fade", view: id });
 | |
|     useState(["view"], ([view = "welcome"]) => {
 | |
|       const active = view.toLowerCase() === id.toLowerCase();
 | |
|       $el.style.background = active ? "var(--theme--bg-hover)" : "";
 | |
|       $el.style.fontWeight = active ? "600" : "";
 | |
|     });
 | |
|   }
 | |
|   return $el;
 | |
| }
 | |
| 
 | |
| function View({ id }, ...children) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     duration = 100,
 | |
|     $el = html`<article
 | |
|       id=${id}
 | |
|       class="notion-enhancer--menu-view h-full
 | |
|       grow overflow-y-auto px-[60px] py-[36px]"
 | |
|     >
 | |
|       ${children}
 | |
|     </article>`;
 | |
|   useState(["view"], ([view = "welcome"]) => {
 | |
|     const [transition] = getState(["transition"]),
 | |
|       isVisible = $el.style.display !== "none",
 | |
|       nowActive = view.toLowerCase() === id.toLowerCase();
 | |
|     if (transition === "fade") {
 | |
|       $el.style.opacity = "0";
 | |
|       $el.style.transition = `opacity ${duration}ms`;
 | |
|       if (isVisible && !nowActive) {
 | |
|         setTimeout(() => ($el.style.display = "none"), duration);
 | |
|       } else if (!isVisible && nowActive) {
 | |
|         setTimeout(() => {
 | |
|           $el.style.display = "";
 | |
|           requestIdleCallback(() => ($el.style.opacity = "1"));
 | |
|         }, duration);
 | |
|       }
 | |
|     } else {
 | |
|       $el.style.transition = "";
 | |
|       $el.style.opacity = nowActive ? "1" : "0";
 | |
|       $el.style.display = nowActive ? "" : "none";
 | |
|     }
 | |
|   });
 | |
|   return $el;
 | |
| }
 | |
| 
 | |
| function List({}, ...children) {
 | |
|   const { html } = globalThis.__enhancerApi;
 | |
|   return html`<div class="flex flex-col gap-y-[14px]">${children}</div>`;
 | |
| }
 | |
| 
 | |
| function Mod({
 | |
|   id,
 | |
|   name,
 | |
|   version,
 | |
|   description,
 | |
|   thumbnail,
 | |
|   tags = [],
 | |
|   authors,
 | |
|   options = [],
 | |
|   _get,
 | |
|   _set,
 | |
|   _src,
 | |
| }) {
 | |
|   const { html, enhancerUrl } = globalThis.__enhancerApi,
 | |
|     toggleId = Math.random().toString(36).slice(2, 5),
 | |
|     $thumbnail = thumbnail
 | |
|       ? html`<img
 | |
|           src="${enhancerUrl(`${_src}/${thumbnail}`)}"
 | |
|           class="rounded-[4px] mr-[12px] h-[74px] my-auto"
 | |
|         />`
 | |
|       : "",
 | |
|     $options = options.length
 | |
|       ? html`<button
 | |
|           class="flex items-center p-[4px] rounded-[4px] transition
 | |
|           text-[color:var(--theme--fg-secondary)] my-auto mr-[8px]
 | |
|           duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
 | |
|           onclick=${() => setState({ transition: "none", view: id })}
 | |
|         >
 | |
|           <i class="i-settings w-[18px] h-[18px]"></i>
 | |
|         </button>`
 | |
|       : "";
 | |
|   return html`<label
 | |
|     for=${toggleId}
 | |
|     class="notion-enhancer--menu-mod flex items-stretch rounded-[4px]
 | |
|     bg-[color:var(--theme--bg-secondary)] w-full py-[18px] px-[16px]
 | |
|     border border-[color:var(--theme--fg-border)] cursor-pointer
 | |
|     transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
 | |
|   >
 | |
|     ${$thumbnail}
 | |
|     <div class="flex flex-col max-w-[50%]">
 | |
|       <div class="flex items-center text-[14px] mb-[5px]">
 | |
|         <h3 class="my-0">${name}</h3>
 | |
|         ${[`v${version}`, ...tags].map((tag) => {
 | |
|           return html`<span
 | |
|             class="text-([12px] [color:var(--theme--fg-secondary)])
 | |
|             ml-[8px] py-[2px] px-[6px] leading-tight tracking-wide
 | |
|             rounded-[3px] bg-[color:var(--theme--bg-hover)]"
 | |
|           >
 | |
|             ${tag}
 | |
|           </span>`;
 | |
|         })}
 | |
|       </div>
 | |
|       <p
 | |
|         class="text-[12px] leading-[16px] mb-[6px]
 | |
|         text-[color:var(--theme--fg-secondary)]"
 | |
|         innerHTML=${description}
 | |
|       ></p>
 | |
|       <div class="mt-auto flex gap-x-[8px] text-[12px] leading-[16px]">
 | |
|         ${authors.map((author) => {
 | |
|           return html`<a href=${author.homepage} class="flex items-center">
 | |
|             <img src=${author.avatar} alt="" class="h-[12px] rounded-full" />
 | |
|             <span class="ml-[6px]">${author.name}</span>
 | |
|           </a>`;
 | |
|         })}
 | |
|       </div>
 | |
|     </div>
 | |
|     <div class="flex ml-auto">
 | |
|       ${$options}
 | |
|       <div class="my-auto scale-[1.15]">
 | |
|         <${Toggle} id=${toggleId} ...${{ _get, _set }} />
 | |
|       </div>
 | |
|     </div>
 | |
|   </label>`;
 | |
| }
 | |
| 
 | |
| function Option({ type, value, description, _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     camelToSentenceCase = (string) =>
 | |
|       string[0].toUpperCase() +
 | |
|       string.replace(/[A-Z]/g, (match) => ` ${match.toLowerCase()}`).slice(1);
 | |
| 
 | |
|   let $input;
 | |
|   const label = props.label ?? camelToSentenceCase(props.key);
 | |
|   switch (type) {
 | |
|     case "heading":
 | |
|       return html`<h4
 | |
|         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}
 | |
|       </h4>`;
 | |
|     case "text":
 | |
|       $input = html`<${TextInput} ...${{ _get, _set }} />`;
 | |
|       break;
 | |
|     case "number":
 | |
|       $input = html`<${NumberInput} ...${{ _get, _set }} />`;
 | |
|       break;
 | |
|     case "hotkey":
 | |
|       $input = html`<${HotkeyInput} ...${{ _get, _set }} />`;
 | |
|       break;
 | |
|     case "color":
 | |
|       $input = html`<${ColorInput} ...${{ _get, _set }} />`;
 | |
|       break;
 | |
|     case "file":
 | |
|       $input = html`<${FileInput}
 | |
|         extensions="${props.extensions}"
 | |
|         ...${{ _get, _set }}
 | |
|       />`;
 | |
|       break;
 | |
|     case "select":
 | |
|       $input = html`<${Select} values=${props.values} ...${{ _get, _set }} />`;
 | |
|       break;
 | |
|     case "toggle":
 | |
|       $input = html`<${Toggle} ...${{ _get, _set }} />`;
 | |
|   }
 | |
|   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%]"}">
 | |
|       <h5 class="text-[14px] mb-[2px] mt-0">${label}</h5>
 | |
|       ${type === "text" ? $input : ""}
 | |
|       <p
 | |
|         class="text-[12px] leading-[16px]
 | |
|         text-[color:var(--theme--fg-secondary)]"
 | |
|         innerHTML=${description}
 | |
|       ></p>
 | |
|     </div>
 | |
|     ${type === "text" ? "" : $input}
 | |
|   <//>`;
 | |
| }
 | |
| 
 | |
| function TextInput({ _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $input = html`<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)]"
 | |
|       ...${props}
 | |
|     />`;
 | |
| 
 | |
|   const { onchange } = $input;
 | |
|   $input.onchange = (event) => {
 | |
|     onchange?.(event);
 | |
|     _set?.($input.value);
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((value) => ($input.value = value));
 | |
|   });
 | |
| 
 | |
|   return html`<label
 | |
|     class="notion-enhancer--menu-text-input
 | |
|     relative block w-full mt-[4px] mb-[8px]"
 | |
|     >${$input}
 | |
|     <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>`;
 | |
| }
 | |
| 
 | |
| function NumberInput({ _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $input = html`<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)]"
 | |
|       ...${props}
 | |
|     />`;
 | |
| 
 | |
|   const { onchange } = $input;
 | |
|   $input.onchange = (event) => {
 | |
|     onchange?.(event);
 | |
|     _set?.($input.value);
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((value) => ($input.value = value));
 | |
|   });
 | |
| 
 | |
|   return html`<label
 | |
|     class="notion-enhancer--menu-number-input
 | |
|     relative shrink-0 w-[192px]"
 | |
|     >${$input}
 | |
|     <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({ _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $input = html`<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)]"
 | |
|       ...${props}
 | |
|     />`,
 | |
|     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)) {
 | |
|         $input.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(key)) return;
 | |
|         keys.push(key.length === 1 ? key.toUpperCase() : key);
 | |
|         $input.value = keys.join("+");
 | |
|       }
 | |
|       $input.dispatchEvent(new Event("input"));
 | |
|       $input.dispatchEvent(new Event("change"));
 | |
|     };
 | |
| 
 | |
|   const { onkeydown } = $input;
 | |
|   $input.onkeydown = (event) => {
 | |
|     updateHotkey(event);
 | |
|     onkeydown?.(event);
 | |
|     _set?.($input.value);
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((value) => ($input.value = value));
 | |
|   });
 | |
| 
 | |
|   return html`<label
 | |
|     class="notion-enhancer--menu-hotkey-input
 | |
|     relative shrink-0 w-[192px]"
 | |
|     >${$input}
 | |
|     <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({ _get, _set, ...props }) {
 | |
|   Coloris({ format: "rgb" });
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $input = html`<input
 | |
|       type="text"
 | |
|       class="appearance-none text-[14px] leading-[1.2]
 | |
|       h-[28px] w-full pl-[8px] pr-[32px] pb-px"
 | |
|       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>`,
 | |
|     updateContrast = () => {
 | |
|       $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 { oninput } = $input;
 | |
|   $input.oninput = (event) => {
 | |
|     oninput?.(event);
 | |
|     _set?.($input.value);
 | |
|     updateContrast();
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((value) => {
 | |
|       $input.value = value;
 | |
|       updateContrast();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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, _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $filename = html`<span>Upload a file</span>`,
 | |
|     $clear = html`<button
 | |
|       class="ml-[8px] h-[14px] cursor-pointer text-[color:var(--theme--fg-secondary)]
 | |
|       transition duration-[20ms] hover:text-[color:var(--theme--fg-primary)] flex"
 | |
|       style="display: none"
 | |
|       onclick=${() => {
 | |
|         $filename.innerText = "Upload a file";
 | |
|         $clear.style.display = "none";
 | |
|         _set?.({ filename: "", content: "" });
 | |
|       }}
 | |
|     >
 | |
|       <i class="i-x w-[14px] h-[14px]"></i>
 | |
|     </button>`;
 | |
| 
 | |
|   const { onchange } = props;
 | |
|   props.onchange = (event) => {
 | |
|     const file = event.target.files[0],
 | |
|       reader = new FileReader();
 | |
|     reader.onload = async (progress) => {
 | |
|       const content = progress.currentTarget.result,
 | |
|         upload = { filename: file.name, content };
 | |
|       $filename.innerText = file.name;
 | |
|       $clear.style.display = "";
 | |
|       _set?.(upload);
 | |
|     };
 | |
|     reader.readAsText(file);
 | |
|     onchange?.(event);
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((file) => {
 | |
|       $filename.innerText = file?.filename || "Upload a file";
 | |
|       $clear.style.display = file?.filename ? "" : "none";
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   return html`<div
 | |
|     class="notion-enhancer--menu-file-input shrink-0 flex items-center"
 | |
|   >
 | |
|     <label
 | |
|       tabindex="0"
 | |
|       class="flex items-center cursor-pointer select-none
 | |
|       h-[28px] text-[14px] leading-[1.2] px-[8px] rounded-[4px]
 | |
|       text-[color:var(--theme--fg-secondary)] bg-[color:var(--theme--bg-secondary)] 
 | |
|       transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
 | |
|     >
 | |
|       <input
 | |
|         type="file"
 | |
|         class="hidden"
 | |
|         accept=${extensions
 | |
|           ?.map((ext) => (ext.startsWith(".") ? ext : `.${ext}`))
 | |
|           .join(",")}
 | |
|         ...${props}
 | |
|       />
 | |
|       <i class="i-file-up w-[16px] h-[16px] mr-[6px]"></i>
 | |
|       ${$filename}
 | |
|     </label>
 | |
|     ${$clear}
 | |
|   </div>`;
 | |
| }
 | |
| 
 | |
| function Select({ values, _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $select = html`<div
 | |
|       dir="rtl"
 | |
|       role="button"
 | |
|       tabindex="0"
 | |
|       class="appearance-none bg-transparent rounded-[4px] cursor-pointer
 | |
|       text-[14px] leading-[28px] h-[28px] max-w-[256px] pl-[8px] pr-[28px]
 | |
|       transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
 | |
|       ...${props}
 | |
|     ></div>`,
 | |
|     $popup = html`<div
 | |
|       class="group absolute top-0 left-0
 | |
|       flex flex-col justify-center items-end
 | |
|       pointer-events-none w-full h-full"
 | |
|     >
 | |
|       <div class="relative right-[100%]">
 | |
|         <div
 | |
|           class="bg-[color:var(--theme--bg-secondary)]
 | |
|           w-[250px] max-w-[calc(100vw-24px)] max-h-[70vh]
 | |
|           py-[6px] px-[4px] drop-shadow-xl overflow-y-auto
 | |
|           transition duration-[200ms] opacity-0 scale-95 rounded-[4px]
 | |
|           group-open:(pointer-events-auto opacity-100 scale-100)"
 | |
|         >
 | |
|           ${values.map((value) => {
 | |
|             return html`<${SelectOption} ...${{ value, _get, _set }} />`;
 | |
|           })}
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>`;
 | |
| 
 | |
|   const { onclick } = $select;
 | |
|   $select.onclick = (event) => {
 | |
|     onclick?.(event);
 | |
|     $popup.setAttribute("open", true);
 | |
|     setState({ popupOpen: true });
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((value) => {
 | |
|       if ($popup.hasAttribute("open")) {
 | |
|         $popup.removeAttribute("open");
 | |
|         $select.style.width = `${$select.offsetWidth}px`;
 | |
|         $select.style.background = "transparent";
 | |
|         $select.innerText = value;
 | |
|         setTimeout(() => {
 | |
|           $select.style.width = "";
 | |
|           $select.style.background = "";
 | |
|           setState({ popupOpen: false });
 | |
|         }, 200);
 | |
|       } else $select.innerText = value;
 | |
|     });
 | |
|   });
 | |
|   document.addEventListener("click", (event) => {
 | |
|     if (!$popup.hasAttribute("open")) return;
 | |
|     if ($popup.contains(event.target) || event.target === $select) return;
 | |
|     _set?.($select.innerText);
 | |
|   });
 | |
| 
 | |
|   return html`<div class="notion-enhancer--menu-select relative">
 | |
|     ${$select}${$popup}
 | |
|     <i
 | |
|       class="i-chevron-down pointer-events-none
 | |
|       absolute right-[6px] top-[6px] w-[16px] h-[16px]
 | |
|       text-[color:var(--theme--fg-secondary)]"
 | |
|     ></i>
 | |
|   </div>`;
 | |
| }
 | |
| 
 | |
| function SelectOption({ value, _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $selected = html`<i class="ml-auto i-check w-[16px] h-[16px]"></i>`,
 | |
|     $option = html`<div
 | |
|       role="button"
 | |
|       tabindex="0"
 | |
|       class="select-none cursor-pointer rounded-[3px]
 | |
|       flex items-center w-full h-[28px] px-[12px] leading-[1.2]
 | |
|       transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
 | |
|       ...${props}
 | |
|     >
 | |
|       <div class="mr-[6px] text-[14px] text-ellipsis overflow-hidden">
 | |
|         ${value}
 | |
|       </div>
 | |
|     </div>`;
 | |
| 
 | |
|   const { onclick } = $option;
 | |
|   $option.onclick = (event) => {
 | |
|     onclick?.(event);
 | |
|     _set?.(value);
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((actualValue) => {
 | |
|       if (actualValue === value) {
 | |
|         $option.append($selected);
 | |
|       } else $selected.remove();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   return $option;
 | |
| }
 | |
| 
 | |
| function Toggle({ _get, _set, ...props }) {
 | |
|   const { html } = globalThis.__enhancerApi,
 | |
|     $input = html`<input
 | |
|       tabindex="-1"
 | |
|       type="checkbox"
 | |
|       class="hidden checked:sibling:children:(
 | |
|       bg-[color:var(--theme--accent-primary)] after:translate-x-[12px])"
 | |
|       ...${props}
 | |
|     />`;
 | |
| 
 | |
|   const { onchange } = $input;
 | |
|   $input.onchange = (event) => {
 | |
|     onchange?.(event);
 | |
|     _set?.($input.checked);
 | |
|   };
 | |
|   useState(["rerender"], () => {
 | |
|     _get?.().then((checked) => ($input.checked = checked));
 | |
|   });
 | |
| 
 | |
|   return html`<div class="notion-enhancer--menu-toggle shrink-0">
 | |
|     ${$input}
 | |
|     <div
 | |
|       tabindex="0"
 | |
|       class="w-[30px] h-[18px] rounded-[44px] cursor-pointer
 | |
|       transition duration-200 bg-[color:var(--theme--bg-hover)]"
 | |
|     >
 | |
|       <div
 | |
|         class="w-full h-full rounded-[44px] text-[12px]
 | |
|         p-[2px] hover:bg-[color:var(--theme--bg-hover)]
 | |
|         transition duration-200 after:(
 | |
|           inline-block w-[14px] h-[14px] rounded-[44px]
 | |
|           bg-[color:var(--theme--accent-primary\\_contrast)]
 | |
|           transition duration-200
 | |
|         )"
 | |
|       ></div>
 | |
|     </div>
 | |
|   </div>`;
 | |
| }
 | |
| 
 | |
| export {
 | |
|   Sidebar,
 | |
|   SidebarSection,
 | |
|   SidebarButton,
 | |
|   View,
 | |
|   List,
 | |
|   Mod,
 | |
|   Option,
 | |
|   TextInput,
 | |
|   NumberInput,
 | |
|   HotkeyInput,
 | |
|   ColorInput,
 | |
|   FileInput,
 | |
|   Select,
 | |
|   Toggle,
 | |
| };
 |