blob: b4bb42ac6cf0f2a192b85b99cfc01ccc5c746e22 [file] [log] [blame]
Adrià Vilanova Martínez09f35be2021-09-06 19:50:09 +02001import {getExtVersion, isFirefox, 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
38 case 'ccdragndropfix':
39 return document.getElementById(opt).checked || false;
40
41 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') {
102 if (!isReleaseVersion()) {
103 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
avm99963bf8eece2021-04-22 00:27:03 +0200185 chrome.storage.sync.get(null, function(items) {
186 items = cleanUpOptions(items, false);
187
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200188 // If some features have been force disabled, communicate this to the user.
189 if (items?._forceDisabledFeatures &&
190 items._forceDisabledFeatures.length > 0) {
191 if (window.CONTEXT == 'options') {
192 document.getElementById('kill-switch-warning')
193 .removeAttribute('hidden');
194 }
Adrià Vilanova Martínez413cb442021-09-06 00:30:45 +0200195 }
196
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +0200197 for (var entry of Object.entries(optionsPrototype)) {
198 var opt = entry[0];
199 var optMeta = entry[1];
200
avm99963bf8eece2021-04-22 00:27:03 +0200201 if (!isOptionShown(opt)) continue;
202
203 if (specialOptions.includes(opt)) {
204 switch (opt) {
205 case 'profileindicatoralt_months':
206 var input = document.createElement('input');
207 input.type = 'number';
208 input.id = 'profileindicatoralt_months';
209 input.max = '12';
210 input.min = '1';
211 input.value = items[opt];
212 input.required = true;
213 document.getElementById('profileindicatoralt_months--container')
214 .appendChild(input);
215 break;
216
217 case 'ccdarktheme_mode':
218 var select = document.createElement('select');
219 select.id = 'ccdarktheme_mode';
220
221 const modes = ['switch', 'system'];
222 for (const mode of modes) {
223 var modeOption = document.createElement('option');
224 modeOption.value = mode;
225 modeOption.textContent =
226 chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
227 if (items.ccdarktheme_mode == mode) modeOption.selected = true;
228 select.appendChild(modeOption);
229 }
230
231 document.getElementById('ccdarktheme_mode--container')
232 .appendChild(select);
233 break;
234
235 // Firefox doesn't support drag and dropping bookmarks into the text
236 // editor while preserving the bookmark title.
237 case 'ccdragndropfix':
238 var showOption = !isFirefox();
239 if (showOption) {
240 document.getElementById('dragndrop-wrapper')
241 .removeAttribute('hidden');
242
243 if (items[opt] === true)
244 document.getElementById(opt).checked = true;
245 }
246 break;
247
248 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)) {
281 if (!optMeta.requiredOptPermissions?.length || !isOptionShown(opt))
282 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
316 document.getElementById('blockdrafts').checked = false;
317 })
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);
324 document.getElementById('blockdrafts').checked = false;
325 });
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 => {
334 console.log(missingPerms);
335 if (missingPerms.length > 0) {
336 checkbox.indeterminate = true;
337 checkbox.setAttribute(kClickShouldEnableFeat, '');
338
339 warningLabel.removeAttribute('hidden');
340
341 if (!shownHeaderMessage) {
342 shownHeaderMessage = true;
343 document.getElementById('optional-permissions-warning')
344 .removeAttribute('hidden');
345 }
346 }
347 })
348 .catch(err => console.error(err));
349 }
350 }
351 })
352 .catch(err => console.error(err));
353
avm99963bf8eece2021-04-22 00:27:03 +0200354 document.querySelector('#save').addEventListener('click', save);
355 });
356});