diff --git a/repo/menu/menu.mjs b/repo/menu/menu.mjs index e12196b..4a27539 100644 --- a/repo/menu/menu.mjs +++ b/repo/menu/menu.mjs @@ -234,8 +234,8 @@ import './styles.mjs'; const mode = mod.tags.includes('light') ? 'light' : 'dark', id = mod.id, mods = await registry.list( - (mod) => - mod.environments.includes(env.name) && + async (mod) => + (await registry.enabled(mod.id)) && mod.tags.includes('theme') && mod.tags.includes(mode) && mod.id !== id diff --git a/repo/tabs/rendererIndex.cjs b/repo/tabs/rendererIndex.cjs index 8ac42bc..5a838dc 100644 --- a/repo/tabs/rendererIndex.cjs +++ b/repo/tabs/rendererIndex.cjs @@ -6,7 +6,7 @@ 'use strict'; -module.exports = async function ({ components, env, web, fs }, db, __exports, __eval) { +module.exports = async function ({ components, env, web, fmt, fs }, db, __exports, __eval) { const url = require('url'), electron = require('electron'), electronWindow = electron.remote.getCurrentWindow(), @@ -32,14 +32,14 @@ module.exports = async function ({ components, env, web, fs }, db, __exports, __ $tabTitle = web.html`v0.11.0 plan v0.11.0 plan v0.11.0 plan v0.11.0 plan`; $closeTab = web.html`${xIcon}`; $tab = web.render( - web.html``, + web.html`
`, this.$tabTitle, this.$closeTab ); constructor($tabs, $root, notionUrl = 'notion://www.notion.so/') { this.$notion.src = notionUrl; - tabCache.set($tab, this); + tabCache.set(this.$tab.id, this); web.render($tabs, this.$tab); web.render($root, this.$search); @@ -56,11 +56,15 @@ module.exports = async function ({ components, env, web, fs }, db, __exports, __ this.$closeTab.addEventListener('click', () => this.closeTab()); this.focusTab(); - this.listen(); + this.$tab.animate([{ width: '0px' }, { width: `${this.$tab.clientWidth}px` }], { + duration: 100, + easing: 'ease-in', + }).finished; + this.listenToNotion(); return this; } - focusTab() { + async focusTab() { document.querySelectorAll('.notion-webview, .search-webview').forEach(($webview) => { if (![this.$notion, this.$search].includes($webview)) $webview.style.display = ''; }); @@ -73,14 +77,21 @@ module.exports = async function ({ components, env, web, fs }, db, __exports, __ this.focusNotion(); focusedTab = this; } - closeTab() { + async closeTab() { const $sibling = this.$tab.nextElementSibling || this.$tab.previousElementSibling; if ($sibling) { + const width = `${this.$tab.clientWidth}px`; + this.$tab.style.width = 0; + this.$tab.style.pointerEvents = 'none'; + await this.$tab.animate([{ width }, { width: '0px' }], { + duration: 100, + easing: 'ease-out', + }).finished; this.$tab.remove(); this.$notion.remove(); this.$search.remove(); if (focusedTab === this) $sibling.click(); - } + } else electronWindow.close(); } webContents() { @@ -97,7 +108,7 @@ module.exports = async function ({ components, env, web, fs }, db, __exports, __ this.$search.focus(); } - listen() { + listenToNotion() { const fromNotion = (channel, listener) => notionIpc.receiveIndexFromNotion.addListener(this.$notion, channel, listener), fromSearch = (channel, listener) => @@ -177,12 +188,68 @@ module.exports = async function ({ components, env, web, fs }, db, __exports, __ window['__start'] = async () => { const $header = web.html`
`, $tabs = web.html`
`, - $newTab = web.html``, + $newTab = web.html`
${await components.feather('plus')}
`, $root = document.querySelector('#root'), $windowActions = web.html`
`; document.body.prepend(web.render($header, $tabs, $newTab, $windowActions)); xIcon = await components.feather('x'); + let $draggedTab; + const getDragTarget = ($el) => { + while (!$el.matches('.tab, header, body')) $el = $el.parentElement; + if ($el.matches('header')) $el = $el.firstElementChild; + return $el.matches('#tabs, .tab') ? $el : undefined; + }, + resetTabs = () => { + document + .querySelectorAll('.dragged-over') + .forEach(($el) => $el.classList.remove('dragged-over')); + }; + $header.addEventListener('dragstart', (event) => { + $draggedTab = getDragTarget(event.target); + $draggedTab.style.opacity = 0.5; + event.dataTransfer.setData( + 'text', + JSON.stringify({ + window: electronWindow.webContents.id, + tab: $draggedTab.id, + title: $draggedTab.children[0].innerText, + url: tabCache.get($draggedTab.id).$notion.src, + }) + ); + }); + $header.addEventListener('dragover', (event) => { + const $target = getDragTarget(event.target); + if ($target) { + resetTabs(); + $target.classList.add('dragged-over'); + event.preventDefault(); + } + }); + $header.addEventListener('dragend', (event) => { + resetTabs(); + $draggedTab.style.opacity = ''; + $draggedTab = undefined; + }); + document.addEventListener('drop', (event) => { + const eventData = JSON.parse(event.dataTransfer.getData('text')), + sameWindow = eventData.window === electronWindow.webContents.id, + $target = getDragTarget(event.target), + movement = + $target && + (!sameWindow || + ($target !== $draggedTab && + $target !== $draggedTab.nextElementSibling && + ($target.matches('#tabs') ? $target.lastElementChild !== $draggedTab : true))); + if (movement) { + if (sameWindow) { + if ($target.matches('#tabs')) { + $target.append($draggedTab); + } else $target.before($draggedTab); + } + } + }); + $newTab.addEventListener('click', () => { new Tab($tabs, $root, url.parse(window.location.href, true).query.path); }); diff --git a/repo/tabs/tabs.css b/repo/tabs/tabs.css index 8cb4c96..d2c3173 100644 --- a/repo/tabs/tabs.css +++ b/repo/tabs/tabs.css @@ -27,7 +27,7 @@ header { display: flex; background: var(--theme--bg_secondary); width: 100%; - padding: 12px 8px 0 8px; + padding: 8px; user-select: none; -webkit-app-region: drag; z-index: 3; @@ -35,13 +35,16 @@ header { #tabs { display: flex; + margin-bottom: -8px; + overflow: hidden; } -#tabs .tab { +.tab { display: flex; flex-grow: 1; + flex-shrink: 1; max-width: 14em; - padding: 10.4px 9.6px 6.4px 9.6px; - margin-top: -4px; + overflow: hidden; + padding: 6.4px 9.6px 6.4px 9.6px; color: var(--theme--text_secondary); background: var(--theme--bg); @@ -52,11 +55,27 @@ header { border-bottom: 3px solid var(--theme--ui_divider); -webkit-app-region: no-drag; } -#tabs .tab .tab-title { +.tab .tab-title { white-space: nowrap; overflow: hidden; } -#tabs .tab .tab-close { + +.tab:hover { + background: var(--theme--ui_interactive-hover); +} +.tab.current { + background: var(--theme--ui_interactive-active); +} + +#tabs.dragged-over { + box-shadow: 2px 0 0 0 var(--theme--accent_blue-selection); +} +.tab.dragged-over { + box-shadow: inset 2px 0 0 0 var(--theme--accent_blue-selection); +} + +.new-tab, +.tab-close { transition: background 20ms ease-in 0s; cursor: pointer; display: inline-flex; @@ -68,57 +87,35 @@ header { width: 20px; padding: 0 0.25px 0 0; - margin-left: auto; border: none; background: transparent; - font-size: 18px; + font-size: 14px; + -webkit-app-region: no-drag; } -#tabs .tab .tab-close svg { +.new-tab svg, +.tab-close svg { width: 14px; height: 14px; fill: var(--theme--icon_secondary); + color: var(--theme--icon_secondary); } -#tabs .tab:hover { +.new-tab:focus, +.new-tab:hover, +.tab-close:focus, +.tab-close:hover { background: var(--theme--ui_interactive-hover); } -#tabs .tab.current { +.new-tab:active, +.tab-close:active { background: var(--theme--ui_interactive-active); } .new-tab { - 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: 6px; - border: none; - background: transparent; - font-size: 18px; - -webkit-app-region: no-drag; + align-self: center; + margin: 0 48px -4px 6px; } -.new-tab svg { - display: block; - width: 20px; - height: 20px; - fill: var(--theme--icon); - color: var(--theme--icon); -} -.new-tab:focus, -.new-tab:hover, -#tabs .tab-close:focus, -#tabs .tab-close:hover { - background: var(--theme--ui_interactive-hover); -} -.new-tab:active, -#tabs .tab-close:active { - background: var(--theme--ui_interactive-active); +.tab-close { + margin-left: auto; } #window-actions { @@ -131,13 +128,11 @@ header { #root { flex-grow: 1; } - .notion-webview { width: 100%; height: 100%; display: none; } - .search-webview { width: 100%; height: 60px; diff --git a/repo/theming/client.mjs b/repo/theming/client.mjs index 778d0f2..c6b6988 100644 --- a/repo/theming/client.mjs +++ b/repo/theming/client.mjs @@ -17,14 +17,15 @@ export default async function ({ web, registry, storage, electron }, db) { } const updateTheme = async () => { - await storage.set( - ['theme'], - document.querySelector('.notion-dark-theme') ? 'dark' : 'light' - ); - const mode = await storage.get(['theme'], 'light'), - inactive = mode === 'light' ? 'dark' : 'light'; + const isDark = + document.querySelector('.notion-dark-theme') || + document.querySelector('.notion-body.dark'), + isLight = document.querySelector('.notion-light-theme'), + mode = isDark ? 'dark' : isLight ? 'light' : ''; + if (!mode) return; + await storage.set(['theme'], mode); document.documentElement.classList.add(mode); - document.documentElement.classList.remove(inactive); + document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); electron.sendMessage('update-theme'); const searchThemeVars = [ 'bg', @@ -47,11 +48,10 @@ export default async function ({ web, registry, storage, electron }, db) { electron.sendMessage('set-search-theme', searchThemeVars); }; web.addDocumentObserver((mutation) => { - const potentialThemeChange = [document.body, document.documentElement].includes( - mutation.target - ); + const potentialThemeChange = mutation.target.matches?.('html, body, .notion-app-inner'); if (potentialThemeChange && document.hasFocus()) updateTheme(); }); updateTheme(); document.addEventListener('visibilitychange', updateTheme); + document.addEventListener('focus', updateTheme); } diff --git a/repo/theming/menu.mjs b/repo/theming/menu.mjs index c4a428e..7e65808 100644 --- a/repo/theming/menu.mjs +++ b/repo/theming/menu.mjs @@ -10,10 +10,9 @@ export default async function ({ web, registry, storage, electron }, db) { await web.whenReady(); const updateTheme = async () => { - const mode = await storage.get(['theme'], 'light'), - inactive = mode === 'light' ? 'dark' : 'light'; + const mode = await storage.get(['theme'], 'light'); document.documentElement.classList.add(mode); - document.documentElement.classList.remove(inactive); + document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); }; document.addEventListener('visibilitychange', updateTheme); electron.onMessage('update-theme', updateTheme); diff --git a/repo/theming/rendererIndex.cjs b/repo/theming/rendererIndex.cjs index c809705..b940278 100644 --- a/repo/theming/rendererIndex.cjs +++ b/repo/theming/rendererIndex.cjs @@ -10,10 +10,9 @@ module.exports = async function ({ registry, web, storage, electron }, db, __exp await web.whenReady(); const updateTheme = async () => { - const mode = await storage.get(['theme'], 'light'), - inactive = mode === 'light' ? 'dark' : 'light'; + const mode = await storage.get(['theme'], 'light'); document.documentElement.classList.add(mode); - document.documentElement.classList.remove(inactive); + document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); }; electron.onMessage('update-theme', updateTheme); updateTheme();