| Index: mobile-options.js |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/mobile-options.js |
| @@ -0,0 +1,432 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-2017 eyeo GmbH |
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +/* globals getDocLink */ |
| + |
| +"use strict"; |
| + |
|
saroyanm
2017/07/18 12:46:28
I'll suggest to encapsulate this file with own blo
Thomas Greiner
2017/07/18 17:42:32
Done.
|
| +const {getMessage} = ext.i18n; |
| + |
| +let whitelistFilter = null; |
| +let promisedAcceptableAdsUrl = getAcceptableAdsUrl(); |
| + |
| +/* Utility functions */ |
| + |
| +function get(selector, origin) |
|
saroyanm
2017/07/18 12:46:28
Detail: Calling function "get" is too generic, as
Thomas Greiner
2017/07/18 17:42:31
Acknowledged.
Note that the inspiration for this
saroyanm
2017/07/31 11:08:54
Now I see your intention, but in that case I think
|
| +{ |
| + return (origin || document).querySelector(selector); |
| +} |
| + |
| +function getAll(selector, origin) |
| +{ |
| + return (origin || document).querySelectorAll(selector); |
| +} |
| + |
| +function create(parent, tagName, content, attributes, onclick) |
| +{ |
| + let element = document.createElement(tagName); |
| + |
| + if (typeof content == "string") |
| + { |
| + element.textContent = content; |
| + } |
| + |
| + if (attributes) |
| + { |
| + for (let name in attributes) |
| + { |
| + element.setAttribute(name, attributes[name]); |
| + } |
| + } |
| + |
| + if (onclick) |
| + { |
| + element.addEventListener("click", (ev) => |
| + { |
| + onclick(ev); |
| + ev.stopPropagation(); |
| + }); |
|
saroyanm
2017/07/18 12:46:28
Note: I do remember before it was required to spec
Thomas Greiner
2017/07/18 17:42:32
We dropped that requirement a while ago. From what
|
| + } |
| + |
| + parent.appendChild(element); |
| + return element; |
| +} |
| + |
| +/* Extension interactions */ |
| + |
| +function getInstalled() |
| +{ |
| + return new Promise((resolve, reject) => |
| + { |
| + ext.backgroundPage.sendMessage( |
| + {type: "subscriptions.get", downloadable: true}, |
| + resolve |
| + ); |
| + }); |
| +} |
| + |
| +function getAcceptableAdsUrl() |
| +{ |
| + return new Promise((resolve, reject) => |
| + { |
| + ext.backgroundPage.sendMessage( |
| + {type: "prefs.get", key: "subscriptions_exceptionsurl"}, |
| + resolve |
| + ); |
| + }); |
| +} |
| + |
| +function getRecommended() |
|
saroyanm
2017/07/18 12:46:28
This function only return recommended subscription
Thomas Greiner
2017/07/18 17:42:33
Good point. Done.
|
| +{ |
| + return fetch("subscriptions.xml") |
| + .then((resp) => resp.text()) |
|
saroyanm
2017/07/18 12:46:27
Detail: we usually are not using short form of the
saroyanm
2017/07/18 12:46:28
Shouldn't this be returned (return response.text()
Thomas Greiner
2017/07/18 17:42:31
No, this is one of the features of arrow functions
Thomas Greiner
2017/07/18 17:42:31
I agree that it makes sense for "response", so I'l
|
| + .then((text) => |
| + { |
| + let doc = new DOMParser().parseFromString(text, "application/xml"); |
| + let elements = Array.from(doc.getElementsByTagName("subscription")); |
| + |
| + return elements |
| + .filter((element) => element.getAttribute("type") == "ads") |
| + .map((element) => |
| + { |
| + return { |
|
saroyanm
2017/07/18 12:46:27
Detail: the brace should go to the next row for co
Thomas Greiner
2017/07/18 17:42:31
Moving this brace to the next row would be interpr
|
| + title: element.getAttribute("title"), |
|
saroyanm
2017/07/18 12:46:28
Shouldn't this titles be translated ?
I think we
Thomas Greiner
2017/07/18 17:42:31
We don't translate filter list titles (e.g. "EasyL
|
| + url: element.getAttribute("url") |
| + }; |
| + }); |
| + }); |
| +} |
| + |
| +function installSubscription(url, title) |
| +{ |
| + ext.backgroundPage.sendMessage({type: "subscriptions.add", url, title}); |
| +} |
| + |
| +function uninstallSubscription(url) |
| +{ |
| + ext.backgroundPage.sendMessage({type: "subscriptions.remove", url}); |
| +} |
| + |
| +/* Actions */ |
| + |
| +function setSubscription({disabled, title, url}, shouldAdd) |
| +{ |
| + if (disabled) |
| + return; |
| + |
| + promisedAcceptableAdsUrl.then((acceptableAdsUrl) => |
| + { |
| + if (url == acceptableAdsUrl) |
| + { |
| + get("#acceptableAds").checked = true; |
| + return; |
| + } |
| + |
| + let listInstalled = get("#subscriptions-installed"); |
| + let installed = get(`[data-url="${url}"]`, listInstalled); |
|
saroyanm
2017/07/18 12:46:28
Detail: I think we are using single quotes "'", ra
Thomas Greiner
2017/07/18 17:42:32
This is a template literal, not a string literal.
|
| + |
| + if (installed) |
| + { |
| + let titleElement = get("span", installed); |
| + titleElement.textContent = title || url; |
| + } |
| + else if (shouldAdd) |
|
saroyanm
2017/07/18 12:46:28
Why are you using "shouldAdd" parameter ?
If the
Thomas Greiner
2017/07/18 17:42:31
Why should we show subscriptions that are not inst
saroyanm
2017/07/31 11:08:54
I think I meant "installed", nevermind, I don't re
|
| + { |
| + let element = create(listInstalled, "li", null, {"data-url": url}); |
| + create(element, "span", title || url); |
| + create(element, "button", null, {class: "remove"}, |
| + () => uninstallSubscription(url) |
| + ); |
| + |
| + let recommended = get(`#subscriptions-recommended [data-url="${url}"]`); |
| + if (recommended) |
| + { |
| + recommended.classList.add("installed"); |
| + } |
| + } |
| + }); |
| +} |
| + |
| +function removeSubscription(url) |
| +{ |
| + promisedAcceptableAdsUrl.then((acceptableAdsUrl) => |
| + { |
| + if (url == acceptableAdsUrl) |
| + { |
| + get("#acceptable-ads").checked = false; |
|
saroyanm
2017/07/18 12:46:27
I think you forget to change all camelCase IDs.
Thomas Greiner
2017/07/18 17:42:32
Yep, seems so. Done.
|
| + return; |
| + } |
| + |
| + let installed = get(`#subscriptions-installed [data-url="${url}"]`); |
| + if (installed) |
| + { |
| + installed.parentNode.removeChild(installed); |
| + } |
| + |
| + let recommended = get(`#subscriptions-recommended [data-url="${url}"]`); |
| + if (recommended) |
| + { |
| + recommended.classList.remove("installed"); |
| + } |
| + }); |
| +} |
| + |
| +function setDialog(id, options) |
| +{ |
| + if (!id) |
| + { |
| + delete document.body.dataset.dialog; |
| + return; |
| + } |
| + |
| + let fields = getAll(`#dialog-${id} input`); |
| + for (let field of fields) |
| + { |
| + field.value = (options && field.name in options) ? options[field.name] : ""; |
| + } |
| + setError(id, null); |
| + |
| + document.body.dataset.dialog = id; |
| +} |
| + |
| +function setError(dialogId, message) |
|
saroyanm
2017/07/18 12:46:28
Detail: Parameter name "message" is misleading, in
Thomas Greiner
2017/07/18 17:42:33
Looks like I forgot to adapt that. Done.
|
| +{ |
| + let dialog = get(`#dialog-${dialogId}`); |
| + if (message) |
| + { |
| + dialog.dataset.error = message; |
| + } |
| + else |
| + { |
| + delete dialog.dataset.error; |
| + } |
| +} |
| + |
| +function populateLists() |
| +{ |
| + Promise.all([getInstalled(), getRecommended()]) |
| + .then(([installed, recommended]) => |
| + { |
| + let listRecommended = get("#subscriptions-recommended"); |
| + for (let {title, url} of recommended) |
| + { |
| + create(listRecommended, "li", title, {"data-url": url}, |
| + (ev) => |
| + { |
| + if (ev.target.classList.contains("installed")) |
| + return; |
| + |
| + setDialog("subscribe", {title, url}); |
|
saroyanm
2017/07/18 12:46:27
Detail: reference to "subscribe" dialog is used qu
Thomas Greiner
2017/07/18 17:42:32
Done.
|
| + } |
| + ); |
| + } |
| + |
| + for (let subscription of installed) |
| + { |
| + if (subscription.disabled) |
| + continue; |
| + |
| + setSubscription(subscription, true); |
| + } |
| + }) |
| + .catch((err) => console.error(err)); |
|
saroyanm
2017/07/18 12:46:28
I think you have mentioned this, so I'll ignore th
Thomas Greiner
2017/07/18 17:42:32
Acknowledged.
|
| +} |
| + |
| +/* Listeners */ |
| + |
| +function onChange(ev) |
| +{ |
| + if (ev.target.id != "acceptable-ads") |
|
saroyanm
2017/07/18 12:46:27
Same as mentioned above, there is no element with
Thomas Greiner
2017/07/18 17:42:33
Done.
|
| + return; |
| + |
| + promisedAcceptableAdsUrl.then((acceptableAdsUrl) => |
| + { |
| + if (ev.target.checked) |
| + { |
| + installSubscription(acceptableAdsUrl, null); |
| + } |
| + else |
| + { |
| + uninstallSubscription(acceptableAdsUrl); |
| + } |
| + }); |
| +} |
| +document.addEventListener("change", onChange); |
| + |
| +function onClick(ev) |
| +{ |
| + switch (ev.target.dataset.action) |
| + { |
| + case "close-dialog": |
|
saroyanm
2017/07/18 12:46:27
Note: Why not to handle all other click events her
Thomas Greiner
2017/07/18 17:42:32
Done. I'm also using it now for the "enabled" togg
|
| + setDialog(null); |
| + break; |
| + case "open-dialog": |
| + setDialog(ev.target.dataset.dialog); |
| + break; |
| + } |
| +} |
| +document.addEventListener("click", onClick); |
| + |
| +function onSubmit(ev) |
| +{ |
| + let fields = ev.target.elements; |
| + let title = fields.title.value; |
| + let url = fields.url.value; |
| + |
| + if (!title) |
| + { |
| + setError("subscribe", "title"); |
| + } |
| + else if (!url) |
| + { |
| + setError("subscribe", "url"); |
| + } |
| + else |
| + { |
| + installSubscription(url, title); |
| + setDialog(null); |
| + } |
| + |
| + ev.preventDefault(); |
| +} |
| +document.addEventListener("submit", onSubmit); |
| + |
| +function onToggleWhitelistFilter(ev) |
| +{ |
| + let checkbox = ev.target; |
|
saroyanm
2017/07/18 12:46:28
Suggestion: We can avoid using element name as a v
Thomas Greiner
2017/07/18 17:42:32
In this case the implementation depends on it bein
|
| + ext.backgroundPage.sendMessage( |
| + { |
| + type: (checkbox.checked) ? "filters.remove" : "filters.add", |
| + text: whitelistFilter |
| + }, (errors) => |
| + { |
| + if (errors.length < 1) |
| + return; |
| + |
| + console.error(errors); |
| + checkbox.checked = !checkbox.checked; |
| + } |
| + ); |
| + ev.preventDefault(); |
| +} |
| + |
| +function onMessage(msg) |
| +{ |
| + switch (msg.type) |
| + { |
| + case "app.respond": { |
| + switch (msg.action) |
| + { |
| + case "addSubscription": |
| + let [subscription] = msg.args; |
| + setDialog("subscribe", { |
| + title: subscription.title, |
| + url: subscription.url |
| + }); |
| + break; |
| + case "showPageOptions": |
| + let [{host, whitelisted}] = msg.args; |
| + whitelistFilter = `@@||${host}^$document`; |
|
saroyanm
2017/07/18 12:46:28
Shouldn't this be promise as well, similar to the
Thomas Greiner
2017/07/18 17:42:32
Even though it's not necessary because the UI usin
|
| + |
| + ext.i18n.setElementText( |
| + get("#enabled-label"), |
| + "mops_enabled_label", |
| + [host] |
| + ); |
| + |
| + let checkbox = get("#enabled"); |
| + checkbox.checked = !whitelisted; |
| + checkbox.addEventListener("click", onToggleWhitelistFilter); |
| + |
| + get("#enabled-container").hidden = false; |
| + break; |
| + } |
| + break; |
| + } |
| + case "filters.respond": { |
| + let [filter] = msg.args; |
| + if (!whitelistFilter || filter.text != whitelistFilter) |
|
saroyanm
2017/07/18 12:46:28
First check for "!whitelistFilter" seems to redund
Thomas Greiner
2017/07/18 17:42:31
While I don't expect `filter.text` to be `null`, i
|
| + break; |
| + |
| + get("#enabled").checked = (msg.action == "removed"); |
| + break; |
| + } |
| + case "subscriptions.respond": { |
| + let [subscription] = msg.args; |
| + switch (msg.action) |
| + { |
| + case "added": |
| + setSubscription(subscription, true); |
| + break; |
| + case "disabled": |
| + if (subscription.disabled) |
| + { |
| + removeSubscription(subscription.url); |
| + } |
| + else |
| + { |
| + setSubscription(subscription, true); |
| + } |
| + break; |
| + case "removed": |
| + removeSubscription(subscription.url); |
| + break; |
| + case "title": |
| + // We're also receiving these messages for subscriptions that are not |
| + // installed so we shouldn't add those by accident |
| + setSubscription(subscription, false); |
| + break; |
| + } |
| + break; |
| + } |
| + } |
| +} |
| +ext.onMessage.addListener(onMessage); |
| + |
| +ext.backgroundPage.sendMessage({ |
| + type: "app.listen", |
| + filter: ["addSubscription", "showPageOptions"] |
| +}); |
| + |
| +ext.backgroundPage.sendMessage({ |
| + type: "filters.listen", |
| + filter: ["added", "removed"] |
| +}); |
| + |
| +ext.backgroundPage.sendMessage({ |
| + type: "subscriptions.listen", |
| + filter: ["added", "disabled", "removed", "title"] |
| +}); |
| + |
| +/* Initialization */ |
| + |
| +populateLists(); |
| + |
| +getDocLink("acceptable_ads", (link) => |
| +{ |
| + get("#acceptableAds-more").href = link; |
| +}); |
| + |
| +get("#dialog-subscribe [name='title']").setAttribute( |
| + "placeholder", |
| + getMessage("mops_subscribe_title") |
| +); |
| + |
| +get("#dialog-subscribe [name='url']").setAttribute( |
| + "placeholder", |
| + getMessage("mops_subscribe_url") |
| +); |