diff --git a/src/_common/events.js b/src/_common/events.js index 4ee92d1..7468be7 100644 --- a/src/_common/events.js +++ b/src/_common/events.js @@ -112,6 +112,7 @@ const modifierAliases = [ keyListeners = keyListeners.filter(([, c]) => c !== callback); }, handleKeypress = (event, keyListeners) => { + console.log(event); for (const [accelerator, callback] of keyListeners) { const acceleratorModifiers = [], combinationTriggered = @@ -125,6 +126,7 @@ const modifierAliases = [ if (key === "space") key = " "; if (key === "plus") key = "equal"; if (key === "minus") key = "-"; + if (key === "\\") key = "backslash"; if (key === ",") key = "comma"; if (key === ".") key = "period"; const keyPressed = [ diff --git a/src/core/client.mjs b/src/core/client.mjs index 9e92adf..5007881 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -121,22 +121,31 @@ const insertMenu = async (api, db) => { const insertPanel = async (api, db) => { const notionFrame = ".notion-frame", - notionTopbarBtn = ".notion-topbar-more-button", togglePanelHotkey = await db.get("togglePanelHotkey"), - { addPanelView, addMutationListener, addKeyListener } = api, - { html, setState, useState } = api; + { addPanelView, addMutationListener } = api, + { html, setState } = api; const $panel = html`<${Panel} - ...${Object.assign( - ...["Width", "Open", "View"].map((key) => ({ - [`_get${key}`]: () => db.get(`sidePanel${key}`), - [`_set${key}`]: async (value) => { - await db.set(`sidePanel${key}`, value); - setState({ rerender: true }); - }, - })) - )} - />`; + hotkey="${togglePanelHotkey}" + ...${Object.assign( + ...["Width", "Open", "View"].map((key) => ({ + [`_get${key}`]: () => db.get(`sidePanel${key}`), + [`_set${key}`]: async (value) => { + await db.set(`panel${key}`, value); + setState({ rerender: true }); + }, + })) + )} + />`, + appendToDom = () => { + const $frame = document.querySelector(notionFrame); + if (!$frame) return; + if (!$frame.contains($panel)) $frame.append($panel); + if (!$frame.style.flexDirection !== "row") + $frame.style.flexDirection = "row"; + }; + addMutationListener(notionFrame, appendToDom); + appendToDom(); const $helloThere = html`
hello there
`, $generalKenobi = html`
general kenobi
`; @@ -158,43 +167,6 @@ const insertPanel = async (api, db) => { $icon: "type", $view: $generalKenobi, }); - // setTimeout(() => { - // removePanelView($helloThere); - // removePanelView($generalKenobi); - // }, 5000); - - const $panelTopbarBtn = html`<${TopbarButton} - aria-label="Open side panel" - icon="panel-right" - onclick=${$panel.toggle} - />`, - appendToDom = () => { - const $frame = document.querySelector(notionFrame); - if (!$frame) return; - if (!$frame.contains($panel)) $frame.append($panel); - if (!$frame.style.flexDirection !== "row") - $frame.style.flexDirection = "row"; - if (!document.contains($panelTopbarBtn)) { - const $notionTopbarBtn = document.querySelector(notionTopbarBtn); - $notionTopbarBtn?.before($panelTopbarBtn); - } - }; - addMutationListener(`${notionFrame}, ${notionTopbarBtn}`, appendToDom); - appendToDom(); - - useState(["panelOpen"], ([panelOpen]) => { - if (panelOpen) $panelTopbarBtn.setAttribute("data-active", true); - else $panelTopbarBtn.removeAttribute("data-active"); - }); - useState(["panelViews"], ([panelViews = []]) => { - $panelTopbarBtn.style.display = panelViews.length ? "" : "none"; - }); - - addKeyListener(togglePanelHotkey, (event) => { - event.preventDefault(); - event.stopPropagation(); - $panel.toggle(); - }); }; export default async (api, db) => diff --git a/src/core/islands/Panel.mjs b/src/core/islands/Panel.mjs index ccc6a01..ec6a28e 100644 --- a/src/core/islands/Panel.mjs +++ b/src/core/islands/Panel.mjs @@ -6,6 +6,7 @@ */ import { Tooltip } from "./Tooltip.mjs"; +import { TopbarButton } from "./TopbarButton.mjs"; import { Select } from "../menu/islands/Select.mjs"; // note: these islands are not reusable. @@ -48,15 +49,12 @@ function View({ _get }) { } function Switcher({ _get, _set, minWidth, maxWidth }) { - const { html, setState, useState } = globalThis.__enhancerApi, + const { html, useState } = globalThis.__enhancerApi, $switcher = html`
`, - setView = (view) => { - _set?.(view); - setState({ activePanelView: view }); - }; + setView = (view) => _set?.(view); useState(["panelViews"], ([panelViews = []]) => { const values = panelViews.map(([{ title, $icon }]) => { // panel switcher internally uses the select island, @@ -76,6 +74,7 @@ function Switcher({ _get, _set, minWidth, maxWidth }) { } function Panel({ + hotkey, _getWidth, _setWidth, _getOpen, @@ -86,8 +85,24 @@ function Panel({ maxWidth = 640, transitionDuration = 300, }) { - const { html, setState, useState } = globalThis.__enhancerApi, + const { html, useState, addKeyListener } = globalThis.__enhancerApi, { addMutationListener, removeMutationListener } = globalThis.__enhancerApi, + $topbarToggle = html`<${TopbarButton} + aria-label="Toggle side panel" + icon="panel-right" + />`, + $panelToggle = html``, $panel = html`
- + ${$panelToggle}
<${View} ...${{ _get: _getView }} /> `; + $panelToggle.onclick = $topbarToggle.onclick = () => $panel.toggle(); + $topbarToggle.addToTopbar(); let preDragWidth, dragStartX, _animatedAt; const getWidth = async (width) => { @@ -206,20 +211,6 @@ function Panel({ group-&[data-peeked]/panel:(my-px h-[calc(100%-2px)] rounded-l-[6px])" > `, - $onResizeClick = html`close`, - $resizeTooltip = html`<${Tooltip}> - Drag to resize
- Click to ${$onResizeClick} - `, - showTooltip = (event) => { - setTimeout(() => { - if (!$resizeHandle.matches(":hover")) return; - const open = $panel.hasAttribute("open"), - { x } = $resizeHandle.getBoundingClientRect(); - $onResizeClick.innerText = open ? "close" : "lock open"; - $resizeTooltip.show(x, event.clientY); - }, 200); - }, startDrag = async (event) => { dragStartX = event.clientX; preDragWidth = await getWidth(); @@ -240,11 +231,43 @@ function Panel({ if (dragStartX - event.clientX === 0) $panel.toggle(); preDragWidth = dragStartX = undefined; }; - $resizeHandle.addEventListener("mouseout", $resizeTooltip.hide); $resizeHandle.addEventListener("mousedown", startDrag); - $resizeHandle.addEventListener("mouseover", showTooltip); $panel.lastElementChild.prepend($resizeHandle); + // add tooltips to panel pin/unpin toggles + const $resizeTooltipClick = html``, + $resizeTooltip = html`<${Tooltip} + onbeforeshow=${() => { + $resizeTooltipClick.innerText = isPinned() ? "close" : "lock open"; + }} + >Drag to resize
+ Click to ${$resizeTooltipClick} + `, + $toggleTooltipClick = html``, + $toggleTooltip = html`<${Tooltip} + class="text-start" + onbeforeshow=${() => { + $toggleTooltipClick.innerText = isPinned() + ? "Close sidebar" + : "Lock sidebar open"; + }} + >${$toggleTooltipClick}
+ ${hotkey} + `, + alignTooltipBelow = ($target) => { + const rect = $target.getBoundingClientRect(); + return { + x: rect.right, + y: rect.bottom + $toggleTooltip.clientHeight / 2 + 6, + }; + }; + $resizeTooltip.attach($resizeHandle, () => { + const rect = $resizeHandle.getBoundingClientRect(); + return { x: rect.x - 6 }; + }); + $toggleTooltip.attach($topbarToggle, () => alignTooltipBelow($topbarToggle)); + $toggleTooltip.attach($panelToggle, () => alignTooltipBelow($panelToggle)); + // hovering over the peek trigger will temporarily // pop out an interactive preview of the panel let _peekDebounce; @@ -274,10 +297,9 @@ function Panel({ if (!$notionHelp) return; width ??= await getWidth(); if (isNaN(width)) width = minWidth; - if (!$panel.hasAttribute("open")) width = 0; + if (isClosed()) width = 0; const to = `${26 + width}px`, - from = $notionHelp.style.getPropertyValue("right"), - opts = { duration: transitionDuration, easing }; + from = $notionHelp.style.getPropertyValue("right"); if (from === to) return; $notionHelp.style.setProperty("right", to); animate($notionHelp, [({ right: from }, { right: to })]); @@ -292,9 +314,9 @@ function Panel({ animate($panel, [closedWidth, openWidth]); $panel.removeAttribute("data-peeked"); $panel.dataset.pinned = true; + $topbarToggle.setAttribute("data-active", true); setInteractive(true); _setOpen(true); - setState({ panelOpen: true }); $panel.resize(); }; $panel.peek = () => { @@ -314,8 +336,8 @@ function Panel({ }; $panel.close = async () => { if (isClosed()) return; - setState({ panelOpen: false }); if (panelViews.length) _setOpen(false); + $topbarToggle.removeAttribute("data-active"); const width = (animationState.width = `${await getWidth()}px`); // only animate container close if it is actually taking up space, // otherwise will unnaturally grow + retrigger peek on peek mouseout @@ -348,9 +370,18 @@ function Panel({ }; useState(["panelViews"], async ([panelViews = []]) => { + $topbarToggle.style.display = panelViews.length ? "" : "none"; if (panelViews.length && (await _getOpen())) $panel.pin(); else $panel.close(); }); + + if (!hotkey) return $panel; + addKeyListener(hotkey, (event) => { + event.preventDefault(); + event.stopPropagation(); + $panel.toggle(); + }); + return $panel; } diff --git a/src/core/islands/Tooltip.mjs b/src/core/islands/Tooltip.mjs index 5d35bd3..fe6b355 100644 --- a/src/core/islands/Tooltip.mjs +++ b/src/core/islands/Tooltip.mjs @@ -8,16 +8,16 @@ function Tooltip(props, ...children) { const { html, extendProps } = globalThis.__enhancerApi; extendProps(props, { role: "dialog", - class: `absolute group/tooltip z-[999] pointer-events-none`, + class: `absolute group/tooltip z-[999] text-center pointer-events-none`, }); const notionApp = ".notion-app-inner", $tooltip = html`
@@ -27,12 +27,14 @@ function Tooltip(props, ...children) { $tooltip.show = (x, y) => { const $notionApp = document.querySelector(notionApp); if (!document.contains($tooltip)) $notionApp?.append($tooltip); + if ($tooltip.hasAttribute("open")) return; requestAnimationFrame(() => { + $tooltip.onbeforeshow?.(); $tooltip.setAttribute("open", true); - x -= $tooltip.clientWidth + 6; - if (x < 0) x += $tooltip.clientWidth + 12; + x -= $tooltip.clientWidth; + if (x < 0) x = $tooltip.clientWidth + 12; y -= $tooltip.clientHeight / 2; - if (y < 0) y += $tooltip.clientHeight / 2; + if (y < 0) y = $tooltip.clientHeight + 12; $tooltip.style.left = `${x}px`; $tooltip.style.top = `${y}px`; $tooltip.onshow?.(); @@ -45,6 +47,16 @@ function Tooltip(props, ...children) { $tooltip.onhide?.(); }, 200); }; + $tooltip.attach = ($target, calcPos) => { + $target.addEventListener("mouseover", (event) => { + setTimeout(() => { + if (!$target.matches(":hover")) return; + const { x = event.clientX, y = event.clientY } = calcPos?.(event) ?? {}; + $tooltip.show(x, y); + }, 200); + }); + $target.addEventListener("mouseout", $tooltip.hide); + }; return $tooltip; } diff --git a/src/core/islands/TopbarButton.mjs b/src/core/islands/TopbarButton.mjs index 6f4cd95..537d006 100644 --- a/src/core/islands/TopbarButton.mjs +++ b/src/core/islands/TopbarButton.mjs @@ -4,8 +4,8 @@ * (https://notion-enhancer.github.io/) under the MIT license */ -function TopbarButton({ icon, ...props }, ...children) { - const { html, extendProps } = globalThis.__enhancerApi; +function TopbarButton({ icon, ...props }) { + const { html, extendProps, addMutationListener } = globalThis.__enhancerApi; extendProps(props, { tabindex: 0, role: "button", @@ -15,12 +15,23 @@ function TopbarButton({ icon, ...props }, ...children) { rounded-[3px] hover:bg-[color:var(--theme--bg-hover)] &[data-active]:bg-[color:var(--theme--bg-hover)]`, }); - return html``; + + const notionTopbar = ".notion-topbar-favorite-button", + $button = html``, + addToTopbar = () => { + if (document.contains($button)) return; + document.querySelector(notionTopbar)?.after($button); + }; + $button.addToTopbar = () => { + addToTopbar(); + addMutationListener(notionTopbar, addToTopbar); + }; + return $button; } export { TopbarButton }; diff --git a/src/core/menu/islands/Input.mjs b/src/core/menu/islands/Input.mjs index 8154551..c12b61e 100644 --- a/src/core/menu/islands/Input.mjs +++ b/src/core/menu/islands/Input.mjs @@ -22,6 +22,7 @@ const updateHotkey = (event) => { if (key === " ") key = "Space"; if (["+", "="].includes(key)) key = "Plus"; if (key === "-") key = "Minus"; + if (key === "|") key = "\\"; if (event.code === "Comma") key = ","; if (event.code === "Period") key = "."; if (key === "Control") key = "Ctrl"; diff --git a/src/core/mod.json b/src/core/mod.json index 867d33d..506491b 100644 --- a/src/core/mod.json +++ b/src/core/mod.json @@ -24,25 +24,25 @@ }, { "type": "hotkey", - "key": "toggleWindowHotkey", - "description": "Toggles focus of the Notion window anywhere, even when your Notion app isn't active.", - "value": "Ctrl+Shift+A" + "key": "togglePanelHotkey", + "description": "Toggles the side panel used by some notion-enhancer extensions to display additional information and interfaces within the Notion app.", + "value": "Ctrl+Shift+\\" }, { "type": "hotkey", - "key": "togglePanelHotkey", - "description": "Toggles the side panel used by some notion-enhancer extensions to display additional information and interfaces within the Notion app.", - "value": "Ctrl+Shift+|" + "key": "toggleWindowHotkey", + "description": "Toggles focus of the Notion window anywhere, even when your Notion app isn't active.", + "value": "Ctrl+Shift+A" }, { "type": "heading", "label": "Appearance" }, { - "type": "select", - "key": "menuButtonIconStyle", - "description": "Sets whether the notion-enhancer icon added to Notion's sidebar should be coloured or monochrome. The latter style will match the theme's icon colour for users who would like the icon to be less noticeable.", - "values": ["Colour", "Monochrome"] + "type": "file", + "key": "customStyles", + "description": "Adds the styles from an uploaded .css file to Notion. Use this if you would like to customise the current theme or otherwise tweak Notion's appearance.", + "extensions": ["css"] }, { "type": "select", @@ -51,10 +51,10 @@ "values": ["Auto", "Enabled", "Disabled"] }, { - "type": "file", - "key": "customStyles", - "description": "Adds the styles from an uploaded .css file to Notion. Use this if you would like to customise the current theme or otherwise tweak Notion's appearance.", - "extensions": ["css"] + "type": "select", + "key": "menuButtonIconStyle", + "description": "Sets whether the notion-enhancer icon added to Notion's sidebar should be coloured or monochrome. The latter style will match the theme's icon colour for users who would like the icon to be less noticeable.", + "values": ["Colour", "Monochrome"] }, { "type": "heading", @@ -66,7 +66,7 @@ "key": "developerMode", "description": "Activates built-in debugging tools accessible through the application menu.", "platforms": ["linux", "win32", "darwin"], - "value": false + "value": true } ], "clientStyles": ["variables.css"],