blob: abfc30667fb0e4f44a9e01db6cf1ef1255f4907e [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
avm99963bf8eece2021-04-22 00:27:03 +020038 default:
39 console.warn('Unrecognized option: ' + opt);
40 return undefined;
41 }
42 }
43
44 return document.getElementById(opt).checked || false;
45}
46
47// Returns whether the option is included in the current context
48function isOptionShown(opt) {
49 if (!optionsPrototype.hasOwnProperty(opt)) return false;
50 return optionsPrototype[opt].context == window.CONTEXT;
51}
52
53function save(e) {
54 // Validation checks before saving
55 if (isOptionShown('profileindicatoralt_months')) {
56 var months = document.getElementById('profileindicatoralt_months');
57 if (!months.checkValidity()) {
58 console.warn(months.validationMessage);
59 return;
60 }
61 }
62
63 e.preventDefault();
64
65 chrome.storage.sync.get(null, function(items) {
66 var options = cleanUpOptions(items, true);
67
68 // Save
69 Object.keys(options).forEach(function(opt) {
70 if (!isOptionShown(opt)) return;
71 options[opt] = getOptionValue(opt);
72 });
73
74 chrome.storage.sync.set(options, function() {
75 window.close();
76
77 // In browsers like Firefox window.close is not supported:
78 if (savedSuccessfullyTimeout !== null)
79 window.clearTimeout(savedSuccessfullyTimeout);
80
81 document.getElementById('save-indicator').innerText =
82 '✓ ' + chrome.i18n.getMessage('options_saved');
83 savedSuccessfullyTimeout = window.setTimeout(_ => {
84 document.getElementById('save-indicator').innerText = '';
85 }, 3699);
86 });
87 });
88}
89
90function i18n() {
91 document.querySelectorAll('[data-i18n]')
92 .forEach(
93 el => el.innerHTML = chrome.i18n.getMessage(
94 'options_' + el.getAttribute('data-i18n')));
95}
96
97window.addEventListener('load', function() {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +020098 if (window.CONTEXT == 'options') {
Adrià Vilanova Martínez0335b512022-01-21 13:34:59 +010099 if (!isProdVersion()) {
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200100 var experimentsLink = document.querySelector('.experiments-link');
101 experimentsLink.removeAttribute('hidden');
102 experimentsLink.addEventListener('click', _ => chrome.tabs.create({
103 url: chrome.runtime.getURL('options/experiments.html'),
104 }));
105 }
106
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200107 // Add options to page
108 let optionsContainer = document.getElementById('options-container');
109 for (let section of optionsPage.sections) {
110 if (section?.name) {
111 let sectionHeader = document.createElement('h4');
112 sectionHeader.setAttribute('data-i18n', section.name);
113 optionsContainer.append(sectionHeader);
114 }
115
116 if (section?.options) {
117 for (let option of section.options) {
118 if (option?.customHTML) {
119 optionsContainer.insertAdjacentHTML('beforeend', option.customHTML);
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200120 } else {
121 let optionEl = document.createElement('div');
122 optionEl.classList.add('option');
123
124 let checkbox = document.createElement('input');
125 checkbox.setAttribute('type', 'checkbox');
126 checkbox.id = option.codename;
127
128 let label = document.createElement('label');
129 label.setAttribute('for', checkbox.id);
130 label.setAttribute('data-i18n', option.codename);
131
132 optionEl.append(checkbox, ' ', label);
133
134 if (option?.experimental) {
135 let experimental = document.createElement('span');
136 experimental.classList.add('experimental-label');
137 experimental.setAttribute('data-i18n', 'experimental_label');
138
139 optionEl.append(' ', experimental);
140 }
141
142 optionsContainer.append(optionEl);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200143 }
144
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100145 // Add optional permissions warning label and kill switch component
146 // after each option.
147 let optionalPermissionsWarningLabel = document.createElement('div');
148 optionalPermissionsWarningLabel.classList.add(
149 'optional-permissions-warning-label');
150 optionalPermissionsWarningLabel.setAttribute('hidden', '');
151 optionalPermissionsWarningLabel.setAttribute(
152 'data-feature', option.codename);
153 optionalPermissionsWarningLabel.setAttribute(
154 'data-i18n', 'optionalpermissionswarning_label');
155
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200156 let killSwitchComponent = document.createElement('div');
157 killSwitchComponent.classList.add('kill-switch-label');
158 killSwitchComponent.setAttribute('hidden', '');
159 killSwitchComponent.setAttribute('data-feature', option.codename);
160 killSwitchComponent.setAttribute('data-i18n', 'killswitchenabled');
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200161
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100162 optionsContainer.append(
163 optionalPermissionsWarningLabel, killSwitchComponent);
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200164 }
165 }
166
167 if (section?.footerHTML) {
168 optionsContainer.insertAdjacentHTML('beforeend', section.footerHTML);
169 }
170 }
171
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200172 var featuresLink = document.querySelector('.features-link');
173 featuresLink.href = getDocURL('features.md');
174
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200175 var profileIndicatorLink =
176 document.getElementById('profileIndicatorMoreInfo');
Adrià Vilanova Martíneze32adc42021-08-30 17:16:49 +0200177 profileIndicatorLink.href = getDocURL('op_indicator.md');
avm999637309b062021-04-22 12:41:08 +0200178 }
179
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +0200180 i18n();
181
Adrià Vilanova Martínez5f8ae6d2021-12-26 12:25:30 +0100182 // Add custom handlers
183 let manageWorkflowsBtn = document.getElementById('manage-workflows');
184 if (manageWorkflowsBtn)
185 manageWorkflowsBtn.addEventListener('click', e => {
186 e.preventDefault();
187 chrome.tabs.create({
188 url: chrome.runtime.getURL('options/workflows.html'),
189 })
190 });
191
avm99963bf8eece2021-04-22 00:27:03 +0200192 chrome.storage.sync.get(null, function(items) {
193 items = cleanUpOptions(items, false);
194
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200195 // If some features have been force disabled, communicate this to the user.
196 if (items?._forceDisabledFeatures &&
197 items._forceDisabledFeatures.length > 0) {
198 if (window.CONTEXT == 'options') {
199 document.getElementById('kill-switch-warning')
200 .removeAttribute('hidden');
201 }
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200202 }
203
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200204 for (var entry of Object.entries(optionsPrototype)) {
205 var opt = entry[0];
206 var optMeta = entry[1];
207
avm99963bf8eece2021-04-22 00:27:03 +0200208 if (!isOptionShown(opt)) continue;
209
210 if (specialOptions.includes(opt)) {
211 switch (opt) {
212 case 'profileindicatoralt_months':
213 var input = document.createElement('input');
214 input.type = 'number';
215 input.id = 'profileindicatoralt_months';
216 input.max = '12';
217 input.min = '1';
218 input.value = items[opt];
219 input.required = true;
220 document.getElementById('profileindicatoralt_months--container')
221 .appendChild(input);
222 break;
223
224 case 'ccdarktheme_mode':
225 var select = document.createElement('select');
226 select.id = 'ccdarktheme_mode';
227
228 const modes = ['switch', 'system'];
229 for (const mode of modes) {
230 var modeOption = document.createElement('option');
231 modeOption.value = mode;
232 modeOption.textContent =
233 chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
234 if (items.ccdarktheme_mode == mode) modeOption.selected = true;
235 select.appendChild(modeOption);
236 }
237
238 document.getElementById('ccdarktheme_mode--container')
239 .appendChild(select);
240 break;
241
avm99963bf8eece2021-04-22 00:27:03 +0200242 default:
243 console.warn('Unrecognized option: ' + opt);
244 break;
245 }
246 continue;
247 }
248
249 if (items[opt] === true) document.getElementById(opt).checked = true;
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200250 if (window.CONTEXT == 'options' &&
251 items?._forceDisabledFeatures?.includes?.(opt))
252 document.querySelector('.kill-switch-label[data-feature="' + opt + '"]')
253 .removeAttribute('hidden');
avm99963bf8eece2021-04-22 00:27:03 +0200254 }
255
256 exclusiveOptions.forEach(exclusive => {
257 if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return;
258
259 exclusive.forEach(
260 el => document.getElementById(el).addEventListener('change', e => {
261 if (document.getElementById(exclusive[0]).checked &&
262 document.getElementById(exclusive[1]).checked) {
263 document
264 .getElementById(
265 exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
266 .checked = false;
267 }
268 }));
269 });
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100270
271 // Handle options which need optional permissions.
272 grantedOptPermissions()
273 .then(grantedPerms => {
274 for (const [opt, optMeta] of Object.entries(optionsPrototype)) {
275 if (!optMeta.requiredOptPermissions?.length || !isOptionShown(opt))
276 continue;
277
278 let warningLabel = document.querySelector(
279 '.optional-permissions-warning-label[data-feature="' + opt +
280 '"]');
281
282 // Ensure we have the appropriate permissions when the checkbox
283 // switches from disabled to enabled.
284 //
285 // Also, if the checkbox was indeterminate because the feature was
286 // enabled but not all permissions had been granted, enable the
287 // feature in order to trigger the permission request again.
288 let checkbox = document.getElementById(opt);
289 if (!checkbox) {
290 console.error('Expected checkbox for feature "' + opt + '".');
291 continue;
292 }
293 checkbox.addEventListener('change', () => {
294 if (checkbox.hasAttribute(kClickShouldEnableFeat)) {
295 checkbox.removeAttribute(kClickShouldEnableFeat);
296 checkbox.checked = true;
297 }
298
299 if (checkbox.checked)
300 ensureOptPermissions(opt)
301 .then(granted => {
302 if (granted) {
303 warningLabel.setAttribute('hidden', '');
304 if (!document.querySelector(
305 '.optional-permissions-warning-label:not([hidden])'))
306 document
307 .getElementById('optional-permissions-warning')
308 .setAttribute('hidden', '');
309 } else
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100310 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100311 })
312 .catch(err => {
313 console.error(
314 'An error ocurred while ensuring that the optional ' +
315 'permissions were granted after the checkbox ' +
316 'was clicked for feature "' + opt + '":',
317 err);
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100318 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100319 });
320 });
321
322 // Add warning message if some permissions are missing and the
323 // feature is enabled.
324 if (items[opt] === true) {
325 let shownHeaderMessage = false;
326 missingPermissions(opt, grantedPerms)
327 .then(missingPerms => {
328 console.log(missingPerms);
329 if (missingPerms.length > 0) {
330 checkbox.indeterminate = true;
331 checkbox.setAttribute(kClickShouldEnableFeat, '');
332
333 warningLabel.removeAttribute('hidden');
334
335 if (!shownHeaderMessage) {
336 shownHeaderMessage = true;
337 document.getElementById('optional-permissions-warning')
338 .removeAttribute('hidden');
339 }
340 }
341 })
342 .catch(err => console.error(err));
343 }
344 }
345 })
346 .catch(err => console.error(err));
347
avm99963bf8eece2021-04-22 00:27:03 +0200348 document.querySelector('#save').addEventListener('click', save);
349 });
350});