diff --git a/src/api/interface.js b/src/api/interface.js index 1504f55..fab4eb2 100644 --- a/src/api/interface.js +++ b/src/api/interface.js @@ -544,6 +544,7 @@ const h = (type, props, ...children) => { : document.createElement(type); for (const prop in props ?? {}) { if (htmlAttributes.includes(prop) || prop.startsWith("data-")) { + if (typeof props[prop] === "boolean" && !props[prop]) continue; elem.setAttribute(prop, props[prop]); } else elem[prop] = props[prop]; } diff --git a/src/core/menu/components.mjs b/src/core/menu/components.mjs index 62d4f73..93bb7a4 100644 --- a/src/core/menu/components.mjs +++ b/src/core/menu/components.mjs @@ -69,46 +69,53 @@ function View({ id }, ...children) { return $el; } -function Option({ mod, type, ...props }) { +function Option({ type, value, description, _update, ...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), - description = props.description; - if (type === "heading") { - return html`

- ${label} -

`; - } - - let $input; - switch (type) { + > + ${label} + `; case "text": - $input = html`<${TextInput} value=${props.value} />`; + $input = html`<${TextInput} ...${{ value, onchange }} />`; break; case "number": - $input = html`<${NumberInput} value=${props.value} />`; + $input = html`<${NumberInput} ...${{ value, onchange }} />`; break; case "hotkey": - $input = html`<${HotkeyInput} value=${props.value} />`; + $input = html`<${HotkeyInput} ...${{ value, onchange }} />`; break; case "color": - $input = html`<${ColorInput} value=${props.value} />`; + $input = html`<${ColorInput} ...${{ value, onchange }} />`; break; case "file": - $input = html`<${FileInput} extensions=${props.extensions} />`; + $input = html`<${FileInput} + extensions="${props.extensions}" + onchange=${onchange} + />`; break; case "select": - $input = html`<${Select} values=${props.values} />`; + $input = html`<${Select} + values=${props.values} + ...${{ value, onchange }} + />`; break; case "toggle": - $input = html`<${Toggle} />`; + $input = html`<${Toggle} + checked=${value} + onchange=${(event) => _update(event.target.checked)} + />`; } return html`<${type === "toggle" ? "label" : "div"} class="notion-enhancer--menu-option flex items-center justify-between @@ -189,10 +196,12 @@ function HotkeyInput({ value, onkeydown, ...props }) { 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; + if (keys.includes(key)) return; keys.push(key.length === 1 ? key.toUpperCase() : key); event.target.value = keys.join("+"); } + event.target.dispatchEvent(new Event("input")); + event.target.dispatchEvent(new Event("change")); }; props.onkeydown = (event) => { updateHotkey(event); @@ -292,7 +301,7 @@ function FileInput({ extensions, ...props }) { `; } -function Select({ values, onchange, ...props }) { +function Select({ values, value, onchange, ...props }) { const { html } = globalThis.__enhancerApi, updateWidth = ($select) => { const $tmp = html``; })} `; + $select.value = value ?? $select.value; updateWidth($select); return html`
diff --git a/src/core/menu/menu.mjs b/src/core/menu/menu.mjs index c473162..eef9b81 100644 --- a/src/core/menu/menu.mjs +++ b/src/core/menu/menu.mjs @@ -13,13 +13,34 @@ import { Option, } from "./components.mjs"; +const renderOptions = async (mod) => { + const { html, platform, getProfile } = globalThis.__enhancerApi, + { optionDefaults, initDatabase } = globalThis.__enhancerApi, + options = await optionDefaults(mod.id), + db = initDatabase([await getProfile(), mod.id], options); + return Promise.all( + mod.options + .map(async (opt) => { + if (!opt.key && (opt.type !== "heading" || !opt.label)) return; + // if (opt.targets && !opt.targets.includes(platform)) return; + if (opt.type === "heading") return html`<${Option} ...${opt} />`; + const value = await db.get(opt.key), + _update = (value) => db.set(opt.key, value); + return html`<${Option} ...${{ ...opt, value, _update }} />`; + }) + .filter((opt) => opt) + ); +}; + let renderStarted; const render = async (iconStyle) => { - const { html, getCore } = globalThis.__enhancerApi; - if (!html || !getCore || renderStarted) return; + const { html, getProfile } = globalThis.__enhancerApi, + { optionDefaults, initDatabase } = globalThis.__enhancerApi; + if (!html || !getProfile || renderStarted) return; renderStarted = true; - const core = await getCore(); + const { getCore, getThemes } = globalThis.__enhancerApi, + { getExtensions, getIntegrations } = globalThis.__enhancerApi; const $sidebar = html`<${Sidebar}> ${[ @@ -69,11 +90,7 @@ const render = async (iconStyle) => { `, $views = [ html`<${View} id="welcome">welcome`, - html`<${View} id="core"> - ${core.options.map( - (opt) => html`<${Option} mod=${core.id} ...${opt} />` - )} - `, + html`<${View} id="core">${await renderOptions(await getCore())}`, html`<${View} id="themes">themes`, html`<${View} id="extensions">extensions`, html`<${View} id="integrations">integrations`, diff --git a/src/core/menuu/client.css b/src/core/menuu/client.css deleted file mode 100644 index 0109a3c..0000000 --- a/src/core/menuu/client.css +++ /dev/null @@ -1,69 +0,0 @@ -/** - * notion-enhancer: menu - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -.enhancer--sidebarMenuLink { - user-select: none; - -webkit-user-select: none; - transition: background 20ms ease-in 0s; - cursor: pointer; - color: var(--theme--text_secondary); -} -.enhancer--sidebarMenuLink:hover { - background: var(--theme--ui_interactive-hover); -} -.enhancer--sidebarMenuLink svg { - width: 16px; - height: 16px; - margin-left: 2px; -} -.enhancer--sidebarMenuLink > div { - display: flex; - align-items: center; - min-height: 27px; - font-size: 14px; - padding: 2px 14px; - width: 100%; -} -.enhancer--sidebarMenuLink > div > :first-child { - flex-shrink: 0; - flex-grow: 0; - border-radius: 3px; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - margin-right: 8px; -} -.enhancer--sidebarMenuLink > div > :nth-child(2) { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.enhancer--sidebarMenuLink:active { - color: var(--theme--text); -} -.enhancer--sidebarMenuLink > div > .enhancer--notificationBubble { - display: flex; -} -.enhancer--sidebarMenuLink > div > .enhancer--notificationBubble > div { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - font-size: 10px; - font-weight: 600; - border-radius: 3px; - color: var(--theme--accent_red-text); - background: var(--theme--accent_red); -} -.enhancer--sidebarMenuLink > div > .enhancer--notificationBubble > div > span { - margin-bottom: 1px; - margin-left: -0.5px; -} diff --git a/src/core/menuu/markdown.css b/src/core/menuu/markdown.css deleted file mode 100644 index 2a23626..0000000 --- a/src/core/menuu/markdown.css +++ /dev/null @@ -1,97 +0,0 @@ -/** - * notion-enhancer: menu - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -.markdown table { - border-spacing: 0; - border: 1px solid var(--theme--ui_divider); -} -.markdown table th { - text-align: left; -} -.markdown table th, -.markdown table td { - padding: 5px 8px 6px; - border: 1px solid var(--theme--ui_divider); -} -.markdown h1 { - font-size: 1.875rem; - margin: 1rem 0 0.5rem 0; -} -.markdown h2 { - font-size: 1.5rem; - margin: 1rem 0 0.5rem 0; -} -.markdown h3 { - font-size: 1.25rem; - margin: 1rem 0 0.5rem 0; -} -.markdown h4 { - font-weight: bold; - margin: 0.5rem 0; -} -.markdown ul, -.markdown ol { - padding-left: 1.25rem; -} -.markdown ul { - list-style: disc; -} -.markdown ol { - list-style: decimal; -} -.markdown li { - margin: 1px 0; -} -.markdown ol li { - padding-left: 0.25rem; -} -.markdown blockquote { - border-left: 2px solid currentColor; - padding-left: 0.75rem; - margin: 0.5rem 0; -} -.markdown hr { - border: 0.5px solid var(--theme--ui_divider); -} -.markdown-inline a, -.markdown a { - opacity: 0.7; - text-decoration: none; - border-bottom: 0.05em solid var(--theme--text_secondary); -} -.markdown-inline a:hover, -.markdown a:hover { - opacity: 0.9; -} - -.markdown :not(pre) > code, -.markdown-inline code { - padding: 0.2em 0.4em; - border-radius: 3px; - background: var(--theme--code_inline); - color: var(--theme--code_inline-text); -} -.markdown pre { - padding: 2em 1.25em; - border-radius: 3px; - tab-size: 2; - white-space: pre; - overflow-x: auto; - background: var(--theme--code); - color: var(--theme--code_plain); -} -.markdown pre, -.markdown code, -.markdown-inline code { - font-family: var(--theme--font_code); - font-size: 0.796875rem; - text-align: left; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - hyphens: none; - line-height: 1.5; -} diff --git a/src/core/menuu/menu.css b/src/core/menuu/menu.css deleted file mode 100644 index 5a11697..0000000 --- a/src/core/menuu/menu.css +++ /dev/null @@ -1,25 +0,0 @@ -/** - * notion-enhancer: menu - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -::selection { - background: var(--theme--accent_blue-selection); -} - -::-webkit-scrollbar { - width: 10px; - height: 10px; - background: transparent; -} -::-webkit-scrollbar-track, -::-webkit-scrollbar-corner { - background: var(--theme--scrollbar_track) !important; -} -::-webkit-scrollbar-thumb { - background: var(--theme--scrollbar_thumb) !important; -} -::-webkit-scrollbar-thumb:hover { - background: var(--theme--scrollbar_thumb-hover) !important; -} diff --git a/src/core/menuu/menu.html b/src/core/menuu/menu.html deleted file mode 100644 index ec6337a..0000000 --- a/src/core/menuu/menu.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - notion-enhancer menu - - - - - diff --git a/src/core/menuu/router.mjs b/src/core/menuu/router.mjs deleted file mode 100644 index 2eb88cc..0000000 --- a/src/core/menuu/router.mjs +++ /dev/null @@ -1,88 +0,0 @@ -/** - * notion-enhancer: menu - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -import { web } from '../../api/index.mjs'; - -const _queryListeners = new Set(); - -export function addView(name, loadFunc) { - const handlerFunc = (newView) => { - if (newView === name) return loadFunc(); - return false; - }; - _queryListeners.add({ param: 'view', viewName: name, handlerFunc }); - handlerFunc(web.queryParams().get('view'), null); -} -export function removeView(name) { - const view = [..._queryListeners].find((view) => view.viewName === name); - if (view) _queryListeners.delete(view); -} -export async function setDefaultView(viewName) { - const viewList = [..._queryListeners].filter((q) => q.viewName).map((v) => v.viewName); - if (!viewList.includes(web.queryParams().get('view'))) { - updateQuery(`?view=${viewName}`, true); - } -} - -export function addQueryListener(param, handlerFunc) { - _queryListeners.add({ param: param, handlerFunc }); - handlerFunc(web.queryParams().get(param), null); -} -export function removeQueryListener(handlerFunc) { - const listener = [..._queryListeners].find((view) => view.handlerFunc === handlerFunc); - if (listener) _queryListeners.delete(listener); -} - -export const updateQuery = (search, replace = false) => { - let query = web.queryParams(); - for (const [key, val] of new URLSearchParams(search)) { - query.set(key, val); - } - query = `?${query.toString()}`; - if (location.search !== query) { - if (replace) { - window.history.replaceState(null, null, query); - } else { - window.history.pushState(null, null, query); - } - triggerQueryListeners(); - } -}; - -function router(event) { - event.preventDefault(); - const anchor = event.path - ? event.path.find((anchor) => anchor.nodeName === 'A') - : event.target; - updateQuery(anchor.getAttribute('href')); -} - -let queryCache = ''; -async function triggerQueryListeners() { - if (location.search === queryCache) return; - const newQuery = web.queryParams(), - oldQuery = new URLSearchParams(queryCache); - queryCache = location.search; - for (const listener of _queryListeners) { - const newParam = newQuery.get(listener.param), - oldParam = oldQuery.get(listener.param); - if (newParam !== oldParam) listener.handlerFunc(newParam, oldParam); - } -} - -window.addEventListener('popstate', triggerQueryListeners); - -web.addDocumentObserver( - (mutation) => { - mutation.target.querySelectorAll('a[href^="?"]').forEach((a) => { - a.removeEventListener('click', router); - a.addEventListener('click', router); - }); - }, - ['a[href^="?"]'] -); diff --git a/src/core/menuu/styles.mjs b/src/core/menuu/styles.mjs deleted file mode 100644 index 0ce7925..0000000 --- a/src/core/menuu/styles.mjs +++ /dev/null @@ -1,182 +0,0 @@ -/** - * notion-enhancer: menu - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -// css-in-js for better component generation - -import { tw, apply, setup } from '../../dep/twind.mjs'; -import { content } from '../../dep/twind-content.mjs'; - -const pseudoContent = content('""'), - mapColorVariables = (color) => ({ - 'text': `var(--theme--text_${color})`, - 'highlight': `var(--theme--highlight_${color})`, - 'highlight-text': `var(--theme--highlight_${color}-text)`, - 'callout': `var(--theme--callout_${color})`, - 'callout-text': `var(--theme--callout_${color}-text)`, - 'tag': `var(--theme--tag_${color})`, - 'tag-text': `var(--theme--tag_${color}-text)`, - 'board': `var(--theme--board_${color})`, - 'board-text': `var(--theme--board_${color}-text)`, - 'board-card': `var(--theme--board_${color}-card)`, - 'board-card_text': `var(--theme--board_${color}-card_text)`, - }); - -const customClasses = { - 'notifications-container': apply`absolute bottom-0 right-0 px-4 py-3 max-w-full w-96 z-10`, - 'notification': ([color = 'default']) => - apply`p-2 border group hover:(filter brightness-125) ${ - color === 'default' - ? 'bg-tag text-tag-text border-divider' - : `bg-${color}-tag text-${color}-tag-text border-${color}-text` - } flex items-center rounded-full mt-3 shadow-md cursor-pointer`, - 'notification-text': apply`text-xs mx-2 flex-auto font-semibold group-hover:(filter brightness-75)`, - 'notification-icon': apply`fill-current opacity-75 h-4 w-4 mx-2`, - 'body-container': apply`flex w-full h-full overflow-hidden`, - 'sidebar': apply`h-full w-96 max-w-3/7 flex-shrink-0 px-4 pt-3 pb-16 overflow-y-auto flex flex-col - bg-notion-secondary border-l border-divider`, - 'content-container': apply`h-full w-full flex flex-col`, - 'nav': apply`px-4 mx-2.5 py-1 flex flex-wrap items-center border-b border-divider - justify-center xl:justify-start`, - 'nav-notion': apply`flex items-center font-semibold text-xl cursor-pointer select-none my-4 - w-full justify-center xl:(mr-4 w-auto justify-start)`, - 'nav-notion-icon': apply`h-6 w-6 mr-2.5`, - 'nav-item': apply`mr-2 px-3 py-2 rounded-md text-sm font-medium - hover:bg-interactive-hover focus:bg-interactive-active mb-2 xl:(mt-1 mb-0)`, - 'nav-item-selected': apply`nav-item ring-1 ring-divider bg-notion-secondary - hover:bg-notion-secondary focus:bg-notion-secondary`, - 'nav-changelog': apply`xl:ml-auto focus:outline-none`, - 'nav-changelog-icon': apply`w-4 h-4`, - 'main': apply`transition px-4 py-3 overflow-y-auto flex-grow`, - 'main-message': apply`mx-2.5 my-2.5 px-px text-sm text-foreground-secondary text-justify`, - 'mods-list': apply`flex flex-wrap`, - 'mod-container': apply`w-full md:w-1/2 lg:w-1/3 xl:w-1/4 2xl:w-1/5 px-2.5 py-2.5 box-border`, - 'mod': apply`relative h-full w-full flex flex-col overflow-hidden rounded-lg shadow-lg - bg-notion-secondary border border-divider cursor-pointer`, - 'mod-selected': apply`mod ring ring-accent-blue-active`, - 'mod-body': apply`px-4 py-3 flex flex-col flex-auto children:cursor-pointer`, - 'mod-preview': apply`object-cover w-full h-32`, - 'mod-title': apply`mb-2 text-xl font-semibold tracking-tight flex items-center`, - 'mod-version': apply`mt-px ml-3 p-1 font-normal text-xs leading-none bg-tag text-tag-text rounded`, - 'mod-tags': apply`text-foreground-secondary mb-2 text-xs`, - 'mod-description': apply`mb-2 text-sm`, - 'mod-authors-container': apply`text-sm font-medium`, - 'mod-author': apply`flex items-center mb-2`, - 'mod-author-avatar': apply`inline object-cover w-5 h-5 rounded-full mr-2`, - 'profile-trigger': apply`block px-4 py-3 mb-2 rounded-md text-sm text-left font-semibold shadow-inner - hover:bg-accent-red-button border border-accent-red text-accent-red focus:(outline-none bg-accent-red-button)`, - 'profile-actions': apply`flex`, - 'profile-save': apply`text-sm px-3 py-2 font-medium mt-2 bg-accent-blue text-accent-blue-text rounded-md flex-grow - hover:bg-accent-blue-hover focus:(bg-accent-blue-active outline-none) text-center`, - 'profile-delete': apply`profile-trigger px-3 py-2 mb-0 ml-2 mt-2 text-center font-medium`, - 'profile-export': apply`profile-save mr-2`, - 'profile-import': apply`profile-save mr-2`, - 'profile-error': apply`text-xs mt-2 text-red-text`, - 'profile-icon-action': apply`w-4 h-4 -mt-1 inline-block`, - 'profile-icon-text': apply`w-4 h-4 -mt-1 inline-block mr-1`, - 'options-container': apply`px-4 py-3 shadow-inner rounded-lg bg-notion border border-divider space-y-3`, - 'options-placeholder': apply`text-sm text-foreground-secondary`, - 'toggle-box': apply`w-9 h-5 p-0.5 flex items-center bg-toggle-off rounded-full duration-300 ease-in-out cursor-pointer`, - 'toggle-label': apply`relative text-sm flex w-full mt-auto`, - 'toggle-check': apply`appearance-none ml-auto checked:sibling:(bg-toggle-on after::translate-x-4)`, - 'toggle-feature': apply`after::(${pseudoContent} w-4 h-4 bg-toggle-feature rounded-full duration-300) cursor-pointer`, - 'input-label': apply`block text-sm mt-2 relative`, - 'input': apply`transition block w-full mt-2 pl-3 pr-14 py-2 text-sm rounded-md flex bg-input text-foreground - appearance-none placeholder-foreground-secondary ring-1 ring-divider focus:(outline-none ring ring-accent-blue-active)`, - 'input-tooltip': apply`h-4 w-4 -mt-1 inline-block mr-2`, - 'input-icon': apply`absolute w-11 h-9 right-0 bottom-0 py-2 px-3 bg-notion-secondary rounded-r-md text-icon`, - 'input-placeholder': apply`text-foreground-secondary`, - 'select-option': apply`bg-notion-secondary`, - 'file-latest': apply`block w-full text-left text-foreground-secondary text-xs mt-2 hover:line-through cursor-pointer`, - 'search-container': apply`block mx-2.5 my-2.5 relative`, - 'search': apply`input pr-12`, - 'important-link': apply`text-accent-red border-b border-accent-red opacity-80 hover:opacity-100`, - 'danger': apply`text-red-text`, - 'link': apply`no-underline border-b border-foreground-secondary opacity-70 hover:opacity-90`, - 'modal': apply`fixed flex z-10 inset-0 overflow-y-auto min-h-screen text-center - ease-out duration-300 transition-opacity opacity-0 pointer-events-none`, - 'modal-visible': { - '@apply': apply`ease-in duration-200 opacity-100 pointer-events-auto`, - '& .modal-box': apply`ease-out duration-300 opacity-100 scale-100`, - }, - 'modal-overlay': apply`fixed inset-0 bg-black bg-opacity-50 transition-opacity`, - 'modal-box': apply`inline-block rounded-lg text-left overflow-hidden shadow-xl - transform transition-all m-auto align-middle - ease-in duration-200 opacity-0 scale-95`, - 'modal-body': apply`bg-notion-secondary p-6 pt-4 max-w-xl w-full`, - 'modal-actions': apply`bg-notion py-3 px-6 flex flex-row-reverse`, - 'modal-title': apply`flex`, - 'modal-title-icon': apply`w-20 mr-6`, - 'modal-title-heading': apply`text-xl leading-6 font-medium`, - 'modal-title-description': apply`mt-2 text-sm text-foreground-secondary`, - 'modal-content': { - '@apply': apply`mt-4 text-sm`, - 'p': apply`mt-2`, - }, - 'modal-button': apply`w-full inline-flex justify-center rounded-md text-base font-medium shadow-sm px-4 py-2 - not-focus:hover:bg-interactive-hover focus:bg-interactive-active focus:outline-none`, -}; - -setup({ - preflight: { - html: apply`w-full h-full`, - body: apply`w-full h-full bg-notion font-sans text-foreground`, - }, - theme: { - fontFamily: { - sans: ['var(--theme--font_sans)'], - mono: ['var(--theme--font_code)'], - }, - colors: { - 'black': 'rgba(0,0,0,var(--tw-bg-opacity));', - 'notion': 'var(--theme--bg)', - 'notion-secondary': 'var(--theme--bg_secondary)', - 'notion-card': 'var(--theme--bg_card)', - 'divider': 'var(--theme--ui_divider)', - 'input': 'var(--theme--ui_input)', - 'icon': 'var(--theme--icon)', - 'icon-secondary': 'var(--theme--icon_secondary)', - 'foreground': 'var(--theme--text)', - 'foreground-secondary': 'var(--theme--text_secondary)', - 'interactive-hover': 'var(--theme--ui_interactive-hover)', - 'interactive-active': 'var(--theme--ui_interactive-active)', - 'tag': 'var(--theme--tag_default)', - 'tag-text': 'var(--theme--tag_default-text)', - 'toggle': { - 'on': 'var(--theme--ui_toggle-on)', - 'off': 'var(--theme--ui_toggle-off)', - 'feature': 'var(--theme--ui_toggle-feature)', - }, - 'accent': { - 'blue': 'var(--theme--accent_blue)', - 'blue-hover': 'var(--theme--accent_blue-hover)', - 'blue-active': 'var(--theme--accent_blue-active)', - 'blue-text': 'var(--theme--accent_blue-text)', - 'red': 'var(--theme--accent_red)', - 'red-button': 'var(--theme--accent_red-button)', - 'red-text': 'var(--theme--accent_red-text)', - }, - 'gray': mapColorVariables('gray'), - 'brown': mapColorVariables('brown'), - 'orange': mapColorVariables('orange'), - 'yellow': mapColorVariables('yellow'), - 'green': mapColorVariables('green'), - 'blue': mapColorVariables('blue'), - 'purple': mapColorVariables('purple'), - 'pink': mapColorVariables('pink'), - 'red': mapColorVariables('red'), - }, - extend: { - maxWidth: { '3/7': 'calc((3 / 7) * 100%);' }, - }, - }, - plugins: customClasses, -}); - -tw`hidden ${Object.keys(customClasses).join(' ')}`; - -export { tw };