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/docs/developers/add_feature.md b/docs/developers/add_feature.md
index 1f3fe0d..15053e4 100644
--- a/docs/developers/add_feature.md
+++ b/docs/developers/add_feature.md
@@ -9,8 +9,8 @@
### How to add the feature switch option
1. First of all, think of a short codename for the feature.
-2. Modify the `//src/common/common.js` file by adding the default value for the
-option in the `defaultOptions` object.
+2. Modify the `//src/common/common.js` file by adding an entry in the
+`optionsPrototype` object.
- All features should have the `false` value set as a default, so existing
users have to explicitly enable the option after they receive the extension
update. Otherwise, it might cause confusion, because users wouldn't know if
@@ -59,7 +59,7 @@
codename appended by an underscore and a suffix
(`{{feature_codename}}_{{suffix}}`).
2. Modify the `//src/common/common.js` file by doing the following things:
- 1. Add a default value for the option in the `defaultOptions` object.
+ 1. Add an entry for the option in the `optionsPrototype` object.
2. Append the option's codename to the `specialOptions` object. This is so
the option can be handled in a specific way when showing/saving it in the
options page, or so it is handled outside of the options page.
diff --git a/src/_locales/ca/messages.json b/src/_locales/ca/messages.json
index c4c74cc..2c3ed92 100644
--- a/src/_locales/ca/messages.json
+++ b/src/_locales/ca/messages.json
@@ -127,6 +127,14 @@
"message": "Desat",
"description": "Message which appears in the options page when the settings are saved"
},
+ "options_experiments_title": {
+ "message": "Experiments",
+ "description": "Title of the experiments page: a page where highly experimental options can be set."
+ },
+ "options_experiments_description": {
+ "message": "<i>Welchrome!</i> Aquí a sota trobaràs una llista d'experiments: funcions que estan en desenvolupament i que encara no estan llestes del tot per ser llençades. Són altament experimentals i podrien trencar-se i/o causar problemes, però si ets valent/a, sisplau activa les que més t'interessin i <a href='https://github.com/avm99963/infinitegforums/discussions/categories/feedback'>envia feedback</a>!",
+ "description": "Description shown in the beginning of the experiments page, below the title."
+ },
"inject_links": {
"message": "Enllaços",
"description": "Heading which we use before the 'previous post' link in a user profile in TW"
diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index f3b71d3..7c8b45b 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -127,6 +127,14 @@
"message": "Saved",
"description": "Message which appears in the options page when the settings are saved"
},
+ "options_experiments_title": {
+ "message": "Experiments",
+ "description": "Title of the experiments page: a page where highly experimental options can be set."
+ },
+ "options_experiments_description": {
+ "message": "Welchrome! Below you'll a find a list of experiments: features which are in development, and are not quite ready for launch. They are highly experimental and so might break and/or cause issues, but if you're brave, please do enable the ones you're most interested in, and <a href='https://github.com/avm99963/infinitegforums/discussions/categories/feedback'>give feedback</a>!",
+ "description": "Description shown in the beginning of the experiments page, below the title."
+ },
"inject_links": {
"message": "Links",
"description": "Heading which we use before the 'previous post' link in a user profile in TW"
diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json
index 1ac1f11..ae9fbdd 100644
--- a/src/_locales/es/messages.json
+++ b/src/_locales/es/messages.json
@@ -127,6 +127,14 @@
"message": "Guardado",
"description": "Message which appears in the options page when the settings are saved"
},
+ "options_experiments_title": {
+ "message": "Experimentos",
+ "description": "Title of the experiments page: a page where highly experimental options can be set."
+ },
+ "options_experiments_description": {
+ "message": "<i>Welchrome!</i> Aquí abajo encontrarás una lista de experimentos: funciones que están en desarrollo y que todavía no están listas del todo para lanzarse. Son altamente experimentales y podrían romperse y/o causar problemas, pero si eres valiente, por favor activa las que más te interesen, ¡y <a href='https://github.com/avm99963/infinitegforums/discussions/categories/feedback'>envia feedback</a>!",
+ "description": "Description shown in the beginning of the experiments page, below the title."
+ },
"inject_links": {
"message": "Enlaces",
"description": "Heading which we use before the 'previous post' link in a user profile in TW"
diff --git a/src/background.js b/src/background.js
index d7aae23..8d0f411 100644
--- a/src/background.js
+++ b/src/background.js
@@ -4,7 +4,7 @@
chrome.runtime.onInstalled.addListener(function(details) {
if (details.reason == 'install' || details.reason == 'update') {
chrome.storage.sync.get(null, function(options) {
- cleanUpOptions(options);
+ cleanUpOptions(options, false);
});
}
});
diff --git a/src/common/common.js b/src/common/common.js
index e4af064..205cdc9 100644
--- a/src/common/common.js
+++ b/src/common/common.js
@@ -1,28 +1,108 @@
-const defaultOptions = {
- 'list': true,
- 'thread': true,
- 'threadall': false,
- 'fixedtoolbar': false,
- 'redirect': false,
- 'history': false,
- 'loaddrafts': false,
- 'batchduplicate': false,
- 'escalatethreads': false,
- 'movethreads': false,
- 'increasecontrast': false,
- 'stickysidebarheaders': false,
- 'profileindicator': false,
- 'profileindicatoralt': false,
- 'profileindicatoralt_months': 12,
- 'ccdarktheme': false,
- 'ccdarktheme_mode': 'switch',
- 'ccdarktheme_switch_enabled': true,
- 'ccforcehidedrawer': false,
- 'ccdragndropfix': false,
- 'batchlock': false,
- 'smei_sortdirection': false,
- 'enhancedannouncementsdot': false,
- 'repositionexpandthread': false,
+const optionsPrototype = {
+ // Available options:
+ 'list': {
+ defaultValue: true,
+ context: 'options',
+ },
+ 'thread': {
+ defaultValue: true,
+ context: 'options',
+ },
+ 'threadall': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'fixedtoolbar': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'redirect': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'history': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'loaddrafts': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'increasecontrast': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'stickysidebarheaders': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'profileindicator': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'profileindicatoralt': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'profileindicatoralt_months': {
+ defaultValue: 12,
+ context: 'options',
+ },
+ 'ccdarktheme': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'ccdarktheme_mode': {
+ defaultValue: 'switch',
+ context: 'options',
+ },
+ 'ccforcehidedrawer': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'ccdragndropfix': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'batchlock': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'smei_sortdirection': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'enhancedannouncementsdot': {
+ defaultValue: false,
+ context: 'options',
+ },
+ 'repositionexpandthread': {
+ defaultValue: false,
+ context: 'options',
+ },
+
+ // Experiments:
+
+
+ // Internal options:
+ 'ccdarktheme_switch_enabled': {
+ defaultValue: true,
+ context: 'internal',
+ },
+
+ // Deprecated options:
+ 'escalatethreads': {
+ defaultValue: false,
+ context: 'deprecated',
+ },
+ 'movethreads': {
+ defaultValue: false,
+ context: 'deprecated',
+ },
+ 'batchduplicate': {
+ defaultValue: false,
+ context: 'deprecated',
+ },
};
const specialOptions = [
@@ -32,34 +112,28 @@
'ccdragndropfix',
];
-const deprecatedOptions = [
- 'escalatethreads',
- 'movethreads',
- 'batchduplicate',
-];
-
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
-function cleanUpOptions(options) {
- console.log('[cleanUpOptions] Previous options', options);
+// Adds missing options with their default value. If |dryRun| is set to false,
+// they are also saved to the sync storage area.
+function cleanUpOptions(options, dryRun = false) {
+ console.log('[cleanUpOptions] Previous options', JSON.stringify(options));
- if (typeof options !== 'object' || options === null) {
- options = defaultOptions;
- } else {
- var ok = true;
- for (const [opt, value] of Object.entries(defaultOptions)) {
- if (!(opt in options)) {
- ok = false;
- options[opt] = value;
- }
+ if (typeof options !== 'object' || options === null) options = {};
+
+ var ok = true;
+ for (const [opt, optMeta] of Object.entries(optionsPrototype)) {
+ if (!(opt in options)) {
+ ok = false;
+ options[opt] = optMeta['defaultValue'];
}
}
- console.log('[cleanUpOptions] New options', options);
+ console.log('[cleanUpOptions] New options', JSON.stringify(options));
- if (!ok) {
+ if (!ok && !dryRun) {
chrome.storage.sync.set(options);
}
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);
+ });
+});
diff --git a/src/sw.js b/src/sw.js
index c214068..2f181cb 100644
--- a/src/sw.js
+++ b/src/sw.js
@@ -6,7 +6,7 @@
chrome.runtime.onInstalled.addListener(details => {
if (details.reason == 'install' || details.reason == 'update') {
chrome.storage.sync.get(null, options => {
- cleanUpOptions(options);
+ cleanUpOptions(options, false);
});
}
});