diff --git a/src/core/client.mjs b/src/core/client.mjs
index 1a1df39..82a4585 100644
--- a/src/core/client.mjs
+++ b/src/core/client.mjs
@@ -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(),
diff --git a/src/core/islands/Panel.mjs b/src/core/islands/Panel.mjs
index ddae96f..1a34cb1 100644
--- a/src/core/islands/Panel.mjs
+++ b/src/core/islands/Panel.mjs
@@ -4,9 +4,118 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
-function Panel(props) {
+//
;
+
+import { Tooltip } from "./Tooltip.mjs";
+
+function Panel(
+ {
+ _getWidth,
+ _setWidth,
+ _getOpen,
+ _setOpen,
+ minWidth = 260,
+ maxWidth = 520,
+ ...props
+ },
+ ...children
+) {
const { html, extendProps } = globalThis.__enhancerApi;
- return html``;
+ 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}>
+ Drag to resize
+ Click to closed
+ />`,
+ $panel = html``;
+
+ 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 };
diff --git a/src/core/islands/Tooltip.mjs b/src/core/islands/Tooltip.mjs
new file mode 100644
index 0000000..286025f
--- /dev/null
+++ b/src/core/islands/Tooltip.mjs
@@ -0,0 +1,48 @@
+/**
+ * notion-enhancer
+ * (c) 2023 dragonwocky (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``;
+ $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 };
diff --git a/src/core/menu/islands/Banner.mjs b/src/core/menu/islands/Banner.mjs
index 10d8199..0e5e791 100644
--- a/src/core/menu/islands/Banner.mjs
+++ b/src/core/menu/islands/Banner.mjs
@@ -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);
});
}
diff --git a/src/core/menu/islands/Popup.mjs b/src/core/menu/islands/Popup.mjs
index 815365b..838eede 100644
--- a/src/core/menu/islands/Popup.mjs
+++ b/src/core/menu/islands/Popup.mjs
@@ -16,25 +16,25 @@ function Popup({ trigger, ...props }, ...children) {
});
const $popup = html`
-
`;
- $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();
}
},
});
diff --git a/src/core/menu/islands/Profiles.mjs b/src/core/menu/islands/Profiles.mjs
index c09a46c..cd2e72d 100644
--- a/src/core/menu/islands/Profiles.mjs
+++ b/src/core/menu/islands/Profiles.mjs
@@ -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
/>
diff --git a/src/core/mod.json b/src/core/mod.json
index 9f464ce..50ab2b3 100644
--- a/src/core/mod.json
+++ b/src/core/mod.json
@@ -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"