diff --git a/crowdin.yml b/crowdin.yml index c2ccb1c7d..a9f073ac0 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -8,6 +8,8 @@ files: translation: browser/browser/zen-general.ftl - source: en-US/browser/browser/zen-split-view.ftl translation: browser/browser/zen-split-view.ftl + - source: en-US/browser/browser/zen-menubar.ftl + translation: browser/browser/zen-menubar.ftl - source: en-US/browser/browser/zen-vertical-tabs.ftl translation: browser/browser/zen-vertical-tabs.ftl - source: en-US/browser/browser/zen-welcome.ftl diff --git a/locales/en-US/browser/browser/zen-general.ftl b/locales/en-US/browser/browser/zen-general.ftl index eb8574867..5a6689d62 100644 --- a/locales/en-US/browser/browser/zen-general.ftl +++ b/locales/en-US/browser/browser/zen-general.ftl @@ -50,12 +50,6 @@ zen-tabs-renamed = Tab has been successfully renamed! zen-background-tab-opened-toast = New background tab opened! zen-workspace-renamed-toast = Workspace has been successfully renamed! -zen-library-sidebar-workspaces = - .label = Spaces - -zen-library-sidebar-mods = - .label = Mods - zen-toggle-compact-mode-button = .label = Compact Mode .tooltiptext = Toggle Compact Mode diff --git a/locales/en-US/browser/browser/zen-menubar.ftl b/locales/en-US/browser/browser/zen-menubar.ftl new file mode 100644 index 000000000..18de7940a --- /dev/null +++ b/locales/en-US/browser/browser/zen-menubar.ftl @@ -0,0 +1,20 @@ +# 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/. + +zen-menubar-toggle-pinned-tabs = + .label = + { $pinnedAreCollapsed -> + [true] Expand Pinned Tabs + *[false] Collapse Pinned Tabs + } + +zen-menubar-appearance = + .label = Website Appearance + +zen-menubar-appearance-auto = + .label = Automatic +zen-menubar-appearance-light = + .label = Light +zen-menubar-appearance-dark = + .label = Dark \ No newline at end of file diff --git a/locales/en-US/browser/browser/zen-workspaces.ftl b/locales/en-US/browser/browser/zen-workspaces.ftl index c7a5b64fd..097a2b02e 100644 --- a/locales/en-US/browser/browser/zen-workspaces.ftl +++ b/locales/en-US/browser/browser/zen-workspaces.ftl @@ -4,6 +4,9 @@ zen-panel-ui-workspaces-text = Spaces +zen-panel-ui-spaces-label = + .label = Spaces + zen-panel-ui-workspaces-create = .label = Create Space @@ -83,3 +86,9 @@ zen-workspaces-close-all-unpinned-tabs-toast = Tabs Closed! Use { $shortcu zen-workspaces-close-all-unpinned-tabs-title = .label = Clear .tooltiptext = Close all unpinned tabs + +zen-panel-ui-workspaces-change-forward = + .label = Next Space + +zen-panel-ui-workspaces-change-back = + .label = Previous Space diff --git a/src/browser/base/content/zen-locales.inc.xhtml b/src/browser/base/content/zen-locales.inc.xhtml index 247fbbc51..aac0742ad 100644 --- a/src/browser/base/content/zen-locales.inc.xhtml +++ b/src/browser/base/content/zen-locales.inc.xhtml @@ -6,6 +6,7 @@ + diff --git a/src/zen/common/emojis/ZenEmojiPicker.mjs b/src/zen/common/emojis/ZenEmojiPicker.mjs index e20001d25..35e888209 100644 --- a/src/zen/common/emojis/ZenEmojiPicker.mjs +++ b/src/zen/common/emojis/ZenEmojiPicker.mjs @@ -196,6 +196,7 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature { this.#currentPromiseReject = null; this.#anchor.removeAttribute('zen-emoji-open'); + this.#anchor.parentElement.removeAttribute('zen-emoji-open'); this.#anchor = null; } @@ -222,6 +223,7 @@ class nsZenEmojiPicker extends nsZenDOMOperatedFeature { }); this.#anchor = anchor; this.#anchor.setAttribute('zen-emoji-open', 'true'); + this.#anchor.parentElement.setAttribute('zen-emoji-open', 'true'); if (onlySvgIcons) { this.#panel.setAttribute('only-svg-icons', 'true'); } else { diff --git a/src/zen/common/jar.inc.mn b/src/zen/common/jar.inc.mn index 2d4172f8b..2cac29cc3 100644 --- a/src/zen/common/jar.inc.mn +++ b/src/zen/common/jar.inc.mn @@ -13,6 +13,7 @@ content/browser/zen-components/ZenSessionStore.mjs (../../zen/common/modules/ZenSessionStore.mjs) content/browser/zen-components/ZenHasPolyfill.mjs (../../zen/common/modules/ZenHasPolyfill.mjs) content/browser/zen-components/ZenSidebarNotification.mjs (../../zen/common/modules/ZenSidebarNotification.mjs) + content/browser/zen-components/ZenMenubar.mjs (../../zen/common/modules/ZenMenubar.mjs) content/browser/zen-components/ZenEmojisData.min.mjs (../../zen/common/emojis/ZenEmojisData.min.mjs) content/browser/zen-components/ZenEmojiPicker.mjs (../../zen/common/emojis/ZenEmojiPicker.mjs) diff --git a/src/zen/common/modules/ZenMenubar.mjs b/src/zen/common/modules/ZenMenubar.mjs new file mode 100644 index 000000000..9a9fdce27 --- /dev/null +++ b/src/zen/common/modules/ZenMenubar.mjs @@ -0,0 +1,91 @@ +// 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/. + +const WINDOW_SCHEME_PREF = 'zen.view.window.scheme'; +const WINDOW_SCHEME_MAPPING = { + dark: 0, + light: 1, + auto: 2, +}; + +class nsZenMenuBar { + init() { + this.#initViewMenu(); + this.#initSpacesMenu(); + } + + #initViewMenu() { + let appearanceMenu = window.MozXULElement.parseXULToFragment(` + + + + + + + `); + const menu = appearanceMenu.querySelector('menu'); + menu.addEventListener('command', (event) => { + const type = event.target.getAttribute('data-type'); + const schemeValue = WINDOW_SCHEME_MAPPING[type]; + Services.prefs.setIntPref(WINDOW_SCHEME_PREF, schemeValue); + }); + const parent = document.getElementById('view-menu'); + const parentPopup = parent.querySelector('menupopup'); + parentPopup.prepend(document.createXULElement('menuseparator')); + parentPopup.prepend(menu); + + const sibling = document.getElementById('viewSidebarMenuMenu'); + const togglePinnedItem = window.MozXULElement.parseXULToFragment( + '' + ).querySelector('menuitem'); + if (!gZenWorkspaces.privateWindowOrDisabled) sibling.after(togglePinnedItem); + + parentPopup.addEventListener('popupshowing', () => { + const currentScheme = Services.prefs.getIntPref(WINDOW_SCHEME_PREF); + for (const [type, value] of Object.entries(WINDOW_SCHEME_MAPPING)) { + let menuItem = menu.querySelector(`menuitem[data-type="${type}"]`); + if (value === currentScheme) { + menuItem.setAttribute('checked', 'true'); + } else { + menuItem.removeAttribute('checked'); + } + } + const pinnedAreCollapsed = + gZenWorkspaces.activeWorkspaceElement?.hasCollapsedPinnedTabs ?? false; + const args = { pinnedAreCollapsed }; + document.l10n.setArgs(togglePinnedItem, args); + }); + + togglePinnedItem.addEventListener('command', () => { + gZenWorkspaces.activeWorkspaceElement?.collapsiblePins.toggle(); + }); + } + + #initSpacesMenu() { + let menubar = window.MozXULElement.parseXULToFragment(` + + + + + + + + + + + `); + document.getElementById('view-menu').after(menubar); + document.getElementById('zen-spaces-menubar').addEventListener('popupshowing', () => { + gZenWorkspaces.updateWorkspacesChangeContextMenu(); + }); + } +} + +export const ZenMenubar = new nsZenMenuBar(); diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index a43e7e708..93b9649fb 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import { nsZenMultiWindowFeature } from 'chrome://browser/content/zen-components/ZenCommonUtils.mjs'; +import { ZenMenubar } from 'chrome://browser/content/zen-components/ZenMenubar.mjs'; window.gZenUIManager = { _popupTrackingElements: [], @@ -60,6 +61,8 @@ window.gZenUIManager = { this._addNewCustomizableButtonsIfNeeded(); this._initOmnibox(); this._initBookmarkCollapseListener(); + + ZenMenubar.init(); }, _addNewCustomizableButtonsIfNeeded() { diff --git a/src/zen/kbs/ZenKeyboardShortcuts.mjs b/src/zen/kbs/ZenKeyboardShortcuts.mjs index ced9f9488..1102400df 100644 --- a/src/zen/kbs/ZenKeyboardShortcuts.mjs +++ b/src/zen/kbs/ZenKeyboardShortcuts.mjs @@ -694,10 +694,12 @@ class nsZenKeyboardShortcutsLoader { newShortcutList.push( new KeyShortcut( `zen-workspace-switch-${i}`, - '', + AppConstants.platform == 'macosx' ? `${i === 10 ? 0 : i}` : '', '', ZEN_WORKSPACE_SHORTCUTS_GROUP, - nsKeyShortcutModifiers.fromObject({}), + nsKeyShortcutModifiers.fromObject( + AppConstants.platform == 'macosx' ? { ctrl: true } : {} + ), `cmd_zenWorkspaceSwitch${i}`, `zen-workspace-shortcut-switch-${i}` ) diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index c8b9ec436..6ab4dd5e2 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -175,17 +175,23 @@ class nsZenWindowSync { // assign one and sync it to other windows. // This should only happen really when updating from an older version // that didn't have this feature. - this.#runOnAllWindows(null, (aWindow) => { - const { gZenWorkspaces } = aWindow; - for (let tab of gZenWorkspaces.allStoredTabs) { - if (!tab.id) { - tab.id = this.#newTabSyncId; - lazy.TabStateFlusher.flush(tab.linkedBrowser); + let previousWindowPromise = Promise.resolve(); + this.#runOnAllWindows(null, async (aWindow) => { + await previousWindowPromise; + previousWindowPromise = new Promise((resolve) => { + const { gZenWorkspaces } = aWindow; + let allPromises = []; + for (let tab of gZenWorkspaces.allStoredTabs) { + if (!tab.id) { + tab.id = this.#newTabSyncId; + lazy.TabStateFlusher.flush(tab.linkedBrowser); + } + if (tab.pinned && !tab._zenPinnedInitialState) { + allPromises.push(this.setPinnedTabState(tab)); + } } - if (tab.pinned && !tab._zenPinnedInitialState) { - this.setPinnedTabState(tab); - } - } + Promise.all(allPromises).then(resolve); + }); }); } @@ -793,6 +799,7 @@ class nsZenWindowSync { */ setPinnedTabState(aTab) { return lazy.TabStateFlusher.flush(aTab.linkedBrowser).finally(() => { + this.log(`Setting pinned initial state for tab ${aTab.id}`); const state = this.#getTabState(aTab); const initialState = { entry: state.entries[state.index - 1], diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index 9f71f9ee8..c2663891c 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -37,6 +37,10 @@ class nsZenCollapsiblePins extends nsZenFolder { super.collapsed = value; gBrowser.tabContainer._invalidateCachedVisibleTabs(); } + + toggle() { + this.collapsed = !this.collapsed; + } } export class nsZenWorkspace extends MozXULElement { diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index fd2cd5e80..1a39f9ee7 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -920,7 +920,7 @@ class nsZenWorkspaces { window.addEventListener('TabSelect', this.onLocationChange.bind(this)); window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this)); - this.#updateWorkspacesChangeContextMenu(); + this.updateWorkspacesChangeContextMenu(); } async selectStartPage() { @@ -1173,12 +1173,15 @@ class nsZenWorkspaces { ); } - generateMenuItemForWorkspace(workspace) { + generateMenuItemForWorkspace(workspace, disableCurrent = false) { const item = document.createXULElement('menuitem'); item.className = 'zen-workspace-context-menu-item'; item.setAttribute('zen-workspace-id', workspace.uuid); + if (!disableCurrent) { + item.setAttribute('type', 'radio'); + } if (workspace.uuid === this.activeWorkspace) { - item.setAttribute('disabled', true); + item.setAttribute(disableCurrent ? 'disabled' : 'checked', true); } let name = workspace.name; const iconIsSvg = workspace.icon && workspace.icon.endsWith('.svg'); @@ -1381,7 +1384,7 @@ class nsZenWorkspaces { this._organizeWorkspaceStripLocations(this.getActiveWorkspaceFromCache()).finally(() => { this.updateTabsContainers(); }); - this.#updateWorkspacesChangeContextMenu(); + this.updateWorkspacesChangeContextMenu(); } async reorderWorkspace(id, newPosition) { @@ -2335,24 +2338,49 @@ class nsZenWorkspaces { } } - #updateWorkspacesChangeContextMenu() { + updateWorkspacesChangeContextMenu() { if (gZenWorkspaces.privateWindowOrDisabled) return; const workspaces = this.getWorkspaces(); - const menuPopup = document.getElementById('moveTabOptionsMenu'); - if (!menuPopup) { + let menuPopupID = 'moveTabOptionsMenu'; + const menuPopup = document.getElementById(menuPopupID); + let menubar = document.getElementById('zen-spaces-menubar'); + if (!menuPopup || !menubar) { return; } - for (const item of menuPopup.querySelectorAll('.zen-workspace-context-menu-item')) { - item.remove(); - } - const separator = document.createXULElement('menuseparator'); - separator.classList.add('zen-workspace-context-menu-item'); - menuPopup.prepend(separator); - for (let workspace of workspaces.reverse()) { - const menuItem = this.generateMenuItemForWorkspace(workspace); - menuItem.setAttribute('command', 'cmd_zenChangeWorkspaceTab'); - menuPopup.prepend(menuItem); + let itemsToFill = [menubar.querySelector('menupopup'), menuPopup]; + for (const popup of itemsToFill) { + let isMoveTabPopup = popup.id === menuPopupID; + for (const item of popup.querySelectorAll('.zen-workspace-context-menu-item')) { + item.remove(); + } + const separator = document.createXULElement('menuseparator'); + separator.classList.add('zen-workspace-context-menu-item'); + if (isMoveTabPopup) { + popup.prepend(separator); + } else { + popup.appendChild(separator); + } + let i = 0; + for (let workspace of isMoveTabPopup ? workspaces.reverse() : workspaces) { + const menuItem = this.generateMenuItemForWorkspace( + workspace, + /* disableCurrent = */ isMoveTabPopup + ); + if (isMoveTabPopup) { + popup.prepend(menuItem); + menuItem.setAttribute('command', 'cmd_zenChangeWorkspaceTab'); + } else { + if (i < 10) { + menuItem.setAttribute('key', `zen-workspace-switch-${i + 1}`); + } + menuItem.addEventListener('command', () => { + this.changeWorkspace(workspace); + }); + popup.appendChild(menuItem); + } + i++; + } } } diff --git a/src/zen/workspaces/zen-workspaces.css b/src/zen/workspaces/zen-workspaces.css index aedad3033..276cda68e 100644 --- a/src/zen/workspaces/zen-workspaces.css +++ b/src/zen/workspaces/zen-workspaces.css @@ -387,7 +387,7 @@ zen-workspace { .zen-current-workspace-indicator-stack { transition: margin-inline-end 0.1s; - &[no-icon='true'] { + &[no-icon='true']:not([zen-emoji-open='true']) { margin-inline-end: calc(-1 * (var(--indicator-gap) + 16px)); } } @@ -399,7 +399,7 @@ zen-workspace { transform: rotate(90deg); padding: 1px; - .zen-current-workspace-indicator-stack[no-icon='true'] & { + .zen-current-workspace-indicator-stack[no-icon='true']:not([zen-emoji-open='true']) & { display: flex; opacity: 0; } @@ -407,17 +407,17 @@ zen-workspace { & 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 { + .zen-current-workspace-indicator-stack:not([zen-emoji-open='true']) { margin-inline-end: 0; - } - .zen-current-workspace-indicator-icon { - display: none; + .zen-current-workspace-indicator-chevron { + display: flex; + opacity: 1; + } + + .zen-current-workspace-indicator-icon { + display: none; + } } }