blob: f8d5c1458f05687460bd0f1f408f27bcc4de965f [file] [log] [blame]
Adrià Vilanova Martínez5a8055b2022-09-29 13:05:19 +02001import {getDocURL, getDocURLWithRef, getExtVersion, isProdVersion} from '../common/extUtils.js';
Adrià Vilanova Martínez310c2902022-07-04 00:31:41 +02002import {ensureOptPermissions, grantedOptPermissions, isPermissionsObjectEmpty, missingPermissions} from '../common/optionsPermissions.js';
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +02003import {cleanUpOptions, optionsPrototype, specialOptions} from '../common/optionsUtils.js';
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +01004
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +02005import optionsPage from './optionsPage.json5';
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02006
avm99963bf8eece2021-04-22 00:27:03 +02007var savedSuccessfullyTimeout = null;
8
9const exclusiveOptions = [['thread', 'threadall']];
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +010010const kClickShouldEnableFeat = 'data-click-should-enable-feature';
avm99963bf8eece2021-04-22 00:27:03 +020011
12// Get the value of the option set in the options/experiments page
13function getOptionValue(opt) {
14 if (specialOptions.includes(opt)) {
15 switch (opt) {
16 case 'profileindicatoralt_months':
17 return document.getElementById(opt).value || 12;
18
19 case 'ccdarktheme_mode':
20 return document.getElementById(opt).value || 'switch';
21
avm99963bf8eece2021-04-22 00:27:03 +020022 default:
23 console.warn('Unrecognized option: ' + opt);
24 return undefined;
25 }
26 }
27
28 return document.getElementById(opt).checked || false;
29}
30
31// Returns whether the option is included in the current context
32function isOptionShown(opt) {
33 if (!optionsPrototype.hasOwnProperty(opt)) return false;
34 return optionsPrototype[opt].context == window.CONTEXT;
35}
36
37function save(e) {
38 // Validation checks before saving
39 if (isOptionShown('profileindicatoralt_months')) {
40 var months = document.getElementById('profileindicatoralt_months');
41 if (!months.checkValidity()) {
42 console.warn(months.validationMessage);
43 return;
44 }
45 }
46
47 e.preventDefault();
48
49 chrome.storage.sync.get(null, function(items) {
50 var options = cleanUpOptions(items, true);
51
52 // Save
53 Object.keys(options).forEach(function(opt) {
54 if (!isOptionShown(opt)) return;
55 options[opt] = getOptionValue(opt);
56 });
57
58 chrome.storage.sync.set(options, function() {
59 window.close();
60
61 // In browsers like Firefox window.close is not supported:
62 if (savedSuccessfullyTimeout !== null)
63 window.clearTimeout(savedSuccessfullyTimeout);
64
65 document.getElementById('save-indicator').innerText =
66 '✓ ' + chrome.i18n.getMessage('options_saved');
67 savedSuccessfullyTimeout = window.setTimeout(_ => {
68 document.getElementById('save-indicator').innerText = '';
69 }, 3699);
70 });
71 });
72}
73
74function i18n() {
75 document.querySelectorAll('[data-i18n]')
76 .forEach(
77 el => el.innerHTML = chrome.i18n.getMessage(
78 'options_' + el.getAttribute('data-i18n')));
79}
80
81window.addEventListener('load', function() {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020082 if (window.CONTEXT == 'options') {
Adrià Vilanova Martínez0335b512022-01-21 13:34:59 +010083 if (!isProdVersion()) {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020084 var experimentsLink = document.querySelector('.experiments-link');
85 experimentsLink.removeAttribute('hidden');
86 experimentsLink.addEventListener('click', _ => chrome.tabs.create({
87 url: chrome.runtime.getURL('options/experiments.html'),
88 }));
89 }
90
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +020091 // Add options to page
92 let optionsContainer = document.getElementById('options-container');
93 for (let section of optionsPage.sections) {
94 if (section?.name) {
95 let sectionHeader = document.createElement('h4');
96 sectionHeader.setAttribute('data-i18n', section.name);
97 optionsContainer.append(sectionHeader);
98 }
99
100 if (section?.options) {
101 for (let option of section.options) {
102 if (option?.customHTML) {
103 optionsContainer.insertAdjacentHTML('beforeend', option.customHTML);
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200104 } else {
105 let optionEl = document.createElement('div');
106 optionEl.classList.add('option');
107
108 let checkbox = document.createElement('input');
109 checkbox.setAttribute('type', 'checkbox');
110 checkbox.id = option.codename;
111
112 let label = document.createElement('label');
113 label.setAttribute('for', checkbox.id);
114 label.setAttribute('data-i18n', option.codename);
115
116 optionEl.append(checkbox, ' ', label);
117
118 if (option?.experimental) {
119 let experimental = document.createElement('span');
120 experimental.classList.add('experimental-label');
121 experimental.setAttribute('data-i18n', 'experimental_label');
122
123 optionEl.append(' ', experimental);
124 }
125
126 optionsContainer.append(optionEl);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200127 }
128
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100129 // Add optional permissions warning label and kill switch component
130 // after each option.
131 let optionalPermissionsWarningLabel = document.createElement('div');
132 optionalPermissionsWarningLabel.classList.add(
133 'optional-permissions-warning-label');
134 optionalPermissionsWarningLabel.setAttribute('hidden', '');
135 optionalPermissionsWarningLabel.setAttribute(
136 'data-feature', option.codename);
137 optionalPermissionsWarningLabel.setAttribute(
138 'data-i18n', 'optionalpermissionswarning_label');
139
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200140 let killSwitchComponent = document.createElement('div');
141 killSwitchComponent.classList.add('kill-switch-label');
142 killSwitchComponent.setAttribute('hidden', '');
143 killSwitchComponent.setAttribute('data-feature', option.codename);
144 killSwitchComponent.setAttribute('data-i18n', 'killswitchenabled');
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200145
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100146 optionsContainer.append(
147 optionalPermissionsWarningLabel, killSwitchComponent);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200148 }
149 }
150
151 if (section?.footerHTML) {
152 optionsContainer.insertAdjacentHTML('beforeend', section.footerHTML);
153 }
154 }
155
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200156 var featuresLink = document.querySelector('.features-link');
157 featuresLink.href = getDocURL('features.md');
158
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200159 var profileIndicatorLink =
160 document.getElementById('profileIndicatorMoreInfo');
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200161 profileIndicatorLink.href = getDocURL('op_indicator.md');
avm999637309b062021-04-22 12:41:08 +0200162 }
163
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200164 i18n();
165
Adrià Vilanova Martínez5f8ae6d2021-12-26 12:25:30 +0100166 // Add custom handlers
167 let manageWorkflowsBtn = document.getElementById('manage-workflows');
168 if (manageWorkflowsBtn)
169 manageWorkflowsBtn.addEventListener('click', e => {
170 e.preventDefault();
171 chrome.tabs.create({
172 url: chrome.runtime.getURL('options/workflows.html'),
173 })
174 });
175
avm99963bf8eece2021-04-22 00:27:03 +0200176 chrome.storage.sync.get(null, function(items) {
177 items = cleanUpOptions(items, false);
178
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200179 // If some features have been force disabled, communicate this to the user.
180 if (items?._forceDisabledFeatures &&
181 items._forceDisabledFeatures.length > 0) {
182 if (window.CONTEXT == 'options') {
183 document.getElementById('kill-switch-warning')
184 .removeAttribute('hidden');
185 }
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200186 }
187
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200188 for (var entry of Object.entries(optionsPrototype)) {
189 var opt = entry[0];
190 var optMeta = entry[1];
191
avm99963bf8eece2021-04-22 00:27:03 +0200192 if (!isOptionShown(opt)) continue;
193
194 if (specialOptions.includes(opt)) {
195 switch (opt) {
196 case 'profileindicatoralt_months':
197 var input = document.createElement('input');
198 input.type = 'number';
199 input.id = 'profileindicatoralt_months';
200 input.max = '12';
201 input.min = '1';
202 input.value = items[opt];
203 input.required = true;
204 document.getElementById('profileindicatoralt_months--container')
205 .appendChild(input);
206 break;
207
208 case 'ccdarktheme_mode':
209 var select = document.createElement('select');
210 select.id = 'ccdarktheme_mode';
211
212 const modes = ['switch', 'system'];
213 for (const mode of modes) {
214 var modeOption = document.createElement('option');
215 modeOption.value = mode;
216 modeOption.textContent =
217 chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
218 if (items.ccdarktheme_mode == mode) modeOption.selected = true;
219 select.appendChild(modeOption);
220 }
221
222 document.getElementById('ccdarktheme_mode--container')
223 .appendChild(select);
224 break;
225
avm99963bf8eece2021-04-22 00:27:03 +0200226 default:
227 console.warn('Unrecognized option: ' + opt);
228 break;
229 }
230 continue;
231 }
232
233 if (items[opt] === true) document.getElementById(opt).checked = true;
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200234 if (window.CONTEXT == 'options' &&
235 items?._forceDisabledFeatures?.includes?.(opt))
236 document.querySelector('.kill-switch-label[data-feature="' + opt + '"]')
237 .removeAttribute('hidden');
avm99963bf8eece2021-04-22 00:27:03 +0200238 }
239
240 exclusiveOptions.forEach(exclusive => {
241 if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return;
242
243 exclusive.forEach(
244 el => document.getElementById(el).addEventListener('change', e => {
245 if (document.getElementById(exclusive[0]).checked &&
246 document.getElementById(exclusive[1]).checked) {
247 document
248 .getElementById(
249 exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
250 .checked = false;
251 }
252 }));
253 });
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100254
255 // Handle options which need optional permissions.
256 grantedOptPermissions()
257 .then(grantedPerms => {
258 for (const [opt, optMeta] of Object.entries(optionsPrototype)) {
Adrià Vilanova Martínez310c2902022-07-04 00:31:41 +0200259 if (!optMeta.requiredOptPermissions || !isOptionShown(opt))
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100260 continue;
261
262 let warningLabel = document.querySelector(
263 '.optional-permissions-warning-label[data-feature="' + opt +
264 '"]');
265
266 // Ensure we have the appropriate permissions when the checkbox
267 // switches from disabled to enabled.
268 //
269 // Also, if the checkbox was indeterminate because the feature was
270 // enabled but not all permissions had been granted, enable the
271 // feature in order to trigger the permission request again.
272 let checkbox = document.getElementById(opt);
273 if (!checkbox) {
274 console.error('Expected checkbox for feature "' + opt + '".');
275 continue;
276 }
277 checkbox.addEventListener('change', () => {
278 if (checkbox.hasAttribute(kClickShouldEnableFeat)) {
279 checkbox.removeAttribute(kClickShouldEnableFeat);
280 checkbox.checked = true;
281 }
282
283 if (checkbox.checked)
284 ensureOptPermissions(opt)
285 .then(granted => {
286 if (granted) {
287 warningLabel.setAttribute('hidden', '');
288 if (!document.querySelector(
289 '.optional-permissions-warning-label:not([hidden])'))
290 document
291 .getElementById('optional-permissions-warning')
292 .setAttribute('hidden', '');
293 } else
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100294 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100295 })
296 .catch(err => {
297 console.error(
298 'An error ocurred while ensuring that the optional ' +
299 'permissions were granted after the checkbox ' +
300 'was clicked for feature "' + opt + '":',
301 err);
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100302 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100303 });
304 });
305
306 // Add warning message if some permissions are missing and the
307 // feature is enabled.
308 if (items[opt] === true) {
309 let shownHeaderMessage = false;
310 missingPermissions(opt, grantedPerms)
311 .then(missingPerms => {
Adrià Vilanova Martínez310c2902022-07-04 00:31:41 +0200312 if (!isPermissionsObjectEmpty(missingPerms)) {
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100313 checkbox.indeterminate = true;
314 checkbox.setAttribute(kClickShouldEnableFeat, '');
315
316 warningLabel.removeAttribute('hidden');
317
318 if (!shownHeaderMessage) {
319 shownHeaderMessage = true;
320 document.getElementById('optional-permissions-warning')
321 .removeAttribute('hidden');
322 }
323 }
324 })
325 .catch(err => console.error(err));
326 }
327 }
328 })
329 .catch(err => console.error(err));
330
avm99963bf8eece2021-04-22 00:27:03 +0200331 document.querySelector('#save').addEventListener('click', save);
332 });
333});