1
0
Fork 1
mirror of https://github.com/zen-browser/desktop.git synced 2025-07-08 00:10:00 +02:00
zen-desktop/src/browser/components/preferences/zen-settings.js

1140 lines
40 KiB
JavaScript

// 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/.
var gZenMarketplaceManager = {
async init() {
const checkForUpdates = document.getElementById('zenThemeMarketplaceCheckForUpdates');
const header = document.getElementById('zenMarketplaceHeader');
if (!checkForUpdates || !header) {
return; // We haven't entered the settings page yet.
}
if (this.__hasInitializedEvents) {
return;
}
header.appendChild(this._initDisableAll());
this.__hasInitializedEvents = true;
await this._buildThemesList();
Services.prefs.addObserver(this.updatePref, this);
const checkForUpdateClick = (event) => {
if (event.target === checkForUpdates) {
event.preventDefault();
this._checkForThemeUpdates(event);
}
};
checkForUpdates.addEventListener('click', checkForUpdateClick);
document.addEventListener('ZenThemeMarketplace:CheckForUpdatesFinished', (event) => {
checkForUpdates.disabled = false;
const updates = event.detail.updates;
const success = document.getElementById('zenThemeMarketplaceUpdatesSuccess');
const error = document.getElementById('zenThemeMarketplaceUpdatesFailure');
if (updates) {
success.hidden = false;
error.hidden = true;
} else {
success.hidden = true;
error.hidden = false;
}
});
window.addEventListener('unload', () => {
Services.prefs.removeObserver(this.updatePref, this);
this.__hasInitializedEvents = false;
document.removeEventListener('ZenThemeMarketplace:CheckForUpdatesFinished', this);
document.removeEventListener('ZenCheckForThemeUpdates', this);
checkForUpdates.removeEventListener('click', checkForUpdateClick);
this.themesList.innerHTML = '';
this._doNotRebuildThemesList = false;
});
},
_initDisableAll() {
const areThemesDisabled = Services.prefs.getBoolPref('zen.themes.disable-all', false);
const browser = ZenThemesCommon.currentBrowser;
const mozToggle = document.createElement('moz-toggle');
mozToggle.className = 'zenThemeMarketplaceItemPreferenceToggle zenThemeMarketplaceDisableAllToggle';
mozToggle.pressed = !areThemesDisabled;
browser.document.l10n.setAttributes(mozToggle, `zen-theme-disable-all-${!areThemesDisabled ? 'enabled' : 'disabled'}`);
mozToggle.addEventListener('toggle', async (event) => {
const { pressed = false } = event.target || {};
this.themesList.style.display = pressed ? '' : 'none';
Services.prefs.setBoolPref('zen.themes.disable-all', !pressed);
browser.document.l10n.setAttributes(mozToggle, `zen-theme-disable-all-${pressed ? 'enabled' : 'disabled'}`);
});
if (areThemesDisabled) {
this.themesList.style.display = 'none';
}
return mozToggle;
},
async observe() {
ZenThemesCommon.resetThemesCache();
await this._buildThemesList();
},
_checkForThemeUpdates(event) {
// Send a message to the child to check for theme updates.
event.target.disabled = true;
// send an event that will be listened by the child process.
document.dispatchEvent(new CustomEvent('ZenCheckForThemeUpdates'));
},
get updatePref() {
return 'zen.themes.updated-value-observer';
},
triggerThemeUpdate() {
Services.prefs.setBoolPref(this.updatePref, !Services.prefs.getBoolPref(this.updatePref));
},
get themesList() {
if (!this._themesList) {
this._themesList = document.getElementById('zenThemeMarketplaceList');
}
return this._themesList;
},
async removeTheme(themeId) {
const themePath = ZenThemesCommon.getThemeFolder(themeId);
console.info(`[ZenThemeMarketplaceParent:settings]: Removing theme ${themePath}`);
await IOUtils.remove(themePath, { recursive: true, ignoreAbsent: true });
const themes = await ZenThemesCommon.getThemes();
delete themes[themeId];
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
this.triggerThemeUpdate();
},
async disableTheme(themeId) {
const themes = await ZenThemesCommon.getThemes();
const theme = themes[themeId];
console.log(`[ZenThemeMarketplaceParent:settings]: Disabling theme ${theme.name}`);
theme.enabled = false;
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
this._doNotRebuildThemesList = true;
this.triggerThemeUpdate();
},
async enableTheme(themeId) {
const themes = await ZenThemesCommon.getThemes();
const theme = themes[themeId];
console.log(`[ZenThemeMarketplaceParent:settings]: Enabling theme ${theme.name}`);
theme.enabled = true;
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
this._doNotRebuildThemesList = true;
this.triggerThemeUpdate();
},
_triggerBuildUpdateWithoutRebuild() {
this._doNotRebuildThemesList = true;
this.triggerThemeUpdate();
},
async _buildThemesList() {
if (!this.themesList) {
return;
}
if (this._doNotRebuildThemesList) {
this._doNotRebuildThemesList = false;
return;
}
const themes = await ZenThemesCommon.getThemes();
const browser = ZenMultiWindowFeature.currentBrowser;
const themeList = document.createElement('div');
for (const theme of Object.values(themes)) {
const sanitizedName = `theme-${theme.name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-z_-]+/g, '')}`;
const isThemeEnabled = theme.enabled === undefined || theme.enabled;
const fragment = window.MozXULElement.parseXULToFragment(`
<vbox class="zenThemeMarketplaceItem">
<vbox class="zenThemeMarketplaceItemContent">
<hbox flex="1" id="zenThemeMarketplaceItemContentHeader">
<label><h3 class="zenThemeMarketplaceItemTitle"></h3></label>
</hbox>
<description class="description-deemphasized zenThemeMarketplaceItemDescription"></description>
</vbox>
<hbox class="zenThemeMarketplaceItemActions">
${theme.preferences ? `<button id="zenThemeMarketplaceItemConfigureButton-${sanitizedName}" class="zenThemeMarketplaceItemConfigureButton" hidden="true"></button>` : ''}
<button class="zenThemeMarketplaceItemUninstallButton" data-l10n-id="zen-theme-marketplace-remove-button" zen-theme-id="${theme.id}"></button>
</hbox>
</vbox>
`);
const themeName = `${theme.name} (v${theme.version || '1.0.0'})`;
const base = fragment.querySelector('.zenThemeMarketplaceItem');
const baseHeader = fragment.querySelector('#zenThemeMarketplaceItemContentHeader');
const dialog = document.createElement('dialog');
const mainDialogDiv = document.createElement('div');
const headerDiv = document.createElement('div');
const headerTitle = document.createElement('h3');
const closeButton = document.createElement('button');
const contentDiv = document.createElement('div');
const mozToggle = document.createElement('moz-toggle');
mainDialogDiv.className = 'zenThemeMarketplaceItemPreferenceDialog';
headerDiv.className = 'zenThemeMarketplaceItemPreferenceDialogTopBar';
headerTitle.textContent = themeName;
browser.document.l10n.setAttributes(headerTitle, 'zen-theme-marketplace-theme-header-title', {
name: sanitizedName,
});
headerTitle.className = 'zenThemeMarketplaceItemTitle';
closeButton.id = `${sanitizedName}-modal-close`;
browser.document.l10n.setAttributes(closeButton, 'zen-theme-marketplace-close-modal');
contentDiv.id = `${sanitizedName}-preferences-content`;
contentDiv.className = 'zenThemeMarketplaceItemPreferenceDialogContent';
mozToggle.className = 'zenThemeMarketplaceItemPreferenceToggle';
mozToggle.pressed = isThemeEnabled;
browser.document.l10n.setAttributes(
mozToggle,
`zen-theme-marketplace-toggle-${isThemeEnabled ? 'enabled' : 'disabled'}-button`
);
baseHeader.appendChild(mozToggle);
headerDiv.appendChild(headerTitle);
headerDiv.appendChild(closeButton);
mainDialogDiv.appendChild(headerDiv);
mainDialogDiv.appendChild(contentDiv);
dialog.appendChild(mainDialogDiv);
base.appendChild(dialog);
closeButton.addEventListener('click', () => {
dialog.close();
});
mozToggle.addEventListener('toggle', async (event) => {
const themeId = event.target
.closest('.zenThemeMarketplaceItem')
.querySelector('.zenThemeMarketplaceItemUninstallButton')
.getAttribute('zen-theme-id');
event.target.setAttribute('disabled', true);
if (!event.target.hasAttribute('pressed')) {
await this.disableTheme(themeId);
browser.document.l10n.setAttributes(mozToggle, 'zen-theme-marketplace-toggle-disabled-button');
if (theme.preferences) {
document.getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`).setAttribute('hidden', true);
}
} else {
await this.enableTheme(themeId);
browser.document.l10n.setAttributes(mozToggle, 'zen-theme-marketplace-toggle-enabled-button');
if (theme.preferences) {
document.getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`).removeAttribute('hidden');
}
}
setTimeout(() => {
// We use a timeout to make sure the theme list has been updated before re-enabling the button.
event.target.removeAttribute('disabled');
}, 400);
});
fragment.querySelector('.zenThemeMarketplaceItemTitle').textContent = themeName;
fragment.querySelector('.zenThemeMarketplaceItemDescription').textContent = theme.description;
fragment.querySelector('.zenThemeMarketplaceItemUninstallButton').addEventListener('click', async (event) => {
const [msg] = await document.l10n.formatValues([{ id: 'zen-theme-marketplace-remove-confirmation' }]);
if (!confirm(msg)) {
return;
}
await this.removeTheme(event.target.getAttribute('zen-theme-id'));
});
if (theme.preferences) {
fragment.querySelector('.zenThemeMarketplaceItemConfigureButton').addEventListener('click', () => {
dialog.showModal();
});
if (isThemeEnabled) {
fragment.querySelector('.zenThemeMarketplaceItemConfigureButton').removeAttribute('hidden');
}
}
const preferences = await ZenThemesCommon.getThemePreferences(theme);
if (preferences.length > 0) {
const preferencesWrapper = document.createXULElement('vbox');
preferencesWrapper.setAttribute('flex', '1');
for (const entry of preferences) {
const { property, label, type, placeholder } = entry;
switch (type) {
case 'dropdown': {
const { options } = entry;
const container = document.createXULElement('hbox');
container.classList.add('zenThemeMarketplaceItemPreference');
container.setAttribute('align', 'center');
container.setAttribute('role', 'group');
const menulist = document.createXULElement('menulist');
const menupopup = document.createXULElement('menupopup');
menulist.setAttribute('sizetopopup', 'none');
menulist.setAttribute('id', property + '-popup-menulist');
const savedValue = Services.prefs.getStringPref(property, 'none');
menulist.setAttribute('value', savedValue);
menulist.setAttribute('tooltiptext', property);
const defaultItem = document.createXULElement('menuitem');
defaultItem.setAttribute('value', 'none');
if (placeholder) {
defaultItem.setAttribute('label', placeholder || '-');
} else {
browser.document.l10n.setAttributes(defaultItem, 'zen-theme-marketplace-dropdown-default-label');
}
menupopup.appendChild(defaultItem);
for (const option of options) {
const { label, value } = option;
const valueType = typeof value;
if (!['string', 'number'].includes(valueType)) {
console.log(
`[ZenThemeMarketplaceParent:settings]: Warning, invalid data type received (${valueType}), skipping.`
);
continue;
}
const menuitem = document.createXULElement('menuitem');
menuitem.setAttribute('value', value.toString());
menuitem.setAttribute('label', label);
menupopup.appendChild(menuitem);
}
menulist.appendChild(menupopup);
menulist.addEventListener('command', () => {
const value = menulist.selectedItem.value;
let element = browser.document.getElementById(sanitizedName);
if (!element) {
element = browser.document.createElement('div');
element.style.display = 'none';
element.setAttribute('id', sanitizedName);
browser.document.body.appendChild(element);
}
element.setAttribute(property?.replaceAll(/\./g, '-'), value);
Services.prefs.setStringPref(property, value === 'none' ? '' : value);
this._triggerBuildUpdateWithoutRebuild();
});
const nameLabel = document.createXULElement('label');
nameLabel.setAttribute('flex', '1');
nameLabel.setAttribute('class', 'zenThemeMarketplaceItemPreferenceLabel');
nameLabel.setAttribute('value', label);
nameLabel.setAttribute('tooltiptext', property);
container.appendChild(nameLabel);
container.appendChild(menulist);
container.setAttribute('aria-labelledby', label);
preferencesWrapper.appendChild(container);
break;
}
case 'checkbox': {
const checkbox = window.MozXULElement.parseXULToFragment(`
<hbox class="zenThemeMarketplaceItemPreference">
<checkbox class="zenThemeMarketplaceItemPreferenceCheckbox"></checkbox>
</hbox>
`);
const checkboxElement = checkbox.querySelector('.zenThemeMarketplaceItemPreferenceCheckbox');
checkboxElement.setAttribute('label', label);
checkboxElement.setAttribute('tooltiptext', property);
checkboxElement.setAttribute('zen-pref', property);
// Checkbox only works with "true" and "false" values, it's not like HTML checkboxes.
if (Services.prefs.getBoolPref(property, false)) {
checkboxElement.setAttribute('checked', 'true');
}
checkboxElement.addEventListener('click', (event) => {
const target = event.target.closest('.zenThemeMarketplaceItemPreferenceCheckbox');
const key = target.getAttribute('zen-pref');
const checked = target.hasAttribute('checked');
if (!checked) {
target.removeAttribute('checked');
} else {
target.setAttribute('checked', 'true');
}
Services.prefs.setBoolPref(key, !checked);
});
preferencesWrapper.appendChild(checkbox);
break;
}
case 'string': {
const container = document.createXULElement('hbox');
container.classList.add('zenThemeMarketplaceItemPreference');
container.setAttribute('align', 'center');
container.setAttribute('role', 'group');
const savedValue = Services.prefs.getStringPref(property, '');
const sanitizedProperty = property?.replaceAll(/\./g, '-');
const input = document.createElement('input');
input.setAttribute('flex', '1');
input.setAttribute('type', 'text');
input.id = `${sanitizedProperty}-input`;
input.value = savedValue;
if (placeholder) {
input.setAttribute('placeholder', placeholder || '-');
} else {
browser.document.l10n.setAttributes(input, 'zen-theme-marketplace-input-default-placeholder');
}
input.addEventListener(
'change',
ZenThemesCommon.throttle((event) => {
const value = event.target.value;
Services.prefs.setStringPref(property, value);
this._triggerBuildUpdateWithoutRebuild();
if (value === '') {
browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`);
} else {
browser.document.querySelector(':root').style.setProperty(`--${sanitizedProperty}`, value);
}
}, 500)
);
const nameLabel = document.createXULElement('label');
nameLabel.setAttribute('flex', '1');
nameLabel.setAttribute('class', 'zenThemeMarketplaceItemPreferenceLabel');
nameLabel.setAttribute('value', label);
nameLabel.setAttribute('tooltiptext', property);
container.appendChild(nameLabel);
container.appendChild(input);
container.setAttribute('aria-labelledby', label);
preferencesWrapper.appendChild(container);
break;
}
default:
console.log(
`[ZenThemeMarketplaceParent:settings]: Warning, unknown preference type received (${type}), skipping.`
);
continue;
}
}
contentDiv.appendChild(preferencesWrapper);
}
themeList.appendChild(fragment);
}
this.themesList.replaceChildren(...themeList.children);
themeList.remove();
},
};
const kZenExtendedSidebar = 'zen.view.sidebar-expanded';
const kZenSingleToolbar = 'zen.view.use-single-toolbar';
var gZenLooksAndFeel = {
init() {
if (this.__hasInitialized) return;
this.__hasInitialized = true;
this._initializeColorPicker(this._getInitialAccentColor());
window.zenPageAccentColorChanged = this._handleAccentColorChange.bind(this);
gZenThemeBuilder.init();
gZenMarketplaceManager.init();
var onPreferColorSchemeChange = this.onPreferColorSchemeChange.bind(this);
window.matchMedia('(prefers-color-scheme: dark)').addListener(onPreferColorSchemeChange);
for (const pref of [kZenExtendedSidebar, kZenSingleToolbar]) {
Services.prefs.addObserver(pref, this);
}
this.onPreferColorSchemeChange();
window.addEventListener('unload', () => {
window.matchMedia('(prefers-color-scheme: dark)').removeListener(onPreferColorSchemeChange);
for (const pref of [kZenExtendedSidebar, kZenSingleToolbar]) {
Services.prefs.removeObserver(pref, this);
}
});
this.setDarkThemeListener();
this.setCompactModeStyle();
this.applySidebarLayout();
},
observe(subject, topic, data) {
this.applySidebarLayout();
},
applySidebarLayout() {
const isSingleToolbar = Services.prefs.getBoolPref(kZenSingleToolbar);
const isExtendedSidebar = Services.prefs.getBoolPref(kZenExtendedSidebar);
for (const layout of document.getElementById('zenLayoutList').children) {
layout.classList.remove('selected');
if (layout.getAttribute('layout') == 'single' && isSingleToolbar) {
layout.classList.add('selected');
} else if (layout.getAttribute('layout') == 'multiple' && !isSingleToolbar && isExtendedSidebar) {
layout.classList.add('selected');
} else if (layout.getAttribute('layout') == 'collapsed' && !isExtendedSidebar) {
layout.classList.add('selected');
}
}
if (this.__hasInitializedLayout) return;
this.__hasInitializedLayout = true;
for (const layout of document.getElementById('zenLayoutList').children) {
layout.addEventListener('click', () => {
if (layout.hasAttribute('disabled')) {
return;
}
for (const el of document.getElementById('zenLayoutList').children) {
el.classList.remove('selected');
}
layout.classList.add('selected');
Services.prefs.setBoolPref(kZenExtendedSidebar, layout.getAttribute('layout') != 'collapsed');
Services.prefs.setBoolPref(kZenSingleToolbar, layout.getAttribute('layout') == 'single');
});
}
},
onPreferColorSchemeChange(event) {
const darkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
let elem = document.getElementById('ZenDarkThemeStyles');
if (darkTheme) {
elem.removeAttribute('hidden');
} else {
elem.setAttribute('hidden', 'true');
}
},
setDarkThemeListener() {
const chooser = document.getElementById('zen-dark-theme-styles-form');
const radios = [...chooser.querySelectorAll('input')];
for (let radio of radios) {
if (radio.value === 'amoled' && Services.prefs.getBoolPref('zen.theme.color-prefs.amoled')) {
radio.checked = true;
} else if (radio.value === 'colorful' && Services.prefs.getBoolPref('zen.theme.color-prefs.colorful')) {
radio.checked = true;
} else if (
radio.value === 'default' &&
!Services.prefs.getBoolPref('zen.theme.color-prefs.amoled') &&
!Services.prefs.getBoolPref('zen.theme.color-prefs.colorful')
) {
radio.checked = true;
}
radio.addEventListener('change', (e) => {
let value = e.target.value;
switch (value) {
case 'amoled':
Services.prefs.setBoolPref('zen.theme.color-prefs.amoled', true);
Services.prefs.setBoolPref('zen.theme.color-prefs.colorful', false);
break;
case 'colorful':
Services.prefs.setBoolPref('zen.theme.color-prefs.amoled', false);
Services.prefs.setBoolPref('zen.theme.color-prefs.colorful', true);
break;
default:
Services.prefs.setBoolPref('zen.theme.color-prefs.amoled', false);
Services.prefs.setBoolPref('zen.theme.color-prefs.colorful', false);
break;
}
});
}
},
setCompactModeStyle() {
const chooser = document.getElementById('zen-compact-mode-styles-form');
const radios = [...chooser.querySelectorAll('input')];
let value = '';
if (
Services.prefs.getBoolPref('zen.view.compact.hide-tabbar') &&
Services.prefs.getBoolPref('zen.view.compact.hide-toolbar')
) {
value = 'both';
} else {
value = Services.prefs.getBoolPref('zen.view.compact.hide-tabbar') ? 'left' : 'top';
}
chooser.querySelector(`[value='${value}']`).checked = true;
for (let radio of radios) {
radio.addEventListener('change', (e) => {
let value = e.target.value;
switch (value) {
case 'left':
Services.prefs.setBoolPref('zen.view.compact.hide-tabbar', true);
Services.prefs.setBoolPref('zen.view.compact.hide-toolbar', false);
break;
case 'top':
Services.prefs.setBoolPref('zen.view.compact.hide-tabbar', false);
Services.prefs.setBoolPref('zen.view.compact.hide-toolbar', true);
break;
default:
Services.prefs.setBoolPref('zen.view.compact.hide-tabbar', true);
Services.prefs.setBoolPref('zen.view.compact.hide-toolbar', true);
break;
}
});
}
},
_initializeColorPicker(accentColor) {
let elem = document.getElementById('zenLooksAndFeelColorOptions');
elem.innerHTML = '';
for (let color of ZenThemesCommon.kZenColors) {
let colorElemParen = document.createElement('div');
let colorElem = document.createElement('div');
colorElemParen.classList.add('zenLooksAndFeelColorOptionParen');
colorElem.classList.add('zenLooksAndFeelColorOption');
colorElem.style.setProperty('--zen-primary-color', color, 'important');
if (accentColor === color) {
colorElemParen.setAttribute('selected', 'true');
}
colorElemParen.addEventListener('click', () => {
Services.prefs.setBoolPref('zen.theme.color-prefs.use-workspace-colors', false);
Services.prefs.setStringPref('zen.theme.accent-color', color);
});
colorElemParen.appendChild(colorElem);
elem.appendChild(colorElemParen);
}
// TODO: add custom color selection!
},
_handleAccentColorChange(accentColor) {
this._initializeColorPicker(accentColor);
},
_getInitialAccentColor() {
return Services.prefs.getStringPref('zen.theme.accent-color', ZenThemesCommon.kZenColors[0]);
},
};
var gZenWorkspacesSettings = {
init() {
var tabsUnloaderPrefListener = {
async observe(subject, topic, data) {
let buttonIndex = await confirmRestartPrompt(true, 1, true, true);
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
}
},
};
Services.prefs.addObserver('zen.workspaces.enabled', this);
Services.prefs.addObserver('zen.tab-unloader.enabled', tabsUnloaderPrefListener);
Services.prefs.addObserver('zen.glance.enabled', tabsUnloaderPrefListener); // We can use the same listener for both prefs
Services.prefs.addObserver('zen.glance.activation-method', tabsUnloaderPrefListener);
window.addEventListener('unload', () => {
Services.prefs.removeObserver('zen.workspaces.enabled', this);
Services.prefs.removeObserver('zen.tab-unloader.enabled', tabsUnloaderPrefListener);
Services.prefs.removeObserver('zen.glance.enabled', tabsUnloaderPrefListener);
Services.prefs.removeObserver('zen.glance.activation-method', tabsUnloaderPrefListener);
});
},
async observe(subject, topic, data) {
await this.onWorkspaceChange(Services.prefs.getBoolPref('zen.workspaces.enabled'));
},
async onWorkspaceChange(checked) {
let buttonIndex = await confirmRestartPrompt(true, 1, true, false);
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
return;
}
},
};
const ZEN_CKS_CLASS_BASE = 'zenCKSOption';
const ZEN_CKS_INPUT_FIELD_CLASS = `${ZEN_CKS_CLASS_BASE}-input`;
const ZEN_CKS_LABEL_CLASS = `${ZEN_CKS_CLASS_BASE}-label`;
const ZEN_CKS_WRAPPER_ID = `${ZEN_CKS_CLASS_BASE}-wrapper`;
const ZEN_CKS_GROUP_PREFIX = `${ZEN_CKS_CLASS_BASE}-group`;
const KEYBIND_ATTRIBUTE_KEY = 'key';
var zenMissingKeyboardShortcutL10n = {
key_quickRestart: 'zen-key-quick-restart',
key_delete: 'zen-key-delete',
goBackKb: 'zen-key-go-back',
goForwardKb: 'zen-key-go-forward',
key_enterFullScreen: 'zen-key-enter-full-screen',
key_exitFullScreen: 'zen-key-exit-full-screen',
key_aboutProcesses: 'zen-key-about-processes',
key_stop: 'zen-key-stop',
key_sanitize: 'zen-key-sanitize',
key_wrCaptureCmd: 'zen-key-wr-capture-cmd',
key_wrToggleCaptureSequenceCmd: 'zen-key-wr-toggle-capture-sequence-cmd',
key_undoCloseWindow: 'zen-key-undo-close-window',
key_selectTab1: 'zen-key-select-tab-1',
key_selectTab2: 'zen-key-select-tab-2',
key_selectTab3: 'zen-key-select-tab-3',
key_selectTab4: 'zen-key-select-tab-4',
key_selectTab5: 'zen-key-select-tab-5',
key_selectTab6: 'zen-key-select-tab-6',
key_selectTab7: 'zen-key-select-tab-7',
key_selectTab8: 'zen-key-select-tab-8',
key_selectLastTab: 'zen-key-select-tab-last',
key_showAllTabs: 'zen-key-show-all-tabs',
key_gotoHistory: 'zen-key-goto-history',
goHome: 'zen-key-go-home',
key_redo: 'zen-key-redo',
// Devtools
key_toggleToolbox: 'zen-devtools-toggle-shortcut',
key_browserToolbox: 'zen-devtools-toggle-browser-toolbox-shortcut',
key_browserConsole: 'zen-devtools-toggle-browser-console-shortcut',
key_responsiveDesignMode: 'zen-devtools-toggle-responsive-design-mode-shortcut',
key_inspector: 'zen-devtools-toggle-inspector-shortcut',
key_webconsole: 'zen-devtools-toggle-web-console-shortcut',
key_jsdebugger: 'zen-devtools-toggle-js-debugger-shortcut',
key_netmonitor: 'zen-devtools-toggle-net-monitor-shortcut',
key_styleeditor: 'zen-devtools-toggle-style-editor-shortcut',
key_performance: 'zen-devtools-toggle-performance-shortcut',
key_storage: 'zen-devtools-toggle-storage-shortcut',
key_dom: 'zen-devtools-toggle-dom-shortcut',
key_accessibility: 'zen-devtools-toggle-accessibility-shortcut',
};
var gZenCKSSettings = {
async init() {
await this._initializeCKS();
if (this.__hasInitialized) return;
this.__hasInitialized = true;
this._currentActionID = null;
this._initializeEvents();
window.addEventListener('unload', () => {
this.__hasInitialized = false;
document.getElementById(ZEN_CKS_WRAPPER_ID).innerHTML = '';
});
},
_initializeEvents() {
const resetAllListener = this.resetAllShortcuts.bind(this);
const handleKeyDown = this._handleKeyDown.bind(this);
window.addEventListener('keydown', handleKeyDown);
const button = document.getElementById('zenCKSResetButton');
button.addEventListener('click', resetAllListener);
window.addEventListener('unload', () => {
window.removeEventListener('keydown', handleKeyDown);
button.removeEventListener('click', resetAllListener);
});
},
async resetAllShortcuts() {
let buttonIndex = await confirmRestartPrompt(true, 1, true, false);
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
await gZenKeyboardShortcutsManager.resetAllShortcuts();
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
}
},
async _initializeCKS() {
let wrapper = document.getElementById(ZEN_CKS_WRAPPER_ID);
wrapper.innerHTML = '';
let shortcuts = await gZenKeyboardShortcutsManager.getModifiableShortcuts();
if (!shortcuts) {
throw Error('No shortcuts defined!');
}
// Generate section per each group
for (let group of VALID_SHORTCUT_GROUPS) {
let groupClass = `${ZEN_CKS_GROUP_PREFIX}-${group}`;
if (!wrapper.querySelector(`[data-group="${groupClass}"]`)) {
let groupElem = document.createElement('h2');
groupElem.setAttribute('data-group', groupClass);
document.l10n.setAttributes(groupElem, groupClass);
wrapper.appendChild(groupElem);
}
}
for (let shortcut of shortcuts) {
const keyID = shortcut.getID();
const action = shortcut.getAction();
const l10nID = shortcut.getL10NID();
const group = shortcut.getGroup();
const keyInString = shortcut.toUserString();
const labelValue = zenMissingKeyboardShortcutL10n[keyID] ?? l10nID;
let fragment = window.MozXULElement.parseXULToFragment(`
<hbox class="${ZEN_CKS_CLASS_BASE}">
<label class="${ZEN_CKS_LABEL_CLASS}" for="${ZEN_CKS_CLASS_BASE}-${keyID}"></label>
<vbox flex="1">
<html:input readonly="1" class="${ZEN_CKS_INPUT_FIELD_CLASS}" id="${ZEN_CKS_INPUT_FIELD_CLASS}-${keyID}" />
</vbox>
</hbox>
`);
const label = fragment.querySelector(`.${ZEN_CKS_LABEL_CLASS}`);
if (!labelValue) {
label.textContent = action; // Just in case
} else {
document.l10n.setAttributes(label, labelValue);
}
let input = fragment.querySelector(`.${ZEN_CKS_INPUT_FIELD_CLASS}`);
if (keyInString && !shortcut.isEmpty()) {
input.value = keyInString;
} else {
this._resetShortcut(input);
}
input.setAttribute(KEYBIND_ATTRIBUTE_KEY, keyID);
input.setAttribute('data-group', group);
input.setAttribute('data-id', keyID);
input.addEventListener('focus', (event) => {
const value = event.target.getAttribute(KEYBIND_ATTRIBUTE_KEY);
this._currentActionID = event.target.getAttribute('data-id');
event.target.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
this._hasSafed = true;
});
input.addEventListener('editDone', (event) => {
const target = event.target;
target.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
});
input.addEventListener('blur', (event) => {
const target = event.target;
target.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
if (!this._hasSafed) {
target.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-unsafed`);
if (!target.nextElementSibling) {
target.after(
window.MozXULElement.parseXULToFragment(`
<label class="${ZEN_CKS_CLASS_BASE}-unsafed" data-l10n-id="zen-key-unsafed"></label>
`)
);
target.value = 'Not set';
}
} else {
target.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-unsafed`);
const sibling = target.nextElementSibling;
if (sibling && sibling.classList.contains(`${ZEN_CKS_CLASS_BASE}-unsafed`)) {
sibling.remove();
}
}
});
const groupElem = wrapper.querySelector(`[data-group="${ZEN_CKS_GROUP_PREFIX}-${group}"]`);
groupElem.after(fragment);
}
},
async _resetShortcut(input) {
input.value = 'Not set';
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-invalid`);
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
input.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`);
if (this._currentActionID) {
this._editDone();
await gZenKeyboardShortcutsManager.setShortcut(this._currentActionID, null, null);
}
},
_editDone(shortcut, modifiers) {
// Check if we have a valid key
if (!shortcut || !modifiers) {
return;
}
gZenKeyboardShortcutsManager.setShortcut(this._currentActionID, shortcut, modifiers);
this._currentActionID = null;
},
//TODO Check for duplicates
async _handleKeyDown(event) {
if (!this._currentActionID || document.hidden) {
return;
}
event.preventDefault();
let input = document.querySelector(`.${ZEN_CKS_INPUT_FIELD_CLASS}[${KEYBIND_ATTRIBUTE_KEY}="${this._currentActionID}"]`);
const modifiers = new KeyShortcutModifiers(event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, false);
const modifiersActive = modifiers.areAnyActive();
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`);
// This is because on some OSs (windows/macos mostly) the key is not the same as the keycode
// e.g. CTRL+ALT+3 may be displayed as the euro sign
let shortcut = event.key;
shortcut = shortcut.replace(/Ctrl|Control|Shift|Alt|Option|Cmd|Meta/, ''); // Remove all modifiers
if (shortcut == 'Tab' && !modifiersActive) {
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
this._latestValidKey = null;
return;
} else if (shortcut == 'Escape' && !modifiersActive) {
const hasConflicts = gZenKeyboardShortcutsManager.checkForConflicts(
this._latestValidKey ? this._latestValidKey : shortcut,
this._latestModifier ? this._latestModifier : modifiers,
this._currentActionID
);
if (!this._latestValidKey && !this._latestModifier) {
} else if (!this._latestValidKey || hasConflicts) {
if (!input.classList.contains(`${ZEN_CKS_INPUT_FIELD_CLASS}-invalid`)) {
input.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-invalid`);
}
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-unsafed`);
if (hasConflicts && !input.nextElementSibling) {
input.after(
window.MozXULElement.parseXULToFragment(`
<label class="${ZEN_CKS_CLASS_BASE}-conflict" data-l10n-id="zen-key-conflict"></label>
`)
);
}
} else {
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
this._editDone(this._latestValidKey, this._latestModifier);
this._latestValidKey = null;
this._latestModifier = null;
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-invalid`);
input.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-valid`);
setTimeout(() => {
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-valid`);
}, 1000);
const sibling = input.nextElementSibling;
if (sibling && sibling.classList.contains(`${ZEN_CKS_CLASS_BASE}-conflict`)) {
sibling.remove();
}
}
this._hasSafed = true;
input.blur();
this._currentActionID = null;
return;
} else if (shortcut == 'Backspace' && !modifiersActive) {
this._resetShortcut(input);
this._latestValidKey = null;
this._latestModifier = null;
this._hasSafed = true;
return;
}
this._latestModifier = modifiers;
this._hasSafed = false;
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-invalid`);
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`);
input.value = modifiers.toUserString() + shortcut;
this._latestValidKey = shortcut;
},
};
Preferences.addAll([
{
id: 'zen.sidebar.enabled',
type: 'bool',
default: true,
},
{
id: 'zen.sidebar.close-on-blur',
type: 'bool',
default: true,
},
{
id: 'zen.view.compact',
type: 'bool',
default: false,
},
{
id: 'zen.view.compact.hide-toolbar',
type: 'bool',
default: false,
},
{
id: 'zen.view.compact.toolbar-flash-popup',
type: 'bool',
default: true,
},
{
id: 'zen.workspaces.enabled',
type: 'bool',
default: true,
},
{
id: 'zen.theme.pill-button',
type: 'bool',
default: true,
},
{
id: 'zen.workspaces.hide-default-container-indicator',
type: 'bool',
default: true,
},
{
id: 'zen.workspaces.individual-pinned-tabs',
type: 'bool',
default: true,
},
{
id: 'zen.workspaces.show-icon-strip',
type: 'bool',
default: true,
},
{
id: 'zen.tab-unloader.enabled',
type: 'bool',
default: true,
},
{
id: 'zen.splitView.change-on-hover',
type: 'bool',
default: true,
},
{
id: 'zen.tab-unloader.timeout-minutes',
type: 'int',
default: 10,
},
{
id: 'zen.view.show-bottom-border',
type: 'bool',
default: false,
},
{
id: 'zen.workspaces.hide-deactivated-workspaces',
type: 'bool',
default: true,
},
{
id: 'zen.pinned-tab-manager.restore-pinned-tabs-to-pinned-url',
type: 'bool',
default: true,
},
{
id: 'zen.pinned-tab-manager.close-shortcut-behavior',
type: 'string',
default: 'switch',
},
{
id: 'zen.workspaces.force-container-workspace',
type: 'bool',
default: true,
},
{
id: 'zen.workspaces.open-new-tab-if-last-unpinned-tab-is-closed',
type: 'bool',
default: true,
},
{
id: 'zen.tabs.show-newtab-under',
type: 'bool',
default: false,
},
{
id: 'zen.glance.activation-method',
type: 'string',
default: 'ctrl',
},
{
id: 'zen.glance.enabled',
type: 'bool',
default: true,
},
{
id: 'zen.theme.color-prefs.use-workspace-colors',
type: 'bool',
default: false,
},
{
id: 'zen.view.compact.color-toolbar',
type: 'bool',
default: true,
},
{
id: 'zen.urlbar.behavior',
type: 'string',
default: 'float',
},
{
id: 'zen.view.compact.color-sidebar',
type: 'bool',
default: true,
},
{
id: 'zen.essentials.enabled',
type: 'bool',
default: true,
},
{
id: 'zen.workspaces.container-specific-essentials-enabled',
type: 'bool',
default: false,
},
{
id: 'zen.tabs.show-newtab-vertical',
type: 'bool',
default: true,
},
{
id: 'zen.view.show-newtab-button-border-top',
type: 'bool',
default: false,
},
{
id: 'zen.view.show-newtab-button-top',
type: 'bool',
default: true,
},
]);