feat(titlebar): render window controls in tabbar if active, otherwise in topbar

This commit is contained in:
dragonwocky 2024-01-24 02:24:56 +11:00
parent 346b61eff4
commit 653d7d9bc8
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
10 changed files with 135 additions and 140 deletions

View File

@ -42,7 +42,7 @@ const connectToPort = () => {
// from worker to client // from worker to client
if (IS_RENDERER) { if (IS_RENDERER) {
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
ipcRenderer.on(channel, listener); ipcRenderer.on(channel, (event, message) => listener(message));
} else if (!IS_ELECTRON) { } else if (!IS_ELECTRON) {
const onMessage = (msg) => { const onMessage = (msg) => {
if (msg?.channel !== channel || msg?.invocation) return; if (msg?.channel !== channel || msg?.invocation) return;

View File

@ -125,8 +125,7 @@ function Panel({
class="border-(l-1 [color:var(--theme--fg-border)]) w-0 class="border-(l-1 [color:var(--theme--fg-border)]) w-0
group-&[data-pinned]/panel:(w-[var(--panel--width,0)]) h-[calc(100vh-45px)] bottom-0) group-&[data-pinned]/panel:(w-[var(--panel--width,0)]) h-[calc(100vh-45px)] bottom-0)
absolute right-0 z-20 bg-[color:var(--theme--bg-primary)] group-&[data-peeked]/panel:( absolute right-0 z-20 bg-[color:var(--theme--bg-primary)] group-&[data-peeked]/panel:(
w-[var(--panel--width,0)] h-[calc(100vh-120px)] bottom-[60px] rounded-l-[8px] border-(t-1 b-1) w-[var(--panel--width,0)] h-[calc(100vh-120px)] bottom-[60px] rounded-l-[8px] border-(t-1 b-1))"
shadow-[rgba(15,15,15,0.1)_0px_0px_0px_1px,rgba(15,15,15,0.2)_0px_3px_6px,rgba(15,15,15,0.4)_0px_9px_24px])"
> >
<div <div
class="flex justify-between items-center class="flex justify-between items-center
@ -141,7 +140,14 @@ function Panel({
</aside> </aside>
</div>`; </div>`;
$panelToggle.onclick = $topbarToggle.onclick = () => $panel.toggle(); $panelToggle.onclick = $topbarToggle.onclick = () => $panel.toggle();
$topbarToggle.addToTopbar();
const topbarFavorite = ".notion-topbar-favorite-button",
addToTopbar = () => {
if (document.contains($topbarToggle)) return;
document.querySelector(topbarFavorite)?.after($topbarToggle);
};
addMutationListener(topbarFavorite, addToTopbar);
addToTopbar(topbarFavorite);
let preDragWidth, dragStartX, _animatedAt; let preDragWidth, dragStartX, _animatedAt;
const getWidth = async (width) => { const getWidth = async (width) => {
@ -175,8 +181,9 @@ function Panel({
borderBottomWidth: "1px", borderBottomWidth: "1px",
borderTopLeftRadius: "8px", borderTopLeftRadius: "8px",
borderBottomLeftRadius: "8px", borderBottomLeftRadius: "8px",
boxShadow: boxShadow: document.body.classList.contains("dark")
"rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 3px 6px, rgba(15, 15, 15, 0.4) 0px 9px 24px", ? "rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 3px 6px, rgba(15, 15, 15, 0.4) 0px 9px 24px"
: "rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px",
}, },
pinAnimation = { pinAnimation = {
height: "calc(100vh - 45px)", height: "calc(100vh - 45px)",
@ -247,7 +254,6 @@ function Panel({
<//>`, <//>`,
$toggleTooltipClick = html`<b></b>`, $toggleTooltipClick = html`<b></b>`,
$toggleTooltip = html`<${Tooltip} $toggleTooltip = html`<${Tooltip}
class="text-start"
onbeforeshow=${() => { onbeforeshow=${() => {
$toggleTooltipClick.innerText = isPinned() $toggleTooltipClick.innerText = isPinned()
? "Close sidebar" ? "Close sidebar"
@ -255,20 +261,10 @@ function Panel({
}} }}
>${$toggleTooltipClick}<br /> >${$toggleTooltipClick}<br />
${hotkey} ${hotkey}
<//>`, <//>`;
alignTooltipBelow = ($target) => { $resizeTooltip.attach($resizeHandle, "left");
const rect = $target.getBoundingClientRect(); $toggleTooltip.attach($topbarToggle, "bottom");
return { $toggleTooltip.attach($panelToggle, "bottom");
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 // hovering over the peek trigger will temporarily
// pop out an interactive preview of the panel // pop out an interactive preview of the panel

View File

@ -26,19 +26,26 @@ function Tooltip(props, ...children) {
${children} ${children}
</div> </div>
</div>`; </div>`;
// can pass each coord as a number or a function
$tooltip.show = (x, y) => { $tooltip.show = (x, y) => {
const $notionApp = document.querySelector(notionApp); const $notionApp = document.querySelector(notionApp);
if (!document.contains($tooltip)) $notionApp?.append($tooltip); if (!document.contains($tooltip)) $notionApp?.append($tooltip);
if ($tooltip.hasAttribute("open")) return; if ($tooltip.hasAttribute("open")) return;
$tooltip.onbeforeshow?.();
const edgePadding = 12,
{ clientHeight, clientWidth } = document.documentElement;
requestAnimationFrame(() => { requestAnimationFrame(() => {
$tooltip.onbeforeshow?.(); if (typeof x === "function") x = x();
$tooltip.setAttribute("open", true); if (typeof y === "function") y = y();
x -= $tooltip.clientWidth; if (x < edgePadding) x = $tooltip.clientWidth + edgePadding;
if (x < 0) x = $tooltip.clientWidth + 12; if (x + $tooltip.clientWidth > clientWidth + edgePadding)
y -= $tooltip.clientHeight / 2; x = clientWidth - $tooltip.clientWidth - edgePadding;
if (y < 0) y = $tooltip.clientHeight + 12; if (y < edgePadding) y = $tooltip.clientHeight + edgePadding;
if (y + $tooltip.clientHeight > clientHeight + edgePadding)
y = clientHeight - $tooltip.clientHeight - edgePadding;
$tooltip.style.left = `${x}px`; $tooltip.style.left = `${x}px`;
$tooltip.style.top = `${y}px`; $tooltip.style.top = `${y}px`;
$tooltip.setAttribute("open", true);
$tooltip.onshow?.(); $tooltip.onshow?.();
}); });
}; };
@ -49,11 +56,30 @@ function Tooltip(props, ...children) {
$tooltip.onhide?.(); $tooltip.onhide?.();
}, 200); }, 200);
}; };
$tooltip.attach = ($target, calcPos) => { $tooltip.attach = ($target, alignment = "") => {
$target.addEventListener("mouseover", (event) => { $target.addEventListener("mouseover", (event) => {
setTimeout(() => { setTimeout(() => {
if (!$target.matches(":hover")) return; if (!$target.matches(":hover")) return;
const { x = event.clientX, y = event.clientY } = calcPos?.(event) ?? {}; const x = () => {
const rect = $target.getBoundingClientRect();
if (["top", "bottom"].includes(alignment)) {
return rect.left + rect.width / 2 - $tooltip.clientWidth / 2;
} else if (alignment === "left") {
return rect.left - $tooltip.clientWidth - 6;
} else if (alignment === "right") {
return rect.right + 6;
} else return event.clientX;
},
y = () => {
const rect = $target.getBoundingClientRect();
if (["left", "right"].includes(alignment)) {
return event.clientY - $tooltip.clientHeight / 2;
} else if (alignment === "top") {
return rect.top - $tooltip.clientHeight - 6;
} else if (alignment === "bottom") {
return rect.bottom + 6;
} else return event.clientY;
};
$tooltip.show(x, y); $tooltip.show(x, y);
}, 200); }, 200);
}); });

View File

@ -6,34 +6,24 @@
"use strict"; "use strict";
function TopbarButton({ icon, ...props }) { function TopbarButton({ icon, ...props }, ...children) {
const { html, extendProps, addMutationListener } = globalThis.__enhancerApi; const { html, extendProps, addMutationListener } = globalThis.__enhancerApi;
extendProps(props, { extendProps(props, {
tabindex: 0, tabindex: 0,
role: "button", role: "button",
class: `notion-enhancer--topbar-button class: `notion-enhancer--topbar-button
text-[color:var(--theme--fg-primary)] mr-[2px]
user-select-none h-[28px] w-[33px] duration-[20ms] user-select-none h-[28px] w-[33px] duration-[20ms]
transition inline-flex items-center justify-center mr-[2px] transition inline-flex items-center justify-center
rounded-[3px] hover:bg-[color:var(--theme--bg-hover)] rounded-[3px] hover:bg-[color:var(--theme--bg-hover)]
&[data-active]:bg-[color:var(--theme--bg-hover)]`, &[data-active]:bg-[color:var(--theme--bg-hover)]`,
}); });
const topbarFavorite = ".notion-topbar-favorite-button", return html`<button ...${props}>
$button = html`<button ...${props}> ${children.length
<i ? children
class="i-${icon} w-[20px] h-[20px] : html`<i class="i-${icon} w-[20px] h-[20px]" />`}
fill-[color:var(--theme--fg-secondary)]" </button>`;
/>
</button>`,
addToTopbar = (after = topbarFavorite) => {
if (document.contains($button)) return;
document.querySelector(after)?.after($button);
};
$button.addToTopbar = (after = topbarFavorite) => {
addMutationListener(after, () => addToTopbar(after));
addToTopbar(after);
};
return $button;
} }
export { TopbarButton }; export { TopbarButton };

View File

@ -1,73 +1,49 @@
/** /**
* notion-enhancer: integrated titlebar * notion-enhancer: titlebar
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license * (https://notion-enhancer.github.io/) under the MIT license
*/ */
'use strict'; "use strict";
export const createWindowButtons = async ({ electron, web, components }, db) => { import { Tooltip } from "../../core/islands/Tooltip.mjs";
let minimizeIcon = (await db.get(['minimize_icon'])) || (await components.feather('minus')), import { TopbarButton } from "../../core/islands/TopbarButton.mjs";
maximizeIcon = (await db.get(['maximize_icon'])) || (await components.feather('maximize')),
unmaximizeIcon =
(await db.get(['unmaximize_icon'])) || (await components.feather('minimize')),
closeIcon = (await db.get(['close_icon'])) || (await components.feather('x'));
minimizeIcon = minimizeIcon.trim();
maximizeIcon = maximizeIcon.trim();
unmaximizeIcon = unmaximizeIcon.trim();
closeIcon = closeIcon.trim();
minimizeIcon = const minimizeLabel = "Minimize window",
minimizeIcon.startsWith('<svg') && minimizeIcon.endsWith('</svg>') maximizeLabel = "Maximize window",
? minimizeIcon unmaximizeLabel = "Unmaximize window",
: web.escape(minimizeIcon); closeLabel = "Close window";
maximizeIcon =
maximizeIcon.startsWith('<svg') && maximizeIcon.endsWith('</svg>')
? maximizeIcon
: web.escape(maximizeIcon);
unmaximizeIcon =
unmaximizeIcon.startsWith('<svg') && unmaximizeIcon.endsWith('</svg>')
? unmaximizeIcon
: web.escape(unmaximizeIcon);
closeIcon =
closeIcon.startsWith('<svg') && closeIcon.endsWith('</svg>')
? closeIcon
: web.escape(closeIcon);
const $windowButtons = web.html`<div class="integrated_titlebar--buttons"></div>`, const createWindowButtons = () => {
$minimize = web.html`<button id="integrated_titlebar--minimize"> const { html } = globalThis.__enhancerApi,
${minimizeIcon} $minimize = html`<${TopbarButton}
</button>`, aria-label="${minimizeLabel}"
$maximize = web.html`<button id="integrated_titlebar--maximize"> icon="minus"
${maximizeIcon} />`,
</button>`, $maximize = html`<${TopbarButton}
$unmaximize = web.html`<button id="integrated_titlebar--unmaximize"> aria-label="${maximizeLabel}"
${unmaximizeIcon} icon="maximize"
</button>`, />`,
$close = web.html`<button id="integrated_titlebar--close"> $unmaximize = html`<${TopbarButton}
${closeIcon} aria-label="${unmaximizeLabel}"
</button>`; icon="minimize"
components.addTooltip($minimize, '**Minimize window**'); />`,
components.addTooltip($maximize, '**Maximize window**'); $close = html`<${TopbarButton}
components.addTooltip($unmaximize, '**Unmaximize window**'); class="!hover:(bg-red-600 text-white)"
components.addTooltip($close, '**Close window**'); aria-label="${closeLabel}"
icon="x"
$minimize.addEventListener('click', () => electron.browser.minimize()); />`;
$maximize.addEventListener('click', () => electron.browser.maximize()); html`<${Tooltip}><b>${minimizeLabel}</b><//>`.attach($minimize, "bottom");
$unmaximize.addEventListener('click', () => electron.browser.unmaximize()); html`<${Tooltip}><b>${maximizeLabel}</b><//>`.attach($maximize, "bottom");
$close.addEventListener('click', () => electron.browser.close()); html`<${Tooltip}><b>${unmaximizeLabel}</b><//>`.attach($unmaximize, "bottom");
electron.browser.on('maximize', () => { html`<${Tooltip}><b>${closeLabel}</b><//>`.attach($close, "bottom");
$maximize.replaceWith($unmaximize); return html`<div>${$minimize}${$maximize}${$unmaximize}${$close}</div>`;
});
electron.browser.on('unmaximize', () => {
$unmaximize.replaceWith($maximize);
});
web.render(
$windowButtons,
$minimize,
electron.browser.isMaximized() ? $unmaximize : $maximize,
$close
);
return $windowButtons;
}; };
if (globalThis.IS_TABS) {
const appendAfter = ".hide-scrollbar",
$buttons = createWindowButtons();
document.querySelector(appendAfter)?.after($buttons);
}
export { createWindowButtons };

View File

@ -9,7 +9,10 @@
user-select: none; user-select: none;
-webkit-app-region: drag; -webkit-app-region: drag;
} }
.notion-tabs :is([aria-label], [draggable]), .notion-tabs :is([aria-label], [draggable], .notion-enhancer--topbar-button),
.notion-topbar > div > * { .notion-topbar > div > * {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
.notion-tabs .notion-enhancer--topbar-button:last-child {
margin-right: 12px;
}

View File

@ -1,31 +1,24 @@
/** /**
* notion-enhancer: integrated titlebar * notion-enhancer: titlebar
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) * (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license * (https://notion-enhancer.github.io/) under the MIT license
*/ */
"use strict"; "use strict";
import { TopbarButton } from "../../core/islands/TopbarButton.mjs"; import { createWindowButtons } from "./buttons.mjs";
// import { createWindowButtons } from './buttons.mjs'; export default (api, db) => {
const { onMessage, addMutationListener } = api,
export default async function ({ html }, db) { $buttons = createWindowButtons(),
const topbarMore = ".notion-topbar-more-button"; topbarMore = ".notion-topbar-more-button",
const $minimizeButton = html`<${TopbarButton} addToTopbar = () => document.querySelector(topbarMore)?.after($buttons),
aria-label="Minimize window" showIfNoTabBar = async () => {
icon="minus" const { isShowingTabBar } = await __electronApi.electronAppFeatures.get();
/>`, $buttons.style.display = isShowingTabBar ? "none" : "";
$maximizeButton = html`<${TopbarButton} };
aria-label="Maximize window" __electronApi?.electronAppFeatures.addListener(showIfNoTabBar);
icon="maximize" showIfNoTabBar();
/>`, addMutationListener(topbarMore, addToTopbar);
$unmaximizeButton = html`<${TopbarButton} addToTopbar(topbarMore);
aria-label="Unmaximize window" };
icon="minimize"
/>`,
$closeButton = html`<${TopbarButton} aria-label="Close window" icon="x" />`;
$closeButton.addToTopbar(topbarMore);
$maximizeButton.addToTopbar(topbarMore);
$minimizeButton.addToTopbar(topbarMore);
}

View File

@ -8,17 +8,27 @@
module.exports = async ({ whenReady }, db) => { module.exports = async ({ whenReady }, db) => {
const api = await whenReady(), const api = await whenReady(),
{ addMutationListener } = api, { html, addMutationListener } = api,
tabSelector = ".hide-scrollbar > div > div", { enhancerUrl, onMessage, sendMessage } = api,
titlebarStyle = await db.get("titlebarStyle"); titlebarStyle = await db.get("titlebarStyle");
// only make area draggable if tabs are visible: // only make area draggable if tabs are visible:
// otherwise dragarea overlaps regular app topbar // otherwise dragarea overlaps regular app topbar
const tabSelector = ".hide-scrollbar > div > div";
addMutationListener(".hide-scrollbar", () => { addMutationListener(".hide-scrollbar", () => {
const tabCount = document.querySelectorAll(tabSelector).length; const tabCount = document.querySelectorAll(tabSelector).length;
if (tabCount > 1) document.body.classList.add("notion-tabs"); if (tabCount > 1) document.body.classList.add("notion-tabs");
else document.body.classList.remove("notion-tabs"); else document.body.classList.remove("notion-tabs");
}); });
onMessage("tabs:set-state", (state) => {
if (state.themeMode === "dark") document.body.classList.add("dark");
else document.body.classList.remove("dark");
});
if (titlebarStyle === "Disabled") return; if (titlebarStyle === "Disabled") return;
const $buttonsScript = document.createElement("script");
$buttonsScript.type = "module";
$buttonsScript.src = enhancerUrl("extensions/titlebar/buttons.mjs");
document.head.append($buttonsScript);
}; };

View File

@ -18,6 +18,7 @@ export default (async () => {
IS_MENU = location.href.startsWith(enhancerUrl("core/menu/index.html")), IS_MENU = location.href.startsWith(enhancerUrl("core/menu/index.html")),
IS_TABS = /\/app\/\.webpack\/renderer\/(draggable_)?tabs\/index.html$/.test(location.href), IS_TABS = /\/app\/\.webpack\/renderer\/(draggable_)?tabs\/index.html$/.test(location.href),
IS_ELECTRON = ['linux', 'win32', 'darwin'].includes(platform); IS_ELECTRON = ['linux', 'win32', 'darwin'].includes(platform);
if (IS_TABS) globalThis.IS_TABS = true;
if (!IS_MENU && !IS_TABS) { if (!IS_MENU && !IS_TABS) {
if (!signedIn || !pageLoaded) return; if (!signedIn || !pageLoaded) return;

View File

@ -121,7 +121,7 @@ if (IS_ELECTRON) {
const { namespace, query, args } = message.data; const { namespace, query, args } = message.data;
return queryDatabase(namespace, query, args); return queryDatabase(namespace, query, args);
}); });
ipcMain.on("notion-enhancer", ({ sender }, message) => { ipcMain.on("notion-enhancer", ({}, message) => {
if (message === "reload-app") reloadApp(); if (message === "reload-app") reloadApp();
}); });
} else { } else {