Add support for experiments
This change refactors the options logic and adds support for
experiments: a new type of options which are not shown in the options
page (their usage will be similar to Chrome flags).
Experiments can be set from the
chrome-extension://{extension_id}/options/experiments.html page.
This code refactoring simplifies the options definition. Each option now
has a default value, and a context: the place where the option is set
(options, experiments, internal, deprecated).
Change-Id: I358ae07c832acae6b4536788c4dbe12a0e4730bf
diff --git a/src/options/experiments.html b/src/options/experiments.html
new file mode 100644
index 0000000..a49e6c9
--- /dev/null
+++ b/src/options/experiments.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Experiments</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="stylesheet" href="options.css">
+ <link rel="stylesheet" href="chrome_style/chrome_style.css">
+ </head>
+ <body>
+ <main>
+ <h1 data-i18n="experiments_title"></h1>
+ <p data-i18n="experiments_description"></p>
+ <form>
+ <div class="actions"><button id="save" data-i18n="save"></button></div>
+ </form>
+ <div id="save-indicator"></div>
+ </main>
+ <script src="../common/common.js"></script>
+ <script src="experiments_bit.js"></script>
+ <script src="options_common.js"></script>
+ </body>
+</html>
diff --git a/src/options/experiments_bit.js b/src/options/experiments_bit.js
new file mode 100644
index 0000000..af8e086
--- /dev/null
+++ b/src/options/experiments_bit.js
@@ -0,0 +1 @@
+window.CONTEXT = 'experiments';
diff --git a/src/options/options.css b/src/options/options.css
index 06b8c40..7ebe04f 100644
--- a/src/options/options.css
+++ b/src/options/options.css
@@ -2,6 +2,11 @@
padding-top: 16px;
}
+main {
+ margin: auto;
+ max-width: 600px;
+}
+
.features-link {
position: absolute;
top: 8px;
diff --git a/src/options/options.html b/src/options/options.html
index 69cddd8..8dc8129 100644
--- a/src/options/options.html
+++ b/src/options/options.html
@@ -8,41 +8,44 @@
<link rel="stylesheet" href="chrome_style/chrome_style.css">
</head>
<body>
- <a href="https://gerrit.avm99963.com/plugins/gitiles/infinitegforums/+/master/docs/features.md" target="_blank" class="features-link">
- <!--
- Material Design Icon - action/help_outline
- - LICENSE: Apache License Version 2.0
- - Source: https://github.com/google/material-design-icons/
- - Author: Google LLC
- -->
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-4h2v2h-2zm1.61-9.96c-2.06-.3-3.88.97-4.43 2.79-.18.58.26 1.17.87 1.17h.2c.41 0 .74-.29.88-.67.32-.89 1.27-1.5 2.3-1.28.95.2 1.65 1.13 1.57 2.1-.1 1.34-1.62 1.63-2.45 2.88 0 .01-.01.01-.01.02-.01.02-.02.03-.03.05-.09.15-.18.32-.25.5-.01.03-.03.05-.04.08-.01.02-.01.04-.02.07-.12.34-.2.75-.2 1.25h2c0-.42.11-.77.28-1.07.02-.03.03-.06.05-.09.08-.14.18-.27.28-.39.01-.01.02-.03.03-.04.1-.12.21-.23.33-.34.96-.91 2.26-1.65 1.99-3.56-.24-1.74-1.61-3.21-3.35-3.47z"/></svg>
- </a>
- <form>
- <div class="option"><input type="checkbox" id="list"> <label for="list" data-i18n="list"></label></div>
- <div class="option"><input type="checkbox" id="thread"> <label for="thread" data-i18n="thread"></label></div>
- <div class="option"><input type="checkbox" id="threadall"> <label for="threadall" data-i18n="threadall"></label></div>
- <h4 data-i18n="enhancements"></h4>
- <div class="option"><input type="checkbox" id="fixedtoolbar"> <label for="fixedtoolbar" data-i18n="fixedtoolbar"></label></div>
- <div class="option"><input type="checkbox" id="redirect"> <label for="redirect" data-i18n="redirect"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><input type="checkbox" id="history"> <label for="history" data-i18n="history"></label></div>
- <div class="option"><input type="checkbox" id="loaddrafts"> <label for="loaddrafts" data-i18n="loaddrafts"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><input type="checkbox" id="increasecontrast"> <label for="increasecontrast" data-i18n="increasecontrast"></label></div>
- <div class="option"><input type="checkbox" id="stickysidebarheaders"> <label for="stickysidebarheaders" data-i18n="stickysidebarheaders"></label></div>
- <div class="option"><input type="checkbox" id="ccdarktheme"> <label for="ccdarktheme" data-i18n="ccdarktheme"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><input type="checkbox" id="ccforcehidedrawer"> <label for="ccforcehidedrawer" data-i18n="ccforcehidedrawer"></label></div>
- <div id="dragndrop-wrapper" class="option" hidden><input type="checkbox" id="ccdragndropfix"> <label for="ccdragndropfix" data-i18n="ccdragndropfix"></label></div>
- <div class="option"><input type="checkbox" id="batchlock"> <label for="batchlock" data-i18n="batchlock"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><input type="checkbox" id="smei_sortdirection"> <label for="smei_sortdirection" data-i18n="smei_sortdirection"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><input type="checkbox" id="enhancedannouncementsdot"> <label for="enhancedannouncementsdot" data-i18n="enhancedannouncementsdot"></label></div>
- <div class="option"><input type="checkbox" id="repositionexpandthread"> <label for="repositionexpandthread" data-i18n="repositionexpandthread"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <h4 data-i18n="profileindicator_header"></h4>
- <div class="option"><input type="checkbox" id="profileindicator"> <label for="profileindicator" data-i18n="profileindicator"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><input type="checkbox" id="profileindicatoralt"> <label for="profileindicatoralt" data-i18n="profileindicatoralt"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
- <div class="option"><a href="https://gerrit.avm99963.com/plugins/gitiles/infinitegforums/+/refs/heads/master/docs/op_indicator.md" target="_blank" rel="noreferrer noopener" data-i18n="profileindicator_moreinfo"></a></div>
- <div class="actions"><button id="save" data-i18n="save"></button></div>
- </form>
- <div id="save-indicator"></div>
+ <main>
+ <a href="https://gerrit.avm99963.com/plugins/gitiles/infinitegforums/+/master/docs/features.md" target="_blank" class="features-link">
+ <!--
+ Material Design Icon - action/help_outline
+ - LICENSE: Apache License Version 2.0
+ - Source: https://github.com/google/material-design-icons/
+ - Author: Google LLC
+ -->
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-4h2v2h-2zm1.61-9.96c-2.06-.3-3.88.97-4.43 2.79-.18.58.26 1.17.87 1.17h.2c.41 0 .74-.29.88-.67.32-.89 1.27-1.5 2.3-1.28.95.2 1.65 1.13 1.57 2.1-.1 1.34-1.62 1.63-2.45 2.88 0 .01-.01.01-.01.02-.01.02-.02.03-.03.05-.09.15-.18.32-.25.5-.01.03-.03.05-.04.08-.01.02-.01.04-.02.07-.12.34-.2.75-.2 1.25h2c0-.42.11-.77.28-1.07.02-.03.03-.06.05-.09.08-.14.18-.27.28-.39.01-.01.02-.03.03-.04.1-.12.21-.23.33-.34.96-.91 2.26-1.65 1.99-3.56-.24-1.74-1.61-3.21-3.35-3.47z"/></svg>
+ </a>
+ <form>
+ <div class="option"><input type="checkbox" id="list"> <label for="list" data-i18n="list"></label></div>
+ <div class="option"><input type="checkbox" id="thread"> <label for="thread" data-i18n="thread"></label></div>
+ <div class="option"><input type="checkbox" id="threadall"> <label for="threadall" data-i18n="threadall"></label></div>
+ <h4 data-i18n="enhancements"></h4>
+ <div class="option"><input type="checkbox" id="fixedtoolbar"> <label for="fixedtoolbar" data-i18n="fixedtoolbar"></label></div>
+ <div class="option"><input type="checkbox" id="redirect"> <label for="redirect" data-i18n="redirect"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><input type="checkbox" id="history"> <label for="history" data-i18n="history"></label></div>
+ <div class="option"><input type="checkbox" id="loaddrafts"> <label for="loaddrafts" data-i18n="loaddrafts"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><input type="checkbox" id="increasecontrast"> <label for="increasecontrast" data-i18n="increasecontrast"></label></div>
+ <div class="option"><input type="checkbox" id="stickysidebarheaders"> <label for="stickysidebarheaders" data-i18n="stickysidebarheaders"></label></div>
+ <div class="option"><input type="checkbox" id="ccdarktheme"> <label for="ccdarktheme" data-i18n="ccdarktheme"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><input type="checkbox" id="ccforcehidedrawer"> <label for="ccforcehidedrawer" data-i18n="ccforcehidedrawer"></label></div>
+ <div id="dragndrop-wrapper" class="option" hidden><input type="checkbox" id="ccdragndropfix"> <label for="ccdragndropfix" data-i18n="ccdragndropfix"></label></div>
+ <div class="option"><input type="checkbox" id="batchlock"> <label for="batchlock" data-i18n="batchlock"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><input type="checkbox" id="smei_sortdirection"> <label for="smei_sortdirection" data-i18n="smei_sortdirection"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><input type="checkbox" id="enhancedannouncementsdot"> <label for="enhancedannouncementsdot" data-i18n="enhancedannouncementsdot"></label></div>
+ <div class="option"><input type="checkbox" id="repositionexpandthread"> <label for="repositionexpandthread" data-i18n="repositionexpandthread"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <h4 data-i18n="profileindicator_header"></h4>
+ <div class="option"><input type="checkbox" id="profileindicator"> <label for="profileindicator" data-i18n="profileindicator"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><input type="checkbox" id="profileindicatoralt"> <label for="profileindicatoralt" data-i18n="profileindicatoralt"></label> <span class="experimental-label" data-i18n="experimental_label"></span></div>
+ <div class="option"><a href="https://gerrit.avm99963.com/plugins/gitiles/infinitegforums/+/refs/heads/master/docs/op_indicator.md" target="_blank" rel="noreferrer noopener" data-i18n="profileindicator_moreinfo"></a></div>
+ <div class="actions"><button id="save" data-i18n="save"></button></div>
+ </form>
+ <div id="save-indicator"></div>
+ </main>
<script src="../common/common.js"></script>
- <script src="options.js"></script>
+ <script src="options_bit.js"></script>
+ <script src="options_common.js"></script>
</body>
</html>
diff --git a/src/options/options.js b/src/options/options.js
deleted file mode 100644
index 46913d4..0000000
--- a/src/options/options.js
+++ /dev/null
@@ -1,153 +0,0 @@
-var savedSuccessfullyTimeout = null;
-
-const exclusiveOptions = [['thread', 'threadall']];
-
-function save(e) {
- var options = defaultOptions;
-
- // Validation checks before saving
- var months = document.getElementById('profileindicatoralt_months');
- if (!months.checkValidity()) {
- console.warn(months.validationMessage);
- return;
- }
-
- e.preventDefault();
-
- // Save
- Object.keys(options).forEach(function(opt) {
- if (deprecatedOptions.includes(opt)) return;
-
- if (specialOptions.includes(opt)) {
- switch (opt) {
- case 'profileindicatoralt_months':
- options[opt] = document.getElementById(opt).value || 12;
- break;
-
- case 'ccdarktheme_mode':
- options[opt] = document.getElementById(opt).value || 'switch';
- break;
-
- // This option is controlled directly in the Community Console.
- case 'ccdarktheme_switch_enabled':
- break;
-
- case 'ccdragndropfix':
- options[opt] = document.getElementById(opt).checked || false;
- break;
-
- default:
- console.warn('Unrecognized option: ' + opt);
- break;
- }
- return;
- }
-
- options[opt] = document.getElementById(opt).checked || false;
- });
-
- chrome.storage.sync.set(options, function() {
- window.close();
-
- // In browsers like Firefox window.close is not supported:
- if (savedSuccessfullyTimeout !== null)
- window.clearTimeout(savedSuccessfullyTimeout);
-
- document.getElementById('save-indicator').innerText =
- '✓ ' + chrome.i18n.getMessage('options_saved');
- savedSuccessfullyTimeout = window.setTimeout(_ => {
- document.getElementById('save-indicator').innerText = '';
- }, 3699);
- });
-}
-
-function i18n() {
- document.querySelectorAll('[data-i18n]')
- .forEach(
- el => el.innerHTML = chrome.i18n.getMessage(
- 'options_' + el.getAttribute('data-i18n')));
-}
-
-window.addEventListener('load', function() {
- i18n();
-
- chrome.storage.sync.get(null, function(items) {
- items = cleanUpOptions(items);
-
- Object.keys(defaultOptions).forEach(function(opt) {
- if (deprecatedOptions.includes(opt)) return;
-
- if (specialOptions.includes(opt)) {
- switch (opt) {
- case 'profileindicatoralt_months':
- var input = document.createElement('input');
- input.type = 'number';
- input.id = 'profileindicatoralt_months';
- input.max = '12';
- input.min = '1';
- input.value = items[opt];
- input.required = true;
- document.getElementById('profileindicatoralt_months--container')
- .appendChild(input);
- break;
-
- case 'ccdarktheme_mode':
- var select = document.createElement('select');
- select.id = 'ccdarktheme_mode';
-
- const modes = ['switch', 'system'];
- for (const mode of modes) {
- var modeOption = document.createElement('option');
- modeOption.value = mode;
- modeOption.textContent =
- chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
- if (items.ccdarktheme_mode == mode) modeOption.selected = true;
- select.appendChild(modeOption);
- }
-
- document.getElementById('ccdarktheme_mode--container')
- .appendChild(select);
- break;
-
- // This option is controlled directly in the Community Console.
- case 'ccdarktheme_switch_enabled':
- break;
-
- // Firefox doesn't support drag and dropping bookmarks into the text
- // editor while preserving the bookmark title.
- case 'ccdragndropfix':
- var showOption = !isFirefox();
- if (showOption) {
- document.getElementById('dragndrop-wrapper')
- .removeAttribute('hidden');
-
- if (items[opt] === true)
- document.getElementById(opt).checked = true;
- }
- break;
-
- default:
- console.warn('Unrecognized option: ' + opt);
- break;
- }
- return;
- }
-
- if (items[opt] === true) document.getElementById(opt).checked = true;
- });
-
- exclusiveOptions.forEach(exclusive => {
- exclusive.forEach(
- el => document.getElementById(el).addEventListener('change', e => {
- if (document.getElementById(exclusive[0]).checked &&
- document.getElementById(exclusive[1]).checked) {
- document
- .getElementById(
- exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
- .checked = false;
- }
- }));
- });
- document.querySelector('#save').addEventListener('click', save);
- });
-});
diff --git a/src/options/options_bit.js b/src/options/options_bit.js
new file mode 100644
index 0000000..a6186d3
--- /dev/null
+++ b/src/options/options_bit.js
@@ -0,0 +1 @@
+window.CONTEXT = 'options';
diff --git a/src/options/options_common.js b/src/options/options_common.js
new file mode 100644
index 0000000..10fee88
--- /dev/null
+++ b/src/options/options_common.js
@@ -0,0 +1,157 @@
+var savedSuccessfullyTimeout = null;
+
+const exclusiveOptions = [['thread', 'threadall']];
+
+// Get the value of the option set in the options/experiments page
+function getOptionValue(opt) {
+ if (specialOptions.includes(opt)) {
+ switch (opt) {
+ case 'profileindicatoralt_months':
+ return document.getElementById(opt).value || 12;
+
+ case 'ccdarktheme_mode':
+ return document.getElementById(opt).value || 'switch';
+
+ case 'ccdragndropfix':
+ return document.getElementById(opt).checked || false;
+
+ default:
+ console.warn('Unrecognized option: ' + opt);
+ return undefined;
+ }
+ }
+
+ return document.getElementById(opt).checked || false;
+}
+
+// Returns whether the option is included in the current context
+function isOptionShown(opt) {
+ if (!optionsPrototype.hasOwnProperty(opt)) return false;
+ return optionsPrototype[opt].context == window.CONTEXT;
+}
+
+function save(e) {
+ // Validation checks before saving
+ if (isOptionShown('profileindicatoralt_months')) {
+ var months = document.getElementById('profileindicatoralt_months');
+ if (!months.checkValidity()) {
+ console.warn(months.validationMessage);
+ return;
+ }
+ }
+
+ e.preventDefault();
+
+ chrome.storage.sync.get(null, function(items) {
+ var options = cleanUpOptions(items, true);
+
+ // Save
+ Object.keys(options).forEach(function(opt) {
+ if (!isOptionShown(opt)) return;
+ options[opt] = getOptionValue(opt);
+ });
+
+ chrome.storage.sync.set(options, function() {
+ window.close();
+
+ // In browsers like Firefox window.close is not supported:
+ if (savedSuccessfullyTimeout !== null)
+ window.clearTimeout(savedSuccessfullyTimeout);
+
+ document.getElementById('save-indicator').innerText =
+ '✓ ' + chrome.i18n.getMessage('options_saved');
+ savedSuccessfullyTimeout = window.setTimeout(_ => {
+ document.getElementById('save-indicator').innerText = '';
+ }, 3699);
+ });
+ });
+}
+
+function i18n() {
+ document.querySelectorAll('[data-i18n]')
+ .forEach(
+ el => el.innerHTML = chrome.i18n.getMessage(
+ 'options_' + el.getAttribute('data-i18n')));
+}
+
+window.addEventListener('load', function() {
+ i18n();
+
+ chrome.storage.sync.get(null, function(items) {
+ items = cleanUpOptions(items, false);
+
+ for ([opt, optMeta] of Object.entries(optionsPrototype)) {
+ if (!isOptionShown(opt)) continue;
+
+ if (specialOptions.includes(opt)) {
+ switch (opt) {
+ case 'profileindicatoralt_months':
+ var input = document.createElement('input');
+ input.type = 'number';
+ input.id = 'profileindicatoralt_months';
+ input.max = '12';
+ input.min = '1';
+ input.value = items[opt];
+ input.required = true;
+ document.getElementById('profileindicatoralt_months--container')
+ .appendChild(input);
+ break;
+
+ case 'ccdarktheme_mode':
+ var select = document.createElement('select');
+ select.id = 'ccdarktheme_mode';
+
+ const modes = ['switch', 'system'];
+ for (const mode of modes) {
+ var modeOption = document.createElement('option');
+ modeOption.value = mode;
+ modeOption.textContent =
+ chrome.i18n.getMessage('options_ccdarktheme_mode_' + mode);
+ if (items.ccdarktheme_mode == mode) modeOption.selected = true;
+ select.appendChild(modeOption);
+ }
+
+ document.getElementById('ccdarktheme_mode--container')
+ .appendChild(select);
+ break;
+
+ // Firefox doesn't support drag and dropping bookmarks into the text
+ // editor while preserving the bookmark title.
+ case 'ccdragndropfix':
+ var showOption = !isFirefox();
+ if (showOption) {
+ document.getElementById('dragndrop-wrapper')
+ .removeAttribute('hidden');
+
+ if (items[opt] === true)
+ document.getElementById(opt).checked = true;
+ }
+ break;
+
+ default:
+ console.warn('Unrecognized option: ' + opt);
+ break;
+ }
+ continue;
+ }
+
+ if (items[opt] === true) document.getElementById(opt).checked = true;
+ }
+
+ exclusiveOptions.forEach(exclusive => {
+ if (!isOptionShown(exclusive[0]) || !isOptionShown(exclusive[1])) return;
+
+ exclusive.forEach(
+ el => document.getElementById(el).addEventListener('change', e => {
+ if (document.getElementById(exclusive[0]).checked &&
+ document.getElementById(exclusive[1]).checked) {
+ document
+ .getElementById(
+ exclusive[(e.currentTarget.id == exclusive[0] ? 1 : 0)])
+ .checked = false;
+ }
+ }));
+ });
+ document.querySelector('#save').addEventListener('click', save);
+ });
+});