From c21f927f010c0837332ec5b51b43922a803cfcfc Mon Sep 17 00:00:00 2001 From: Santhosh C Date: Tue, 28 Oct 2025 20:18:36 +0530 Subject: [PATCH] feat: close all unpinned tabs in active workspace, p=#10739 * feat: close all unpinned tabs using keyboard shortcut * feat: use toast instead of prompt * feat: add toolbarbutton to close all unpinned tabs in a workspace * feat: Fix bootstraping and other small nitpicks, b=no-bug, c=common, tabs, workspaces * feat: improve the site popup ui, p=#10765 * style: improve site popup top buttons * feat: add filled icons for permissions * style: align and resize items in the site popup * feat: site popup ui improvements and more permission icons * chore: Better organize preferences files, b=no-bug, c=compact-mode, folders, glance, kbs, media, mods, split-view, welcome, workspaces * feat: improve the site popup ui (part 2), p=#10768 * feat: Add a bounce to the glance element float and format, b=no-bug, c=common, glance * chore: Update workflows to use macos 26 SDK, b=no-bug, c=workflows * New Crowdin updates, p=#10769 * New translations zen-preferences.ftl (French) * New translations zen-preferences.ftl (Lithuanian) * New translations zen-workspaces.ftl (Lithuanian) * New translations zen-general.ftl (Lithuanian) * list shortcut in settings page, refactor code based on PR review * feat: show restore tabs shortcut in toast message * feat: Add toggle compact mode to the default placements, b=no-bug, c=common, glance * chore: Only run autopep8 if python files changed, b=no-bug, c=workflows * chore: Revert to the new folder animation, p=#10783, c=folders * feat: Use close icon for pinned reset if the preference says so, p=#10786, c=no-component * feat: Improve glance animation, p=#10790, c=glance * update l10n string * feat: Finished UI, b=no-bug, c=workspaces, common, tabs * chore: Fix lint, b=no-bug, c=workspaces --------- Co-authored-by: mr. m Co-authored-by: reizumi Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com> --- .../browser/preferences/zen-preferences.ftl | 1 + .../en-US/browser/browser/zen-workspaces.ftl | 7 ++ .../base/content/zen-commands.inc.xhtml | 1 + src/browser/themes/shared/zen-icons/icons.css | 7 +- .../themes/shared/zen-icons/jar.inc.mn | 3 + .../themes/shared/zen-icons/lin/dart-down.svg | 5 ++ src/zen/common/styles/zen-omnibox.css | 2 +- src/zen/common/styles/zen-popup.css | 10 +++ src/zen/common/zen-sets.js | 3 + src/zen/kbs/ZenKeyboardShortcuts.mjs | 17 ++++- src/zen/tabs/zen-tabs/vertical-tabs.css | 69 +++++++++++++++---- src/zen/workspaces/ZenWorkspace.mjs | 8 ++- src/zen/workspaces/ZenWorkspaces.mjs | 32 +++++++++ src/zen/zen.globals.js | 1 + 14 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 src/browser/themes/shared/zen-icons/lin/dart-down.svg diff --git a/locales/en-US/browser/browser/preferences/zen-preferences.ftl b/locales/en-US/browser/browser/preferences/zen-preferences.ftl index 43f48460d..d2e2c7589 100644 --- a/locales/en-US/browser/browser/preferences/zen-preferences.ftl +++ b/locales/en-US/browser/browser/preferences/zen-preferences.ftl @@ -337,3 +337,4 @@ zen-devtools-toggle-performance-shortcut = Toggle Performance zen-devtools-toggle-storage-shortcut = Toggle Storage zen-devtools-toggle-dom-shortcut = Toggle DOM zen-devtools-toggle-accessibility-shortcut = Toggle Accessibility +zen-close-all-unpinned-tabs-shortcut = Close All Unpinned Tabs diff --git a/locales/en-US/browser/browser/zen-workspaces.ftl b/locales/en-US/browser/browser/zen-workspaces.ftl index 0933f225c..db5bf6574 100644 --- a/locales/en-US/browser/browser/zen-workspaces.ftl +++ b/locales/en-US/browser/browser/zen-workspaces.ftl @@ -73,3 +73,10 @@ zen-workspace-creation-label = Spaces are used to organize your tabs and session zen-workspaces-delete-workspace-title = Delete Space? zen-workspaces-delete-workspace-body = Are you sure you want to delete { $name }? This action cannot be undone. + +# Note that the html tag MUST not be changed or removed, as it is used to better +# display the shortcut in the toast notification. +zen-workspaces-close-all-unpinned-tabs-toast = Tabs Closed! Use { $shortcut } to undo. +zen-workspaces-close-all-unpinned-tabs-title = + .label = Clear + .tooltiptext = Close all unpinned tabs diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml index d1cbacd7a..66cfe1c5b 100644 --- a/src/browser/base/content/zen-commands.inc.xhtml +++ b/src/browser/base/content/zen-commands.inc.xhtml @@ -60,4 +60,5 @@ + diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 9cddd5e1f..dd9b5451c 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -6,7 +6,8 @@ .subviewbutton, #zen-welcome-start-button, -.zen-toast button { +.zen-toast button, +.pinned-tabs-container-separator toolbarbutton { -moz-context-properties: fill, fill-opacity !important; fill: currentColor !important; } @@ -392,6 +393,10 @@ list-style-image: url('arrow-down.svg') !important; } +.zen-workspace-close-unpinned-tabs-button { + list-style-image: url('dart-down.svg') !important; +} + .zen-workspace-actions-reorder-icon, .zen-tab-rearrange-button { list-style-image: url('drag-indicator.svg') !important; diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index b97b3837b..d52bde3b5 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -29,6 +29,7 @@ * skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) * skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) * skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) +* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) * skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) * skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) * skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) @@ -181,6 +182,7 @@ * skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) * skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) * skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) +* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) * skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) * skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) * skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) @@ -333,6 +335,7 @@ * skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) * skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) * skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) +* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) * skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) * skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) * skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) diff --git a/src/browser/themes/shared/zen-icons/lin/dart-down.svg b/src/browser/themes/shared/zen-icons/lin/dart-down.svg new file mode 100644 index 000000000..5ab92a59b --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/dart-down.svg @@ -0,0 +1,5 @@ +#filter dumbComments emptyLines substitution +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + \ No newline at end of file diff --git a/src/zen/common/styles/zen-omnibox.css b/src/zen/common/styles/zen-omnibox.css index e85ac7d25..98b157154 100644 --- a/src/zen/common/styles/zen-omnibox.css +++ b/src/zen/common/styles/zen-omnibox.css @@ -147,7 +147,7 @@ visibility 0.15s; } - #navigator-toolbox:hover &, + #navigator-toolbox[zen-has-hover='true'] &, &[open], #urlbar[has-popup-open='true'] &, #identity-box[pageproxystate='invalid'] & { diff --git a/src/zen/common/styles/zen-popup.css b/src/zen/common/styles/zen-popup.css index 54cd9e60b..e1194594d 100644 --- a/src/zen/common/styles/zen-popup.css +++ b/src/zen/common/styles/zen-popup.css @@ -381,6 +381,16 @@ menuseparator { & label { margin: 0 4px; + + & span { + background: light-dark(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.1)); + border-radius: 4px; + padding: 5px 7px; + font-size: 12px; + font-weight: 600; + margin: 0 2px; + word-spacing: 2px; + } } & button { diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js index e9ca4b8bc..b7b6c00ad 100644 --- a/src/zen/common/zen-sets.js +++ b/src/zen/common/zen-sets.js @@ -122,6 +122,9 @@ document.addEventListener( } break; } + case 'cmd_zenCloseUnpinnedTabs': + gZenWorkspaces.closeAllUnpinnedTabs(); + break; case 'cmd_zenUnloadWorkspace': { gZenWorkspaces.unloadWorkspace(); break; diff --git a/src/zen/kbs/ZenKeyboardShortcuts.mjs b/src/zen/kbs/ZenKeyboardShortcuts.mjs index 0fb29b239..777d1fbfe 100644 --- a/src/zen/kbs/ZenKeyboardShortcuts.mjs +++ b/src/zen/kbs/ZenKeyboardShortcuts.mjs @@ -49,6 +49,7 @@ const defaultKeyboardGroups = { 'zen-key-enter-full-screen', 'zen-key-exit-full-screen', 'zen-quit-app-shortcut', + 'zen-close-all-unpinned-tabs-shortcut', 'zen-close-tab-shortcut', 'zen-close-shortcut', 'id:key_selectTab1', @@ -799,7 +800,7 @@ class nsZenKeyboardShortcutsLoader { } class nsZenKeyboardShortcutsVersioner { - static LATEST_KBS_VERSION = 12; + static LATEST_KBS_VERSION = 13; constructor() {} @@ -1081,6 +1082,20 @@ class nsZenKeyboardShortcutsVersioner { data = data.filter((shortcut) => shortcut.getID() != 'zen-compact-mode-show-toolbar'); } + if (version < 13) { + data.push( + new KeyShortcut( + 'zen-close-all-unpinned-tabs', + 'K', + '', + ZEN_WORKSPACE_SHORTCUTS_GROUP, + nsKeyShortcutModifiers.fromObject({ accel: true, shift: true }), + 'cmd_zenCloseUnpinnedTabs', + 'zen-close-all-unpinned-tabs-shortcut' + ) + ); + } + return data; } } diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 79c97db00..240e16eb5 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -130,31 +130,76 @@ Pinned Tabs Separator ========================================================================== */ .pinned-tabs-container-separator { - background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); - margin: 8px auto; + height: 24px; border: none; - height: 1px; - max-height: 1px; width: 98%; transition: - margin 0.1s ease-in-out, - background 0.1s ease-in-out, - max-height 0.1s ease-in-out; + height 0.08s ease-in-out, + opacity 0.08s ease-in-out; overflow: hidden; position: relative; opacity: 1; + align-items: center; + + & toolbarseparator { + height: 1px; + background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); + padding: 0; + margin: auto 4px; + + &::before { + border-inline-start: none; + } + } #tabbrowser-tabs[movingtab] & { transition: - margin 0.1s ease-in-out, - background 0.1s ease-in-out, - max-height 0.1s ease-in-out, + height 0.08s ease-in-out, + opacity 0.08s ease-in-out, transform 0.1s ease-in-out; } .zen-workspace-pinned-tabs-section[hide-separator] & { - max-height: 0; - margin: 0 auto; + height: 0px; + opacity: 0; + } + + & toolbarbutton { + appearance: none; + opacity: 0; + visibility: collapse; + transition: + opacity 0.15s, + visibility 0.15s; + font-size: 10px; + font-weight: 500; + gap: 2px; + + #navigator-toolbox[zen-has-hover='true'] & { + visibility: visible; + opacity: 0.5; + } + + &:hover { + opacity: 1 !important; + } + + :root:not([zen-sidebar-expanded='true']) & { + display: none; + } + + & .toolbarbutton-text { + display: flex; + } + + & .toolbarbutton-icon { + width: 10px; + transition: transform 0.15s ease-in-out; + } + + &:active:hover .toolbarbutton-icon { + transform: translateY(2px); + } } } diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index a7d1eee3d..aa92b13f7 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -12,7 +12,13 @@ - + + + + diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 7a50b72bb..6ee9386ff 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -1483,6 +1483,19 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { }); } + unpinnedTabsInWorkspace(workspaceID) { + return Array.from(this.allStoredTabs).filter( + (tab) => tab.getAttribute('zen-workspace-id') === workspaceID && tab.visible && !tab.pinned + ); + } + + #deleteAllUnpinnedTabsInWorkspace(tabs) { + gBrowser.removeTabs(tabs, { + animate: false, + closeWindowWithLastTab: false, + }); + } + async unloadWorkspace() { const workspaceId = this.#contextMenuData?.workspaceId || this.activeWorkspace; @@ -2654,6 +2667,25 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { this.tabContainer._invalidateCachedTabs(); } + async closeAllUnpinnedTabs() { + const workspaceId = this.#contextMenuData?.workspaceId || this.activeWorkspace; + const unpinnedTabs = await this.unpinnedTabsInWorkspace(workspaceId); + + if (!unpinnedTabs.length) return; + + this.#deleteAllUnpinnedTabsInWorkspace(unpinnedTabs); + + const restoreClosedTabsShortcut = gZenKeyboardShortcutsManager.getShortcutDisplayFromCommand( + 'History:RestoreLastClosedTabOrWindowOrSession' + ); + + gZenUIManager.showToast('zen-workspaces-close-all-unpinned-tabs-toast', { + l10nArgs: { + shortcut: restoreClosedTabsShortcut, + }, + }); + } + async contextDeleteWorkspace() { const workspaceId = this.#contextMenuData?.workspaceId || this.activeWorkspace; const [title, body] = await document.l10n.formatValues([ diff --git a/src/zen/zen.globals.js b/src/zen/zen.globals.js index a225ca767..ce22cd4a8 100644 --- a/src/zen/zen.globals.js +++ b/src/zen/zen.globals.js @@ -22,6 +22,7 @@ export default [ 'gZenStartup', 'gZenWorkspaces', + 'gZenKeyboardShortcutsManager', 'ZenWorkspacesEngine', 'ZenWorkspacesStorage', 'ZenWorkspaceBookmarksStorage',