diff --git a/src/common/markup.js b/src/common/markup.js index fe19ee7..5e91096 100644 --- a/src/common/markup.js +++ b/src/common/markup.js @@ -95,6 +95,7 @@ twind.install({ ["siblings", "&~*"], ["sibling", "&+*"], [/^&/, (match) => match.input], + [/^has-\[([^\]]+)\]/, (match) => `&:has(${match[1]})`], ], }); @@ -552,7 +553,11 @@ const h = (type, props, ...children) => { : document.createElement(type); for (const prop in props ?? {}) { if (typeof props[prop] === "undefined") continue; - if (htmlAttributes.includes(prop) || prop.startsWith("data-")) { + const isAttr = + htmlAttributes.includes(prop) || + prop.startsWith("data-") || + prop.startsWith("aria-"); + if (isAttr) { if (typeof props[prop] === "boolean") { if (!props[prop]) continue; elem.setAttribute(prop, ""); diff --git a/src/core/islands/Panel.mjs b/src/core/islands/Panel.mjs index c00d0b2..fa7fc4f 100644 --- a/src/core/islands/Panel.mjs +++ b/src/core/islands/Panel.mjs @@ -87,15 +87,12 @@ function Panel({ maxWidth = 640, transitionDuration = 300, }) { - const { html, useState, addKeyListener } = globalThis.__enhancerApi, + const { modDatabase, isEnabled } = globalThis.__enhancerApi, + { html, useState, addKeyListener } = globalThis.__enhancerApi, { addMutationListener, removeMutationListener } = globalThis.__enhancerApi, - $topbarToggle = html`<${TopbarButton} - aria-label="Toggle side panel" - icon="panel-right" - />`, $panelToggle = html``, - $unpin = web.html``; - components.addTooltip($pin, '**Pin window to top**'); - components.addTooltip($unpin, '**Unpin window from top**'); - web.render($button, $pin); - - $pin.addEventListener('click', () => { - $pin.replaceWith($unpin); - electron.browser.setAlwaysOnTop(true); - }); - $unpin.addEventListener('click', () => { - $unpin.replaceWith($pin); - electron.browser.setAlwaysOnTop(false); - }); - - return $button; -}; diff --git a/src/extensions/always-on-top/client.mjs b/src/extensions/always-on-top/client.mjs deleted file mode 100644 index 7262f15..0000000 --- a/src/extensions/always-on-top/client.mjs +++ /dev/null @@ -1,17 +0,0 @@ -/** - * notion-enhancer: always on top - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -import { createButton } from './button.mjs'; - -export default async function (api, db) { - const { web } = api, - topbarActionsSelector = '.notion-topbar-action-buttons'; - - await web.whenReady([topbarActionsSelector]); - const $topbarActions = document.querySelector(topbarActionsSelector), - $button = await createButton(api, db); - $topbarActions.after($button); -} diff --git a/src/extensions/always-on-top/menu.mjs b/src/extensions/always-on-top/menu.mjs deleted file mode 100644 index 1250d12..0000000 --- a/src/extensions/always-on-top/menu.mjs +++ /dev/null @@ -1,20 +0,0 @@ -/** - * notion-enhancer: always on top - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -import { createButton } from './button.mjs'; - -export default async function (api, db) { - const { web } = api, - sidebarSelector = '.sidebar', - windowButtonsSelector = '.integrated_titlebar--buttons'; - - await web.whenReady([sidebarSelector]); - await new Promise(requestAnimationFrame); - const $sidebar = document.querySelector(sidebarSelector), - $windowButtons = document.querySelector(windowButtonsSelector), - $button = await createButton(api, db); - ($windowButtons || $sidebar).prepend($button); -} diff --git a/src/extensions/always-on-top/mod.json b/src/extensions/always-on-top/mod.json deleted file mode 100644 index efaee88..0000000 --- a/src/extensions/always-on-top/mod.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "always on top", - "id": "be2d75f5-48f5-4ece-98bd-772244e559f3", - "environments": ["linux", "win32", "darwin"], - "version": "0.2.0", - "description": "adds a button that can be used to pin the notion window on top of all other windows at all times.", - "preview": "always-on-top.jpg", - "tags": ["extension", "app"], - "authors": [ - { - "name": "dragonwocky", - "email": "thedragonring.bod@gmail.com", - "homepage": "https://dragonwocky.me/", - "avatar": "https://dragonwocky.me/avatar.jpg" - } - ], - "css": { - "client": ["button.css"], - "menu": ["button.css"] - }, - "js": { - "client": ["client.mjs"], - "menu": ["menu.mjs"] - }, - "options": [ - { - "type": "text", - "key": "pin_icon", - "label": "pin 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": "unpin_icon", - "label": "unpin 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": "" - } - ] -} diff --git a/src/extensions/titlebar/client.mjs b/src/extensions/titlebar/client.mjs index 11a8c9d..d226c76 100644 --- a/src/extensions/titlebar/client.mjs +++ b/src/extensions/titlebar/client.mjs @@ -15,7 +15,10 @@ export default async (api, db) => { const { onMessage, addMutationListener } = api, $buttons = await createWindowButtons(), topbarMore = ".notion-topbar-more-button", - addToTopbar = () => document.querySelector(topbarMore)?.after($buttons), + addToTopbar = () => { + if (document.contains($buttons)) return; + document.querySelector(topbarMore)?.after($buttons) + }, showIfNoTabBar = async () => { const { isShowingTabBar } = await __electronApi.electronAppFeatures.get(); $buttons.style.display = isShowingTabBar ? "none" : ""; diff --git a/src/extensions/titlebar/mod.json b/src/extensions/titlebar/mod.json index 9f09bbe..44d59a6 100644 --- a/src/extensions/titlebar/mod.json +++ b/src/extensions/titlebar/mod.json @@ -1,11 +1,10 @@ { - "id": "a5658d03-21c6-4088-bade-fa4780459133", "name": "Titlebar", "version": "0.11.1", - "platforms": ["linux", "win32"], + "id": "a5658d03-21c6-4088-bade-fa4780459133", "description": "Replaces the operating system's default window titlebar with buttons inset into the app.", "thumbnail": "integrated-titlebar.jpg", - "tags": ["extension", "layout"], + "platforms": ["linux", "win32"], "authors": [ { "name": "dragonwocky", @@ -24,25 +23,25 @@ { "type": "file", "key": "minimizeIcon", - "description": "Replaces the default icon used for the integrated titlebar's maximize button with an uploaded .svg image.", + "description": "Replaces the icon used for the integrated titlebar's maximize button with the uploaded .svg image.", "extensions": ["svg"] }, { "type": "file", "key": "maximizeIcon", - "description": "Replaces the default icon used for the integrated titlebar's maximize button with an uploaded .svg image.", + "description": "Replaces the icon used for the integrated titlebar's maximize button with the uploaded .svg image.", "extensions": ["svg"] }, { "type": "file", "key": "unmaximizeIcon", - "description": "Replaces the default icon used for the integrated titlebar's maximize button with an uploaded .svg image.", + "description": "Replaces the icon used for the integrated titlebar's maximize button with the uploaded .svg image.", "extensions": ["svg"] }, { "type": "file", "key": "closeIcon", - "description": "Replaces the default icon used for the integrated titlebar's maximize button with an uploaded .svg image.", + "description": "Replaces the icon used for the integrated titlebar's maximize button with the uploaded .svg image.", "extensions": ["svg"] } ], diff --git a/src/extensions/topbar-icons/client.mjs b/src/extensions/topbar-icons/client.mjs deleted file mode 100644 index 0e57db2..0000000 --- a/src/extensions/topbar-icons/client.mjs +++ /dev/null @@ -1,59 +0,0 @@ -/** - * notion-enhancer: topbar icons - * (c) 2020 CloudHill (https://github.com/CloudHill) - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -export default async function ({ web, components }, db) { - await web.whenReady(['.notion-topbar-action-buttons']); - - const observeButton = (selector, label = '') => { - const updateButton = () => { - const $btns = document.querySelectorAll(selector); - $btns.forEach(($btn) => { - $btn.style.width = 'auto'; - $btn.style.fontSize = '14px'; - $btn.style.lineHeight = '1.2'; - $btn.style.paddingLeft = '8px'; - $btn.style.paddingRight = '8px'; - const innerHTML = label || $btn.ariaLabel; - if ($btn.innerHTML !== innerHTML) $btn.innerHTML = innerHTML; - }); - }; - web.addDocumentObserver(updateButton, [selector]); - updateButton(); - }; - - if ((await db.get(['share'])) === true) { - const selector = '.notion-topbar-share-menu', - label = await components.feather('share-2', { - style: 'width:16px;height:16px;color:var(--theme--icon);', - }); - observeButton(selector, label); - } - - if ((await db.get(['comments'])) === false) { - const selector = '.notion-topbar-comments-button'; - observeButton(selector); - } - - if ((await db.get(['updates'])) === false) { - const selector = - '.notion-topbar-updates-button, .notion-topbar-share-menu ~ [aria-label="Updates"]'; - observeButton(selector); - } - - if ((await db.get(['favorite'])) === false) { - const selector = '.notion-topbar-share-menu ~ [aria-label^="Fav"]'; - observeButton(selector); - } - - if ((await db.get(['more'])) === false) { - const selector = '.notion-topbar-more-button', - label = 'More'; - observeButton(selector, label); - } -} diff --git a/src/extensions/topbar-icons/mod.json b/src/extensions/topbar-icons/mod.json deleted file mode 100644 index 895872e..0000000 --- a/src/extensions/topbar-icons/mod.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "topbar icons", - "id": "e0700ce3-a9ae-45f5-92e5-610ded0e348d", - "version": "0.3.0", - "description": "choose between text or icons for the topbar buttons.", - "preview": "topbar-icons.jpg", - "tags": ["extension", "customisation"], - "authors": [ - { - "name": "CloudHill", - "email": "rh.cloudhill@gmail.com", - "homepage": "https://github.com/CloudHill", - "avatar": "https://avatars.githubusercontent.com/u/54142180" - } - ], - "js": { - "client": ["client.mjs"] - }, - "css": {}, - "options": [ - { - "type": "toggle", - "key": "share", - "label": "share", - "tooltip": "**on = icon, off = text**", - "value": false - }, - { - "type": "toggle", - "key": "comments", - "label": "comments", - "tooltip": "**on = icon, off = text**", - "value": true - }, - { - "type": "toggle", - "key": "updates", - "label": "updates", - "tooltip": "**on = icon, off = text**", - "value": true - }, - { - "type": "toggle", - "key": "favorite", - "label": "favorite", - "tooltip": "**on = icon, off = text**", - "value": true - }, - { - "type": "toggle", - "key": "more", - "label": "more (3 dots)", - "tooltip": "**on = icon, off = text**", - "value": true - } - ] -} diff --git a/src/extensions/topbar/client.mjs b/src/extensions/topbar/client.mjs new file mode 100644 index 0000000..7888b27 --- /dev/null +++ b/src/extensions/topbar/client.mjs @@ -0,0 +1,132 @@ +/** + * notion-enhancer: topbar + * (c) 2020 CloudHill (https://github.com/CloudHill) + * (c) 2024 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +"use strict"; + +import { Tooltip } from "../../core/islands/Tooltip.mjs"; +import { TopbarButton } from "../../core/islands/TopbarButton.mjs"; + +const pinLabel = "Pin always on top", + unpinLabel = "Unpin always on top", + pinTooltip = "Pin window as always on top", + unpinTooltip = "Unpin window from always on top"; + +export default async function (api, db) { + const { html, sendMessage, addMutationListener } = api, + displayLabel = ($btn) => { + if ($btn.innerHTML === $btn.ariaLabel) return; + $btn.style.width = "auto"; + $btn.style.fontSize = "14px"; + $btn.style.lineHeight = "1.2"; + $btn.style.padding = "0px 8px"; + $btn.innerHTML = $btn.ariaLabel; + }, + displayIcon = ($btn, icon) => { + if ($btn.innerHTML === icon) return; + $btn.style.width = "33px"; + $btn.style.padding = "0px"; + $btn.style.justifyContent = "center"; + $btn.innerHTML = icon; + }; + + // share button is text by default + const shareSelector = ".notion-topbar-share-menu", + shareButton = await db.get("shareButton"), + shareIcon = await db.get("shareIcon"); + addMutationListener(shareSelector, () => { + const $btn = document.querySelector(shareSelector); + let icon = shareIcon?.content; + icon ??= ``; + if (shareButton === "Icon") displayIcon($btn, icon); + if (shareButton === "Disabled") $btn.style.display = "none"; + }); + + const commentsSelector = ".notion-topbar-comments-button", + commentsButton = await db.get("commentsButton"), + commentsIcon = await db.get("commentsIcon"); + addMutationListener(commentsSelector, () => { + const $btn = document.querySelector(commentsSelector), + icon = commentsIcon?.content; + if (commentsButton === "Text") displayLabel($btn); + if (commentsButton === "Icon" && icon) displayIcon($btn, icon); + if (commentsButton === "Disabled") $btn.style.display = "none"; + }); + + const updatesSelector = ".notion-topbar-updates-button", + updatesButton = await db.get("updatesButton"), + updatesIcon = await db.get("updatesIcon"); + addMutationListener(updatesSelector, () => { + const $btn = document.querySelector(updatesSelector), + icon = updatesIcon?.content; + if (updatesButton === "Text") displayLabel($btn); + if (updatesButton === "Icon" && icon) displayIcon($btn, icon); + if (updatesButton === "Disabled") $btn.style.display = "none"; + }); + + const favoriteSelector = ".notion-topbar-favorite-button", + favoriteButton = await db.get("favoriteButton"), + favoriteIcon = await db.get("favoriteIcon"); + addMutationListener(favoriteSelector, () => { + const $btn = document.querySelector(favoriteSelector), + icon = favoriteIcon?.content; + if (favoriteButton === "Text") displayLabel($btn); + if (favoriteButton === "Icon" && icon) displayIcon($btn, icon); + if (favoriteButton === "Disabled") $btn.style.display = "none"; + }); + + const moreSelector = ".notion-topbar-more-button", + moreButton = await db.get("moreButton"), + moreIcon = await db.get("moreIcon"); + addMutationListener(moreSelector, () => { + const $btn = document.querySelector(moreSelector), + icon = moreIcon?.content; + $btn.ariaLabel = "More"; + if (moreButton === "Text") displayLabel($btn); + if (moreButton === "Icon" && icon) displayIcon($btn, icon); + if (moreButton === "Disabled") $btn.style.display = "none"; + }); + + const alwaysOnTopButton = await db.get("alwaysOnTopButton"); + if (alwaysOnTopButton === "Disabled") return; + + const topbarFavorite = ".notion-topbar-favorite-button", + pinIcon = await db.get("pinIcon"), + unpinIcon = await db.get("unpinIcon"), + $pin = html`<${TopbarButton} + onclick=${() => { + sendMessage("notion-enhancer:topbar", "pin-always-on-top"); + $pin.style.display = "none"; + $unpin.style.display = ""; + }} + aria-label="${pinLabel}" + innerHTML="${alwaysOnTopButton === "Icon" + ? pinIcon?.content + : `${pinLabel}`}" + icon="pin" + />`, + $unpin = html`<${TopbarButton} + onclick=${() => { + sendMessage("notion-enhancer:topbar", "unpin-always-on-top"); + $unpin.style.display = "none"; + $pin.style.display = ""; + }} + aria-label="${unpinLabel}" + innerHTML="${alwaysOnTopButton === "Icon" + ? unpinIcon?.content + : `${unpinLabel}`}" + style="display: none" + icon="pin-off" + />`, + addToTopbar = () => { + if (document.contains($pin) && document.contains($unpin)) return; + document.querySelector(topbarFavorite)?.after($pin, $unpin); + }; + html`<${Tooltip}>${pinTooltip}`.attach($pin, "bottom"); + html`<${Tooltip}>${unpinTooltip}`.attach($unpin, "bottom"); + addMutationListener(topbarFavorite, addToTopbar); + addToTopbar(topbarFavorite); +} diff --git a/src/extensions/topbar/electron.cjs b/src/extensions/topbar/electron.cjs new file mode 100644 index 0000000..1f7f929 --- /dev/null +++ b/src/extensions/topbar/electron.cjs @@ -0,0 +1,16 @@ +/** + * notion-enhancer: topbar + * (c) 2024 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +"use strict"; + +module.exports = async ({}, db) => { + const { ipcMain, BrowserWindow } = require("electron"); + ipcMain.on("notion-enhancer:topbar", ({ sender }, message) => { + const window = BrowserWindow.fromWebContents(sender); + if (message === "pin-always-on-top") window.setAlwaysOnTop(true); + if (message === "unpin-always-on-top") window.setAlwaysOnTop(false); + }); +}; diff --git a/src/extensions/topbar/mod.json b/src/extensions/topbar/mod.json new file mode 100644 index 0000000..5368439 --- /dev/null +++ b/src/extensions/topbar/mod.json @@ -0,0 +1,123 @@ +{ + "name": "Topbar", + "version": "0.4.0", + "id": "e0700ce3-a9ae-45f5-92e5-610ded0e348d", + "description": "Customise the Notion interface's topbar and add an always on top toggle to the window on desktop.", + "thumbnail": "topbar-icons.jpg", + "authors": [ + { + "name": "dragonwocky", + "homepage": "https://dragonwocky.me/", + "avatar": "https://dragonwocky.me/avatar.jpg" + }, + { + "name": "CloudHill", + "homepage": "https://github.com/CloudHill", + "avatar": "https://avatars.githubusercontent.com/u/54142180" + } + ], + "options": [ + { "type": "heading", "label": "Share" }, + { + "type": "select", + "key": "shareButton", + "description": "Configures how the share button in the Notion topbar is displayed.", + "values": ["Text", "Icon", "Disabled"] + }, + { + "type": "file", + "key": "shareIcon", + "description": "Replaces the icon used for the share button with the uploaded .svg image.", + "extensions": ["svg"] + }, + { "type": "heading", "label": "Comments" }, + { + "type": "select", + "key": "commentsButton", + "description": "Configures how the comments button in the Notion topbar is displayed.", + "values": ["Icon", "Text", "Disabled"] + }, + { + "type": "file", + "key": "commentsIcon", + "description": "Replaces the icon used for the comments button with the uploaded .svg image.", + "extensions": ["svg"] + }, + { "type": "heading", "label": "Updates" }, + { + "type": "select", + "key": "updatesButton", + "description": "Configures how the updates button in the Notion topbar is displayed.", + "values": ["Icon", "Text", "Disabled"] + }, + { + "type": "file", + "key": "updatesIcon", + "description": "Replaces the icon used for the updates button with the uploaded .svg image.", + "extensions": ["svg"] + }, + { "type": "heading", "label": "Favorite" }, + { + "type": "select", + "key": "favoriteButton", + "description": "Configures how the favorite button in the Notion topbar is displayed.", + "values": ["Icon", "Text", "Disabled"] + }, + { + "type": "file", + "key": "favoriteIcon", + "description": "Replaces the icon used for the favorite button with the uploaded .svg image.", + "extensions": ["svg"] + }, + { "type": "heading", "label": "Always on Top" }, + { + "type": "select", + "key": "alwaysOnTopButton", + "description": "Adds a button to the topbar that can be used to pin the Notion window above all other windows regardless of which window currently has focus.", + "platforms": ["linux", "win32", "darwin"], + "values": ["Icon", "Text", "Disabled"] + }, + { + "type": "file", + "key": "pinIcon", + "description": "Replaces the icon used for the always on top pin button with the uploaded .svg image.", + "platforms": ["linux", "win32", "darwin"], + "extensions": ["svg"] + }, + { + "type": "file", + "key": "unpinIcon", + "description": "Replaces the icon used for the always on top unpin button with the uploaded .svg image.", + "platforms": ["linux", "win32", "darwin"], + "extensions": ["svg"] + }, + { "type": "heading", "label": "Panel" }, + { + "type": "select", + "key": "panelButton", + "description": "This button activates an additional sidebar on the right-hand side of the screen and will appear in the topbar only if an extension is enabled that displays information through this panel (e.g. the Outliner).", + "values": ["Icon", "Text"] + }, + { + "type": "file", + "key": "panelIcon", + "description": "Replaces the icon used for the panel button with the uploaded .svg image.", + "extensions": ["svg"] + }, + { "type": "heading", "label": "More" }, + { + "type": "select", + "key": "moreButton", + "description": "Configures how the more button in the Notion topbar is displayed.", + "values": ["Icon", "Text", "Disabled"] + }, + { + "type": "file", + "key": "moreIcon", + "description": "Replaces the icon used for the more button with the uploaded .svg image.", + "extensions": ["svg"] + } + ], + "clientScripts": ["client.mjs"], + "electronScripts": [[".webpack/main/index", "electron.cjs"]] +} diff --git a/src/extensions/topbar-icons/topbar-icons.jpg b/src/extensions/topbar/topbar-icons.jpg similarity index 100% rename from src/extensions/topbar-icons/topbar-icons.jpg rename to src/extensions/topbar/topbar-icons.jpg diff --git a/src/init.js b/src/init.js index 3efac25..4c7c544 100644 --- a/src/init.js +++ b/src/init.js @@ -19,11 +19,8 @@ if (isElectron()) { const { enhancerUrl } = globalThis.__enhancerApi, { getMods, isEnabled, modDatabase } = globalThis.__enhancerApi; - const mainScript = ".webpack/main/index", - rendererScript = ".webpack/renderer/tab_browser_view/preload"; - module.exports = async (target, __exports, __eval) => { - if (target === mainScript) require("./worker.js"); + if (target === ".webpack/main/index") require("./worker.js"); else { // expose globalThis.__enhancerApi to scripts const { contextBridge } = require("electron"), diff --git a/src/registry.json b/src/registry.json index d4122ea..d2a91d3 100644 --- a/src/registry.json +++ b/src/registry.json @@ -1 +1 @@ -["core", "extensions/titlebar", "themes/classic-dark"] +["core", "extensions/titlebar", "extensions/topbar", "themes/classic-dark"]