| import {getDocURL, getDocURLWithRef, getExtVersion, isProdVersion} from '../common/extUtils.js'; |
| import {ensureOptPermissions, grantedOptPermissions, isPermissionsObjectEmpty, missingPermissions} from '../common/optionsPermissions.js'; |
| import {cleanUpOptions, optionsPrototype, specialOptions} from '../common/optionsUtils.js'; |
| |
| import optionsPage from './optionsPage.json5'; |
| |
| var savedSuccessfullyTimeout = null; |
| |
| const exclusiveOptions = [['thread', 'threadall']]; |
| const kClickShouldEnableFeat = 'data-click-should-enable-feature'; |
| |
| // Get the value of the option set in the options/experiments page |
| function getOptionValue(opt) { |
| if (specialOptions.includes(opt)) { |
| switch (opt) { |
| case 'profileindicatoralt_months': |
| return document.getElementById(opt).value || 12; |
| |
| case 'ccdarktheme_mode': |
| return document.getElementById(opt).value || 'switch'; |
| |
| case 'interopthreadpage_mode': |
| return document.getElementById(opt).value || 'previous'; |
| |
| default: |
| console.warn('Unrecognized option: ' + opt); |
| return undefined; |
| } |
| } |
| |
| return document.getElementById(opt).checked || false; |
| } |
| |
| // Returns whether the option is included in the current context |
| function isOptionShown(opt) { |
| if (!optionsPrototype.hasOwnProperty(opt)) return false; |
| return optionsPrototype[opt].context == window.CONTEXT; |
| } |
| |
| function save(e) { |
| // Validation checks before saving |
| if (isOptionShown('profileindicatoralt_months')) { |
| var months = document.getElementById('profileindicatoralt_months'); |
| if (!months.checkValidity()) { |
| console.warn(months.validationMessage); |
| return; |
| } |
| } |
| |
| e.preventDefault(); |
| |
| chrome.storage.sync.get(null, function(items) { |
| var options = cleanUpOptions(items, true); |
| |
| // Save |
| Object.keys(options).forEach(function(opt) { |
| if (!isOptionShown(opt)) return; |
| options[opt] = getOptionValue(opt); |
| }); |
| |
| chrome.storage.sync.set(options, function() { |
| window.close(); |
| |
| // In browsers like Firefox window.close is not supported: |
| if (savedSuccessfullyTimeout !== null) |
| window.clearTimeout(savedSuccessfullyTimeout); |
| |
| document.getElementById('save-indicator').innerText = |
| '✓ ' + chrome.i18n.getMessage('options_saved'); |
| savedSuccessfullyTimeout = window.setTimeout(_ => { |
| document.getElementById('save-indicator').innerText = ''; |
| }, 3699); |
| }); |
| }); |
| } |
| |
| function i18n() { |
| document.querySelectorAll('[data-i18n]') |
| .forEach( |
| el => el.innerHTML = chrome.i18n.getMessage( |
| 'options_' + el.getAttribute('data-i18n'))); |
| } |
| |
| window.addEventListener('load', function() { |
| if (window.CONTEXT == 'options') { |
| if (!isProdVersion()) { |
| var experimentsLink = document.querySelector('.experiments-link'); |
| experimentsLink.removeAttribute('hidden'); |
| experimentsLink.addEventListener('click', _ => chrome.tabs.create({ |
| url: chrome.runtime.getURL('options/experiments.html'), |
| })); |
| } |
| |
| // Add options to page |
| let optionsContainer = document.getElementById('options-container'); |
| for (let section of optionsPage.sections) { |
| if (section?.name) { |
| let sectionHeader = document.createElement('h4'); |
| sectionHeader.setAttribute('data-i18n', section.name); |
| optionsContainer.append(sectionHeader); |
| } |
| |
| if (section?.options) { |
| for (let option of section.options) { |
| if (option?.customHTML) { |
| optionsContainer.insertAdjacentHTML('beforeend', option.customHTML); |
| } else { |
| let optionEl = document.createElement('div'); |
| optionEl.classList.add('option'); |
| |
| let checkbox = document.createElement('input'); |
| checkbox.setAttribute('type', 'checkbox'); |
| checkbox.id = option.codename; |
| |
| let label = document.createElement('label'); |
| label.setAttribute('for', checkbox.id); |
| label.setAttribute('data-i18n', option.codename); |
| |
| optionEl.append(checkbox, ' ', label); |
| |
| if (option?.experimental) { |
| let experimental = document.createElement('span'); |
| experimental.classList.add('experimental-label'); |
| experimental.setAttribute('data-i18n', 'experimental_label'); |
| |
| optionEl.append(' ', experimental); |
| } |
| |
| optionsContainer.append(optionEl); |
| } |
| |
| // Add optional permissions warning label and kill switch component |
| // after each option. |
| let optionalPermissionsWarningLabel = document.createElement('div'); |
| optionalPermissionsWarningLabel.classList.add( |
| 'optional-permissions-warning-label'); |
| optionalPermissionsWarningLabel.setAttribute('hidden', ''); |
| optionalPermissionsWarningLabel.setAttribute( |
| 'data-feature', option.codename); |
| optionalPermissionsWarningLabel.setAttribute( |
| 'data-i18n', 'optionalpermissionswarning_label'); |
| |
| let killSwitchComponent = document.createElement('div'); |
| killSwitchComponent.classList.add('kill-switch-label'); |
| killSwitchComponent.setAttribute('hidden', ''); |
| killSwitchComponent.setAttribute('data-feature', option.codename); |
| killSwitchComponent.setAttribute('data-i18n', 'killswitchenabled'); |
| |
| optionsContainer.append( |
| optionalPermissionsWarningLabel, killSwitchComponent); |
| } |
| } |
| |
| if (section?.footerHTML) { |
| optionsContainer.insertAdjacentHTML('beforeend', section.footerHTML); |
| } |
| } |
| |
| var featuresLink = document.querySelector('.features-link'); |
| featuresLink.href = getDocURL('features.md'); |
| |
| var profileIndicatorLink = |
| document.getElementById('profileIndicatorMoreInfo'); |
| profileIndicatorLink.href = getDocURL('op_indicator.md'); |
| } |
| |
| i18n(); |
| |
| // Add custom handlers |
| let manageWorkflowsBtn = document.getElementById('manage-workflows'); |
| if (manageWorkflowsBtn) |
| manageWorkflowsBtn.addEventListener('click', e => { |
| e.preventDefault(); |
| chrome.tabs.create({ |
| url: chrome.runtime.getURL('options/workflows.html'), |
| }) |
| }); |
| |
| chrome.storage.sync.get(null, function(items) { |
| items = cleanUpOptions(items, false); |
| |
| // If some features have been force disabled, communicate this to the user. |
| if (items?._forceDisabledFeatures && |
| items._forceDisabledFeatures.length > 0) { |
| if (window.CONTEXT == 'options') { |
| document.getElementById('kill-switch-warning') |
| .removeAttribute('hidden'); |
| } |
| } |
| |
| for (var entry of Object.entries(optionsPrototype)) { |
| var opt = entry[0]; |
| var optMeta = entry[1]; |
| |
| if (!isOptionShown(opt)) continue; |
| |
| if (specialOptions.includes(opt)) { |
| switch (opt) { |
| case 'profileindicatoralt_months': |
| var input = document.createElement('input'); |
| input.type = 'number'; |
| input.id = 'profileindicatoralt_months'; |
| input.max = '12'; |
| input.min = '1'; |
| input.value = items[opt]; |
| input.required = true; |
| document.getElementById('profileindicatoralt_months--container') |
| .appendChild(input); |
| break; |
| |
| case 'ccdarktheme_mode': |
| var select = document.createElement('select'); |
| select.id = 'ccdarktheme_mode'; |
| |
| const modes = ['switch', 'system']; |
| for (const mode of modes) { |
| var modeOption = document.createElement('option'); |
| modeOption.value = mode; |
| modeOption.textContent = |
| chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode); |
| if (items.ccdarktheme_mode == mode) modeOption.selected = true; |
| select.appendChild(modeOption); |
| } |
| |
| document.getElementById('ccdarktheme_mode--container') |
| .appendChild(select); |
| break; |
| |
| case 'interopthreadpage_mode': |
| var select = document.createElement('select'); |
| select.id = 'interopthreadpage_mode'; |
| |
| const threadPageModes = ['previous', 'next']; |
| for (const mode of threadPageModes) { |
| let modeOption = document.createElement('option'); |
| modeOption.value = mode; |
| modeOption.textContent = chrome.i18n.getMessage( |
| 'options_interopthreadpage_mode_' + mode); |
| if (items.interopthreadpage_mode == mode) |
| modeOption.selected = true; |
| select.appendChild(modeOption); |
| } |
| |
| document.getElementById('interopthreadpage_mode--container') |
| .appendChild(select); |
| break; |
| |
| default: |
| console.warn('Unrecognized option: ' + opt); |
| break; |
| } |
| continue; |
| } |
| |
| if (items[opt] === true) document.getElementById(opt).checked = true; |
| if (window.CONTEXT == 'options' && |
| items?._forceDisabledFeatures?.includes?.(opt)) |
| document.querySelector('.kill-switch-label[data-feature="' + opt + '"]') |
| .removeAttribute('hidden'); |
| } |
| |
| exclusiveOptions.forEach(exclusive => { |
| if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return; |
| |
| exclusive.forEach( |
| el => document.getElementById(el).addEventListener('change', e => { |
| if (document.getElementById(exclusive[0]).checked && |
| document.getElementById(exclusive[1]).checked) { |
| document |
| .getElementById( |
| exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)]) |
| .checked = false; |
| } |
| })); |
| }); |
| |
| // Handle options which need optional permissions. |
| grantedOptPermissions() |
| .then(grantedPerms => { |
| for (const [opt, optMeta] of Object.entries(optionsPrototype)) { |
| if (!optMeta.requiredOptPermissions || !isOptionShown(opt)) |
| continue; |
| |
| let warningLabel = document.querySelector( |
| '.optional-permissions-warning-label[data-feature="' + opt + |
| '"]'); |
| |
| // Ensure we have the appropriate permissions when the checkbox |
| // switches from disabled to enabled. |
| // |
| // Also, if the checkbox was indeterminate because the feature was |
| // enabled but not all permissions had been granted, enable the |
| // feature in order to trigger the permission request again. |
| let checkbox = document.getElementById(opt); |
| if (!checkbox) { |
| console.error('Expected checkbox for feature "' + opt + '".'); |
| continue; |
| } |
| checkbox.addEventListener('change', () => { |
| if (checkbox.hasAttribute(kClickShouldEnableFeat)) { |
| checkbox.removeAttribute(kClickShouldEnableFeat); |
| checkbox.checked = true; |
| } |
| |
| if (checkbox.checked) |
| ensureOptPermissions(opt) |
| .then(granted => { |
| if (granted) { |
| warningLabel.setAttribute('hidden', ''); |
| if (!document.querySelector( |
| '.optional-permissions-warning-label:not([hidden])')) |
| document |
| .getElementById('optional-permissions-warning') |
| .setAttribute('hidden', ''); |
| } else |
| document.getElementById(opt).checked = false; |
| }) |
| .catch(err => { |
| console.error( |
| 'An error ocurred while ensuring that the optional ' + |
| 'permissions were granted after the checkbox ' + |
| 'was clicked for feature "' + opt + '":', |
| err); |
| document.getElementById(opt).checked = false; |
| }); |
| }); |
| |
| // Add warning message if some permissions are missing and the |
| // feature is enabled. |
| if (items[opt] === true) { |
| let shownHeaderMessage = false; |
| missingPermissions(opt, grantedPerms) |
| .then(missingPerms => { |
| if (!isPermissionsObjectEmpty(missingPerms)) { |
| checkbox.indeterminate = true; |
| checkbox.setAttribute(kClickShouldEnableFeat, ''); |
| |
| warningLabel.removeAttribute('hidden'); |
| |
| if (!shownHeaderMessage) { |
| shownHeaderMessage = true; |
| document.getElementById('optional-permissions-warning') |
| .removeAttribute('hidden'); |
| } |
| } |
| }) |
| .catch(err => console.error(err)); |
| } |
| } |
| }) |
| .catch(err => console.error(err)); |
| |
| document.querySelector('#save').addEventListener('click', save); |
| }); |
| }); |