blob: 6d41a39992c37e37d2b2da105fc365388d77c990 [file] [log] [blame]
Adrià Vilanova Martínez5a8055b2022-09-29 13:05:19 +02001import {getDocURL, getDocURLWithRef, getExtVersion, isProdVersion} from '../common/extUtils.js';
Adrià Vilanova Martínezb523be92024-05-25 19:14:19 +02002import {ensureOptPermissions, grantedOptPermissions, isPermissionsObjectEmpty, missingPermissions} from '../common/options/optionsPermissions.js';
3import {cleanUpOptions, optionsPrototype, specialOptions} from '../common/options/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
Adrià Vilanova61f782f2024-05-31 22:57:24 +000022 case 'interopthreadpage_mode':
23 return document.getElementById(opt).value || 'previous';
24
avm99963bf8eece2021-04-22 00:27:03 +020025 default:
26 console.warn('Unrecognized option: ' + opt);
27 return undefined;
28 }
29 }
30
31 return document.getElementById(opt).checked || false;
32}
33
34// Returns whether the option is included in the current context
35function isOptionShown(opt) {
36 if (!optionsPrototype.hasOwnProperty(opt)) return false;
37 return optionsPrototype[opt].context == window.CONTEXT;
38}
39
40function save(e) {
41 // Validation checks before saving
42 if (isOptionShown('profileindicatoralt_months')) {
43 var months = document.getElementById('profileindicatoralt_months');
44 if (!months.checkValidity()) {
45 console.warn(months.validationMessage);
46 return;
47 }
48 }
49
50 e.preventDefault();
51
52 chrome.storage.sync.get(null, function(items) {
53 var options = cleanUpOptions(items, true);
54
55 // Save
56 Object.keys(options).forEach(function(opt) {
57 if (!isOptionShown(opt)) return;
58 options[opt] = getOptionValue(opt);
59 });
60
61 chrome.storage.sync.set(options, function() {
62 window.close();
63
64 // In browsers like Firefox window.close is not supported:
65 if (savedSuccessfullyTimeout !== null)
66 window.clearTimeout(savedSuccessfullyTimeout);
67
68 document.getElementById('save-indicator').innerText =
69 '✓ ' + chrome.i18n.getMessage('options_saved');
70 savedSuccessfullyTimeout = window.setTimeout(_ => {
71 document.getElementById('save-indicator').innerText = '';
72 }, 3699);
73 });
74 });
75}
76
77function i18n() {
78 document.querySelectorAll('[data-i18n]')
79 .forEach(
80 el => el.innerHTML = chrome.i18n.getMessage(
81 'options_' + el.getAttribute('data-i18n')));
82}
83
84window.addEventListener('load', function() {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020085 if (window.CONTEXT == 'options') {
Adrià Vilanova Martínez0335b512022-01-21 13:34:59 +010086 if (!isProdVersion()) {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020087 var experimentsLink = document.querySelector('.experiments-link');
88 experimentsLink.removeAttribute('hidden');
89 experimentsLink.addEventListener('click', _ => chrome.tabs.create({
90 url: chrome.runtime.getURL('options/experiments.html'),
91 }));
92 }
93
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +020094 // Add options to page
95 let optionsContainer = document.getElementById('options-container');
96 for (let section of optionsPage.sections) {
97 if (section?.name) {
98 let sectionHeader = document.createElement('h4');
99 sectionHeader.setAttribute('data-i18n', section.name);
100 optionsContainer.append(sectionHeader);
101 }
102
103 if (section?.options) {
104 for (let option of section.options) {
105 if (option?.customHTML) {
106 optionsContainer.insertAdjacentHTML('beforeend', option.customHTML);
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200107 } else {
108 let optionEl = document.createElement('div');
109 optionEl.classList.add('option');
110
111 let checkbox = document.createElement('input');
112 checkbox.setAttribute('type', 'checkbox');
113 checkbox.id = option.codename;
114
115 let label = document.createElement('label');
116 label.setAttribute('for', checkbox.id);
117 label.setAttribute('data-i18n', option.codename);
118
119 optionEl.append(checkbox, ' ', label);
120
121 if (option?.experimental) {
122 let experimental = document.createElement('span');
123 experimental.classList.add('experimental-label');
124 experimental.setAttribute('data-i18n', 'experimental_label');
125
126 optionEl.append(' ', experimental);
127 }
128
129 optionsContainer.append(optionEl);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200130 }
131
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100132 // Add optional permissions warning label and kill switch component
133 // after each option.
134 let optionalPermissionsWarningLabel = document.createElement('div');
135 optionalPermissionsWarningLabel.classList.add(
136 'optional-permissions-warning-label');
137 optionalPermissionsWarningLabel.setAttribute('hidden', '');
138 optionalPermissionsWarningLabel.setAttribute(
139 'data-feature', option.codename);
140 optionalPermissionsWarningLabel.setAttribute(
141 'data-i18n', 'optionalpermissionswarning_label');
142
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200143 let killSwitchComponent = document.createElement('div');
144 killSwitchComponent.classList.add('kill-switch-label');
145 killSwitchComponent.setAttribute('hidden', '');
146 killSwitchComponent.setAttribute('data-feature', option.codename);
147 killSwitchComponent.setAttribute('data-i18n', 'killswitchenabled');
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200148
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100149 optionsContainer.append(
150 optionalPermissionsWarningLabel, killSwitchComponent);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200151 }
152 }
153
154 if (section?.footerHTML) {
155 optionsContainer.insertAdjacentHTML('beforeend', section.footerHTML);
156 }
157 }
158
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200159 var featuresLink = document.querySelector('.features-link');
160 featuresLink.href = getDocURL('features.md');
161
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200162 var profileIndicatorLink =
163 document.getElementById('profileIndicatorMoreInfo');
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200164 profileIndicatorLink.href = getDocURL('op_indicator.md');
avm999637309b062021-04-22 12:41:08 +0200165 }
166
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200167 i18n();
168
Adrià Vilanova Martínez5f8ae6d2021-12-26 12:25:30 +0100169 // Add custom handlers
170 let manageWorkflowsBtn = document.getElementById('manage-workflows');
171 if (manageWorkflowsBtn)
172 manageWorkflowsBtn.addEventListener('click', e => {
173 e.preventDefault();
174 chrome.tabs.create({
175 url: chrome.runtime.getURL('options/workflows.html'),
176 })
177 });
178
avm99963bf8eece2021-04-22 00:27:03 +0200179 chrome.storage.sync.get(null, function(items) {
180 items = cleanUpOptions(items, false);
181
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200182 // If some features have been force disabled, communicate this to the user.
183 if (items?._forceDisabledFeatures &&
184 items._forceDisabledFeatures.length > 0) {
185 if (window.CONTEXT == 'options') {
186 document.getElementById('kill-switch-warning')
187 .removeAttribute('hidden');
188 }
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200189 }
190
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200191 for (var entry of Object.entries(optionsPrototype)) {
192 var opt = entry[0];
193 var optMeta = entry[1];
194
avm99963bf8eece2021-04-22 00:27:03 +0200195 if (!isOptionShown(opt)) continue;
196
197 if (specialOptions.includes(opt)) {
198 switch (opt) {
199 case 'profileindicatoralt_months':
200 var input = document.createElement('input');
201 input.type = 'number';
202 input.id = 'profileindicatoralt_months';
203 input.max = '12';
204 input.min = '1';
205 input.value = items[opt];
206 input.required = true;
207 document.getElementById('profileindicatoralt_months--container')
208 .appendChild(input);
209 break;
210
211 case 'ccdarktheme_mode':
212 var select = document.createElement('select');
213 select.id = 'ccdarktheme_mode';
214
215 const modes = ['switch', 'system'];
216 for (const mode of modes) {
217 var modeOption = document.createElement('option');
218 modeOption.value = mode;
219 modeOption.textContent =
220 chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
221 if (items.ccdarktheme_mode == mode) modeOption.selected = true;
222 select.appendChild(modeOption);
223 }
224
225 document.getElementById('ccdarktheme_mode--container')
226 .appendChild(select);
227 break;
228
Adrià Vilanova61f782f2024-05-31 22:57:24 +0000229 case 'interopthreadpage_mode':
230 var select = document.createElement('select');
231 select.id = 'interopthreadpage_mode';
232
233 const threadPageModes = ['previous', 'next'];
234 for (const mode of threadPageModes) {
235 let modeOption = document.createElement('option');
236 modeOption.value = mode;
237 modeOption.textContent = chrome.i18n.getMessage(
238 'options_interopthreadpage_mode_' + mode);
239 if (items.interopthreadpage_mode == mode)
240 modeOption.selected = true;
241 select.appendChild(modeOption);
242 }
243
244 document.getElementById('interopthreadpage_mode--container')
245 .appendChild(select);
246 break;
247
avm99963bf8eece2021-04-22 00:27:03 +0200248 default:
249 console.warn('Unrecognized option: ' + opt);
250 break;
251 }
252 continue;
253 }
254
255 if (items[opt] === true) document.getElementById(opt).checked = true;
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200256 if (window.CONTEXT == 'options' &&
257 items?._forceDisabledFeatures?.includes?.(opt))
258 document.querySelector('.kill-switch-label[data-feature="' + opt + '"]')
259 .removeAttribute('hidden');
avm99963bf8eece2021-04-22 00:27:03 +0200260 }
261
262 exclusiveOptions.forEach(exclusive => {
263 if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return;
264
265 exclusive.forEach(
266 el => document.getElementById(el).addEventListener('change', e => {
267 if (document.getElementById(exclusive[0]).checked &&
268 document.getElementById(exclusive[1]).checked) {
269 document
270 .getElementById(
271 exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
272 .checked = false;
273 }
274 }));
275 });
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100276
277 // Handle options which need optional permissions.
278 grantedOptPermissions()
279 .then(grantedPerms => {
280 for (const [opt, optMeta] of Object.entries(optionsPrototype)) {
Adrià Vilanova Martínez310c2902022-07-04 00:31:41 +0200281 if (!optMeta.requiredOptPermissions || !isOptionShown(opt))
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100282 continue;
283
284 let warningLabel = document.querySelector(
285 '.optional-permissions-warning-label[data-feature="' + opt +
286 '"]');
287
288 // Ensure we have the appropriate permissions when the checkbox
289 // switches from disabled to enabled.
290 //
291 // Also, if the checkbox was indeterminate because the feature was
292 // enabled but not all permissions had been granted, enable the
293 // feature in order to trigger the permission request again.
294 let checkbox = document.getElementById(opt);
295 if (!checkbox) {
296 console.error('Expected checkbox for feature "' + opt + '".');
297 continue;
298 }
299 checkbox.addEventListener('change', () => {
300 if (checkbox.hasAttribute(kClickShouldEnableFeat)) {
301 checkbox.removeAttribute(kClickShouldEnableFeat);
302 checkbox.checked = true;
303 }
304
305 if (checkbox.checked)
306 ensureOptPermissions(opt)
307 .then(granted => {
308 if (granted) {
309 warningLabel.setAttribute('hidden', '');
310 if (!document.querySelector(
311 '.optional-permissions-warning-label:not([hidden])'))
312 document
313 .getElementById('optional-permissions-warning')
314 .setAttribute('hidden', '');
315 } else
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100316 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100317 })
318 .catch(err => {
319 console.error(
320 'An error ocurred while ensuring that the optional ' +
321 'permissions were granted after the checkbox ' +
322 'was clicked for feature "' + opt + '":',
323 err);
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100324 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100325 });
326 });
327
328 // Add warning message if some permissions are missing and the
329 // feature is enabled.
330 if (items[opt] === true) {
331 let shownHeaderMessage = false;
332 missingPermissions(opt, grantedPerms)
333 .then(missingPerms => {
Adrià Vilanova Martínez310c2902022-07-04 00:31:41 +0200334 if (!isPermissionsObjectEmpty(missingPerms)) {
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100335 checkbox.indeterminate = true;
336 checkbox.setAttribute(kClickShouldEnableFeat, '');
337
338 warningLabel.removeAttribute('hidden');
339
340 if (!shownHeaderMessage) {
341 shownHeaderMessage = true;
342 document.getElementById('optional-permissions-warning')
343 .removeAttribute('hidden');
344 }
345 }
346 })
347 .catch(err => console.error(err));
348 }
349 }
350 })
351 .catch(err => console.error(err));
352
avm99963bf8eece2021-04-22 00:27:03 +0200353 document.querySelector('#save').addEventListener('click', save);
354 });
355});