desktop-only extension: integrated titlebar (inc. customisable icons)

This commit is contained in:
dragonwocky 2021-12-02 17:58:24 +11:00
parent 022613d66b
commit 7f53445ab0
11 changed files with 289 additions and 80 deletions

View File

@ -6,7 +6,7 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
:root {
:root:not(.dark) {
/*
* variables are named for consistency with gruvbox dark,
* but the _light variants are actually darker here

View File

@ -0,0 +1,58 @@
/*
* notion-enhancer: integrated titlebar
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
.integrated_titlebar--dragarea {
width: 100%;
display: block;
-webkit-app-region: drag;
background: var(--theme--bg_secondary);
}
.integrated_titlebar--buttons {
display: flex;
align-items: center;
}
.integrated_titlebar--buttons[data-in-enhancer-menu] {
margin: -0.5rem 0 0.75rem auto;
}
.integrated_titlebar--buttons button {
user-select: none;
transition: background 20ms ease-in 0s;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 3px;
height: 28px;
width: 33px;
padding: 0 0.25px 0 0;
margin-left: 2px;
border: none;
background: transparent;
font-size: 18px;
}
.integrated_titlebar--buttons button svg {
display: block;
width: 20px;
height: 20px;
}
.integrated_titlebar--buttons button:focus,
.integrated_titlebar--buttons button:hover {
background: var(--theme--ui_interactive-hover);
}
.integrated_titlebar--buttons button:active {
background: var(--theme--ui_interactive-active);
}
#integrated_titlebar--close:focus,
#integrated_titlebar--close:hover,
#integrated_titlebar--close:active {
background: var(--theme--accent_red);
color: var(--theme--accent_red-text);
}

View File

@ -0,0 +1,69 @@
/*
* notion-enhancer: integrated titlebar
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
'use strict';
export const createWindowButtons = async ({ web, components }, db) => {
let minimizeIcon = (await db.get(['minimize_icon'])) || (await components.feather('minus')),
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 =
minimizeIcon.startsWith('<svg') && minimizeIcon.endsWith('</svg>')
? minimizeIcon
: web.escape(minimizeIcon);
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>`,
$minimize = web.html`<button id="integrated_titlebar--minimize">
${minimizeIcon}
</button>`,
$maximize = web.html`<button id="integrated_titlebar--maximize">
${maximizeIcon}
</button>`,
$unmaximize = web.html`<button id="integrated_titlebar--unmaximize">
${unmaximizeIcon}
</button>`,
$close = web.html`<button id="integrated_titlebar--close">
${closeIcon}
</button>`;
$minimize.addEventListener('click', () => __enhancerElectronApi.browser.minimize());
$maximize.addEventListener('click', () => __enhancerElectronApi.browser.maximize());
$unmaximize.addEventListener('click', () => __enhancerElectronApi.browser.unmaximize());
$close.addEventListener('click', () => __enhancerElectronApi.browser.close());
__enhancerElectronApi.browser.on('maximize', () => {
$maximize.replaceWith($unmaximize);
});
__enhancerElectronApi.browser.on('unmaximize', () => {
$unmaximize.replaceWith($maximize);
});
web.render(
$windowButtons,
$minimize,
__enhancerElectronApi.browser.isMaximized() ? $unmaximize : $maximize,
$close
);
return $windowButtons;
};

View File

@ -6,6 +6,47 @@
'use strict';
import { createWindowButtons } from './buttons.mjs';
export default async function (api, db) {
//
const { web } = api,
tilingMode = await db.get(['tiling']),
dragareaHeight = await db.get(['dragarea_height']),
sidebarSelector = '.notion-sidebar',
panelSelector = '#enhancer--panel',
topbarSelector = '.notion-topbar',
topbarActionsSelector = '.notion-topbar-action-buttons';
if (tilingMode) return;
let sidebarWidth = '0px',
panelWidth = '0px';
const updateDragareaOffsets = () => {
const $sidebar = document.querySelector(sidebarSelector),
newSidebarWidth = $sidebar.style.height === 'auto' ? '0px' : $sidebar.style.width,
$panel = document.querySelector(panelSelector),
newPanelWidth =
$panel && $panel.dataset.enhancerPanelPinned === 'true'
? window
.getComputedStyle(document.documentElement)
.getPropertyValue('--component--panel-width')
: '0px';
if (newSidebarWidth !== sidebarWidth) {
sidebarWidth = newSidebarWidth;
__enhancerElectronApi.sendMessageToHost('sidebar-width', sidebarWidth);
}
if (newPanelWidth !== panelWidth) {
panelWidth = newPanelWidth;
__enhancerElectronApi.sendMessageToHost('panel-width', panelWidth);
}
};
web.addDocumentObserver(updateDragareaOffsets);
await web.whenReady([topbarSelector, topbarActionsSelector]);
const $topbar = document.querySelector(topbarSelector),
$dragarea = web.html`<div class="integrated_titlebar--dragarea" style="height:${dragareaHeight}px"></div>`;
$topbar.prepend($dragarea);
const $topbarActions = document.querySelector(topbarActionsSelector),
$windowButtons = await createWindowButtons(api, db);
web.render($topbarActions.parentElement, $windowButtons);
}

View File

@ -6,77 +6,21 @@
'use strict';
module.exports = async function (api, db, __exports) {
const notionCreateWindow = __exports.createWindow;
// __exports.createWindow = (relativeUrl = '', args) => {
// const windowState = require('electron-window-state').default({
// defaultWidth: 1320,
// defaultHeight: 860,
// });
// const focusedWindow = electron_1.BrowserWindow.getFocusedWindow();
// const rect = getRectFromFocusedWindow(windowState);
// const windowCreationArgs = Object.assign(Object.assign({}, rect), {
// show: false,
// backgroundColor: '#ffffff',
// titleBarStyle: 'hiddenInset',
// autoHideMenuBar: true,
// webPreferences: {
// preload: path_1.default.resolve(__dirname, '../renderer/index.js'),
// webviewTag: true,
// session: electron_1.session.fromPartition(constants_1.electronSessionPartition),
// enableRemoteModule: true,
// },
// });
// const { window, warmed } = getNextWindow(windowCreationArgs);
// window.setMenuBarVisibility(false);
// warmWindowState.warmedWindow = undefined;
// window.once('ready-to-show', () => {
// if (args && args.isLocalhost) {
// return;
// }
// if (!warmed) {
// window.show();
// }
// });
// if (warmed) {
// if (warmWindowState.warmedLoaded) {
// notionIpc.sendMainToNotionWindow(window, 'notion:navigate-to-url', relativeUrl);
// } else {
// void window.loadURL(urlHelpers_1.getIndexUrl(relativeUrl));
// }
// window.setBounds(getRectFromFocusedWindow(windowState));
// window.show();
// } else {
// void window.loadURL(urlHelpers_1.getIndexUrl(relativeUrl));
// }
// if (focusedWindow) {
// if (focusedWindow.isFullScreen()) {
// window.setFullScreen(true);
// }
// } else {
// if (windowState.isFullScreen) {
// window.setFullScreen(true);
// }
// }
// window.on('close', () => {
// windowState.saveState(window);
// if (process.platform === 'win32') {
// const currentWindows = electron_1.BrowserWindow.getAllWindows();
// const hasNoOtherOpenWindows = currentWindows.every((currentWindow) =>
// Boolean(
// currentWindow.id === window.id ||
// (warmWindowState.warmedWindow &&
// currentWindow.id === warmWindowState.warmedWindow.id)
// )
// );
// if (hasNoOtherOpenWindows) {
// electron_2.app.quit();
// }
// }
// });
// setImmediate(() => {
// warmWindowState.warmedLoaded = false;
// });
// return window;
// };
module.exports = async function (api, db, __exports, __eval) {
__eval(`
const notionRectFromFocusedWindow = getRectFromFocusedWindow;
getRectFromFocusedWindow = (windowState) => {
const rect = notionRectFromFocusedWindow(windowState);
rect.frame = false;
return rect;
};
`);
};
// hide on open
// maybe only do once to hide first window?
// exports.createWindow = (...args) => {
// warmWindowState.warmedWindow = true;
// createWindow(...args);
// };

View File

@ -6,6 +6,24 @@
'use strict';
import { createWindowButtons } from './buttons.mjs';
export default async function (api, db) {
//
const { web } = api,
tilingMode = await db.get(['tiling']),
dragareaHeight = await db.get(['dragarea_height']),
bodyContainerSelector = '.body-container',
sidebarSelector = '.sidebar';
if (tilingMode) return;
await web.whenReady([bodyContainerSelector, sidebarSelector]);
const $bodyContainer = document.querySelector(bodyContainerSelector),
$dragarea = web.html`<div class="integrated_titlebar--dragarea" style="height:${dragareaHeight}px"></div>`;
document.body.prepend($dragarea);
$bodyContainer.style.height = `calc(100% - ${dragareaHeight}px)`;
const $sidebar = document.querySelector(sidebarSelector),
$windowButtons = await createWindowButtons(api, db);
$windowButtons.dataset.inEnhancerMenu = true;
$sidebar.prepend($windowButtons);
}

View File

@ -20,7 +20,10 @@
"js": {
"client": ["client.mjs"],
"menu": ["menu.mjs"],
"electron": [{ "source": "createWindow.cjs", "target": "main/createWindow.js" }]
"electron": [
{ "source": "createWindow.cjs", "target": "main/createWindow.js" },
{ "source": "rendererIndex.cjs", "target": "renderer/index.js" }
]
},
"options": [
{
@ -29,6 +32,41 @@
"label": "tiling window manager mode",
"tooltip": "**completely remove the close/minimise/maximise buttons** (only for advanced use, not recommended)",
"value": false
},
{
"type": "number",
"key": "dragarea_height",
"label": "dragarea height (px)",
"tooltip": "**the height of the rectangle added to the top of the window, used to drag/move the window around** (dragging is not possible in a frameless window without this bar)",
"value": 12
},
{
"type": "text",
"key": "minimize_icon",
"label": "minimize window icon",
"tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)",
"value": ""
},
{
"type": "text",
"key": "maximize_icon",
"label": "maximize window icon",
"tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)",
"value": ""
},
{
"type": "text",
"key": "unmaximize_icon",
"label": "unmaximize window icon",
"tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)",
"value": ""
},
{
"type": "text",
"key": "close_icon",
"label": "close window icon",
"tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)",
"value": ""
}
]
}

View File

@ -0,0 +1,41 @@
/*
* notion-enhancer: integrated titlebar
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
'use strict';
module.exports = async function (api, db, __exports, __eval) {
const dragareaSelector = '[style*="-webkit-app-region: drag;"]';
await new Promise((res, rej) => {
let isReadyInt;
isReadyInt = setInterval(isReadyTest, 100);
function isReadyTest() {
if (document.querySelector(dragareaSelector)) {
clearInterval(isReadyInt);
res(true);
}
}
isReadyTest();
});
const tilingMode = await db.get(['tiling']),
dragareaHeight = await db.get(['dragarea_height']);
const dragarea = document.querySelector(dragareaSelector);
dragarea.style.top = '2px';
dragarea.style.height = tilingMode ? '0' : `${dragareaHeight}px`;
document.getElementById('notion').addEventListener('ipc-message', (event) => {
switch (event.channel) {
case 'notion-enhancer:sidebar-width':
dragarea.style.left = event.args[0];
break;
case 'notion-enhancer:panel-width':
dragarea.style.right = event.args[0];
break;
}
});
};

View File

@ -5,7 +5,7 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
:root {
:root:not(.dark) {
--theme--accent_blue: var(--light_plus--accent_blue, rgb(46, 170, 220));
--theme--accent_blue-selection: var(
--light_plus--accent_blue-selection,

View File

@ -5,7 +5,7 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
:root {
:root:not(.dark) {
--pinky_boom--brown: #a52a2a80;
--pinky_boom--orange: #ffa60080;
--pinky_boom--yellow: #ffff0080;

View File

@ -31,7 +31,7 @@
--theme--accent_red-text: #fff;
}
:root {
:root:not(.dark) {
--theme--bg: #fff;
--theme--bg_secondary: rgb(247, 246, 243);
--theme--bg_card: #fff;