mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-10 15:39:01 +00:00
feat(menu): render profile list, create and switch b/w profiles
This commit is contained in:
parent
7bafbedc67
commit
106d776d85
@ -44,29 +44,32 @@ const initDatabase = (namespace, fallbacks = {}) => {
|
|||||||
const fallback = fallbacks[key];
|
const fallback = fallbacks[key];
|
||||||
key = key.startsWith(namespace) ? key : namespace + key;
|
key = key.startsWith(namespace) ? key : namespace + key;
|
||||||
return new Promise((res, _rej) => {
|
return new Promise((res, _rej) => {
|
||||||
chrome.storage.local.get([key], ({ [key]: value }) =>
|
chrome.storage.local.get([key], ({ [key]: value }) => {
|
||||||
res(value ?? fallback)
|
return res(value ?? fallback);
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
set: async (key, value) => {
|
set: async (key, value) => {
|
||||||
key = key.startsWith(namespace) ? key : namespace + key;
|
key = key.startsWith(namespace) ? key : namespace + key;
|
||||||
return new Promise((res, _rej) => {
|
return new Promise((res, _rej) => {
|
||||||
chrome.storage.local.set({ [key]: value }, () => res(value));
|
chrome.storage.local.set({ [key]: value }, () => res(true));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
dump: async () => {
|
export: async () => {
|
||||||
const obj = await new Promise((res, _rej) => {
|
const obj = await new Promise((res, _rej) => {
|
||||||
chrome.storage.local.get((value) => res(value));
|
chrome.storage.local.get((value) => res(value));
|
||||||
});
|
});
|
||||||
if (!namespace) return obj;
|
if (!namespace) return obj;
|
||||||
let entries = Object.entries(obj);
|
const entries = Object.entries(obj)
|
||||||
entries = entries.filter(([key]) => key.startsWith(namespace));
|
.filter(([key]) => key.startsWith(namespace))
|
||||||
|
.map(([key, value]) => [key.slice(namespace.length), value]);
|
||||||
return Object.fromEntries(entries);
|
return Object.fromEntries(entries);
|
||||||
},
|
},
|
||||||
populate: async (obj) => {
|
import: async (obj) => {
|
||||||
|
const entries = Object.entries(obj) //
|
||||||
|
.map(([key, value]) => [namespace + key, value]);
|
||||||
return new Promise((res, _rej) => {
|
return new Promise((res, _rej) => {
|
||||||
chrome.storage.local.set(obj, () => res(obj));
|
chrome.storage.local.set(Object.fromEntries(entries), () => res(true));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -51,6 +51,13 @@ const initDatabase = (namespace, fallbacks = {}) => {
|
|||||||
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
||||||
namespace = namespace ? namespace + "__" : "";
|
namespace = namespace ? namespace + "__" : "";
|
||||||
|
|
||||||
|
// schema:
|
||||||
|
// - ("profileIds") = $profileId[]
|
||||||
|
// - ("activeProfile") -> $profileId
|
||||||
|
// - $profileId: ("profileName") -> string
|
||||||
|
// - $profileId__enabledMods: ($modId) -> boolean
|
||||||
|
// - $profileId__$modId: ($optionKey) -> value
|
||||||
|
|
||||||
const table = "settings",
|
const table = "settings",
|
||||||
sqlite = require("better-sqlite3"),
|
sqlite = require("better-sqlite3"),
|
||||||
db = __db ?? sqlite(path.resolve(`${os.homedir()}/.notion-enhancer.db`)),
|
db = __db ?? sqlite(path.resolve(`${os.homedir()}/.notion-enhancer.db`)),
|
||||||
@ -93,14 +100,19 @@ const initDatabase = (namespace, fallbacks = {}) => {
|
|||||||
} else update.run(value, key);
|
} else update.run(value, key);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
dump: () => {
|
export: () => {
|
||||||
const entries = dump
|
const entries = dump
|
||||||
.all()
|
.all()
|
||||||
.map(({ key, value }) => [key, value])
|
.filter(({ key }) => key.startsWith(namespace))
|
||||||
.filter(([key]) => key.startsWith(namespace));
|
.map(({ key, value }) => [key.slice(namespace.length), value]);
|
||||||
return Promise.resolve(Object.fromEntries(entries));
|
return Promise.resolve(Object.fromEntries(entries));
|
||||||
},
|
},
|
||||||
populate,
|
import: (obj) => {
|
||||||
|
const entries = Object.entries(obj) //
|
||||||
|
.map(([key, value]) => [key.slice(namespace.length), value]);
|
||||||
|
populate(Object.fromEntries(entries));
|
||||||
|
return Promise.resolve(true);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ const encodeSvg = (svg) =>
|
|||||||
svg = hToString(type, props, ...children);
|
svg = hToString(type, props, ...children);
|
||||||
}
|
}
|
||||||
// https://antfu.me/posts/icons-in-pure-css
|
// https://antfu.me/posts/icons-in-pure-css
|
||||||
if (!svg) console.log(icon);
|
|
||||||
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";
|
||||||
|
@ -37,8 +37,10 @@ const getMods = async () => {
|
|||||||
|
|
||||||
const getProfile = async () => {
|
const getProfile = async () => {
|
||||||
const { initDatabase } = globalThis.__enhancerApi,
|
const { initDatabase } = globalThis.__enhancerApi,
|
||||||
currentProfile = await initDatabase().get("currentProfile");
|
db = initDatabase();
|
||||||
return currentProfile ?? "default";
|
let activeProfile = await db.get("activeProfile");
|
||||||
|
activeProfile ??= await db.get("profileIds")?.[0];
|
||||||
|
return activeProfile ?? "default";
|
||||||
},
|
},
|
||||||
isEnabled = async (id) => {
|
isEnabled = async (id) => {
|
||||||
const { platform } = globalThis.__enhancerApi,
|
const { platform } = globalThis.__enhancerApi,
|
||||||
|
@ -6,6 +6,70 @@
|
|||||||
|
|
||||||
import { setState, useState, getState } from "./state.mjs";
|
import { setState, useState, getState } from "./state.mjs";
|
||||||
|
|
||||||
|
// generic
|
||||||
|
|
||||||
|
function _Button(
|
||||||
|
{ type, size, icon, primary, class: cls = "", ...props },
|
||||||
|
...children
|
||||||
|
) {
|
||||||
|
const { html } = globalThis.__enhancerApi,
|
||||||
|
iconSize =
|
||||||
|
size === "sm" && children.length
|
||||||
|
? "w-[14px] h-[14px]"
|
||||||
|
: "w-[18px] h-[18px]";
|
||||||
|
return html`<${type}
|
||||||
|
class="flex gap-[8px] items-center px-[12px] shrink-0
|
||||||
|
rounded-[4px] ${size === "sm" ? "h-[28px]" : "h-[32px]"}
|
||||||
|
transition duration-[20ms] ${primary
|
||||||
|
? `text-[color:var(--theme--accent-primary_contrast)]
|
||||||
|
font-medium bg-[color:var(--theme--accent-primary)]
|
||||||
|
hover:bg-[color:var(--theme--accent-primary\\_hover)]`
|
||||||
|
: `border-(& [color:var(--theme--fg-border)])
|
||||||
|
hover:bg-[color:var(--theme--bg-hover)]`} ${cls}"
|
||||||
|
...${props}
|
||||||
|
>
|
||||||
|
${icon ? html`<i class="i-${icon} ${iconSize}"></i>` : ""}
|
||||||
|
<span class="text-[${size === "sm" ? "13" : "14"}px] empty:hidden">
|
||||||
|
${children}
|
||||||
|
</span>
|
||||||
|
<//>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Button(props, ...children) {
|
||||||
|
const { html } = globalThis.__enhancerApi;
|
||||||
|
return html`<${_Button} type="button" ...${props}>${children}<//>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Label(props, ...children) {
|
||||||
|
const { html } = globalThis.__enhancerApi;
|
||||||
|
return html`<${_Button} type="label" ...${props}>${children}<//>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Description({ class: cls = "", ...props }, ...children) {
|
||||||
|
const { html } = globalThis.__enhancerApi;
|
||||||
|
return html`<p
|
||||||
|
class="notion-enhancer--menu-description leading-[16px]
|
||||||
|
text-([12px] [color:var(--theme--fg-secondary)]) ${cls}"
|
||||||
|
...${props}
|
||||||
|
>
|
||||||
|
${children}
|
||||||
|
</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Icon({ icon, ...props }) {
|
||||||
|
const { html } = globalThis.__enhancerApi;
|
||||||
|
return html`<button
|
||||||
|
class="h-[14px] transition duration-[20ms]
|
||||||
|
text-[color:var(--theme--fg-secondary)]
|
||||||
|
hover:text-[color:var(--theme--fg-primary)]"
|
||||||
|
...${props}
|
||||||
|
>
|
||||||
|
<i class="i-${icon} w-[14px] h-[14px]"></i>
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// layout
|
||||||
|
|
||||||
function Sidebar({}, ...children) {
|
function Sidebar({}, ...children) {
|
||||||
const { html } = globalThis.__enhancerApi;
|
const { html } = globalThis.__enhancerApi;
|
||||||
return html`<aside
|
return html`<aside
|
||||||
@ -55,6 +119,15 @@ function SidebarButton({ id, icon, ...props }, ...children) {
|
|||||||
return $el;
|
return $el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function List({ id, description }, ...children) {
|
||||||
|
const { html } = globalThis.__enhancerApi;
|
||||||
|
return html`<div class="flex flex-col gap-y-[14px]">
|
||||||
|
<${Search} type=${id} items=${children} />
|
||||||
|
<${Description} innerHTML=${description} />
|
||||||
|
${children}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function Footer({}, ...children) {
|
function Footer({}, ...children) {
|
||||||
const { html } = globalThis.__enhancerApi;
|
const { html } = globalThis.__enhancerApi;
|
||||||
return html`<div
|
return html`<div
|
||||||
@ -81,15 +154,20 @@ function View({ id }, ...children) {
|
|||||||
nowActive = view.toLowerCase() === id.toLowerCase();
|
nowActive = view.toLowerCase() === id.toLowerCase();
|
||||||
switch (transition) {
|
switch (transition) {
|
||||||
case "fade": {
|
case "fade": {
|
||||||
const duration = 100;
|
const duration = 100,
|
||||||
$el.style.transition = `opacity ${duration}ms`;
|
cssTransition = `opacity ${duration}ms`;
|
||||||
$el.style.opacity = "0";
|
|
||||||
if (isVisible && !nowActive) {
|
if (isVisible && !nowActive) {
|
||||||
|
$el.style.transition = cssTransition;
|
||||||
|
$el.style.opacity = "0";
|
||||||
setTimeout(() => ($el.style.display = "none"), duration);
|
setTimeout(() => ($el.style.display = "none"), duration);
|
||||||
} else if (!isVisible && nowActive) {
|
} else if (!isVisible && nowActive) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
$el.style.opacity = "0";
|
||||||
$el.style.display = "";
|
$el.style.display = "";
|
||||||
requestIdleCallback(() => ($el.style.opacity = "1"));
|
requestIdleCallback(() => {
|
||||||
|
$el.style.transition = cssTransition;
|
||||||
|
$el.style.opacity = "1";
|
||||||
|
});
|
||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -133,203 +211,16 @@ function View({ id }, ...children) {
|
|||||||
return $el;
|
return $el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function List({ id, description }, ...children) {
|
// input
|
||||||
const { html } = globalThis.__enhancerApi;
|
|
||||||
return html`<div class="flex flex-col gap-y-[14px]">
|
|
||||||
<${Search} type=${id} items=${children} />
|
|
||||||
<p
|
|
||||||
class="notion-enhancer--menu-description
|
|
||||||
text-([12px] [color:var(--theme--fg-secondary)])"
|
|
||||||
innerHTML=${description}
|
|
||||||
></p>
|
|
||||||
${children}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Search({ type, items, oninput, ...props }) {
|
function Input({
|
||||||
const { html, addKeyListener } = globalThis.__enhancerApi,
|
size,
|
||||||
$search = html`<${Input}
|
icon,
|
||||||
size="lg"
|
transparent,
|
||||||
type="text"
|
onrerender,
|
||||||
placeholder="Search ${items.length} ${items.length === 1
|
class: cls = "",
|
||||||
? type.replace(/s$/, "")
|
...props
|
||||||
: type} (Press '/' to focus)"
|
|
||||||
icon="search"
|
|
||||||
oninput=${(event) => {
|
|
||||||
oninput?.(event);
|
|
||||||
const query = event.target.value.toLowerCase();
|
|
||||||
for (const $item of items) {
|
|
||||||
const matches = $item.innerText.toLowerCase().includes(query);
|
|
||||||
$item.style.display = matches ? "" : "none";
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
...${props}
|
|
||||||
/>`;
|
|
||||||
addKeyListener("/", (event) => {
|
|
||||||
if (document.activeElement?.nodeName === "INPUT") return;
|
|
||||||
// offsetParent == null if parent has "display: none;"
|
|
||||||
if ($search.offsetParent) {
|
|
||||||
event.preventDefault();
|
|
||||||
$search.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return $search;
|
|
||||||
}
|
|
||||||
|
|
||||||
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: "slide-to-right", 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="notion-enhancer--menu-description leading-[16px]
|
|
||||||
mb-[6px] text-([12px] [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-(& [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="notion-enhancer--menu-description leading-[16px]
|
|
||||||
text-([12px] [color:var(--theme--fg-secondary)])"
|
|
||||||
innerHTML=${description}
|
|
||||||
></p>
|
|
||||||
</div>
|
|
||||||
${type === "text" ? "" : $input}
|
|
||||||
<//>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Button({ primary, icon, class: cls, ...props }, children) {
|
|
||||||
const { html } = globalThis.__enhancerApi,
|
|
||||||
$icon = icon
|
|
||||||
? html`<i class="i-${icon} w-[18px] h-[18px] mr-[8px]"></i>`
|
|
||||||
: "";
|
|
||||||
return html`<button
|
|
||||||
class="flex items-center h-[32px] px-[12px] ${cls}
|
|
||||||
rounded-[4px] transition duration-[20ms] ${primary
|
|
||||||
? `text-[color:var(--theme--accent-primary_contrast)]
|
|
||||||
font-medium bg-[color:var(--theme--accent-primary)]
|
|
||||||
hover:bg-[color:var(--theme--accent-primary\\_hover)]`
|
|
||||||
: `border-(& [color:var(--theme--fg-border)])
|
|
||||||
hover:bg-[color:var(--theme--bg-hover)]`}"
|
|
||||||
...${props}
|
|
||||||
>
|
|
||||||
${$icon}
|
|
||||||
<span class="text-[14px]">${children}</span>
|
|
||||||
</button>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Input({ size, icon, transparent, onrerender, ...props }) {
|
|
||||||
const { html } = globalThis.__enhancerApi,
|
const { html } = globalThis.__enhancerApi,
|
||||||
$input = html`<input
|
$input = html`<input
|
||||||
class="${size === "lg"
|
class="${size === "lg"
|
||||||
@ -350,13 +241,13 @@ function Input({ size, icon, transparent, onrerender, ...props }) {
|
|||||||
class="notion-enhancer--menu-input
|
class="notion-enhancer--menu-input
|
||||||
relative overflow-hidden rounded-[4px]
|
relative overflow-hidden rounded-[4px]
|
||||||
focus-within:ring-(& [color:var(--theme--accent-primary)])
|
focus-within:ring-(& [color:var(--theme--accent-primary)])
|
||||||
${size === "lg" ? "block w-full" : ""}
|
${size === "lg" ? "h-[36px] block w-full" : ""}
|
||||||
${size === "md" ? "block w-full mt-[4px] mb-[8px]" : ""}
|
${size === "md" ? "h-[28px] block w-full" : ""}
|
||||||
${size === "sm" ? "shrink-0 w-[192px]" : ""}
|
${size === "sm" ? "h-[28px] shrink-0 w-[192px]" : ""}
|
||||||
bg-${transparent
|
bg-${transparent
|
||||||
? `([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)]
|
? `([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])`
|
[position:0_0,4px_4px] [size:8px_8px])`
|
||||||
: "[color:var(--theme--bg-hover)]"}"
|
: "[color:var(--theme--bg-hover)]"} ${cls}"
|
||||||
>${$input}${$icon}
|
>${$input}${$icon}
|
||||||
</label>`;
|
</label>`;
|
||||||
}
|
}
|
||||||
@ -367,6 +258,7 @@ function TextInput({ _get, _set, onchange, ...props }) {
|
|||||||
size="md"
|
size="md"
|
||||||
type="text"
|
type="text"
|
||||||
icon="text-cursor"
|
icon="text-cursor"
|
||||||
|
class="mt-[4px] mb-[8px]"
|
||||||
onchange=${(event) => {
|
onchange=${(event) => {
|
||||||
onchange?.(event);
|
onchange?.(event);
|
||||||
_set?.(event.target.value);
|
_set?.(event.target.value);
|
||||||
@ -481,18 +373,15 @@ function ColorInput({ _get, _set, oninput, ...props }) {
|
|||||||
function FileInput({ extensions, _get, _set, ...props }) {
|
function FileInput({ extensions, _get, _set, ...props }) {
|
||||||
const { html } = globalThis.__enhancerApi,
|
const { html } = globalThis.__enhancerApi,
|
||||||
$filename = html`<span>Upload a file</span>`,
|
$filename = html`<span>Upload a file</span>`,
|
||||||
$clear = html`<button
|
$clear = html`<${Icon}
|
||||||
class="ml-[8px] h-[14px] cursor-pointer text-[color:var(--theme--fg-secondary)]
|
icon="x"
|
||||||
transition duration-[20ms] hover:text-[color:var(--theme--fg-primary)] flex"
|
|
||||||
style="display: none"
|
style="display: none"
|
||||||
onclick=${() => {
|
onclick=${() => {
|
||||||
$filename.innerText = "Upload a file";
|
$filename.innerText = "Upload a file";
|
||||||
$clear.style.display = "none";
|
$clear.style.display = "none";
|
||||||
_set?.({ filename: "", content: "" });
|
_set?.({ filename: "", content: "" });
|
||||||
}}
|
}}
|
||||||
>
|
/>`;
|
||||||
<i class="i-x w-[14px] h-[14px]"></i>
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
const { onchange } = props;
|
const { onchange } = props;
|
||||||
props.onchange = (event) => {
|
props.onchange = (event) => {
|
||||||
@ -516,7 +405,8 @@ function FileInput({ extensions, _get, _set, ...props }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return html`<div
|
return html`<div
|
||||||
class="notion-enhancer--menu-file-input shrink-0 flex items-center"
|
class="notion-enhancer--menu-file-input
|
||||||
|
shrink-0 flex items-center gap-[8px]"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@ -659,7 +549,6 @@ function SelectOption({ value, _get, _set, ...props }) {
|
|||||||
function Toggle({ _get, _set, ...props }) {
|
function Toggle({ _get, _set, ...props }) {
|
||||||
const { html } = globalThis.__enhancerApi,
|
const { html } = globalThis.__enhancerApi,
|
||||||
$input = html`<input
|
$input = html`<input
|
||||||
tabindex="-1"
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="hidden checked:sibling:children:(
|
class="hidden checked:sibling:children:(
|
||||||
bg-[color:var(--theme--accent-primary)] after:translate-x-[12px])"
|
bg-[color:var(--theme--accent-primary)] after:translate-x-[12px])"
|
||||||
@ -695,14 +584,289 @@ function Toggle({ _get, _set, ...props }) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Checkbox({ _get, _set, ...props }) {
|
||||||
|
const { html } = globalThis.__enhancerApi,
|
||||||
|
$input = html`<input
|
||||||
|
type="checkbox"
|
||||||
|
class="hidden checked:sibling:(px-[1px]
|
||||||
|
bg-[color:var(--theme--accent-primary)])
|
||||||
|
not-checked:sibling:(children:text-transparent
|
||||||
|
border-(& [color:var(--theme--fg-primary)])
|
||||||
|
hover:bg-[color:var(--theme--bg-hover)])"
|
||||||
|
...${props}
|
||||||
|
/>`;
|
||||||
|
|
||||||
|
const { onchange } = $input;
|
||||||
|
$input.onchange = (event) => {
|
||||||
|
onchange?.(event);
|
||||||
|
_set?.($input.checked);
|
||||||
|
};
|
||||||
|
useState(["rerender"], () => {
|
||||||
|
_get?.().then((checked) => ($input.checked = checked));
|
||||||
|
});
|
||||||
|
|
||||||
|
return html`<label tabindex="0" class="cursor-pointer">
|
||||||
|
${$input}
|
||||||
|
<div class="flex items-center h-[16px] transition duration-[200ms]">
|
||||||
|
<i class="i-check w-[14px] h-[14px]"></i>
|
||||||
|
</div>
|
||||||
|
</label>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Search({ type, items, oninput, ...props }) {
|
||||||
|
const { html, addKeyListener } = globalThis.__enhancerApi,
|
||||||
|
$search = html`<${Input}
|
||||||
|
size="lg"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search ${items.length} ${items.length === 1
|
||||||
|
? type.replace(/s$/, "")
|
||||||
|
: type} (Press '/' to focus)"
|
||||||
|
icon="search"
|
||||||
|
oninput=${(event) => {
|
||||||
|
oninput?.(event);
|
||||||
|
const query = event.target.value.toLowerCase();
|
||||||
|
for (const $item of items) {
|
||||||
|
const matches = $item.innerText.toLowerCase().includes(query);
|
||||||
|
$item.style.display = matches ? "" : "none";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
...${props}
|
||||||
|
/>`;
|
||||||
|
addKeyListener("/", (event) => {
|
||||||
|
if (document.activeElement?.nodeName === "INPUT") return;
|
||||||
|
// offsetParent == null if parent has "display: none;"
|
||||||
|
if ($search.offsetParent) {
|
||||||
|
event.preventDefault();
|
||||||
|
$search.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return $search;
|
||||||
|
}
|
||||||
|
|
||||||
|
// representative
|
||||||
|
|
||||||
|
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)]
|
||||||
|
active:text-[color:var(--theme--fg-primary)]"
|
||||||
|
onclick=${() => setState({ transition: "slide-to-right", 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>
|
||||||
|
<${Description} class="mb-[6px]" innerHTML=${description} />
|
||||||
|
<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-(& [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 : ""}
|
||||||
|
<${Description} innerHTML=${description} />
|
||||||
|
</div>
|
||||||
|
${type === "text" ? "" : $input}
|
||||||
|
<//>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Profile({
|
||||||
|
getName,
|
||||||
|
setName,
|
||||||
|
isActive,
|
||||||
|
setActive,
|
||||||
|
exportData,
|
||||||
|
importData,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const { html } = globalThis.__enhancerApi,
|
||||||
|
uploadProfile = (event) => {
|
||||||
|
const file = event.target.files[0],
|
||||||
|
reader = new FileReader();
|
||||||
|
reader.onload = async (progress) => {
|
||||||
|
try {
|
||||||
|
const res = JSON.parse(progress.currentTarget.result);
|
||||||
|
importData(res);
|
||||||
|
} catch {
|
||||||
|
// throw error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
},
|
||||||
|
downloadProfile = async () => {
|
||||||
|
const now = new Date(),
|
||||||
|
year = now.getFullYear().toString(),
|
||||||
|
month = (now.getMonth() + 1).toString().padStart(2, "0"),
|
||||||
|
day = now.getDate().toString().padStart(2, "0"),
|
||||||
|
hour = now.getHours().toString().padStart(2, "0"),
|
||||||
|
min = now.getMinutes().toString().padStart(2, "0"),
|
||||||
|
sec = now.getSeconds().toString().padStart(2, "0"),
|
||||||
|
date = year + month + day + hour + min + sec;
|
||||||
|
|
||||||
|
const $a = html`<a
|
||||||
|
class="hidden"
|
||||||
|
download="notion-enhancer_${await getName()}_${date}.json"
|
||||||
|
href="data:text/plain;charset=utf-8,${encodeURIComponent(
|
||||||
|
JSON.stringify(await exportData())
|
||||||
|
)}"
|
||||||
|
/>`;
|
||||||
|
document.body.append($a);
|
||||||
|
$a.click();
|
||||||
|
$a.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`<li class="flex items-center my-[14px] gap-[8px]" ...${props}>
|
||||||
|
<${Checkbox}
|
||||||
|
checked=${isActive}
|
||||||
|
disabled=${isActive}
|
||||||
|
...${{ _set: setActive }}
|
||||||
|
/>
|
||||||
|
<${Input}
|
||||||
|
size="md"
|
||||||
|
type="text"
|
||||||
|
icon="file-cog"
|
||||||
|
onchange=${(event) => setName(event.target.value)}
|
||||||
|
onrerender=${($input) => {
|
||||||
|
getName().then((value) => ($input.value = value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<${Label} size="sm" icon="import">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
class="hidden"
|
||||||
|
accept=".json"
|
||||||
|
onchange=${uploadProfile}
|
||||||
|
/>
|
||||||
|
Import
|
||||||
|
<//>
|
||||||
|
<${Button} size="sm" icon="upload" onclick=${downloadProfile}> Export <//>
|
||||||
|
<${Icon} icon="x" />
|
||||||
|
</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
Button,
|
||||||
|
Label,
|
||||||
|
Description,
|
||||||
|
Icon,
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarSection,
|
SidebarSection,
|
||||||
SidebarButton,
|
SidebarButton,
|
||||||
|
List,
|
||||||
Footer,
|
Footer,
|
||||||
View,
|
View,
|
||||||
List,
|
Input,
|
||||||
|
TextInput,
|
||||||
|
NumberInput,
|
||||||
|
HotkeyInput,
|
||||||
|
ColorInput,
|
||||||
|
FileInput,
|
||||||
|
Select,
|
||||||
|
Toggle,
|
||||||
|
Checkbox,
|
||||||
|
Search,
|
||||||
Mod,
|
Mod,
|
||||||
Button,
|
|
||||||
Option,
|
Option,
|
||||||
|
Profile,
|
||||||
};
|
};
|
||||||
|
@ -58,12 +58,6 @@
|
|||||||
<div class="shimmer icon"></div>
|
<div class="shimmer icon"></div>
|
||||||
<div class="shimmer" style="width: 75px"></div>
|
<div class="shimmer" style="width: 75px"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row row-group">
|
|
||||||
<div class="shimmer" style="width: 44px"></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="shimmer" style="width: 74px"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -6,15 +6,18 @@
|
|||||||
|
|
||||||
import { getState, setState, useState } from "./state.mjs";
|
import { getState, setState, useState } from "./state.mjs";
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
|
Description,
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarSection,
|
SidebarSection,
|
||||||
SidebarButton,
|
SidebarButton,
|
||||||
|
List,
|
||||||
Footer,
|
Footer,
|
||||||
View,
|
View,
|
||||||
List,
|
Input,
|
||||||
Mod,
|
Mod,
|
||||||
Button,
|
|
||||||
Option,
|
Option,
|
||||||
|
Profile,
|
||||||
} from "./components.mjs";
|
} from "./components.mjs";
|
||||||
|
|
||||||
const compatibleMods = (mods) => {
|
const compatibleMods = (mods) => {
|
||||||
@ -80,8 +83,8 @@ const renderSidebar = (items, categories) => {
|
|||||||
renderOptions = async (mod) => {
|
renderOptions = async (mod) => {
|
||||||
const { html, platform, getProfile } = globalThis.__enhancerApi,
|
const { html, platform, getProfile } = globalThis.__enhancerApi,
|
||||||
{ optionDefaults, initDatabase } = globalThis.__enhancerApi,
|
{ optionDefaults, initDatabase } = globalThis.__enhancerApi,
|
||||||
profile = await getProfile();
|
profile = await getProfile(),
|
||||||
const db = initDatabase([profile, mod.id], await optionDefaults(mod.id));
|
db = initDatabase([profile, mod.id], await optionDefaults(mod.id));
|
||||||
let options = mod.options.reduce((options, opt, i) => {
|
let options = mod.options.reduce((options, opt, i) => {
|
||||||
if (!opt.key && (opt.type !== "heading" || !opt.label)) return options;
|
if (!opt.key && (opt.type !== "heading" || !opt.label)) return options;
|
||||||
if (opt.platforms && !opt.platforms.includes(platform)) return options;
|
if (opt.platforms && !opt.platforms.includes(platform)) return options;
|
||||||
@ -105,7 +108,84 @@ const renderSidebar = (items, categories) => {
|
|||||||
});
|
});
|
||||||
return Promise.all(options);
|
return Promise.all(options);
|
||||||
},
|
},
|
||||||
renderMods = async (category, mods) => {
|
renderProfiles = async () => {
|
||||||
|
const { html, getProfile, initDatabase, reloadApp } =
|
||||||
|
globalThis.__enhancerApi,
|
||||||
|
db = initDatabase();
|
||||||
|
|
||||||
|
let profileIds;
|
||||||
|
const $list = html`<ul></ul>`,
|
||||||
|
activeProfile = await getProfile(),
|
||||||
|
renderProfile = (id) => {
|
||||||
|
const profile = initDatabase([id]);
|
||||||
|
return html`<${Profile}
|
||||||
|
getName=${async () =>
|
||||||
|
(await profile.get("profileName")) ??
|
||||||
|
(id === "default" ? "default" : "")}
|
||||||
|
setName=${(name) => profile.set("profileName", name)}
|
||||||
|
isActive=${id === activeProfile}
|
||||||
|
setActive=${async (active) => {
|
||||||
|
if (!active) return;
|
||||||
|
await db.set("activeProfile", id);
|
||||||
|
reloadApp();
|
||||||
|
}}
|
||||||
|
exportData=${profile.export}
|
||||||
|
importData=${async (data) => {
|
||||||
|
await profile.import(data);
|
||||||
|
setState({ rerender: true, databaseUpdated: true });
|
||||||
|
}}
|
||||||
|
/>`;
|
||||||
|
},
|
||||||
|
refreshProfiles = async () => {
|
||||||
|
profileIds = (await db.get("profileIds")) ?? ["default"];
|
||||||
|
const profiles = await Promise.all(profileIds.map(renderProfile));
|
||||||
|
$list.replaceChildren(...profiles);
|
||||||
|
},
|
||||||
|
addProfile = async (name) => {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
await db.set("profileIds", [...profileIds, id]);
|
||||||
|
const profile = initDatabase([id]);
|
||||||
|
await profile.set("profileName", name);
|
||||||
|
refreshProfiles();
|
||||||
|
};
|
||||||
|
useState(["rerender"], () => {
|
||||||
|
refreshProfiles();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo: deleting profiles inc. clearing db keys,
|
||||||
|
// throwing errors on invalid json upload
|
||||||
|
|
||||||
|
const $input = html`<${Input}
|
||||||
|
size="md"
|
||||||
|
type="text"
|
||||||
|
icon="file-cog"
|
||||||
|
onkeydown=${(event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
if (!$input.children[0].value) return;
|
||||||
|
addProfile($input.children[0].value);
|
||||||
|
$input.children[0].value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>`;
|
||||||
|
return html`<div>
|
||||||
|
${$list}
|
||||||
|
<div class="flex items-center my-[14px] gap-[8px]">
|
||||||
|
${$input}
|
||||||
|
<${Button}
|
||||||
|
size="sm"
|
||||||
|
icon="plus"
|
||||||
|
onclick=${() => {
|
||||||
|
if (!$input.children[0].value) return;
|
||||||
|
addProfile($input.children[0].value);
|
||||||
|
$input.children[0].value = "";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Profile
|
||||||
|
<//>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
},
|
||||||
|
renderMods = async (mods) => {
|
||||||
const { html, getProfile, initDatabase } = globalThis.__enhancerApi,
|
const { html, getProfile, initDatabase } = globalThis.__enhancerApi,
|
||||||
enabledMods = initDatabase([await getProfile(), "enabledMods"]);
|
enabledMods = initDatabase([await getProfile(), "enabledMods"]);
|
||||||
mods = mods
|
mods = mods
|
||||||
@ -205,14 +285,25 @@ const render = async () => {
|
|||||||
// view wrapper necessary for transitions
|
// view wrapper necessary for transitions
|
||||||
const $views = html`<div class="grow relative overflow-hidden">
|
const $views = html`<div class="grow relative overflow-hidden">
|
||||||
<${View} id="welcome">welcome<//>
|
<${View} id="welcome">welcome<//>
|
||||||
<${View} id="core">${await renderOptions(await getCore())}<//>
|
<${View} id="core">
|
||||||
|
${await renderOptions(await getCore())}
|
||||||
|
<${Option} type="heading" label="Profiles" />
|
||||||
|
<${Description}>
|
||||||
|
Profiles can be used to preserve and switch between notion-enhancer
|
||||||
|
configurations.
|
||||||
|
<//>
|
||||||
|
${await renderProfiles()}
|
||||||
|
<//>
|
||||||
</div>`;
|
</div>`;
|
||||||
for (const { id, title, description, mods } of categories) {
|
for (const { id, description, mods } of categories) {
|
||||||
const $list = await renderList(id, mods, description),
|
const $list = await renderList(id, mods, description),
|
||||||
$mods = await renderMods({ id, title }, mods);
|
$mods = await renderMods(mods);
|
||||||
$views.append(html`<${View} id=${id}>${$list}<//>`, ...$mods);
|
$views.append(html`<${View} id=${id}>${$list}<//>`, ...$mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// footer appears only if buttons are visible
|
||||||
|
// - the matching category button appears on a mod's options page
|
||||||
|
// - the reload button appears if any options are changed
|
||||||
categories.forEach((c) => {
|
categories.forEach((c) => {
|
||||||
c.button = html`<${Button}
|
c.button = html`<${Button}
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
@ -237,7 +328,7 @@ const render = async () => {
|
|||||||
updateFooter = () => {
|
updateFooter = () => {
|
||||||
const buttons = [...$footer.children],
|
const buttons = [...$footer.children],
|
||||||
renderFooter = buttons.some(($el) => $el.style.display === "");
|
renderFooter = buttons.some(($el) => $el.style.display === "");
|
||||||
$main.style.height = renderFooter ? "100%" : "calc(100% + 65px)";
|
$main.style.height = renderFooter ? "100%" : "calc(100% + 33px)";
|
||||||
};
|
};
|
||||||
useState(["view"], ([view]) => {
|
useState(["view"], ([view]) => {
|
||||||
for (const { mods, button } of categories) {
|
for (const { mods, button } of categories) {
|
||||||
|
Loading…
Reference in New Issue
Block a user