aboutsummaryrefslogtreecommitdiff
path: root/html/preferences_panel/pref.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/preferences_panel/pref.js')
-rw-r--r--html/preferences_panel/pref.js570
1 files changed, 285 insertions, 285 deletions
diff --git a/html/preferences_panel/pref.js b/html/preferences_panel/pref.js
index f8d8fdf..c3597e1 100644
--- a/html/preferences_panel/pref.js
+++ b/html/preferences_panel/pref.js
@@ -21,290 +21,290 @@
*/
(() => {
- "use strict";
-
- const LIST_NAMES = ["white", "black"];
-
- var Model = {
- lists: {},
- prefs: null,
-
- malformedUrl(url) {
- let error = null;
- try {
- let objUrl = new URL(url);
- url = objUrl.href;
- if (!objUrl.protocol.startsWith("http")) {
- error = "Please enter http:// or https:// URLs only";
- } else if (!/^[^*]+\*?$/.test(url)) {
- error = "Only one single trailing path wildcard (/*) allowed";
- }
- } catch (e) {
- if (/^https?:\/\/\*\./.test(url)) {
- return this.malformedUrl(url.replace("*.", ""));
- }
- error = "Invalid URL";
- if (url && !url.includes("://")) error += ": missing protocol, either http:// or https://";
- else if (url.endsWith("://")) error += ": missing domain name";
- }
- return error;
- },
-
- async save(prefs = this.prefs) {
- if (prefs !== this.prefs) {
- this.prefs = Object.assign(this.prefs, prefs);
- }
- this.saving = true;
- try {
- return await browser.storage.local.set(prefs);
- } finally {
- this.saving = false;
- }
- },
-
- async addToList(list, ...items) {
- let other = list === Model.lists.black ? Model.lists.white : Model.lists.black;
- this.saving = true;
- try {
- await Promise.all([
- other.remove(...items),
- list.store(...items)
- ]);
- } finally {
- this.saving = false;
- }
- }
- };
- Model.loading = (async () => {
- let prefsNames = [
- "whitelist",
- "blacklist",
- "subject",
- "body"
- ];
- Model.prefs = await browser.storage.local.get(prefsNames.map(name => `pref_${name}`));
-
- for (let listName of LIST_NAMES) {
- let prefName = `pref_${listName}list`;
- await (Model.lists[listName] = new ListStore(prefName, Storage.CSV))
- .load(Model.prefs[prefName]);
- }
- })();
-
- var Controller = {
- init() {
- let widgetsRoot = this.root = document.getElementById("widgets");
- for (let widget of widgetsRoot.querySelectorAll('[id^="pref_"]')) {
- if (widget.id in Model.lists) {
- populateListUI(widget);
- } else if (widget.id in Model.prefs) {
- widget.value = Model.prefs[widget.id];
- }
- }
-
- this.populateListUI();
- this.syncAll();
-
- for (let ev in Listeners) {
- widgetsRoot.addEventListener(ev, Listeners[ev]);
- }
- document.getElementById("site").onfocus = e => {
- if (!e.target.value.trim()) {
- e.target.value = "https://";
- }
- };
-
- browser.storage.onChanged.addListener(changes => {
- if (!Model.saving &&
- ("pref_whitelist" in changes || "pref_blacklist" in changes)) {
- setTimeout(() => {
- this.populateListUI();
- this.syncAll();
- }, 10);
- }
- });
- },
-
- async addSite(list) {
- let url = document.getElementById("site").value.trim();
-
- if (url && !Model.malformedUrl(url)) {
- await this.addToList(list, url);
- }
- },
- async addToList(list, ...items) {
- await Model.addToList(list, ...items);
- this.populateListUI();
- this.syncAll();
- },
- async swapSelection(list) {
- let origin = list === Model.lists.black ? "white" : "black";
- await this.addToList(list, ...Array.prototype.map.call(
- document.querySelectorAll(`select#${origin} option:checked`),
- option => option.value)
- );
- },
-
- syncAll() {
- this.syncListsUI();
- this.syncSiteUI();
- },
-
- syncSiteUI() {
- let widget = document.getElementById("site");
- let list2button = listName => document.getElementById(`cmd-${listName}list-site`);
-
- for (let bi of LIST_NAMES.map(list2button)) {
- bi.disabled = true;
- }
-
- let url = widget.value.trim();
- let malformedUrl = url && Model.malformedUrl(url);
- widget.classList.toggle("error", !!malformedUrl);
- document.getElementById("site-error").textContent = malformedUrl || "";
- if (!url) return;
- if (url !== widget.value) {
- widget.value = url;
- }
-
- for (let listName of LIST_NAMES) {
- let list = Model.lists[listName];
- if (!list.contains(url)) {
- list2button(listName).disabled = false;
- }
- }
- },
-
- syncListsUI() {
- let total = 0;
- for (let id of ["black", "white"]) {
- let selected = document.querySelectorAll(`select#${id} option:checked`).length;
- let other = id === "black" ? "white" : "black";
- document.getElementById(`cmd-${other}list`).disabled = selected === 0;
- total += selected;
- }
- document.getElementById("cmd-delete").disabled = total === 0;
- },
-
- async deleteSelection() {
- for (let id of ["black", "white"]) {
- let selection = document.querySelectorAll(`select#${id} option:checked`);
- await Model.lists[id].remove(...Array.prototype.map.call(selection, option => option.value));
- }
- this.populateListUI();
- this.syncAll();
- },
-
- populateListUI(widget) {
- if (!widget) {
- for(let id of ["white", "black"]) {
- this.populateListUI(document.getElementById(id));
- }
- return;
- }
- widget.innerHTML = "";
- let items = [...Model.lists[widget.id].items].sort();
- let options = new DocumentFragment();
- for (let item of items) {
- let option = document.createElement("option");
- option.value = option.textContent = option.title = item;
- options.appendChild(option);
- }
- widget.appendChild(options);
- }
- };
-
- var KeyEvents = {
- Delete(e) {
- if (e.target.matches("#lists select")) {
- Controller.deleteSelection();
- }
- },
- Enter(e) {
- if (e.target.id === "site") {
- e.target.parentElement.querySelector("button[default]").click();
- }
- },
- KeyA(e) {
- if (e.target.matches("select") && e.ctrlKey) {
- for (let o of e.target.options) {
- o.selected = true;
- }
- Controller.syncListsUI();
- }
- }
- }
-
- var Listeners = {
- async change(e) {
- let {target} = e;
- let {id} = target;
-
- if (id in Model.lists) {
- Controller.syncListsUI();
- let selection = target.querySelectorAll("option:checked");
- if (selection.length === 1) {
- document.getElementById("site").value = selection[0].value;
- }
- return;
- }
- },
-
- click(e) {
- let {target} = e;
-
- if (!/^cmd-(white|black|delete)(list-site)?/.test(target.id)) return;
- e.preventDefault();
- let cmd = RegExp.$1;
- if (cmd === "delete") {
- Controller.deleteSelection();
- return;
- }
- let list = Model.lists[cmd];
- if (list) {
- Controller[RegExp.$2 ? "addSite" : "swapSelection"](list);
- return;
- }
- },
-
- keypress(e) {
- let {code} = e;
- if (code && typeof KeyEvents[code] === "function") {
- if (KeyEvents[code](e) === false) {
- e.preventDefault();
- }
- return;
- }
- },
-
- async input(e) {
- let {target} = e;
- let {id} = target;
- if (!id) return;
-
- if (id === "site") {
- Controller.syncSiteUI();
- let url = target.value;
- if (url) {
- let o = document.querySelector(`#lists select option[value="${url}"]`);
- if (o) {
- o.scrollIntoView();
- o.selected = true;
- }
- }
- return;
- }
-
- if (id.startsWith("pref_")) {
- await Model.save({[id]: target.value});
- return;
- }
- }
- };
-
- window.addEventListener("DOMContentLoaded", async e => {
- await Model.loading;
- Controller.init();
- });
+ "use strict";
+
+ const LIST_NAMES = ["white", "black"];
+
+ var Model = {
+ lists: {},
+ prefs: null,
+
+ malformedUrl(url) {
+ let error = null;
+ try {
+ let objUrl = new URL(url);
+ url = objUrl.href;
+ if (!objUrl.protocol.startsWith("http")) {
+ error = "Please enter http:// or https:// URLs only";
+ } else if (!/^[^*]+\*?$/.test(url)) {
+ error = "Only one single trailing path wildcard (/*) allowed";
+ }
+ } catch (e) {
+ if (/^https?:\/\/\*\./.test(url)) {
+ return this.malformedUrl(url.replace("*.", ""));
+ }
+ error = "Invalid URL";
+ if (url && !url.includes("://")) error += ": missing protocol, either http:// or https://";
+ else if (url.endsWith("://")) error += ": missing domain name";
+ }
+ return error;
+ },
+
+ async save(prefs = this.prefs) {
+ if (prefs !== this.prefs) {
+ this.prefs = Object.assign(this.prefs, prefs);
+ }
+ this.saving = true;
+ try {
+ return await browser.storage.local.set(prefs);
+ } finally {
+ this.saving = false;
+ }
+ },
+
+ async addToList(list, ...items) {
+ let other = list === Model.lists.black ? Model.lists.white : Model.lists.black;
+ this.saving = true;
+ try {
+ await Promise.all([
+ other.remove(...items),
+ list.store(...items)
+ ]);
+ } finally {
+ this.saving = false;
+ }
+ }
+ };
+ Model.loading = (async () => {
+ let prefsNames = [
+ "whitelist",
+ "blacklist",
+ "subject",
+ "body"
+ ];
+ Model.prefs = await browser.storage.local.get(prefsNames.map(name => `pref_${name}`));
+
+ for (let listName of LIST_NAMES) {
+ let prefName = `pref_${listName}list`;
+ await (Model.lists[listName] = new ListStore(prefName, Storage.CSV))
+ .load(Model.prefs[prefName]);
+ }
+ })();
+
+ var Controller = {
+ init() {
+ let widgetsRoot = this.root = document.getElementById("widgets");
+ for (let widget of widgetsRoot.querySelectorAll('[id^="pref_"]')) {
+ if (widget.id in Model.lists) {
+ populateListUI(widget);
+ } else if (widget.id in Model.prefs) {
+ widget.value = Model.prefs[widget.id];
+ }
+ }
+
+ this.populateListUI();
+ this.syncAll();
+
+ for (let ev in Listeners) {
+ widgetsRoot.addEventListener(ev, Listeners[ev]);
+ }
+ document.getElementById("site").onfocus = e => {
+ if (!e.target.value.trim()) {
+ e.target.value = "https://";
+ }
+ };
+
+ browser.storage.onChanged.addListener(changes => {
+ if (!Model.saving &&
+ ("pref_whitelist" in changes || "pref_blacklist" in changes)) {
+ setTimeout(() => {
+ this.populateListUI();
+ this.syncAll();
+ }, 10);
+ }
+ });
+ },
+
+ async addSite(list) {
+ let url = document.getElementById("site").value.trim();
+
+ if (url && !Model.malformedUrl(url)) {
+ await this.addToList(list, url);
+ }
+ },
+ async addToList(list, ...items) {
+ await Model.addToList(list, ...items);
+ this.populateListUI();
+ this.syncAll();
+ },
+ async swapSelection(list) {
+ let origin = list === Model.lists.black ? "white" : "black";
+ await this.addToList(list, ...Array.prototype.map.call(
+ document.querySelectorAll(`select#${origin} option:checked`),
+ option => option.value)
+ );
+ },
+
+ syncAll() {
+ this.syncListsUI();
+ this.syncSiteUI();
+ },
+
+ syncSiteUI() {
+ let widget = document.getElementById("site");
+ let list2button = listName => document.getElementById(`cmd-${listName}list-site`);
+
+ for (let bi of LIST_NAMES.map(list2button)) {
+ bi.disabled = true;
+ }
+
+ let url = widget.value.trim();
+ let malformedUrl = url && Model.malformedUrl(url);
+ widget.classList.toggle("error", !!malformedUrl);
+ document.getElementById("site-error").textContent = malformedUrl || "";
+ if (!url) return;
+ if (url !== widget.value) {
+ widget.value = url;
+ }
+
+ for (let listName of LIST_NAMES) {
+ let list = Model.lists[listName];
+ if (!list.contains(url)) {
+ list2button(listName).disabled = false;
+ }
+ }
+ },
+
+ syncListsUI() {
+ let total = 0;
+ for (let id of ["black", "white"]) {
+ let selected = document.querySelectorAll(`select#${id} option:checked`).length;
+ let other = id === "black" ? "white" : "black";
+ document.getElementById(`cmd-${other}list`).disabled = selected === 0;
+ total += selected;
+ }
+ document.getElementById("cmd-delete").disabled = total === 0;
+ },
+
+ async deleteSelection() {
+ for (let id of ["black", "white"]) {
+ let selection = document.querySelectorAll(`select#${id} option:checked`);
+ await Model.lists[id].remove(...Array.prototype.map.call(selection, option => option.value));
+ }
+ this.populateListUI();
+ this.syncAll();
+ },
+
+ populateListUI(widget) {
+ if (!widget) {
+ for (let id of ["white", "black"]) {
+ this.populateListUI(document.getElementById(id));
+ }
+ return;
+ }
+ widget.innerHTML = "";
+ let items = [...Model.lists[widget.id].items].sort();
+ let options = new DocumentFragment();
+ for (let item of items) {
+ let option = document.createElement("option");
+ option.value = option.textContent = option.title = item;
+ options.appendChild(option);
+ }
+ widget.appendChild(options);
+ }
+ };
+
+ var KeyEvents = {
+ Delete(e) {
+ if (e.target.matches("#lists select")) {
+ Controller.deleteSelection();
+ }
+ },
+ Enter(e) {
+ if (e.target.id === "site") {
+ e.target.parentElement.querySelector("button[default]").click();
+ }
+ },
+ KeyA(e) {
+ if (e.target.matches("select") && e.ctrlKey) {
+ for (let o of e.target.options) {
+ o.selected = true;
+ }
+ Controller.syncListsUI();
+ }
+ }
+ }
+
+ var Listeners = {
+ async change(e) {
+ let { target } = e;
+ let { id } = target;
+
+ if (id in Model.lists) {
+ Controller.syncListsUI();
+ let selection = target.querySelectorAll("option:checked");
+ if (selection.length === 1) {
+ document.getElementById("site").value = selection[0].value;
+ }
+ return;
+ }
+ },
+
+ click(e) {
+ let { target } = e;
+
+ if (!/^cmd-(white|black|delete)(list-site)?/.test(target.id)) return;
+ e.preventDefault();
+ let cmd = RegExp.$1;
+ if (cmd === "delete") {
+ Controller.deleteSelection();
+ return;
+ }
+ let list = Model.lists[cmd];
+ if (list) {
+ Controller[RegExp.$2 ? "addSite" : "swapSelection"](list);
+ return;
+ }
+ },
+
+ keypress(e) {
+ let { code } = e;
+ if (code && typeof KeyEvents[code] === "function") {
+ if (KeyEvents[code](e) === false) {
+ e.preventDefault();
+ }
+ return;
+ }
+ },
+
+ async input(e) {
+ let { target } = e;
+ let { id } = target;
+ if (!id) return;
+
+ if (id === "site") {
+ Controller.syncSiteUI();
+ let url = target.value;
+ if (url) {
+ let o = document.querySelector(`#lists select option[value="${url}"]`);
+ if (o) {
+ o.scrollIntoView();
+ o.selected = true;
+ }
+ }
+ return;
+ }
+
+ if (id.startsWith("pref_")) {
+ await Model.save({ [id]: target.value });
+ return;
+ }
+ }
+ };
+
+ window.addEventListener("DOMContentLoaded", async e => {
+ await Model.loading;
+ Controller.init();
+ });
})();