diff --git a/src/browser/components/sessionstore/SessionStore-sys-mjs.patch b/src/browser/components/sessionstore/SessionStore-sys-mjs.patch index fe61001fd..7eaf8210e 100644 --- a/src/browser/components/sessionstore/SessionStore-sys-mjs.patch +++ b/src/browser/components/sessionstore/SessionStore-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs -index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..4439fe5fb3c7002b173415b615892ef356b22959 100644 +index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..697dde4378c43ae6db46a6b7eb2997982201ec27 100644 --- a/browser/components/sessionstore/SessionStore.sys.mjs +++ b/browser/components/sessionstore/SessionStore.sys.mjs @@ -127,6 +127,8 @@ const TAB_EVENTS = [ @@ -177,7 +177,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..4439fe5fb3c7002b173415b615892ef3 + winData.folders = aWindow.gZenFolders?.storeDataForSessionStore() || []; + winData.activeZenSpace = aWindow.gZenWorkspaces?.activeWorkspace || null; -+ winData.spaces = aWindow.gZenWorkspaces?.getWorkspaces(); ++ winData.spaces = aWindow.gZenWorkspaces?.getWorkspacesForSessionStore(); // update tab group state for this window winData.groups = []; for (let tabGroup of aWindow.gBrowser.tabGroups) { diff --git a/src/browser/components/tabbrowser/content/tab-js.patch b/src/browser/components/tabbrowser/content/tab-js.patch index dd0edd05f..c7be0f3eb 100644 --- a/src/browser/components/tabbrowser/content/tab-js.patch +++ b/src/browser/components/tabbrowser/content/tab-js.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js -index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7600f564a 100644 +index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..160e277d64eaac8408aed90eaf62606479424001 100644 --- a/browser/components/tabbrowser/content/tab.js +++ b/browser/components/tabbrowser/content/tab.js @@ -21,6 +21,7 @@ @@ -87,7 +87,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7 } get lastAccessed() { -@@ -382,7 +395,12 @@ +@@ -382,7 +395,18 @@ } get group() { @@ -97,11 +97,17 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7 + } + if (gBrowser.isTabGroup(this.parentElement?.parentElement)) { + return this.parentElement.parentElement; ++ } ++ if (this.pinned) { ++ let collapsiblePins = gZenWorkspaces.workspaceElement(this.getAttribute('zen-workspace-id'))?.collapsiblePins; ++ if (collapsiblePins?.collapsed) { ++ return collapsiblePins; ++ } + } } get splitview() { -@@ -473,6 +491,8 @@ +@@ -473,6 +497,8 @@ this.style.MozUserFocus = "ignore"; } else if ( event.target.classList.contains("tab-close-button") || @@ -110,7 +116,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7 event.target.classList.contains("tab-icon-overlay") || event.target.classList.contains("tab-audio-button") ) { -@@ -527,6 +547,10 @@ +@@ -527,6 +553,10 @@ this.style.MozUserFocus = ""; } @@ -121,7 +127,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7 on_click(event) { if (event.button != 0) { return; -@@ -587,6 +611,14 @@ +@@ -587,6 +617,14 @@ // (see tabbrowser-tabs 'click' handler). gBrowser.tabContainer._blockDblClick = true; } @@ -136,7 +142,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7 } on_dblclick(event) { -@@ -610,6 +642,8 @@ +@@ -610,6 +648,8 @@ animate: true, triggeringEvent: event, }); diff --git a/src/browser/components/tabbrowser/content/tabgroup-js.patch b/src/browser/components/tabbrowser/content/tabgroup-js.patch index 3842442c7..1fb4ef153 100644 --- a/src/browser/components/tabbrowser/content/tabgroup-js.patch +++ b/src/browser/components/tabbrowser/content/tabgroup-js.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js -index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057ebc71c2f5f 100644 +index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..e92927612abf12631c384a8f54b6a607fb699424 100644 --- a/browser/components/tabbrowser/content/tabgroup.js +++ b/browser/components/tabbrowser/content/tabgroup.js @@ -14,11 +14,11 @@ @@ -68,10 +68,10 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb - return false; - }); + this.appendChild = function (child) { -+ this.querySelector(".tab-group-container").appendChild(child); ++ this.groupContainer.appendChild(child); + for (let tab of this.tabs) { + if (tab.hasAttribute("zen-empty-tab") && tab.group === this) { -+ this.querySelector(".zen-tab-group-start").after(tab); ++ this.groupStartElement.after(tab); + } + } + }; @@ -92,7 +92,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb }); } - this.#tabChangeObserver.observe(this, { childList: true }); -+ const container = this.querySelector(".tab-group-container"); ++ const container = this.groupContainer; + if (container) { + this.#tabChangeObserver.observe(container, { childList: true }); + } @@ -117,7 +117,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb }); } -@@ -466,13 +492,57 @@ +@@ -466,13 +492,65 @@ * @returns {MozTabbrowserTab[]} */ get tabs() { @@ -126,20 +126,29 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb - if (childrenArray[i].tagName == "tab-split-view-wrapper") { - childrenArray.splice(i, 1, ...childrenArray[i].tabs); + // add other group tabs if they are under this group -+ let childs = Array.from(this.querySelector(".tab-group-container")?.children ?? []); ++ let childs = Array.from(this.groupContainer?.children ?? []); + const tabsCollect = []; + for (let item of childs) { + tabsCollect.push(item); + if (gBrowser.isTabGroup(item)) { + tabsCollect.push(...item.tabs); -+ } -+ } + } + } +- return childrenArray.filter(node => node.matches("tab")); + return tabsCollect.filter(node => node.matches("tab")); + } + ++ get groupContainer() { ++ return this.querySelector(".tab-group-container"); ++ } ++ ++ get groupStartElement() { ++ return this.querySelector(".zen-tab-group-start"); ++ } ++ + get childGroupsAndTabs() { + const result = []; -+ const container = this.querySelector(".tab-group-container"); ++ const container = this.groupContainer; + + for (const item of Array.from(container.children)) { + if (gBrowser.isTab(item)) { @@ -169,9 +178,8 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb + currentGroup = currentGroup?.group; + if (currentGroup.collapsed) { + return false; - } - } -- return childrenArray.filter(node => node.matches("tab")); ++ } ++ } + return true; + } + @@ -180,7 +188,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb } /** -@@ -553,7 +623,6 @@ +@@ -553,7 +631,6 @@ addTabs(tabs, metricsContext) { for (let tab of tabs) { if (tab.pinned) { @@ -188,7 +196,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb } let tabToMove = this.ownerGlobal === tab.ownerGlobal -@@ -616,7 +685,7 @@ +@@ -616,7 +693,7 @@ */ on_click(event) { let isToggleElement = @@ -197,7 +205,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb event.target === this.#overflowCountLabel; if (isToggleElement && event.button === 0) { event.preventDefault(); -@@ -687,5 +756,6 @@ +@@ -687,5 +764,6 @@ } } diff --git a/src/browser/components/tabbrowser/content/tabs-js.patch b/src/browser/components/tabbrowser/content/tabs-js.patch index ef5bb94e3..9c2dcf099 100644 --- a/src/browser/components/tabbrowser/content/tabs-js.patch +++ b/src/browser/components/tabbrowser/content/tabs-js.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js -index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac8793422474052f476573 100644 +index 6b6c04599fe80983d13d2069ca62b99d8ad70271..04144081560f1678dc9673736ef2bd9d9ca3f478 100644 --- a/browser/components/tabbrowser/content/tabs.js +++ b/browser/components/tabbrowser/content/tabs.js @@ -436,7 +436,7 @@ @@ -54,7 +54,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 return this.hasAttribute("overflow"); } -@@ -837,29 +839,54 @@ +@@ -837,29 +839,56 @@ if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") { pinnedChildren.pop(); } @@ -81,6 +81,8 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 + tabs.splice(i, 1); + // add the tabs in the group to the list + tabs.splice(i, 0, ...tab.tabs); ++ } else if (tab.classList.contains("zen-tab-group-start")) { ++ tabs.splice(i, 1); + } + } + }; @@ -119,7 +121,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 } /** -@@ -926,17 +953,10 @@ +@@ -926,17 +955,10 @@ let elementIndex = 0; @@ -139,7 +141,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 if (isTab(child) && child.visible) { child.elementIndex = elementIndex++; focusableItems.push(child); -@@ -944,11 +964,13 @@ +@@ -944,11 +966,13 @@ child.labelElement.elementIndex = elementIndex++; focusableItems.push(child.labelElement); @@ -154,7 +156,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 } else if (child.tagName == "tab-split-view-wrapper") { let visibleTabsInSplitView = child.tabs.filter(tab => tab.visible); visibleTabsInSplitView.forEach(tab => { -@@ -992,6 +1014,7 @@ +@@ -992,6 +1016,7 @@ _invalidateCachedTabs() { this.#allTabs = null; this._invalidateCachedVisibleTabs(); @@ -162,7 +164,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 } _invalidateCachedVisibleTabs() { -@@ -1095,7 +1118,7 @@ +@@ -1095,7 +1120,7 @@ if (node == null) { // We have a container for non-tab elements at the end of the scrollbox. @@ -171,7 +173,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 } node.before(tab); -@@ -1193,7 +1216,7 @@ +@@ -1193,7 +1218,7 @@ // There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and // for when the tab strip is overflowed (which is shared by vertical and horizontal tabs); // Attach the long click popup to all of them. @@ -180,7 +182,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 const newTab2 = this.newTabButton; const newTabVertical = document.getElementById( "vertical-tabs-newtab-button" -@@ -1294,8 +1317,10 @@ +@@ -1294,8 +1319,10 @@ */ _handleTabSelect(aInstant) { let selectedTab = this.selectedItem; @@ -191,7 +193,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 selectedTab._notselectedsinceload = false; } -@@ -1304,7 +1329,7 @@ +@@ -1304,7 +1331,7 @@ * @param {boolean} [shouldScrollInstantly=false] */ #ensureTabIsVisible(tab, shouldScrollInstantly = false) { @@ -200,7 +202,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405 if (arrowScrollbox?.overflowing) { arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly); } -@@ -1437,7 +1462,7 @@ +@@ -1437,7 +1464,7 @@ } _notifyBackgroundTab(aTab) { diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 80db0d71d..37443d6bc 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -7,6 +7,7 @@ .subviewbutton, #zen-welcome-start-button, .zen-toast button, +.zen-current-workspace-indicator-chevron, .pinned-tabs-container-separator toolbarbutton { -moz-context-properties: fill, fill-opacity !important; fill: currentColor !important; @@ -116,7 +117,7 @@ } } -#zen-rice-share-options .options-header, +.zen-current-workspace-indicator-chevron, #PanelUI-zen-gradient-generator-color-page-right { list-style-image: url('arrow-right.svg'); } diff --git a/src/browser/themes/shared/zen-icons/lin/arrow-right.svg b/src/browser/themes/shared/zen-icons/lin/arrow-right.svg index 6f6854344..88847cd96 100644 --- a/src/browser/themes/shared/zen-icons/lin/arrow-right.svg +++ b/src/browser/themes/shared/zen-icons/lin/arrow-right.svg @@ -2,4 +2,4 @@ # 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/. - + diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index ae63ace1e..70c9d10ba 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -125,8 +125,6 @@ window.gZenUIManager = { } menu.setAttribute('hidden', 'true'); } - // The first separator in the tab context menu is now useless. - document.getElementById('tabContextMenu').querySelector('menuseparator').remove(); }, _initCreateNewPopup() { diff --git a/src/zen/folders/ZenFolder.mjs b/src/zen/folders/ZenFolder.mjs index 6c1d2dbc7..3c3413e6a 100644 --- a/src/zen/folders/ZenFolder.mjs +++ b/src/zen/folders/ZenFolder.mjs @@ -2,7 +2,7 @@ // 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/. -class ZenFolder extends MozTabbrowserTabGroup { +export class nsZenFolder extends MozTabbrowserTabGroup { #initialized = false; static markup = ` @@ -68,7 +68,7 @@ class ZenFolder extends MozTabbrowserTabGroup { } this.#initialized = true; this._activeTabs = []; - this.icon.appendChild(ZenFolder.rawIcon.cloneNode(true)); + this.icon.appendChild(nsZenFolder.rawIcon.cloneNode(true)); this.labelElement.parentElement.setAttribute('context', 'zenFolderActions'); @@ -81,7 +81,7 @@ class ZenFolder extends MozTabbrowserTabGroup { }; if (this.collapsed) { - this.querySelector('.tab-group-container').setAttribute('hidden', true); + this.groupContainer.setAttribute('hidden', true); } } @@ -141,7 +141,7 @@ class ZenFolder extends MozTabbrowserTabGroup { gZenFolders.createFolder([], { renameFolder: !gZenUIManager.testingEnabled, label: 'Subfolder', - insertAfter: this.querySelector('.tab-group-container').lastElementChild, + insertAfter: this.groupContainer.lastElementChild, }); } @@ -181,8 +181,12 @@ class ZenFolder extends MozTabbrowserTabGroup { } get allItems() { - return [...this.querySelector('.tab-group-container').children].filter( - (child) => !child.classList.contains('zen-tab-group-start') + return [...this.groupContainer.children].filter( + (child) => + !( + child.classList.contains('zen-tab-group-start') || + child.classList.contains('pinned-tabs-container-separator') + ) ); } @@ -274,4 +278,4 @@ class ZenFolder extends MozTabbrowserTabGroup { } } -customElements.define('zen-folder', ZenFolder); +customElements.define('zen-folder', nsZenFolder); diff --git a/src/zen/folders/ZenFolders.mjs b/src/zen/folders/ZenFolders.mjs index 4cf42f469..e1fcd7c97 100644 --- a/src/zen/folders/ZenFolders.mjs +++ b/src/zen/folders/ZenFolders.mjs @@ -189,6 +189,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { window.addEventListener('TabSelect', this); window.addEventListener('TabOpen', this); const onNewFolder = this.#onNewFolder.bind(this); + document.getElementById('zen-context-menu-new-folder').addEventListener('command', onNewFolder); document .getElementById('zen-context-menu-new-folder-toolbar') .addEventListener('command', onNewFolder); @@ -803,6 +804,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature { if (!isTab && !groupElem?.hasAttribute('selected') && !forCollapse) { groupElem = null; // Don't indent if the group is not selected } + if (groupElem?.tagName.toLowerCase() === 'zen-workspace-collapsible-pins') { + groupElem = null; // Don't indent if it's inside the collapsible pinned tabs + } let level = groupElem?.level + 1 || 0; if (gBrowser.isTabGroupLabel(groupElem)) { // If it is a group label, we should not increase its level by one. @@ -1036,8 +1040,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { } default: { // Should insert after zen-empty-tab - const start = - parentWorkingData.node.querySelector('.zen-tab-group-start').nextElementSibling; + const start = parentWorkingData.node.groupStartElement.nextElementSibling; start.after(node); } } @@ -1128,8 +1131,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { const dropElementGroup = dropElement?.isZenFolder ? dropElement : dropElement?.group; const isSplitGroup = dropElement?.group?.hasAttribute('split-view-group'); - let firstGroupElem = - dropElementGroup?.querySelector('.zen-tab-group-start')?.nextElementSibling; + let firstGroupElem = dropElementGroup?.groupStartElement.nextElementSibling; if (gBrowser.isTabGroup(firstGroupElem)) firstGroupElem = firstGroupElem.labelElement; const isInMiddleZone = @@ -1212,6 +1214,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature { return heightShift; } else { heightShift += window.windowUtils.getBoundsWithoutFlushing(tabsContainer).height; + if (tabsContainer.separatorElement) { + heightShift -= window.windowUtils.getBoundsWithoutFlushing( + tabsContainer.separatorElement + ).height; + } } return heightShift; } @@ -1225,8 +1232,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature { const activeFoldersIds = new Set(); const itemsToHide = []; - const tabsContainer = group.querySelector('.tab-group-container'); - const groupStart = group.querySelector('.zen-tab-group-start'); + const tabsContainer = group.groupContainer; + const groupStart = group.groupStartElement; const groupItems = this.#collectGroupItems(group, { selectedTabs, @@ -1304,11 +1311,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature { const animations = []; const itemsToHide = []; - const tabsContainer = group.querySelector('.tab-group-container'); + const tabsContainer = group.groupContainer; tabsContainer.removeAttribute('hidden'); tabsContainer.style.overflow = 'hidden'; - const groupStart = group.querySelector('.zen-tab-group-start'); + const groupStart = group.groupStartElement; const itemsToShow = this.#normalizeGroupItems(group.childGroupsAndTabs); const activeFolders = group.childActiveGroups; @@ -1422,7 +1429,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { folder.removeAttribute('has-active'); folder.activeTabs = []; const groupItems = this.#normalizeGroupItems(folder.allItems); - const tabsContainer = folder.querySelector('.tab-group-container'); + const tabsContainer = folder.groupContainer; // Set correct margin-top after animation const afterAnimate = () => { @@ -1436,7 +1443,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { groupStart.style.marginTop = `${-(collapsedHeight + 4)}px`; }; - const groupStart = folder.querySelector('.zen-tab-group-start'); + const groupStart = folder.groupStartElement; const collapsedHeight = this.#calculateHeightShift(tabsContainer, []); // Collect animations for this specific folder becoming inactive @@ -1474,7 +1481,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { animations.push(async () => { folder.removeAttribute('has-active'); const groupItems = this.#normalizeGroupItems(folder.allItems); - const tabsContainer = folder.querySelector('.tab-group-container'); + const tabsContainer = folder.groupContainer; // Set correct margin-top after animation const afterAnimate = () => { @@ -1488,7 +1495,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature { groupStart.style.marginTop = `${-(collapsedHeight + 4)}px`; }; - const groupStart = folder.querySelector('.zen-tab-group-start'); + const groupStart = folder.groupStartElement; const collapsedHeight = this.#calculateHeightShift(tabsContainer, []); // Collect animations for this specific folder becoming inactive @@ -1573,8 +1580,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature { currentGroup.activeTabs = activeTabs; } - const tabsContainer = currentGroup.querySelector('.tab-group-container'); - const groupStart = currentGroup.querySelector('.zen-tab-group-start'); + const tabsContainer = currentGroup.groupContainer; + const groupStart = currentGroup.groupStartElement; tabsContainer.style.overflow = 'clip'; if (tabsContainer.hasAttribute('hidden')) tabsContainer.removeAttribute('hidden'); @@ -1673,8 +1680,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature { animateGroupMove(group, expand = false) { if (!group?.isZenFolder) return; - const groupStart = group.querySelector('.zen-tab-group-start'); - const tabsContainer = group.querySelector('.tab-group-container'); + const groupStart = group.groupStartElement; + const tabsContainer = group.groupContainer; const heightContainer = expand ? 0 : this.#calculateHeightShift(tabsContainer, []); tabsContainer.style.overflow = 'clip'; diff --git a/src/zen/folders/zen-folders.css b/src/zen/folders/zen-folders.css index f1be64211..1cd1cb43d 100644 --- a/src/zen/folders/zen-folders.css +++ b/src/zen/folders/zen-folders.css @@ -202,16 +202,6 @@ zen-folder { } } - &[collapsed] { - & > .tabbrowser-tab:not([hidden]) { - display: flex; - } - - &:not([has-active]) > .tab-group-container { - overflow-y: clip; - } - } - :root[zen-sidebar-expanded] &[has-active] > .tab-group-label-container { & .tab-reset-button { display: flex; @@ -224,6 +214,11 @@ zen-folder { } } +zen-workspace[collapsedpinnedtabs] .zen-workspace-pinned-tabs-section, +zen-folder[collapsed]:not([has-active]) > .tab-group-container { + overflow-y: clip; +} + /* Tabs popup */ #zen-folder-tabs-popup { --arrowpanel-padding: 0; diff --git a/src/zen/sessionstore/ZenSessionManager.sys.mjs b/src/zen/sessionstore/ZenSessionManager.sys.mjs index 254d1e379..9b82ae703 100644 --- a/src/zen/sessionstore/ZenSessionManager.sys.mjs +++ b/src/zen/sessionstore/ZenSessionManager.sys.mjs @@ -224,6 +224,7 @@ export class nsZenSessionManager { // If there's no initial state, nothing to restore. This would // happen if the file is empty or corrupted. if (!initialState) { + this.log('No initial state to restore!'); return; } // If there are no windows, we create an empty one. By default, @@ -249,7 +250,7 @@ export class nsZenSessionManager { // guarantee that all tabs, groups, folders and split view data // are properly synced across all windows. this.log(`Restoring Zen session data into ${initialState.windows?.length || 0} windows`); - for (const winData of initialState.windows || []) { + for (const winData of initialState.windows) { this.#restoreWindowData(winData); } } diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index 2d8878751..c8f2dfd17 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -176,8 +176,8 @@ class nsZenWindowSync { // This should only happen really when updating from an older version // that didn't have this feature. this.#runOnAllWindows(null, (aWindow) => { - const { gBrowser } = aWindow; - for (let tab of gBrowser.tabs) { + const { gZenWorkspaces } = aWindow; + for (let tab of gZenWorkspaces.allStoredTabs) { if (!tab.id) { tab.id = this.#newTabSyncId; lazy.TabStateFlusher.flush(tab.linkedBrowser); diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs index fda9a0ebd..1ab19ee08 100644 --- a/src/zen/tabs/ZenPinnedTabManager.mjs +++ b/src/zen/tabs/ZenPinnedTabManager.mjs @@ -322,11 +322,14 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { const state = this.#getTabState(tab); const initialState = tab._zenPinnedInitialState; + if (!initialState?.entry) { + return; + } // Remove everything except the entry we want to keep state.entries = [initialState.entry]; - state.image = initialState.image; + state.image = tab.zenStaticIcon || initialState.image; state.index = 0; SessionStore.setTabState(tab, state); diff --git a/src/zen/workspaces/ZenGradientGenerator.mjs b/src/zen/workspaces/ZenGradientGenerator.mjs index a1495d4ed..ad0ee2497 100644 --- a/src/zen/workspaces/ZenGradientGenerator.mjs +++ b/src/zen/workspaces/ZenGradientGenerator.mjs @@ -1,6 +1,6 @@ -// 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/. +/* 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/. */ import { nsZenMultiWindowFeature } from 'chrome://browser/content/zen-components/ZenCommonUtils.mjs'; diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index 7cb1af16d..a888b6732 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -1,17 +1,59 @@ -// 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/. +/* 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/. */ + +import { nsZenFolder } from 'chrome://browser/content/zen-components/ZenFolder.mjs'; + +// A helper class to manage collapsible pinned tabs in a workspace. +class nsZenCollapsiblePins extends nsZenFolder { + #spaceElement; + + connectedCallback() { + this.setAttribute('hidden', 'true'); + this.#spaceElement = this.parentElement; + super.connectedCallback(); + } + + get groupContainer() { + return this.#spaceElement.pinnedTabsContainer; + } + + get groupStartElement() { + // Fetch this instead of the tab-group-start since it is not guaranteed this + // element will be the first child of the pinned tabs container. + return this.#spaceElement.pinnedTabsContainer.querySelector('.space-fake-collapsible-start'); + } + + get collapsed() { + return super.collapsed; + } + + set collapsed(value) { + if (value) { + this.#spaceElement.setAttribute('collapsedpinnedtabs', 'true'); + } else { + this.#spaceElement.removeAttribute('collapsedpinnedtabs'); + } + super.collapsed = value; + } +} + +export class nsZenWorkspace extends MozXULElement { + #initialPinnedElementChildrenCount; -class nsZenWorkspace extends MozXULElement { static get markup() { return ` - + + + + + { + if (this.hasPinnedTabs) { + // Prevent renaming when there are pinned tabs + event.stopPropagation(); + } + }); this.pinnedTabsContainer.scrollbox = this.scrollbox; + this.#initialPinnedElementChildrenCount = this.pinnedTabsContainer.children.length; this.indicator .querySelector('.zen-workspaces-actions') @@ -92,10 +144,20 @@ class nsZenWorkspace extends MozXULElement { this.indicator .querySelector('.zen-current-workspace-indicator-icon') .addEventListener('dblclick', (event) => { + if (this.hasPinnedTabs) { + return; + } event.stopPropagation(); gZenWorkspaces.changeWorkspaceIcon(); }); + this.indicator.addEventListener('click', (event) => { + if (this.hasPinnedTabs) { + event.stopPropagation(); + this.collapsiblePins.collapsed = !this.collapsiblePins.collapsed; + } + }); + if (!gZenWorkspaces.currentWindowIsSyncing) { let actionsButton = this.indicator.querySelector('.zen-workspaces-actions'); const moveTabToFragment = window.MozXULElement.parseXULToFragment( @@ -169,11 +231,26 @@ class nsZenWorkspace extends MozXULElement { this.tabsContainer.setAttribute('zen-workspace-id', this.id); this.pinnedTabsContainer.setAttribute('zen-workspace-id', this.id); + this.collapsiblePins = document.createXULElement('zen-workspace-collapsible-pins'); + this.prepend(this.collapsiblePins); + this.#updateOverflow(); this.onGradientCacheChanged = this.#onGradientCacheChanged.bind(this); window.addEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged); + const tabPinCallback = () => { + this.checkPinsExistence(); + }; + + this.addEventListener('TabPinned', tabPinCallback); + this.addEventListener('TabUnpinned', tabPinCallback); + this.addEventListener('TabClose', (event) => { + if (event.target.pinned) { + tabPinCallback(); + } + }); + this.dispatchEvent( new CustomEvent('ZenWorkspaceAttached', { bubbles: true, @@ -185,6 +262,7 @@ class nsZenWorkspace extends MozXULElement { disconnectedCallback() { window.removeEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged); + super.disconnectedCallback(); } get active() { @@ -200,6 +278,14 @@ class nsZenWorkspace extends MozXULElement { this.#updateOverflow(); } + get hasPinnedTabs() { + return this.hasAttribute('haspinnedtabs'); + } + + get hasCollapsedPinnedTabs() { + return this.hasAttribute('collapsedpinnedtabs'); + } + #updateOverflow() { if (!this.scrollbox) return; if (this.overflows) { @@ -273,6 +359,15 @@ class nsZenWorkspace extends MozXULElement { this.style.setProperty('--zen-primary-color', primaryColor); } + checkPinsExistence() { + if (this.pinnedTabsContainer.children.length > this.#initialPinnedElementChildrenCount) { + this.setAttribute('haspinnedtabs', 'true'); + } else { + this.removeAttribute('haspinnedtabs'); + this.collapsiblePins.collapsed = false; + } + } + clearThemeStyles() { this.style.colorScheme = ''; this.style.removeProperty('--toolbox-textcolor'); @@ -311,3 +406,4 @@ class nsZenWorkspace extends MozXULElement { } customElements.define('zen-workspace', nsZenWorkspace); +customElements.define('zen-workspace-collapsible-pins', nsZenCollapsiblePins); diff --git a/src/zen/workspaces/ZenWorkspaceCreation.mjs b/src/zen/workspaces/ZenWorkspaceCreation.mjs index d037bade7..23b74e073 100644 --- a/src/zen/workspaces/ZenWorkspaceCreation.mjs +++ b/src/zen/workspaces/ZenWorkspaceCreation.mjs @@ -1,6 +1,6 @@ -// 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/. +/* 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/. */ class nsZenWorkspaceCreation extends MozXULElement { #wasInCollapsedMode = false; diff --git a/src/zen/workspaces/ZenWorkspaceIcons.mjs b/src/zen/workspaces/ZenWorkspaceIcons.mjs index 9672c4a27..a9b8b99a3 100644 --- a/src/zen/workspaces/ZenWorkspaceIcons.mjs +++ b/src/zen/workspaces/ZenWorkspaceIcons.mjs @@ -1,6 +1,6 @@ -// 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/. +/* 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/. */ class nsZenWorkspaceIcons extends MozXULElement { constructor() { diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 872b6af93..d70e70dc7 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -1,6 +1,6 @@ -// 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/. +/* 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/. */ import { nsZenThemePicker } from 'chrome://browser/content/zen-components/ZenGradientGenerator.mjs'; @@ -465,6 +465,7 @@ class nsZenWorkspaces { workspaceWrapper.pinnedTabsContainer, tabs ); + workspaceWrapper.checkPinsExistence(); resolve(); }, { once: true } @@ -827,6 +828,20 @@ class nsZenWorkspaces { return [...this._workspaceCache]; } + getWorkspacesForSessionStore() { + const spaces = this.getWorkspaces(); + let spacesForSS = []; + for (const space of spaces) { + let newSpace = { ...space }; + const element = this.workspaceElement(space.uuid); + if (element) { + newSpace.hasCollapsedPinnedTabs = element.hasCollapsedPinnedTabs; + } + spacesForSS.push(newSpace); + } + return spacesForSS; + } + async workspaceBookmarks() { if (this.privateWindowOrDisabled) { this._workspaceBookmarksCache = { @@ -854,11 +869,22 @@ class nsZenWorkspaces { if (this.#hasInitialized) { return; } - this._workspaceCache = aWinData.spaces?.length - ? aWinData.spaces + const spacesFromStore = aWinData.spaces || []; + this._workspaceCache = spacesFromStore.length + ? [...spacesFromStore] : [await this.createAndSaveWorkspace('Space', undefined, true)]; + for (const workspace of this._workspaceCache) { + // We don't want to depend on this by mistake + delete workspace.hasCollapsedPinnedTabs; + } this.activeWorkspace = aWinData.activeZenSpace || this._workspaceCache[0].uuid; await this.initializeWorkspaces(); + for (const workspace of spacesFromStore) { + const element = this.workspaceElement(workspace.uuid); + if (element) { + element.collapsiblePins.collapsed = workspace.hasCollapsedPinnedTabs || false; + } + } this.#hasInitialized = true; } @@ -1788,11 +1814,14 @@ class nsZenWorkspaces { } const indicatorName = workspaceIndicator.querySelector('.zen-current-workspace-indicator-name'); const indicatorIcon = workspaceIndicator.querySelector('.zen-current-workspace-indicator-icon'); + const iconStack = workspaceIndicator.querySelector('.zen-current-workspace-indicator-stack'); if (this.workspaceHasIcon(currentWorkspace)) { indicatorIcon.removeAttribute('no-icon'); + iconStack.removeAttribute('no-icon'); } else { indicatorIcon.setAttribute('no-icon', 'true'); + iconStack.setAttribute('no-icon', 'true'); } const icon = this.getWorkspaceIcon(currentWorkspace); indicatorIcon.innerHTML = ''; diff --git a/src/zen/workspaces/ZenWorkspacesStorage.mjs b/src/zen/workspaces/ZenWorkspacesStorage.mjs index 80c7a82e5..f7df8a31e 100644 --- a/src/zen/workspaces/ZenWorkspacesStorage.mjs +++ b/src/zen/workspaces/ZenWorkspacesStorage.mjs @@ -1,6 +1,6 @@ -// 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/. +/* 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/. */ // Integration of workspace-specific bookmarks into Places window.ZenWorkspaceBookmarksStorage = { diff --git a/src/zen/workspaces/zen-workspaces.css b/src/zen/workspaces/zen-workspaces.css index 584c366b1..7c258c712 100644 --- a/src/zen/workspaces/zen-workspaces.css +++ b/src/zen/workspaces/zen-workspaces.css @@ -155,13 +155,14 @@ /* Mark workspaces indicator */ .zen-current-workspace-indicator { + --indicator-gap: 10px; margin-top: 1px; padding: calc(2px + var(--tab-inline-padding) + var(--zen-toolbox-padding)); font-weight: 500; position: relative; max-height: var(--zen-workspace-indicator-height); min-height: var(--zen-workspace-indicator-height); - gap: 10px; + gap: var(--indicator-gap); align-items: center; flex-direction: row !important; max-width: 100%; @@ -368,3 +369,52 @@ zen-workspace { } %include create-workspace-form.css + +/* Pinned tabs collapse styles */ + +.zen-current-workspace-indicator-chevron { + display: none; +} + +:root[zen-sidebar-expanded] { + .zen-current-workspace-indicator-stack { + transition: margin-inline-end 0.1s; + + &[no-icon='true'] { + margin-inline-end: calc(-1 * (var(--indicator-gap) + 16px)); + } + } + + .zen-current-workspace-indicator-chevron { + width: 16px; + height: 16px; + transition: transform 0.2s, opacity 0.2s; + transform: rotate(90deg); + padding: 2px; + + .zen-current-workspace-indicator-stack[no-icon='true'] & { + display: flex; + opacity: 0; + } + } + + & zen-workspace[haspinnedtabs] .zen-current-workspace-indicator:hover, + & zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator { + .zen-current-workspace-indicator-chevron { + display: flex; + opacity: 1; + } + + .zen-current-workspace-indicator-stack { + margin-inline-end: 0; + } + + .zen-current-workspace-indicator-icon { + display: none; + } + } + + zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator-chevron { + transform: rotate(0deg); + } +}