blob: 863232d82472d155b7092c4594f5aa9ac416c9fe [file] [log] [blame]
Adrià Vilanova Martínezeebc0ac2022-01-05 14:45:53 +01001import {getExtVersion, isReleaseVersion} 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) {
22 if (!isReleaseVersion()) return getDocURLWithRef(doc, 'HEAD');
23
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') {
99 if (!isReleaseVersion()) {
100 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
avm99963bf8eece2021-04-22 00:27:03 +0200182 chrome.storage.sync.get(null, function(items) {
183 items = cleanUpOptions(items, false);
184
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200185 // If some features have been force disabled, communicate this to the user.
186 if (items?._forceDisabledFeatures &&
187 items._forceDisabledFeatures.length > 0) {
188 if (window.CONTEXT == 'options') {
189 document.getElementById('kill-switch-warning')
190 .removeAttribute('hidden');
191 }
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200192 }
193
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200194 for (var entry of Object.entries(optionsPrototype)) {
195 var opt = entry[0];
196 var optMeta = entry[1];
197
avm99963bf8eece2021-04-22 00:27:03 +0200198 if (!isOptionShown(opt)) continue;
199
200 if (specialOptions.includes(opt)) {
201 switch (opt) {
202 case 'profileindicatoralt_months':
203 var input = document.createElement('input');
204 input.type = 'number';
205 input.id = 'profileindicatoralt_months';
206 input.max = '12';
207 input.min = '1';
208 input.value = items[opt];
209 input.required = true;
210 document.getElementById('profileindicatoralt_months--container')
211 .appendChild(input);
212 break;
213
214 case 'ccdarktheme_mode':
215 var select = document.createElement('select');
216 select.id = 'ccdarktheme_mode';
217
218 const modes = ['switch', 'system'];
219 for (const mode of modes) {
220 var modeOption = document.createElement('option');
221 modeOption.value = mode;
222 modeOption.textContent =
223 chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
224 if (items.ccdarktheme_mode == mode) modeOption.selected = true;
225 select.appendChild(modeOption);
226 }
227
228 document.getElementById('ccdarktheme_mode--container')
229 .appendChild(select);
230 break;
231
avm99963bf8eece2021-04-22 00:27:03 +0200232 default:
233 console.warn('Unrecognized option: ' + opt);
234 break;
235 }
236 continue;
237 }
238
239 if (items[opt] === true) document.getElementById(opt).checked = true;
Adrià Vilanova Martínezc591bf72021-09-06 20:23:06 +0200240 if (window.CONTEXT == 'options' &&
241 items?._forceDisabledFeatures?.includes?.(opt))
242 document.querySelector('.kill-switch-label[data-feature="' + opt + '"]')
243 .removeAttribute('hidden');
avm99963bf8eece2021-04-22 00:27:03 +0200244 }
245
246 exclusiveOptions.forEach(exclusive => {
247 if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return;
248
249 exclusive.forEach(
250 el => document.getElementById(el).addEventListener('change', e => {
251 if (document.getElementById(exclusive[0]).checked &&
252 document.getElementById(exclusive[1]).checked) {
253 document
254 .getElementById(
255 exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
256 .checked = false;
257 }
258 }));
259 });
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100260
261 // Handle options which need optional permissions.
262 grantedOptPermissions()
263 .then(grantedPerms => {
264 for (const [opt, optMeta] of Object.entries(optionsPrototype)) {
265 if (!optMeta.requiredOptPermissions?.length || !isOptionShown(opt))
266 continue;
267
268 let warningLabel = document.querySelector(
269 '.optional-permissions-warning-label[data-feature="' + opt +
270 '"]');
271
272 // Ensure we have the appropriate permissions when the checkbox
273 // switches from disabled to enabled.
274 //
275 // Also, if the checkbox was indeterminate because the feature was
276 // enabled but not all permissions had been granted, enable the
277 // feature in order to trigger the permission request again.
278 let checkbox = document.getElementById(opt);
279 if (!checkbox) {
280 console.error('Expected checkbox for feature "' + opt + '".');
281 continue;
282 }
283 checkbox.addEventListener('change', () => {
284 if (checkbox.hasAttribute(kClickShouldEnableFeat)) {
285 checkbox.removeAttribute(kClickShouldEnableFeat);
286 checkbox.checked = true;
287 }
288
289 if (checkbox.checked)
290 ensureOptPermissions(opt)
291 .then(granted => {
292 if (granted) {
293 warningLabel.setAttribute('hidden', '');
294 if (!document.querySelector(
295 '.optional-permissions-warning-label:not([hidden])'))
296 document
297 .getElementById('optional-permissions-warning')
298 .setAttribute('hidden', '');
299 } else
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100300 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100301 })
302 .catch(err => {
303 console.error(
304 'An error ocurred while ensuring that the optional ' +
305 'permissions were granted after the checkbox ' +
306 'was clicked for feature "' + opt + '":',
307 err);
Adrià Vilanova Martínezd7ada322022-01-05 04:15:46 +0100308 document.getElementById(opt).checked = false;
Adrià Vilanova Martínez5120dbb2022-01-04 03:21:17 +0100309 });
310 });
311
312 // Add warning message if some permissions are missing and the
313 // feature is enabled.
314 if (items[opt] === true) {
315 let shownHeaderMessage = false;
316 missingPermissions(opt, grantedPerms)
317 .then(missingPerms => {
318 console.log(missingPerms);
319 if (missingPerms.length > 0) {
320 checkbox.indeterminate = true;
321 checkbox.setAttribute(kClickShouldEnableFeat, '');
322
323 warningLabel.removeAttribute('hidden');
324
325 if (!shownHeaderMessage) {
326 shownHeaderMessage = true;
327 document.getElementById('optional-permissions-warning')
328 .removeAttribute('hidden');
329 }
330 }
331 })
332 .catch(err => console.error(err));
333 }
334 }
335 })
336 .catch(err => console.error(err));
337
avm99963bf8eece2021-04-22 00:27:03 +0200338 document.querySelector('#save').addEventListener('click', save);
339 });
340});