feat: add toggleable side panel w/ hotkey + tooltip

This commit is contained in:
dragonwocky 2023-08-04 22:46:53 +10:00
parent 19e6d5451c
commit 8d679ae3c5
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
7 changed files with 214 additions and 19 deletions

View File

@ -8,6 +8,7 @@ import { checkForUpdate } from "./updateCheck.mjs";
import { sendTelemetryPing } from "./sendTelemetry.mjs";
import { Modal, Frame } from "./islands/Modal.mjs";
import { MenuButton } from "./islands/MenuButton.mjs";
import { Panel } from "./islands/Panel.mjs";
const shouldLoadThemeOverrides = async (db) => {
const { getMods, isEnabled } = globalThis.__enhancerApi,
@ -73,7 +74,7 @@ const insertMenu = async (db) => {
src="${enhancerUrl("core/menu/index.html")}"
onload=${function () {
// pass notion-enhancer api to electron menu process
if (["darwin", "win32", "linux"].includes(platform)) {
if (["linux", "win32", "darwin"].includes(platform)) {
const apiKey = "__enhancerApi";
this.contentWindow[apiKey] = globalThis[apiKey];
}
@ -92,9 +93,8 @@ const insertMenu = async (db) => {
<//>`;
const appendToDom = () => {
if (!document.contains($modal)) document.body.append($modal);
if (!document.querySelector(notionSidebar)?.contains($button)) {
if (!document.querySelector(notionSidebar)?.contains($button))
document.querySelector(notionSettingsAndMembers)?.after($button);
}
};
addMutationListener(notionSidebar, appendToDom);
addMutationListener("body", updateMenuTheme);
@ -102,6 +102,7 @@ const insertMenu = async (db) => {
addKeyListener(openMenuHotkey, (event) => {
event.preventDefault();
event.stopPropagation();
$modal.open();
});
window.addEventListener("focus", triggerMenuRender);
@ -117,9 +118,40 @@ const insertMenu = async (db) => {
});
};
const insertPanel = async (db) => {
const notionFrame = ".notion-frame",
{ html, addKeyListener, addMutationListener } = globalThis.__enhancerApi,
togglePanelHotkey = await db.get("togglePanelHotkey");
const $panel = html`<${Panel}
_getWidth=${() => db.get("sidePanelWidth")}
_setWidth=${(width) => db.set("sidePanelWidth", width)}
_getOpen=${() => db.get("sidePanelOpen")}
_setOpen=${(open) => db.set("sidePanelOpen", open)}
/>`,
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();
addKeyListener(togglePanelHotkey, (event) => {
event.preventDefault();
event.stopPropagation();
if ($panel.hasAttribute("open")) {
$panel.close();
} else $panel.open();
});
};
export default async (api, db) =>
Promise.all([
insertMenu(db),
insertPanel(db),
insertCustomStyles(db),
loadThemeOverrides(db),
sendTelemetryPing(),

View File

@ -4,9 +4,118 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
function Panel(props) {
// <div class="absolute top-0">
// <svg
// role="graphics-symbol"
// viewBox="0 0 16 16"
// class="doubleChevronRight"
// style="width: 16px; height: 16px; display: block; fill: rgba(255, 255, 255, 0.443); flex-shrink: 0;"
// >
// <path d="M2.25781 14.1211C2.47656 14.1211 2.66797 14.0391 2.81836 13.8887L8.14355 8.67969C8.32812 8.49512 8.41699 8.29688 8.41699 8.06445C8.41699 7.8252 8.32812 7.62012 8.14355 7.44922L2.81836 2.24023C2.66797 2.08984 2.4834 2.00781 2.25781 2.00781C1.81348 2.00781 1.46484 2.35645 1.46484 2.80078C1.46484 3.0127 1.55371 3.21777 1.7041 3.375L6.50977 8.05762L1.7041 12.7539C1.55371 12.9043 1.46484 13.1094 1.46484 13.3281C1.46484 13.7725 1.81348 14.1211 2.25781 14.1211ZM8.36914 14.1211C8.58789 14.1211 8.77246 14.0391 8.92285 13.8887L14.2549 8.67969C14.4395 8.49512 14.5283 8.29688 14.5283 8.06445C14.5283 7.8252 14.4326 7.62012 14.2549 7.44922L8.92285 2.24023C8.77246 2.08984 8.58789 2.00781 8.36914 2.00781C7.9248 2.00781 7.56934 2.35645 7.56934 2.80078C7.56934 3.0127 7.66504 3.21777 7.81543 3.375L12.6211 8.05762L7.81543 12.7539C7.66504 12.9043 7.56934 13.1094 7.56934 13.3281C7.56934 13.7725 7.9248 14.1211 8.36914 14.1211Z"></path>
// </svg>
// </div>;
import { Tooltip } from "./Tooltip.mjs";
function Panel(
{
_getWidth,
_setWidth,
_getOpen,
_setOpen,
minWidth = 260,
maxWidth = 520,
...props
},
...children
) {
const { html, extendProps } = globalThis.__enhancerApi;
return html`<iframe ...${props}></iframe>`;
extendProps(props, {
class: `notion-enhancer--side-panel order-2 shrink-0
transition-[width] open:w-[var(--side\\_panel--width)]
w-0 border-l-1 border-[color:var(--theme--fg-border)]
relative bg-[color:var(--theme--bg-primary)]`,
});
const $tooltip = html`<${Tooltip}>
<span>Drag</span> to resize<br />
<span>Click</span> to closed
<//>`,
$panel = html`<aside ...${props}>
<div
class="absolute h-full w-[3px] left-[-3px] z-10
transition duration-300 hover:(cursor-col-resize
shadow-[var(--theme--fg-border)_-2px_0px_0px_0px_inset])"
onmouseover=${function (event) {
setTimeout(() => {
const panelOpen = $panel.hasAttribute("open"),
handleHovered = this.matches(":hover");
console.log(panelOpen, handleHovered);
if (!panelOpen || !handleHovered) return;
const { x } = this.getBoundingClientRect();
$tooltip.show(x, event.clientY);
}, 200);
}}
onmouseout=${() => $tooltip.hide()}
onclick=${() => $panel.close()}
></div>
<div>Hello world.</div>
</aside>`;
const notionHelp = ".notion-help-button",
repositionHelp = async (attempts = 0) => {
const $notionHelp = document.querySelector(notionHelp);
if (!$notionHelp) {
if (attempts < 20) setTimeout(() => repositionHelp(attempts + 1), 150);
return;
}
let width = await _getWidth?.();
if (isNaN(width)) width = minWidth;
if (!$panel.hasAttribute("open")) width = 0;
const position = $notionHelp.style.getPropertyValue("right"),
destination = `${26 + width}px`,
keyframes = [{ right: position }, { right: destination }],
options = { duration: 150, easing: "cubic-bezier(0.4, 0, 0.2, 1)" };
$notionHelp.style.setProperty("right", destination);
$notionHelp.animate(keyframes, options);
};
$panel.resize = async (width) => {
$tooltip.hide();
if (width) {
width = Math.max(width, minWidth);
width = Math.min(width, maxWidth);
_setWidth?.(width);
} else width = await _getWidth?.();
if (isNaN(width)) width = minWidth;
$panel.style.setProperty("--side_panel--width", `${width}px`);
repositionHelp();
};
$panel.open = () => {
$panel.setAttribute("open", true);
$panel.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0));
$panel.onopen?.();
_setOpen(true);
$panel.resize();
};
$panel.close = () => {
$tooltip.hide();
$panel.onbeforeclose?.();
$panel.removeAttribute("open");
$panel.style.pointerEvents = "auto";
$panel.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1));
repositionHelp();
_setOpen(false);
setTimeout(() => {
$panel.style.pointerEvents = "";
$panel.onclose?.();
}, 150);
};
_getOpen().then((open) => {
if (open) $panel.open();
});
return $panel;
}
export { Panel };

View File

@ -0,0 +1,48 @@
/**
* notion-enhancer
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
function Tooltip(props, ...children) {
const { html, extendProps } = globalThis.__enhancerApi;
extendProps(props, {
role: "dialog",
class: `absolute group z-[999] pointer-events-none`,
});
const notionApp = ".notion-app-inner",
$tooltip = html`<div ...${props}>
<div
class="bg-[color:var(--theme--bg-secondary)]
text-([color:var(--theme--fg-secondary)] [12px] center)
leading-[1.4] font-medium py-[4px] px-[8px] rounded-[4px]
drop-shadow-md transition duration-200 opacity-0
group-open:(pointer-events-auto opacity-100)
children:text-([color:var(--theme--fg-primary)]"
>
${children}
</div>
</div>`;
$tooltip.show = (x, y) => {
const $notionApp = document.querySelector(notionApp);
if (!document.contains($tooltip)) $notionApp?.append($tooltip);
requestAnimationFrame(() => {
$tooltip.setAttribute("open", true);
$tooltip.style.left = `${x - $tooltip.clientWidth - 6}px`;
$tooltip.style.top = `${y}px`;
$tooltip.onshow?.();
});
};
$tooltip.hide = () => {
$tooltip.onbeforehide?.();
$tooltip.removeAttribute("open");
setTimeout(() => {
$tooltip.onhide?.();
}, 200);
};
return $tooltip;
}
export { Tooltip };

View File

@ -107,7 +107,7 @@ function Banner({ updateAvailable, isDevelopmentBuild }) {
useState(["focus", "view"], ([, view = "welcome"]) => {
if (view !== "welcome") return;
// delayed appearance = movement attracts eye
setTimeout(() => $version.lastElementChild.show(), 400);
setTimeout(() => $popup.open(), 400);
});
}

View File

@ -16,25 +16,25 @@ function Popup({ trigger, ...props }, ...children) {
});
const $popup = html`<div ...${props}>
<div class="relative right-[100%]">
<div class="relative right-[calc(100%+8px)]">
<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]
transition duration-200 opacity-0 scale-95 rounded-[4px]
group-open:(pointer-events-auto opacity-100 scale-100)"
>
${children}
</div>
</div>
</div>`;
$popup.show = () => {
$popup.open = () => {
$popup.setAttribute("open", true);
$popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0));
setState({ popupOpen: true });
$popup.onopen?.();
};
$popup.hide = () => {
$popup.close = () => {
$popup.onbeforeclose?.();
$popup.removeAttribute("open");
$popup.style.pointerEvents = "auto";
@ -51,19 +51,19 @@ function Popup({ trigger, ...props }, ...children) {
if (!$popup.hasAttribute("open")) return;
if ($popup.contains(event.target) || $popup === event.target) return;
if (trigger?.contains(event.target) || trigger === event.target) return;
$popup.hide();
$popup.close();
});
useState(["rerender"], () => {
if ($popup.hasAttribute("open")) $popup.hide();
if ($popup.hasAttribute("open")) $popup.close();
});
if (!trigger) return $popup;
extendProps(trigger, {
onclick: $popup.show,
onclick: $popup.open,
onkeydown(event) {
if ([" ", "Enter"].includes(event.key)) {
event.preventDefault();
$popup.show();
$popup.open();
}
},
});

View File

@ -65,11 +65,11 @@ function Profile({ id }) {
delete res["profileName"];
await profile.import(res);
setState({ rerender: true });
$uploadSuccess.show();
setTimeout(() => $uploadSuccess.hide(), 2000);
$uploadSuccess.open();
setTimeout(() => $uploadSuccess.close(), 2000);
} catch (err) {
$uploadError.show();
setTimeout(() => $uploadError.hide(), 2000);
$uploadError.open();
setTimeout(() => $uploadError.close(), 2000);
}
// clear input value to allow repeat uploads
event.target.value = "";
@ -144,7 +144,7 @@ function Profile({ id }) {
<${Button}
tabindex="0"
class="justify-center"
onclick=${() => $confirm.hide()}
onclick=${() => $confirm.close()}
>
Cancel
<//>

View File

@ -22,6 +22,12 @@
"description": "Opens the notion-enhancer menu from within Notion.",
"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+|"
},
{
"type": "heading",
"label": "Appearance"