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 |