feat: Better menu bar integration, p=#11780, c=workspaces, common, kbs

* feat: Better menu bar integration, b=no-bug, c=workspaces, common, kbs
This commit is contained in:
mr. m
2025-12-31 18:34:30 +01:00
committed by GitHub
parent 7af0aab07f
commit be0668561f
14 changed files with 210 additions and 46 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <span>{ $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

View File

@@ -6,6 +6,7 @@
<link rel="localization" href="browser/zen-workspaces.ftl"/>
<link rel="localization" href="browser/zen-split-view.ftl"/>
<link rel="localization" href="browser/zen-general.ftl"/>
<link rel="localization" href="browser/zen-menubar.ftl"/>
<link rel="localization" href="browser/zen-vertical-tabs.ftl"/>
<link rel="localization" href="browser/zen-folders.ftl"/>
</linkset>

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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(`
<menu data-l10n-id="zen-menubar-appearance">
<menupopup>
<menuitem data-l10n-id="zen-menubar-appearance-auto" data-type="auto" type="radio" checked="true" />
<menuitem data-l10n-id="zen-menubar-appearance-light" data-type="light" type="radio" />
<menuitem data-l10n-id="zen-menubar-appearance-dark" data-type="dark" type="radio" />
</menupopup>
</menu>`);
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(
'<menuitem data-l10n-id="zen-menubar-toggle-pinned-tabs" />'
).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(`
<menu id="zen-spaces-menubar" data-l10n-id="zen-panel-ui-spaces-label">
<menupopup>
<menuitem data-l10n-id="zen-panel-ui-workspaces-create" command="cmd_zenOpenWorkspaceCreation"/>
<menuitem data-l10n-id="zen-workspaces-change-theme" command="cmd_zenOpenZenThemePicker"/>
<menuitem data-l10n-id="zen-workspaces-panel-change-name" command="cmd_zenChangeWorkspaceName"/>
<menuitem data-l10n-id="zen-workspaces-panel-change-icon" command="cmd_zenChangeWorkspaceIcon"/>
<menuseparator/>
<menuitem
data-l10n-id="zen-panel-ui-workspaces-change-forward"
command="cmd_zenWorkspaceForward"
key="zen-workspace-forward"/>
<menuitem
data-l10n-id="zen-panel-ui-workspaces-change-back"
command="cmd_zenWorkspaceBack"
key="zen-workspace-backward"/>
</menupopup>
</menu>`);
document.getElementById('view-menu').after(menubar);
document.getElementById('zen-spaces-menubar').addEventListener('popupshowing', () => {
gZenWorkspaces.updateWorkspacesChangeContextMenu();
});
}
}
export const ZenMenubar = new nsZenMenuBar();

View File

@@ -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() {

View File

@@ -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}`
)

View File

@@ -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) => {
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) {
this.setPinnedTabState(tab);
allPromises.push(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],

View File

@@ -37,6 +37,10 @@ class nsZenCollapsiblePins extends nsZenFolder {
super.collapsed = value;
gBrowser.tabContainer._invalidateCachedVisibleTabs();
}
toggle() {
this.collapsed = !this.collapsed;
}
}
export class nsZenWorkspace extends MozXULElement {

View File

@@ -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')) {
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');
menuPopup.prepend(separator);
for (let workspace of workspaces.reverse()) {
const menuItem = this.generateMenuItemForWorkspace(workspace);
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');
menuPopup.prepend(menuItem);
} else {
if (i < 10) {
menuItem.setAttribute('key', `zen-workspace-switch-${i + 1}`);
}
menuItem.addEventListener('command', () => {
this.changeWorkspace(workspace);
});
popup.appendChild(menuItem);
}
i++;
}
}
}

View File

@@ -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,19 +407,19 @@ zen-workspace {
& zen-workspace[haspinnedtabs] .zen-current-workspace-indicator:hover,
& zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator {
.zen-current-workspace-indicator-stack:not([zen-emoji-open='true']) {
margin-inline-end: 0;
.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);