blob: 99874e517ab0a252092e79e3b6cb0a3c1e78dc7e [file] [log] [blame]
Adrià Vilanova Martínez0335b512022-01-21 13:34:59 +01001import {getExtVersion, isProdVersion} from '../common/extUtils.js';
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +01002import {ensureOptPermissions, grantedOptPermissions, 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
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020012// Get a URL to a document which is part of the extension documentation (using
13// |ref| as the Git ref).
14function getDocURLWithRef(doc, ref) {
15 return 'https://gerrit.avm99963.com/plugins/gitiles/infinitegforums/+/' +
16 ref + '/docs/' + doc;
17}
18
19// Get a URL to a document which is part of the extension documentation
20// (autodetect the appropriate Git ref)
21function getDocURL(doc) {
Adrià Vilanova Martínez0335b512022-01-21 13:34:59 +010022 if (!isProdVersion()) return getDocURLWithRef(doc, 'HEAD');
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020023
24 var version = getExtVersion();
25 return getDocURLWithRef(doc, 'refs/tags/v' + version);
26}
27
avm99963bf8eece2021-04-22 00:27:03 +020028// Get the value of the option set in the options/experiments page
29function getOptionValue(opt) {
30 if (specialOptions.includes(opt)) {
31 switch (opt) {
32 case 'profileindicatoralt_months':
33 return document.getElementById(opt).value || 12;
34
35 case 'ccdarktheme_mode':
36 return document.getElementById(opt).value || 'switch';
37
Adrià Vilanova Martínez849ede62022-06-20 19:10:28 +020038 case 'interopthreadpage_mode':
39 return document.getElementById(opt).value || 'previous';
40
avm99963bf8eece2021-04-22 00:27:03 +020041 default:
42 console.warn('Unrecognized option: ' + opt);
43 return undefined;
44 }
45 }
46
47 return document.getElementById(opt).checked || false;
48}
49
50// Returns whether the option is included in the current context
51function isOptionShown(opt) {
52 if (!optionsPrototype.hasOwnProperty(opt)) return false;
53 return optionsPrototype[opt].context == window.CONTEXT;
54}
55
56function save(e) {
57 // Validation checks before saving
58 if (isOptionShown('profileindicatoralt_months')) {
59 var months = document.getElementById('profileindicatoralt_months');
60 if (!months.checkValidity()) {
61 console.warn(months.validationMessage);
62 return;
63 }
64 }
65
66 e.preventDefault();
67
68 chrome.storage.sync.get(null, function(items) {
69 var options = cleanUpOptions(items, true);
70
71 // Save
72 Object.keys(options).forEach(function(opt) {
73 if (!isOptionShown(opt)) return;
74 options[opt] = getOptionValue(opt);
75 });
76
77 chrome.storage.sync.set(options, function() {
78 window.close();
79
80 // In browsers like Firefox window.close is not supported:
81 if (savedSuccessfullyTimeout !== null)
82 window.clearTimeout(savedSuccessfullyTimeout);
83
84 document.getElementById('save-indicator').innerText =
85 '✓ ' + chrome.i18n.getMessage('options_saved');
86 savedSuccessfullyTimeout = window.setTimeout(_ => {
87 document.getElementById('save-indicator').innerText = '';
88 }, 3699);
89 });
90 });
91}
92
93function i18n() {
94 document.querySelectorAll('[data-i18n]')
95 .forEach(
96 el => el.innerHTML = chrome.i18n.getMessage(
97 'options_' + el.getAttribute('data-i18n')));
98}
99
100window.addEventListener('load', function() {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200101 if (window.CONTEXT == 'options') {
Adrià Vilanova Martínez0335b512022-01-21 13:34:59 +0100102 if (!isProdVersion()) {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200103 var experimentsLink = document.querySelector('.experiments-link');
104 experimentsLink.removeAttribute('hidden');
105 experimentsLink.addEventListener('click', _ => chrome.tabs.create({
106 url: chrome.runtime.getURL('options/experiments.html'),
107 }));
108 }
109
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200110 // Add options to page
111 let optionsContainer = document.getElementById('options-container');
112 for (let section of optionsPage.sections) {
113 if (section?.name) {
114 let sectionHeader = document.createElement('h4');
115 sectionHeader.setAttribute('data-i18n', section.name);
116 optionsContainer.append(sectionHeader);
117 }
118
119 if (section?.options) {
120 for (let option of section.options) {
121 if (option?.customHTML) {
122 optionsContainer.insertAdjacentHTML('beforeend', option.customHTML);
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200123 } else {
124 let optionEl = document.createElement('div');
125 optionEl.classList.add('option');
126
127 let checkbox = document.createElement('input');
128 checkbox.setAttribute('type', 'checkbox');
129 checkbox.id = option.codename;
130
131 let label = document.createElement('label');
132 label.setAttribute('for', checkbox.id);
133 label.setAttribute('data-i18n', option.codename);
134
135 optionEl.append(checkbox, ' ', label);
136
137 if (option?.experimental) {
138 let experimental = document.createElement('span');
139 experimental.classList.add('experimental-label');
140 experimental.setAttribute('data-i18n', 'experimental_label');
141
142 optionEl.append(' ', experimental);
143 }
144
145 optionsContainer.append(optionEl);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200146 }
147
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100148 // Add optional permissions warning label and kill switch component
149 // after each option.
150 let optionalPermissionsWarningLabel = document.createElement('div');
151 optionalPermissionsWarningLabel.classList.add(
152 'optional-permissions-warning-label');
153 optionalPermissionsWarningLabel.setAttribute('hidden', '');
154 optionalPermissionsWarningLabel.setAttribute(
155 'data-feature', option.codename);
156 optionalPermissionsWarningLabel.setAttribute(
157 'data-i18n', 'optionalpermissionswarning_label');
158
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200159 let killSwitchComponent = document.createElement('div');
160 killSwitchComponent.classList.add('kill-switch-label');
161 killSwitchComponent.setAttribute('hidden', '');
162 killSwitchComponent.setAttribute('data-feature', option.codename);
163 killSwitchComponent.setAttribute('data-i18n', 'killswitchenabled');
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200164
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100165 optionsContainer.append(
166 optionalPermissionsWarningLabel, killSwitchComponent);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200167 }
168 }
169
170 if (section?.footerHTML) {
171 optionsContainer.insertAdjacentHTML('beforeend', section.footerHTML);
172 }
173 }
174
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200175 var featuresLink = document.querySelector('.features-link');
176 featuresLink.href = getDocURL('features.md');
177
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200178 var profileIndicatorLink =
179 document.getElementById('profileIndicatorMoreInfo');
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200180 profileIndicatorLink.href = getDocURL('op_indicator.md');
avm999637309b062021-04-22 12:41:08 +0200181 }
182
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200183 i18n();
184
Adrià Vilanova Martínez5f8ae6d2021-12-26 12:25:30 +0100185 // Add custom handlers
186 let manageWorkflowsBtn = document.getElementById('manage-workflows');
187 if (manageWorkflowsBtn)
188 manageWorkflowsBtn.addEventListener('click', e => {
189 e.preventDefault();
190 chrome.tabs.create({
191 url: chrome.runtime.getURL('options/workflows.html'),
192 })
193 });
194
avm99963bf8eece2021-04-22 00:27:03 +0200195 chrome.storage.sync.get(null, function(items) {
196 items = cleanUpOptions(items, false);
197
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200198 // If some features have been force disabled, communicate this to the user.
199 if (items?._forceDisabledFeatures &&
200 items._forceDisabledFeatures.length > 0) {
201 if (window.CONTEXT == 'options') {
202 document.getElementById('kill-switch-warning')
203 .removeAttribute('hidden');
204 }
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200205 }
206
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200207 for (var entry of Object.entries(optionsPrototype)) {
208 var opt = entry[0];
209 var optMeta = entry[1];
210
avm99963bf8eece2021-04-22 00:27:03 +0200211 if (!isOptionShown(opt)) continue;
212
213 if (specialOptions.includes(opt)) {
214 switch (opt) {
215 case 'profileindicatoralt_months':
216 var input = document.createElement('input');
217 input.type = 'number';
218 input.id = 'profileindicatoralt_months';
219 input.max = '12';
220 input.min = '1';
221 input.value = items[opt];
222 input.required = true;
223 document.getElementById('profileindicatoralt_months--container')
224 .appendChild(input);
225 break;
226
227 case 'ccdarktheme_mode':
228 var select = document.createElement('select');
229 select.id = 'ccdarktheme_mode';
230
231 const modes = ['switch', 'system'];
232 for (const mode of modes) {
233 var modeOption = document.createElement('option');
234 modeOption.value = mode;
235 modeOption.textContent =
236 chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
237 if (items.ccdarktheme_mode == mode) modeOption.selected = true;
238 select.appendChild(modeOption);
239 }
240
241 document.getElementById('ccdarktheme_mode--container')
242 .appendChild(select);
243 break;
244
Adrià Vilanova Martínez849ede62022-06-20 19:10:28 +0200245 case 'interopthreadpage_mode':
246 var select = document.createElement('select');
247 select.id = 'interopthreadpage_mode';
248
249 const threadPageModes = ['previous', 'next'];
250 for (const mode of threadPageModes) {
251 let modeOption = document.createElement('option');
252 modeOption.value = mode;
253 modeOption.textContent =
254 chrome.i18n.getMessage('options_interopthreadpage_mode_' + mode);
255 if (items.interopthreadpage_mode == mode) modeOption.selected = true;
256 select.appendChild(modeOption);
257 }
258
259 document.getElementById('interopthreadpage_mode--container')
260 .appendChild(select);
261 break;
262
avm99963bf8eece2021-04-22 00:27:03 +0200263 default:
264 console.warn('Unrecognized option: ' + opt);
265 break;
266 }
267 continue;
268 }
269
270 if (items[opt] === true) document.getElementById(opt).checked = true;
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200271 if (window.CONTEXT == 'options' &&
272 items?._forceDisabledFeatures?.includes?.(opt))
273 document.querySelector('.kill-switch-label[data-feature="' + opt + '"]')
274 .removeAttribute('hidden');
avm99963bf8eece2021-04-22 00:27:03 +0200275 }
276
277 exclusiveOptions.forEach(exclusive => {
278 if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return;
279
280 exclusive.forEach(
281 el => document.getElementById(el).addEventListener('change', e => {
282 if (document.getElementById(exclusive[0]).checked &&
283 document.getElementById(exclusive[1]).checked) {
284 document
285 .getElementById(
286 exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
287 .checked = false;
288 }
289 }));
290 });
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100291
292 // Handle options which need optional permissions.
293 grantedOptPermissions()
294 .then(grantedPerms => {
295 for (const [opt, optMeta] of Object.entries(optionsPrototype)) {
296 if (!optMeta.requiredOptPermissions?.length || !isOptionShown(opt))
297 continue;
298
299 let warningLabel = document.querySelector(
300 '.optional-permissions-warning-label[data-feature="' + opt +
301 '"]');
302
303 // Ensure we have the appropriate permissions when the checkbox
304 // switches from disabled to enabled.
305 //
306 // Also, if the checkbox was indeterminate because the feature was
307 // enabled but not all permissions had been granted, enable the
308 // feature in order to trigger the permission request again.
309 let checkbox = document.getElementById(opt);
310 if (!checkbox) {
311 console.error('Expected checkbox for feature "' + opt + '".');
312 continue;
313 }
314 checkbox.addEventListener('change', () => {
315 if (checkbox.hasAttribute(kClickShouldEnableFeat)) {
316 checkbox.removeAttribute(kClickShouldEnableFeat);
317 checkbox.checked = true;
318 }
319
320 if (checkbox.checked)
321 ensureOptPermissions(opt)
322 .then(granted => {
323 if (granted) {
324 warningLabel.setAttribute('hidden', '');
325 if (!document.querySelector(
326 '.optional-permissions-warning-label:not([hidden])'))
327 document
328 .getElementById('optional-permissions-warning')
329 .setAttribute('hidden', '');
330 } else
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100331 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100332 })
333 .catch(err => {
334 console.error(
335 'An error ocurred while ensuring that the optional ' +
336 'permissions were granted after the checkbox ' +
337 'was clicked for feature "' + opt + '":',
338 err);
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100339 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100340 });
341 });
342
343 // Add warning message if some permissions are missing and the
344 // feature is enabled.
345 if (items[opt] === true) {
346 let shownHeaderMessage = false;
347 missingPermissions(opt, grantedPerms)
348 .then(missingPerms => {
349 console.log(missingPerms);
350 if (missingPerms.length > 0) {
351 checkbox.indeterminate = true;
352 checkbox.setAttribute(kClickShouldEnableFeat, '');
353
354 warningLabel.removeAttribute('hidden');
355
356 if (!shownHeaderMessage) {
357 shownHeaderMessage = true;
358 document.getElementById('optional-permissions-warning')
359 .removeAttribute('hidden');
360 }
361 }
362 })
363 .catch(err => console.error(err));
364 }
365 }
366 })
367 .catch(err => console.error(err));
368
avm99963bf8eece2021-04-22 00:27:03 +0200369 document.querySelector('#save').addEventListener('click', save);
370 });
371});