| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2015 Eyeo GmbH |
| 4 * | 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
| 8 * | 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
| 13 * | 13 * |
| 14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 "use strict"; | 18 "use strict"; |
| 19 | 19 |
| 20 (function() | 20 (function() |
| 21 { | 21 { |
| 22 var subscriptionsMap = Object.create(null); | 22 var subscriptionsMap = Object.create(null); |
| 23 var recommendationsMap = Object.create(null); | 23 var recommendationsMap = Object.create(null); |
| 24 var filtersMap = Object.create(null); | 24 var filtersMap = Object.create(null); |
| 25 var collections = Object.create(null); | 25 var collections = Object.create(null); |
| 26 var maxLabelId = 0; | |
| 26 | 27 |
| 27 function Collection(details) | 28 function Collection(details) |
| 28 { | 29 { |
| 29 this.details = details; | 30 this.details = details; |
| 30 this.items = []; | 31 this.items = []; |
| 31 } | 32 } |
| 32 | 33 |
| 33 Collection.prototype._setEmpty = function(table, text) | 34 Collection.prototype._setEmpty = function(table, text) |
| 34 { | 35 { |
| 35 var placeholder = table.querySelector(".empty-placeholder"); | 36 var placeholder = table.querySelector(".empty-placeholder"); |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 57 return aValue.localeCompare(bValue); | 58 return aValue.localeCompare(bValue); |
| 58 }); | 59 }); |
| 59 | 60 |
| 60 for (var j = 0; j < this.details.length; j++) | 61 for (var j = 0; j < this.details.length; j++) |
| 61 { | 62 { |
| 62 var table = E(this.details[j].id); | 63 var table = E(this.details[j].id); |
| 63 var template = table.querySelector("template"); | 64 var template = table.querySelector("template"); |
| 64 for (var i = 0; i < arguments.length; i++) | 65 for (var i = 0; i < arguments.length; i++) |
| 65 { | 66 { |
| 66 var item = arguments[i]; | 67 var item = arguments[i]; |
| 67 var text = item.title || item.url || item.text; | |
| 68 var listItem = document.createElement("li"); | 68 var listItem = document.createElement("li"); |
| 69 listItem.appendChild(document.importNode(template.content, true)); | 69 listItem.appendChild(document.importNode(template.content, true)); |
| 70 listItem.setAttribute("data-access", item.url || item.text); | 70 listItem.setAttribute("data-access", item.url || item.text); |
| 71 listItem.querySelector(".display").textContent = text; | |
| 72 if (text) | |
| 73 listItem.setAttribute("data-search", text.toLowerCase()); | |
| 74 | 71 |
| 72 var labelId = "label-" + (++maxLabelId); | |
| 73 var label = listItem.querySelector(".display"); | |
|
saroyanm
2016/01/19 11:19:50
Nit:
we can have two lines merged:
listItem.query
Thomas Greiner
2016/01/19 15:15:04
Done.
| |
| 74 label.setAttribute("id", labelId); | |
| 75 var control = listItem.querySelector(".control"); | 75 var control = listItem.querySelector(".control"); |
| 76 if (control) | 76 if (control) |
| 77 { | 77 { |
| 78 // We use aria-labelledby to avoid triggering the control when | |
| 79 // interacting with the label | |
| 80 control.setAttribute("aria-labelledby", labelId); | |
| 78 control.addEventListener("click", this.details[j].onClick, false); | 81 control.addEventListener("click", this.details[j].onClick, false); |
| 79 control.checked = item.disabled == false; | |
| 80 } | 82 } |
| 81 | 83 |
| 82 this._setEmpty(table, null); | 84 this._setEmpty(table, null); |
| 83 if (table.hasChildNodes()) | 85 if (table.hasChildNodes()) |
| 84 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]); | 86 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]); |
| 85 else | 87 else |
| 86 table.appendChild(listItem); | 88 table.appendChild(listItem); |
| 89 this.updateItem(item); | |
| 87 } | 90 } |
| 88 } | 91 } |
| 89 return length; | 92 return length; |
| 90 }; | 93 }; |
| 91 | 94 |
| 92 Collection.prototype.removeItem = function(item) | 95 Collection.prototype.removeItem = function(item) |
| 93 { | 96 { |
| 94 var index = this.items.indexOf(item); | 97 var index = this.items.indexOf(item); |
| 95 if (index == -1) | 98 if (index == -1) |
| 96 return; | 99 return; |
| 97 | 100 |
| 98 this.items.splice(index, 1); | 101 this.items.splice(index, 1); |
| 99 for (var i = 0; i < this.details.length; i++) | 102 for (var i = 0; i < this.details.length; i++) |
| 100 { | 103 { |
| 101 var table = E(this.details[i].id); | 104 var table = E(this.details[i].id); |
| 102 var element = table.childNodes[index]; | 105 var element = table.childNodes[index]; |
| 106 | |
| 107 // Element gets removed so make sure to handle focus appropriately | |
| 108 var control = element.querySelector(".control"); | |
| 109 if (control && control == document.activeElement) | |
| 110 { | |
| 111 if (!focusNextElement(element.parentElement, control)) | |
| 112 { | |
| 113 // Fall back to next focusable element within same tab | |
| 114 var tab = element.parentElement; | |
| 115 while (true) | |
|
saroyanm
2016/01/19 11:19:49
What about ?
while (tab.tagName == "BODY") ?
In t
Thomas Greiner
2016/01/19 15:15:04
Done. Checking for the element type should be the
saroyanm
2016/01/25 14:45:43
you can also check for the element itself "documen
| |
| 116 { | |
| 117 if (tab.classList.contains("tab-content")) | |
| 118 break; | |
| 119 | |
| 120 tab = tab.parentElement; | |
| 121 if (!tab) | |
| 122 { | |
| 123 tab = document; | |
| 124 break; | |
| 125 } | |
| 126 } | |
| 127 focusNextElement(tab, control); | |
| 128 } | |
| 129 } | |
| 130 | |
| 103 element.parentElement.removeChild(element); | 131 element.parentElement.removeChild(element); |
| 104 if (this.items.length == 0) | 132 if (this.items.length == 0) |
| 105 this._setEmpty(table, this.details[i].emptyText); | 133 this._setEmpty(table, this.details[i].emptyText); |
| 106 } | 134 } |
| 107 }; | 135 }; |
| 108 | 136 |
| 137 Collection.prototype.updateItem = function(item) | |
|
saroyanm
2016/01/19 11:19:50
I think it make sense to have second optional argu
Thomas Greiner
2016/01/19 15:15:04
But an item can be associated with multiple list e
| |
| 138 { | |
| 139 var access = (item.url || item.text).replace(/'/g, "\\'"); | |
| 140 for (var i = 0; i < this.details.length; i++) | |
| 141 { | |
| 142 var table = E(this.details[i].id); | |
| 143 var element = table.querySelector("[data-access='" + access + "']"); | |
| 144 if (!element) | |
| 145 continue; | |
| 146 | |
| 147 var text = item.title || item.url || item.text; | |
| 148 element.querySelector(".display").textContent = text; | |
| 149 if (text) | |
| 150 element.setAttribute("data-search", text.toLowerCase()); | |
| 151 var control = element.querySelector(".control[role='checkbox']"); | |
| 152 if (control) | |
| 153 control.setAttribute("aria-checked", item.disabled == false); | |
| 154 } | |
| 155 }; | |
| 156 | |
| 109 Collection.prototype.clearAll = function() | 157 Collection.prototype.clearAll = function() |
| 110 { | 158 { |
| 111 this.items = []; | 159 this.items = []; |
| 112 for (var i = 0; i < this.details.length; i++) | 160 for (var i = 0; i < this.details.length; i++) |
| 113 { | 161 { |
| 114 var table = E(this.details[i].id); | 162 var table = E(this.details[i].id); |
| 115 var template = table.querySelector("template"); | 163 var template = table.querySelector("template"); |
| 116 table.innerHTML = ""; | 164 table.innerHTML = ""; |
| 117 table.appendChild(template); | 165 table.appendChild(template); |
| 118 this._setEmpty(table, this.details[i].emptyText); | 166 this._setEmpty(table, this.details[i].emptyText); |
| 119 } | 167 } |
| 120 }; | 168 }; |
| 121 | 169 |
| 170 function focusNextElement(container, currentElement) | |
| 171 { | |
| 172 var focusables = container.querySelectorAll("a, button, .control"); | |
|
saroyanm
2016/01/19 11:19:50
What about have a focus for input elements as well
Thomas Greiner
2016/01/19 15:15:04
Done.
| |
| 173 focusables = Array.prototype.slice.call(focusables); | |
| 174 var index = focusables.indexOf(currentElement); | |
| 175 if (index + 1 < focusables.length) | |
|
saroyanm
2016/01/19 11:19:49
I think we can write this one line, something like
Thomas Greiner
2016/01/19 15:15:04
Done.
| |
| 176 index += 1; | |
| 177 else if (index < focusables.length) | |
| 178 index -= 1; | |
| 179 | |
| 180 var nextElement = focusables[index]; | |
| 181 if (!nextElement) | |
| 182 return false; | |
| 183 | |
| 184 nextElement.focus(); | |
| 185 return true; | |
| 186 } | |
| 187 | |
| 122 function onToggleSubscriptionClick(e) | 188 function onToggleSubscriptionClick(e) |
| 123 { | 189 { |
| 124 e.preventDefault(); | 190 e.preventDefault(); |
| 125 var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); | 191 var checkbox = e.target; |
| 126 if (!e.target.checked) | 192 var subscriptionUrl = checkbox.parentElement.getAttribute("data-access"); |
| 193 if (checkbox.getAttribute("aria-checked") == "true") | |
| 127 { | 194 { |
| 128 ext.backgroundPage.sendMessage({ | 195 ext.backgroundPage.sendMessage({ |
| 129 type: "subscriptions.remove", | 196 type: "subscriptions.remove", |
| 130 url: subscriptionUrl | 197 url: subscriptionUrl |
| 131 }); | 198 }); |
| 132 } | 199 } |
| 133 else | 200 else |
| 134 addEnableSubscription(subscriptionUrl); | 201 addEnableSubscription(subscriptionUrl); |
| 135 } | 202 } |
| 136 | 203 |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 213 var subscriptionUrl = subscription.url; | 280 var subscriptionUrl = subscription.url; |
| 214 var knownSubscription = subscriptionsMap[subscriptionUrl]; | 281 var knownSubscription = subscriptionsMap[subscriptionUrl]; |
| 215 if (knownSubscription) | 282 if (knownSubscription) |
| 216 knownSubscription.disabled = subscription.disabled; | 283 knownSubscription.disabled = subscription.disabled; |
| 217 else | 284 else |
| 218 { | 285 { |
| 219 getAcceptableAdsURL(function(acceptableAdsUrl) | 286 getAcceptableAdsURL(function(acceptableAdsUrl) |
| 220 { | 287 { |
| 221 function onObjectChanged() | 288 function onObjectChanged() |
| 222 { | 289 { |
| 223 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' "); | 290 for (var i in collections) |
| 224 var elements = document.querySelectorAll("[data-access='" + access + " ']"); | 291 collections[i].updateItem(subscription); |
| 225 for (var i = 0; i < elements.length; i++) | 292 |
| 293 var recommendation = recommendationsMap[subscriptionUrl]; | |
| 294 if (recommendation && recommendation.type == "ads") | |
| 226 { | 295 { |
| 227 var element = elements[i]; | 296 if (subscription.disabled == false) |
| 228 var control = element.querySelector(".control"); | |
| 229 if (control.localName == "input") | |
| 230 control.checked = subscription.disabled == false; | |
| 231 if (subscriptionUrl in recommendationsMap) | |
| 232 { | 297 { |
| 233 var recommendation = recommendationsMap[subscriptionUrl]; | 298 collections.allLangs.removeItem(subscription); |
| 234 if (recommendation.type == "ads") | 299 collections.langs.addItems(subscription); |
| 235 { | 300 } |
| 236 if (subscription.disabled == false) | 301 else |
| 237 { | 302 { |
| 238 collections.allLangs.removeItem(subscription); | 303 collections.allLangs.addItems(subscription); |
| 239 collections.langs.addItems(subscription); | 304 collections.langs.removeItem(subscription); |
| 240 } | |
| 241 else | |
| 242 { | |
| 243 collections.allLangs.addItems(subscription); | |
| 244 collections.langs.removeItem(subscription); | |
| 245 } | |
| 246 } | |
| 247 } | 305 } |
| 248 } | 306 } |
| 249 } | 307 } |
| 250 | 308 |
| 251 if (!Object.observe) | 309 if (!Object.observe) |
| 252 { | 310 { |
| 253 // Currently only "disabled" property of subscription used for observa tion | 311 // Currently only "disabled" property of subscription used for observa tion |
| 254 // but with Advanced tab implementation we should also add more proper ties. | 312 // but with Advanced tab implementation we should also add more proper ties. |
| 255 ["disabled"].forEach(function(property) | 313 ["disabled"].forEach(function(property) |
| 256 { | 314 { |
| (...skipping 560 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 817 filter: ["added", "loaded", "removed"] | 875 filter: ["added", "loaded", "removed"] |
| 818 }); | 876 }); |
| 819 ext.backgroundPage.sendMessage( | 877 ext.backgroundPage.sendMessage( |
| 820 { | 878 { |
| 821 type: "subscriptions.listen", | 879 type: "subscriptions.listen", |
| 822 filter: ["added", "disabled", "homepage", "removed", "title"] | 880 filter: ["added", "disabled", "homepage", "removed", "title"] |
| 823 }); | 881 }); |
| 824 | 882 |
| 825 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); | 883 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); |
| 826 })(); | 884 })(); |
| OLD | NEW |