// 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; } if (!window.gZenMods) { window.gZenMods = ZenMultiWindowFeature.currentBrowser.gZenMods; } header.appendChild(this._initDisableAll()); this._initImportExport(); this.__hasInitializedEvents = true; await this._buildModsList(); Services.prefs.addObserver(gZenMods.updatePref, this); const checkForUpdateClick = (event) => { if (event.target === checkForUpdates) { event.preventDefault(); this._checkForThemeUpdates(event); } }; checkForUpdates.addEventListener('click', checkForUpdateClick); document.addEventListener('ZenModsMarketplace: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(gZenMods.updatePref, this); this.__hasInitializedEvents = false; document.removeEventListener('ZenModsMarketplace:CheckForUpdatesFinished', this); document.removeEventListener('ZenCheckForModUpdates', this); checkForUpdates.removeEventListener('click', checkForUpdateClick); this.modsList.innerHTML = ''; this._doNotRebuildModsList = false; }); }, _initImportExport() { const importButton = document.getElementById('zenThemeMarketplaceImport'); const exportButton = document.getElementById('zenThemeMarketplaceExport'); if (importButton) { importButton.addEventListener('click', this._importThemes.bind(this)); } if (exportButton) { exportButton.addEventListener('click', this._exportThemes.bind(this)); } }, _initDisableAll() { const areModsDisabled = Services.prefs.getBoolPref('zen.themes.disable-all', false); const browser = ZenMultiWindowFeature.currentBrowser; const mozToggle = document.createElement('moz-toggle'); mozToggle.className = 'zenThemeMarketplaceItemPreferenceToggle zenThemeMarketplaceDisableAllToggle'; mozToggle.pressed = !areModsDisabled; browser.document.l10n.setAttributes( mozToggle, `zen-theme-disable-all-${!areModsDisabled ? 'enabled' : 'disabled'}` ); mozToggle.addEventListener('toggle', async (event) => { const { pressed = false } = event.target || {}; this.modsList.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 (areModsDisabled) { this.modsList.style.display = 'none'; } return mozToggle; }, async observe() { await this._buildModsList(); }, _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('ZenCheckForModUpdates')); }, get modsList() { if (!this._modsList) { this._modsList = document.getElementById('zenThemeMarketplaceList'); } return this._modsList; }, _triggerBuildUpdateWithoutRebuild() { this._doNotRebuildModsList = true; gZenMods.triggerModsUpdate(); }, async removeMod(modId) { await gZenMods.removeMod(modId); gZenMods.triggerModsUpdate(); }, async disableMod(modId) { await gZenMods.disableMod(modId); this._triggerBuildUpdateWithoutRebuild(); }, async enableMod(modId) { await gZenMods.enableMod(modId); this._triggerBuildUpdateWithoutRebuild(); }, async _importThemes() { const errorBox = document.getElementById('zenThemeMarketplaceImportFailure'); const successBox = document.getElementById('zenThemeMarketplaceImportSuccess'); successBox.hidden = true; errorBox.hidden = true; const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.style.display = 'none'; input.setAttribute('moz-accept', '.json'); input.setAttribute('accept', '.json'); let timeout; const filePromise = new Promise((resolve) => { input.addEventListener('change', (event) => { if (timeout) { clearTimeout(timeout); } const file = event.target.files[0]; resolve(file); }); timeout = setTimeout(() => { console.warn('[ZenSettings:ZenMods]: Import timeout reached, aborting.'); resolve(null); }, 60000); }); input.addEventListener('cancel', () => { console.warn('[ZenSettings:ZenMods]: Import cancelled by user.'); clearTimeout(timeout); }); input.click(); try { const file = await filePromise; if (!file) { return; } const content = await file.text(); const mods = JSON.parse(content); for (const mod of Object.values(mods)) { mod.modId = mod.id; await window.ZenInstallMod(mod); } } catch (error) { console.error('[ZenSettings:ZenMods]: Error while importing mods:', error); errorBox.hidden = false; } if (input) { input.remove(); } }, async _exportThemes() { const errorBox = document.getElementById('zenThemeMarketplaceExportFailure'); const successBox = document.getElementById('zenThemeMarketplaceExportSuccess'); successBox.hidden = true; errorBox.hidden = true; let temporalAnchor, temporalUrl; try { const mods = await gZenMods.getMods(); const modsJson = JSON.stringify(mods, null, 2); const blob = new Blob([modsJson], { type: 'application/json' }); temporalUrl = URL.createObjectURL(blob); // Creating a link to download the JSON file temporalAnchor = document.createElement('a'); temporalAnchor.href = temporalUrl; temporalAnchor.download = 'zen-mods-export.json'; document.body.appendChild(temporalAnchor); temporalAnchor.click(); temporalAnchor.remove(); successBox.hidden = false; } catch (error) { console.error('[ZenSettings:ZenMods]: Error while exporting mods:', error); errorBox.hidden = false; } if (temporalAnchor) { temporalAnchor.remove(); } if (temporalUrl) { URL.revokeObjectURL(temporalUrl); } }, async _buildModsList() { if (!this.modsList) { return; } if (this._doNotRebuildModsList) { this._doNotRebuildModsList = false; return; } const mods = await gZenMods.getMods(); const browser = ZenMultiWindowFeature.currentBrowser; const modList = document.createElement('div'); for (const mod of Object.values(mods).sort((a, b) => a.name.localeCompare(b.name))) { const sanitizedName = gZenMods.sanitizeModName(mod.name); const isModEnabled = mod.enabled === undefined || mod.enabled; const fragment = window.MozXULElement.parseXULToFragment(` ${mod.preferences ? `` : ''} ${mod.homepage ? `` : ''} `); const modName = `${mod.name} (v${mod.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 = modName; 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 = isModEnabled; browser.document.l10n.setAttributes( mozToggle, `zen-theme-marketplace-toggle-${isModEnabled ? '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 modId = event.target .closest('.zenThemeMarketplaceItem') .querySelector('.zenThemeMarketplaceItemUninstallButton') .getAttribute('zen-mod-id'); event.target.setAttribute('disabled', true); if (!event.target.hasAttribute('pressed')) { await this.disableMod(modId); browser.document.l10n.setAttributes( mozToggle, 'zen-theme-marketplace-toggle-disabled-button' ); if (mod.preferences) { document .getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`) .setAttribute('hidden', true); } } else { await this.enableMod(modId); browser.document.l10n.setAttributes( mozToggle, 'zen-theme-marketplace-toggle-enabled-button' ); if (mod.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 = modName; fragment.querySelector('.zenThemeMarketplaceItemDescription').textContent = mod.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.removeMod(event.target.getAttribute('zen-mod-id')); }); if (mod.homepage) { const homepageButton = fragment.querySelector('.zenThemeMarketplaceItemHomepageButton'); homepageButton.addEventListener('click', () => { // open the homepage url in a new tab const url = mod.homepage; window.open(url, '_blank'); }); } if (mod.preferences) { fragment .querySelector('.zenThemeMarketplaceItemConfigureButton') .addEventListener('click', () => { dialog.showModal(); }); if (isModEnabled) { fragment .querySelector('.zenThemeMarketplaceItemConfigureButton') .removeAttribute('hidden'); } } const preferences = await gZenMods.getModPreferences(mod); if (preferences.length > 0) { const preferencesWrapper = document.createXULElement('vbox'); preferencesWrapper.setAttribute('flex', '1'); for (const entry of preferences) { const { property, label, type, placeholder, defaultValue } = 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, defaultValue ?? '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( `[ZenSettings:ZenMods]: 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(` `); 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, defaultValue ?? 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, defaultValue ?? ''); 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', gZenMods.debounce((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( `[ZenSettings:ZenMods]: Warning, unknown preference type received (${type}), skipping.` ); continue; } } contentDiv.appendChild(preferencesWrapper); } modList.appendChild(fragment); } this.modsList.replaceChildren(...modList.children); modList.remove(); }, }; const kZenExtendedSidebar = 'zen.view.sidebar-expanded'; const kZenSingleToolbar = 'zen.view.use-single-toolbar'; var gZenLooksAndFeel = { init() { if (this.__hasInitialized) return; this.__hasInitialized = true; gZenMarketplaceManager.init(); for (const pref of [kZenExtendedSidebar, kZenSingleToolbar]) { Services.prefs.addObserver(pref, this); } window.addEventListener('unload', () => { for (const pref of [kZenExtendedSidebar, kZenSingleToolbar]) { Services.prefs.removeObserver(pref, this); } }); 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'); }); } }, 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', false) && Services.prefs.getBoolPref('zen.view.compact.hide-toolbar', false) ) { 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; } }); } }, }; 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.tab-unloader.enabled', tabsUnloaderPrefListener); Services.prefs.addObserver('zen.glance.enabled', tabsUnloaderPrefListener); // We can use the same listener for both prefs Services.prefs.addObserver('zen.workspaces.separate-essentials', tabsUnloaderPrefListener); Services.prefs.addObserver('zen.glance.activation-method', tabsUnloaderPrefListener); window.addEventListener('unload', () => { Services.prefs.removeObserver('zen.tab-unloader.enabled', tabsUnloaderPrefListener); Services.prefs.removeObserver('zen.glance.enabled', tabsUnloaderPrefListener); Services.prefs.removeObserver('zen.glance.activation-method', tabsUnloaderPrefListener); Services.prefs.removeObserver('zen.workspaces.separate-essentials', tabsUnloaderPrefListener); }); }, }; 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', key_inspectorMac: 'zen-key-inspector-mac', // 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 zenIgnoreKeyboardShortcutL10n = [ 'zen-full-zoom-reduce-shortcut-alt-b', 'zen-full-zoom-reduce-shortcut-alt-a', ]; 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; if (zenIgnoreKeyboardShortcutL10n.includes(labelValue)) { continue; } let fragment = window.MozXULElement.parseXULToFragment(` `); 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(` `) ); 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(); } } if (target.classList.contains(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`)) { target.label = 'Not set'; } }); 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 nsKeyShortcutModifiers( event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, false ); const modifiersActive = modifiers.areAnyActive(); input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`); // First, try to read the *physical* key via event.code. // If event.code is like "KeyS", "KeyA", ..., strip off "Key" → "S". // Otherwise, fall back to event.key (e.g. "F5", "Enter", etc.). let shortcut; if (event.code && event.code.startsWith('Key')) shortcut = event.code.slice(3); else 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}-not-set`); 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(` `) ); } } else { input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`); this._editDone(this._latestValidKey, this._latestModifier); if (this.name == 'Not set') { input.classList.add(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`); } 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; const sibling = input.nextElementSibling; if (sibling && sibling.classList.contains(`${ZEN_CKS_CLASS_BASE}-conflict`)) { sibling.remove(); } 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.view.compact.hide-toolbar', type: 'bool', default: false, }, { id: 'zen.view.compact.toolbar-flash-popup', type: 'bool', default: true, }, { id: 'zen.workspaces.hide-default-container-indicator', type: 'bool', default: true, }, { id: 'zen.tab-unloader.enabled', type: 'bool', default: true, }, { id: 'zen.tab-unloader.timeout-minutes', type: 'int', default: 10, }, { 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.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.workspaces.separate-essentials', 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, }, { id: 'media.videocontrols.picture-in-picture.enabled', type: 'bool', default: true, }, { id: 'zen.workspaces.continue-where-left-off', type: 'bool', default: false, }, { id: 'zen.mods.auto-update', type: 'bool', default: true, }, ]);