 Issue 29333819:
  Issue 2375 - Implement "Blocking lists" section in new options page  (Closed)
    
  
    Issue 29333819:
  Issue 2375 - Implement "Blocking lists" section in new options page  (Closed) 
  | Index: options.js | 
| =================================================================== | 
| --- a/options.js | 
| +++ b/options.js | 
| @@ -72,6 +72,7 @@ | 
| if (text) | 
| listItem.setAttribute("data-search", text.toLowerCase()); | 
| + updateTimeDate(listItem, item); | 
| var control = listItem.querySelector(".control"); | 
| if (control) | 
| { | 
| @@ -81,7 +82,13 @@ | 
| this._setEmpty(table, null); | 
| if (table.hasChildNodes()) | 
| - table.insertBefore(listItem, table.childNodes[this.items.indexOf(item)]); | 
| + { | 
| + var index = this.items.indexOf(item); | 
| + if (table.firstChild.classList.contains("head")) | 
| + index++; | 
| + | 
| + table.insertBefore(listItem, table.childNodes[index]); | 
| + } | 
| else | 
| table.appendChild(listItem); | 
| } | 
| @@ -96,10 +103,11 @@ | 
| return; | 
| this.items.splice(index, 1); | 
| + var access = (item.url || item.text).replace(/'/g, "\\'"); | 
| 
Thomas Greiner
2016/01/19 11:27:28
I notice the issues with accessing elements by the
 
saroyanm
2016/01/22 09:55:08
Done, but wouldn't it be simpler to just have a pr
 
Thomas Greiner
2016/01/25 15:40:28
By returning a function we only create the "access
 | 
| for (var i = 0; i < this.details.length; i++) | 
| { | 
| var table = E(this.details[i].id); | 
| - var element = table.childNodes[index]; | 
| + var element = table.querySelector("[data-access='" + access + "']"); | 
| element.parentElement.removeChild(element); | 
| if (this.items.length == 0) | 
| this._setEmpty(table, this.details[i].emptyText); | 
| @@ -113,12 +121,79 @@ | 
| { | 
| var table = E(this.details[i].id); | 
| var template = table.querySelector("template"); | 
| + var staticElements = []; | 
| 
Thomas Greiner
2016/01/19 11:27:29
This logic is quite complicated. Assuming that you
 
saroyanm
2016/01/22 09:55:10
Currently we are not using createDocumentFragment
 
Thomas Greiner
2016/01/25 15:40:27
You could use `Element.nextElementSibling` to avoi
 
saroyanm
2016/01/26 18:36:15
I assume while you didn't commented under updated
 
Thomas Greiner
2016/01/27 17:16:56
Thanks for reminding me. I wrote down some parts t
 | 
| + var element = table.firstChild; | 
| + while (element) | 
| + { | 
| + if (element.tagName == "TEMPLATE" || | 
| + (element.classList && element.classList.contains("static"))) | 
| + staticElements.push(element); | 
| + element = element.nextSibling | 
| + } | 
| + | 
| table.innerHTML = ""; | 
| - table.appendChild(template); | 
| + for (var j = 0; j < staticElements.length; j++) | 
| + table.appendChild(staticElements[j]); | 
| + | 
| this._setEmpty(table, this.details[i].emptyText); | 
| } | 
| }; | 
| + Collection.prototype.getTableIds = function() | 
| 
Thomas Greiner
2016/01/19 11:27:29
This information is an implementation detail and s
 
saroyanm
2016/01/22 09:55:09
updated the method to check if the collection has
 | 
| + { | 
| + var ids = []; | 
| + for (var i = 0; i < this.details.length; i++) | 
| + ids.push(this.details[i].id) | 
| + | 
| + return ids; | 
| + }; | 
| + | 
| + function updateTimeDate(listItem, subscription) | 
| + { | 
| + var dateElement = listItem.querySelector(".date"); | 
| + var timeElement = listItem.querySelector(".time"); | 
| + | 
| + if(dateElement && timeElement) | 
| + { | 
| + if (subscription.downloadStatus && | 
| + subscription.downloadStatus != "synchronize_ok") | 
| + { | 
| + var map = | 
| + { | 
| + "synchronize_invalid_url": "options_subscription_lastDownload_invalidURL", | 
| + "synchronize_connection_error": "options_subscription_lastDownload_connectionError", | 
| + "synchronize_invalid_data": "options_subscription_lastDownload_invalidData", | 
| + "synchronize_checksum_mismatch": "options_subscription_lastDownload_checksumMismatch" | 
| + }; | 
| + if (subscription.downloadStatus in map) | 
| + timeElement.textContent = ext.i18n.getMessage(map[subscription.downloadStatus]); | 
| + else | 
| + timeElement.textContent = subscription.downloadStatus; | 
| + } | 
| + else if (subscription.lastDownload > 0) | 
| + { | 
| + var timedate = i18n_timeDateStrings(subscription.lastDownload * 1000); | 
| 
Thomas Greiner
2016/01/19 11:27:29
This function returns the localized date-time stri
 
saroyanm
2016/01/22 09:55:10
I see, I was trying to make it consistent with old
 
Thomas Greiner
2016/01/25 15:40:28
The mere formatting of the string should be generi
 
saroyanm
2016/01/26 18:36:15
Done.
 | 
| + if (timedate[1]) | 
| + dateElement.textContent = timedate[1].split("/").reverse().join("-"); | 
| + else | 
| + { | 
| + var today = new Date(); | 
| + dateElement.textContent = today.getFullYear() + "-" + | 
| + today.getMonth()+1 + "-" + today.getDate(); | 
| + } | 
| + var time = timedate[0].split(":"); | 
| + time[2] = time[2].split(" ")[1]; | 
| + if (time[2] == "AM" && time[0] == "12") | 
| + time[0] = 0; | 
| + else if (time[2] == "PM") | 
| + time[0] = parseInt(time[0]) + 12; | 
| + | 
| + time.pop(); | 
| + timeElement.textContent = time.join(":"); | 
| + } | 
| + } | 
| + } | 
| + | 
| function onToggleSubscriptionClick(e) | 
| 
Thomas Greiner
2016/01/19 11:27:29
I'd suggest renaming this function to "toggleRemov
 
saroyanm
2016/01/22 09:55:08
Done.
 | 
| { | 
| e.preventDefault(); | 
| @@ -134,6 +209,17 @@ | 
| addEnableSubscription(subscriptionUrl); | 
| } | 
| + function onToggleSubscriptionStateClick(e) | 
| + { | 
| + e.preventDefault(); | 
| + var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); | 
| 
Thomas Greiner
2016/01/19 11:27:29
What about reusing `getParentAccessElement()` sinc
 
saroyanm
2016/01/22 09:55:10
Done.
 
Thomas Greiner
2016/01/25 15:40:28
I don't see that you changed anything here.
 
saroyanm
2016/01/26 18:36:15
Now for sure, thanks.
 | 
| + ext.backgroundPage.sendMessage( | 
| + { | 
| + type: "subscriptions.toggleState", | 
| + url: subscriptionUrl | 
| + }); | 
| + } | 
| + | 
| function onAddLanguageSubscriptionClick(e) | 
| { | 
| e.preventDefault(); | 
| @@ -207,31 +293,40 @@ | 
| emptyText: "options_customFilters_empty" | 
| } | 
| ]); | 
| + collections.blockingLists = new Collection( | 
| + [ | 
| + { | 
| + id: "blocking-lists-table", | 
| + onClick: onToggleSubscriptionStateClick | 
| + } | 
| + ]); | 
| - function updateSubscription(subscription) | 
| + function observeSubscription(subscription) | 
| { | 
| - var subscriptionUrl = subscription.url; | 
| - var knownSubscription = subscriptionsMap[subscriptionUrl]; | 
| - if (knownSubscription) | 
| - knownSubscription.disabled = subscription.disabled; | 
| - else | 
| + function onObjectChanged(change) | 
| { | 
| - getAcceptableAdsURL(function(acceptableAdsUrl) | 
| + for (var i = 0; i < change.length; i++) | 
| { | 
| - function onObjectChanged() | 
| + var property = change[i].name; | 
| + if (property == "disabled") | 
| { | 
| - var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\'"); | 
| + var access = (subscription.url || | 
| + subscription.text).replace(/'/g, "\\'"); | 
| var elements = document.querySelectorAll("[data-access='" + access + "']"); | 
| for (var i = 0; i < elements.length; i++) | 
| { | 
| var element = elements[i]; | 
| + var tableId = element.parentElement ? element.parentElement.id : ""; | 
| var control = element.querySelector(".control"); | 
| - if (control.localName == "input") | 
| + if (control && control.localName == "input") | 
| control.checked = subscription.disabled == false; | 
| - if (subscriptionUrl in recommendationsMap) | 
| + if (subscription.url in recommendationsMap) | 
| { | 
| - var recommendation = recommendationsMap[subscriptionUrl]; | 
| - if (recommendation.type == "ads") | 
| + var recommendation = recommendationsMap[subscription.url]; | 
| + var langids = collections.langs.getTableIds(); | 
| + var allLangIds = collections.allLangs.getTableIds(); | 
| + if (recommendation.type == "ads" && | 
| + langids.concat(allLangIds).indexOf(tableId) != -1) | 
| { | 
| if (subscription.disabled == false) | 
| { | 
| @@ -247,33 +342,58 @@ | 
| } | 
| } | 
| } | 
| + else | 
| + { | 
| + var blockingListId = collections.blockingLists.details[0].id; | 
| + var blockingList = document.getElementById(blockingListId); | 
| + var listItem = blockingList.querySelector("[data-access='" + | 
| + subscription.url + "']"); | 
| + updateTimeDate(listItem, subscription); | 
| + } | 
| + } | 
| + } | 
| - if (!Object.observe) | 
| + if (!Object.observe) | 
| + { | 
| + ["disabled", "lastDownload"].forEach(function(property) | 
| + { | 
| + subscription["$" + property] = subscription[property]; | 
| + Object.defineProperty(subscription, property, | 
| { | 
| - // Currently only "disabled" property of subscription used for observation | 
| - // but with Advanced tab implementation we should also add more properties. | 
| - ["disabled"].forEach(function(property) | 
| + get: function() | 
| { | 
| - subscription["$" + property] = subscription[property]; | 
| - Object.defineProperty(subscription, property, | 
| - { | 
| - get: function() | 
| - { | 
| - return this["$" + property]; | 
| - }, | 
| - set: function(value) | 
| - { | 
| - this["$" + property] = value; | 
| - onObjectChanged(); | 
| - } | 
| - }); | 
| - }); | 
| - } | 
| - else | 
| - { | 
| - Object.observe(subscription, onObjectChanged); | 
| - } | 
| + return this["$" + property]; | 
| + }, | 
| + set: function(value) | 
| + { | 
| + this["$" + property] = value; | 
| + var change = Object.create(null); | 
| + change.name = property; | 
| + onObjectChanged([change]); | 
| + } | 
| + }); | 
| + }); | 
| + } | 
| + else | 
| + { | 
| + Object.observe(subscription, onObjectChanged); | 
| + } | 
| + } | 
| + function updateSubscription(subscription) | 
| + { | 
| + var subscriptionUrl = subscription.url; | 
| + var knownSubscription = subscriptionsMap[subscriptionUrl]; | 
| + if (knownSubscription) | 
| + { | 
| + knownSubscription.disabled = subscription.disabled; | 
| + knownSubscription.lastDownload = subscription.lastDownload; | 
| + } | 
| + else | 
| + { | 
| + observeSubscription(subscription); | 
| + getAcceptableAdsURL(function(acceptableAdsUrl) | 
| + { | 
| var collection = null; | 
| if (subscriptionUrl in recommendationsMap) | 
| { | 
| @@ -366,6 +486,15 @@ | 
| element = element.parentElement; | 
| } | 
| + function getParentAccessElement() | 
| 
Thomas Greiner
2016/01/19 11:27:30
Please avoid declaring functions inside other func
 
saroyanm
2016/01/22 09:55:09
Good point.
 | 
| + { | 
| + var elementCopy = element; | 
| + while (!elementCopy.dataset.access) | 
| 
Thomas Greiner
2016/01/19 11:27:29
We can't use `Element.dataset` due to older Safari
 
saroyanm
2016/01/22 09:55:09
Right, done.
 | 
| + elementCopy = elementCopy.parentNode; | 
| + | 
| + return elementCopy; | 
| + } | 
| + | 
| var actions = element.getAttribute("data-action").split(","); | 
| for (var i = 0; i < actions.length; i++) | 
| { | 
| @@ -419,18 +548,83 @@ | 
| document.body.setAttribute("data-tab", | 
| element.getAttribute("data-tab")); | 
| break; | 
| + case "update-all-subscriptions": | 
| + ext.backgroundPage.sendMessage( | 
| + { | 
| + type: "subscriptions.updateAll" | 
| + }); | 
| + break; | 
| + case "open-context-menu": | 
| 
Thomas Greiner
2016/01/19 11:27:29
We should improve this mechanism because it requir
 
saroyanm
2016/01/22 09:55:09
Done, but bit hacky, not sure if it's what you mea
 
Thomas Greiner
2016/01/27 17:16:56
Better but what about replacing it with "toggle-co
 
saroyanm
2016/01/28 17:00:10
Done.
 | 
| + var listItem = getParentAccessElement(); | 
| + var contextMenu = listItem.querySelector(".content"); | 
| + listItem.classList.add("context"); | 
| + | 
| + function mouseover() | 
| + { | 
| + contextMenu.addEventListener("mouseout", mouseout, false); | 
| + contextMenu.removeEventListener("mouseover", mouseover); | 
| + } | 
| + function mouseout(event) | 
| + { | 
| + if (event.target.parentElement != contextMenu) | 
| + { | 
| + var relatedTarget = event.relatedTarget; | 
| + if (relatedTarget.parentNode == this || relatedTarget == this) | 
| + return; | 
| + listItem.classList.remove("context"); | 
| + contextMenu.removeEventListener("mouseout", mouseout); | 
| + } | 
| + } | 
| + | 
| + contextMenu.addEventListener("mouseover", mouseover, false); | 
| + break; | 
| + case "update-now": | 
| + ext.backgroundPage.sendMessage( | 
| + { | 
| + type: "subscriptions.update", | 
| + url: getParentAccessElement().dataset.access | 
| + }); | 
| + break; | 
| + case "website": | 
| + ext.backgroundPage.sendMessage( | 
| + { | 
| + type: "subscriptions.website", | 
| + url: getParentAccessElement().dataset.access | 
| + }); | 
| + break; | 
| + case "source": | 
| + window.open(getParentAccessElement().dataset.access); | 
| + break; | 
| + case "delete": | 
| + ext.backgroundPage.sendMessage( | 
| + { | 
| + type: "subscriptions.remove", | 
| + url: getParentAccessElement().dataset.access | 
| + }); | 
| + break; | 
| } | 
| } | 
| } | 
| function onDOMLoaded() | 
| { | 
| - var recommendationTemplate = document.querySelector("#recommend-list-table template"); | 
| - var popularText = ext.i18n.getMessage("options_popular"); | 
| - recommendationTemplate.content.querySelector(".popular").textContent = popularText; | 
| - var languagesTemplate = document.querySelector("#all-lang-table template"); | 
| - var buttonText = ext.i18n.getMessage("options_button_add"); | 
| - languagesTemplate.content.querySelector(".button-add span").textContent = buttonText; | 
| + function updateTemplate(template, selector, messageId) | 
| 
Thomas Greiner
2016/01/19 11:27:29
This functionality belongs into "i18n.js". If elem
 
saroyanm
2016/01/22 09:55:08
Done.
 | 
| + { | 
| + template.content.querySelector(selector). | 
| + textContent = ext.i18n.getMessage(messageId); | 
| + } | 
| + | 
| + var template = document.querySelector("#recommend-list-table template"); | 
| + updateTemplate(template, ".popular", "options_popular"); | 
| + | 
| + template = document.querySelector("#all-lang-table template"); | 
| + updateTemplate(template, ".button-add span", "options_button_add"); | 
| + | 
| + template = document.querySelector("#blocking-lists-table template"); | 
| + updateTemplate(template, ".update-now", "options_blockingList_update_now"); | 
| + updateTemplate(template, ".website", "options_blockingList_website"); | 
| + updateTemplate(template, ".source", "options_blockingList_source"); | 
| + updateTemplate(template, ".delete", "options_blockingList_delete"); | 
| populateLists(); | 
| @@ -705,14 +899,27 @@ | 
| switch (action) | 
| { | 
| case "added": | 
| + updateSubscription(subscription); | 
| + updateShareLink(); | 
| + | 
| + var knownSubscription = subscriptionsMap[subscription.url]; | 
| + if (knownSubscription) | 
| + collections.blockingLists.addItems(knownSubscription); | 
| + else | 
| + collections.blockingLists.addItems(subscription); | 
| + break; | 
| case "disabled": | 
| updateSubscription(subscription); | 
| updateShareLink(); | 
| break; | 
| + case "updated": | 
| + updateSubscription(subscription); | 
| 
Thomas Greiner
2016/01/19 11:27:29
That's not what "updated" is meant to notify you o
 
saroyanm
2016/01/22 09:55:10
Ahh right, thanks for pointing that out. Done.
 | 
| + break; | 
| case "homepage": | 
| // TODO: NYI | 
| break; | 
| case "removed": | 
| + var knownSubscription = subscriptionsMap[subscription.url]; | 
| getAcceptableAdsURL(function(acceptableAdsUrl) | 
| { | 
| if (subscription.url == acceptableAdsUrl) | 
| @@ -722,7 +929,6 @@ | 
| } | 
| else | 
| { | 
| - var knownSubscription = subscriptionsMap[subscription.url]; | 
| if (subscription.url in recommendationsMap) | 
| knownSubscription.disabled = true; | 
| else | 
| @@ -733,6 +939,7 @@ | 
| } | 
| updateShareLink(); | 
| }); | 
| + collections.blockingLists.removeItem(knownSubscription); | 
| break; | 
| case "title": | 
| // TODO: NYI | 
| @@ -821,7 +1028,7 @@ | 
| ext.backgroundPage.sendMessage( | 
| { | 
| type: "subscriptions.listen", | 
| - filter: ["added", "disabled", "homepage", "removed", "title"] | 
| + filter: ["added", "disabled", "updated", "homepage", "removed", "title"] | 
| }); | 
| window.addEventListener("DOMContentLoaded", onDOMLoaded, false); |