Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: options.js

Issue 29333819: Issue 2375 - Implement "Blocking lists" section in new options page (Closed)
Left Patch Set: Created Jan. 18, 2016, 9:50 a.m.
Right Patch Set: Fixed the progress indicator and small fixes Created Feb. 4, 2016, 5:43 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« background.js ('K') | « options.html ('k') | skin/options.css » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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-2016 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;
27 var getMessage = ext.i18n.getMessage;
28 var filterErrors =
29 {
30 "synchronize_invalid_url": "options_filterList_lastDownload_invalidURL",
31 "synchronize_connection_error": "options_filterList_lastDownload_connectionE rror",
32 "synchronize_invalid_data": "options_filterList_lastDownload_invalidData",
33 "synchronize_checksum_mismatch": "options_filterList_lastDownload_checksumMi smatch"
34 };
26 35
27 function Collection(details) 36 function Collection(details)
28 { 37 {
29 this.details = details; 38 this.details = details;
30 this.items = []; 39 this.items = [];
31 } 40 }
32 41
33 Collection.prototype._setEmpty = function(table, text) 42 Collection.prototype._setEmpty = function(table, text)
34 { 43 {
35 var placeholder = table.querySelector(".empty-placeholder"); 44 var placeholder = table.querySelector(".empty-placeholder");
36 if (text && !placeholder) 45 if (text && !placeholder)
37 { 46 {
38 placeholder = document.createElement("li"); 47 placeholder = document.createElement("li");
39 placeholder.className = "empty-placeholder"; 48 placeholder.className = "empty-placeholder";
40 placeholder.textContent = ext.i18n.getMessage(text); 49 placeholder.textContent = getMessage(text);
41 table.appendChild(placeholder); 50 table.appendChild(placeholder);
42 } 51 }
43 else if (placeholder) 52 else if (placeholder)
44 table.removeChild(placeholder); 53 table.removeChild(placeholder);
45 } 54 }
55
56 Collection.prototype._createElementQuery = function(item)
57 {
58 var access = (item.url || item.text).replace(/'/g, "\\'");
59 return function(container)
60 {
61 return container.querySelector("[data-access='" + access + "']");
62 };
63 };
46 64
47 Collection.prototype.addItems = function() 65 Collection.prototype.addItems = function()
48 { 66 {
49 var length = Array.prototype.push.apply(this.items, arguments); 67 var length = Array.prototype.push.apply(this.items, arguments);
50 if (length == 0) 68 if (length == 0)
51 return; 69 return;
52 70
53 this.items.sort(function(a, b) 71 this.items.sort(function(a, b)
54 { 72 {
55 var aValue = (a.title || a.text || a.url).toLowerCase(); 73 var aValue = (a.title || a.text || a.url).toLowerCase();
56 var bValue = (b.title || b.text || b.url).toLowerCase(); 74 var bValue = (b.title || b.text || b.url).toLowerCase();
57 return aValue.localeCompare(bValue); 75 return aValue.localeCompare(bValue);
58 }); 76 });
59 77
60 for (var j = 0; j < this.details.length; j++) 78 for (var j = 0; j < this.details.length; j++)
61 { 79 {
62 var table = E(this.details[j].id); 80 var table = E(this.details[j].id);
63 var template = table.querySelector("template"); 81 var template = table.querySelector("template");
64 for (var i = 0; i < arguments.length; i++) 82 for (var i = 0; i < arguments.length; i++)
65 { 83 {
66 var item = arguments[i]; 84 var item = arguments[i];
67 var text = item.title || item.url || item.text;
68 var listItem = document.createElement("li"); 85 var listItem = document.createElement("li");
69 listItem.appendChild(document.importNode(template.content, true)); 86 listItem.appendChild(document.importNode(template.content, true));
70 listItem.setAttribute("data-access", item.url || item.text); 87 listItem.setAttribute("data-access", item.url || item.text);
71 listItem.querySelector(".display").textContent = text; 88
72 if (text) 89 var labelId = "label-" + (++maxLabelId);
73 listItem.setAttribute("data-search", text.toLowerCase()); 90 listItem.querySelector(".display").setAttribute("id", labelId);
74
75 updateTimeDate(listItem, item);
76 var control = listItem.querySelector(".control"); 91 var control = listItem.querySelector(".control");
77 if (control) 92 if (control)
78 { 93 {
94 // We use aria-labelledby to avoid triggering the control when
95 // interacting with the label
96 control.setAttribute("aria-labelledby", labelId);
79 control.addEventListener("click", this.details[j].onClick, false); 97 control.addEventListener("click", this.details[j].onClick, false);
80 control.checked = item.disabled == false;
81 } 98 }
82 99
83 this._setEmpty(table, null); 100 this._setEmpty(table, null);
84 if (table.hasChildNodes()) 101 if (table.hasChildNodes())
85 { 102 {
86 var index = this.items.indexOf(item); 103 table.insertBefore(listItem,
87 if (table.firstChild.classList.contains("head")) 104 table.childNodes[this.items.indexOf(item)]);
88 index++;
89
90 table.insertBefore(listItem, table.childNodes[index]);
91 } 105 }
92 else 106 else
93 table.appendChild(listItem); 107 table.appendChild(listItem);
108 this.updateItem(item);
94 } 109 }
95 } 110 }
96 return length; 111 return length;
97 }; 112 };
98 113
99 Collection.prototype.removeItem = function(item) 114 Collection.prototype.removeItem = function(item)
100 { 115 {
101 var index = this.items.indexOf(item); 116 var index = this.items.indexOf(item);
102 if (index == -1) 117 if (index == -1)
103 return; 118 return;
104 119
105 this.items.splice(index, 1); 120 this.items.splice(index, 1);
106 var access = (item.url || item.text).replace(/'/g, "\\'"); 121 var getListElement = this._createElementQuery(item);
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
107 for (var i = 0; i < this.details.length; i++) 122 for (var i = 0; i < this.details.length; i++)
108 { 123 {
109 var table = E(this.details[i].id); 124 var table = E(this.details[i].id);
110 var element = table.querySelector("[data-access='" + access + "']"); 125 var element = getListElement(table);
126
127 // Element gets removed so make sure to handle focus appropriately
128 var control = element.querySelector(".control");
129 if (control && control == document.activeElement)
130 {
131 if (!focusNextElement(element.parentElement, control))
132 {
133 // Fall back to next focusable element within same tab or dialog
134 var focusableElement = element.parentElement;
135 while (focusableElement)
136 {
137 if (focusableElement.classList.contains("tab-content")
138 || focusableElement.classList.contains("dialog-content"))
139 break;
140
141 focusableElement = focusableElement.parentElement;
142 }
143 focusNextElement(focusableElement || document, control);
144 }
145 }
146
111 element.parentElement.removeChild(element); 147 element.parentElement.removeChild(element);
112 if (this.items.length == 0) 148 if (this.items.length == 0)
113 this._setEmpty(table, this.details[i].emptyText); 149 this._setEmpty(table, this.details[i].emptyText);
114 } 150 }
115 }; 151 };
116 152
153 Collection.prototype.updateItem = function(item)
154 {
155 var access = (item.url || item.text).replace(/'/g, "\\'");
156 for (var i = 0; i < this.details.length; i++)
157 {
158 var table = E(this.details[i].id);
159 var element = table.querySelector("[data-access='" + access + "']");
160 if (!element)
161 continue;
162
163 var title = item.title || item.url || item.text;
164 element.querySelector(".display").textContent = title;
165 if (title)
166 element.setAttribute("data-search", title.toLowerCase());
167 var control = element.querySelector(".control[role='checkbox']");
168 if (control)
169 control.setAttribute("aria-checked", item.disabled == false);
170
171 var downloadStatus = item.downloadStatus;
172 var dateElement = element.querySelector(".date");
173 var timeElement = element.querySelector(".time");
174 if(dateElement && timeElement)
175 {
176 var message = element.querySelector(".message");
177 ext.backgroundPage.sendMessage(
178 {
179 type: "subscriptions.isDownloading",
180 url: item.url
181 },
182 function(isDownloading)
183 {
184 if (isDownloading)
185 {
186 var text = getMessage("options_filterList_lastDownload_inProgress");
187 message.textContent = text;
188 element.classList.add("show-message");
189 }
190 else if (downloadStatus && downloadStatus != "synchronize_ok")
191 {
192 if (downloadStatus in filterErrors)
193 message.textContent = getMessage(filterErrors[downloadStatus]);
194 else
195 message.textContent = item.downloadStatus;
196 element.classList.add("show-message");
197 }
198 else if (item.lastDownload > 0)
199 {
200 var dateTime = i18n_formatDateTime(item.lastDownload * 1000);
201 dateElement.textContent = dateTime[0];
202 timeElement.textContent = dateTime[1];
203 element.classList.remove("show-message");
204 }
205 });
206 }
207 var websiteElement = element.querySelector(".context-menu .website");
208 var sourceElement = element.querySelector(".context-menu .source");
209 if (websiteElement && item.homepage)
210 websiteElement.setAttribute("href", item.homepage);
211 if (sourceElement)
212 sourceElement.setAttribute("href", item.url);
213 }
214 };
215
117 Collection.prototype.clearAll = function() 216 Collection.prototype.clearAll = function()
118 { 217 {
119 this.items = []; 218 this.items = [];
120 for (var i = 0; i < this.details.length; i++) 219 for (var i = 0; i < this.details.length; i++)
121 { 220 {
122 var table = E(this.details[i].id); 221 var table = E(this.details[i].id);
123 var template = table.querySelector("template");
124 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
125 var element = table.firstChild; 222 var element = table.firstChild;
126 while (element) 223 while (element)
127 { 224 {
128 if (element.tagName == "TEMPLATE" || 225 if (element.tagName == "LI" && !element.classList.contains("static"))
129 (element.classList && element.classList.contains("static"))) 226 table.removeChild(element);
130 staticElements.push(element); 227 element = element.nextElementSibling;
131 element = element.nextSibling
132 } 228 }
133 229
134 table.innerHTML = "";
135 for (var j = 0; j < staticElements.length; j++)
136 table.appendChild(staticElements[j]);
137
138 this._setEmpty(table, this.details[i].emptyText); 230 this._setEmpty(table, this.details[i].emptyText);
139 } 231 }
140 }; 232 };
141 233
142 Collection.prototype.getTableIds = function() 234 function focusNextElement(container, currentElement)
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
143 { 235 {
144 var ids = []; 236 var focusables = container.querySelectorAll("a, button, input, .control");
145 for (var i = 0; i < this.details.length; i++) 237 focusables = Array.prototype.slice.call(focusables);
146 ids.push(this.details[i].id) 238 var index = focusables.indexOf(currentElement);
147 239 index += (index == focusables.length - 1) ? -1 : 1;
148 return ids; 240
149 }; 241 var nextElement = focusables[index];
150 242 if (!nextElement)
151 function updateTimeDate(listItem, subscription) 243 return false;
152 { 244
153 var dateElement = listItem.querySelector(".date"); 245 nextElement.focus();
154 var timeElement = listItem.querySelector(".time"); 246 return true;
155 247 }
156 if(dateElement && timeElement) 248
157 { 249 function toggleRemoveSubscription(e)
158 if (subscription.downloadStatus &&
159 subscription.downloadStatus != "synchronize_ok")
160 {
161 var map =
162 {
163 "synchronize_invalid_url": "options_subscription_lastDownload_invalidU RL",
164 "synchronize_connection_error": "options_subscription_lastDownload_con nectionError",
165 "synchronize_invalid_data": "options_subscription_lastDownload_invalid Data",
166 "synchronize_checksum_mismatch": "options_subscription_lastDownload_ch ecksumMismatch"
167 };
168 if (subscription.downloadStatus in map)
169 timeElement.textContent = ext.i18n.getMessage(map[subscription.downloa dStatus]);
170 else
171 timeElement.textContent = subscription.downloadStatus;
172 }
173 else if (subscription.lastDownload > 0)
174 {
175 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.
176 if (timedate[1])
177 dateElement.textContent = timedate[1].split("/").reverse().join("-");
178 else
179 {
180 var today = new Date();
181 dateElement.textContent = today.getFullYear() + "-" +
182 today.getMonth()+1 + "-" + today.getDate();
183 }
184 var time = timedate[0].split(":");
185 time[2] = time[2].split(" ")[1];
186 if (time[2] == "AM" && time[0] == "12")
187 time[0] = 0;
188 else if (time[2] == "PM")
189 time[0] = parseInt(time[0]) + 12;
190
191 time.pop();
192 timeElement.textContent = time.join(":");
193 }
194 }
195 }
196
197 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.
198 { 250 {
199 e.preventDefault(); 251 e.preventDefault();
200 var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); 252 var subscriptionUrl = findParentData(e.target, "access", false);
201 if (!e.target.checked) 253 if (e.target.getAttribute("aria-checked") == "true")
202 { 254 {
203 ext.backgroundPage.sendMessage({ 255 ext.backgroundPage.sendMessage({
204 type: "subscriptions.remove", 256 type: "subscriptions.remove",
205 url: subscriptionUrl 257 url: subscriptionUrl
206 }); 258 });
207 } 259 }
208 else 260 else
209 addEnableSubscription(subscriptionUrl); 261 addEnableSubscription(subscriptionUrl);
210 } 262 }
211 263
212 function onToggleSubscriptionStateClick(e) 264 function toggleDisableSubscription(e)
213 { 265 {
214 e.preventDefault(); 266 e.preventDefault();
215 var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); 267 var subscriptionUrl = findParentData(e.target, "access", false);
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.
216 ext.backgroundPage.sendMessage( 268 ext.backgroundPage.sendMessage(
217 { 269 {
218 type: "subscriptions.toggleState", 270 type: "subscriptions.toggle",
271 keepInstalled: true,
219 url: subscriptionUrl 272 url: subscriptionUrl
220 }); 273 });
221 } 274 }
222 275
223 function onAddLanguageSubscriptionClick(e) 276 function onAddLanguageSubscriptionClick(e)
224 { 277 {
225 e.preventDefault(); 278 e.preventDefault();
226 var url = this.parentNode.getAttribute("data-access"); 279 var url = findParentData(this, "access", false);
227 addEnableSubscription(url); 280 addEnableSubscription(url);
228 } 281 }
229 282
230 function onRemoveFilterClick() 283 function onRemoveFilterClick()
231 { 284 {
232 var filter = this.parentNode.getAttribute("data-access"); 285 var filter = findParentData(this, "access", false);
233 ext.backgroundPage.sendMessage( 286 ext.backgroundPage.sendMessage(
234 { 287 {
235 type: "filters.remove", 288 type: "filters.remove",
236 text: filter 289 text: filter
237 }); 290 });
238 } 291 }
239 292
240 collections.popular = new Collection( 293 collections.popular = new Collection(
241 [ 294 [
242 { 295 {
243 id: "recommend-list-table", 296 id: "recommend-list-table",
244 onClick: onToggleSubscriptionClick 297 onClick: toggleRemoveSubscription
245 } 298 }
246 ]); 299 ]);
247 collections.langs = new Collection( 300 collections.langs = new Collection(
248 [ 301 [
249 { 302 {
250 id: "blocking-languages-table", 303 id: "blocking-languages-table",
251 emptyText: "options_dialog_language_added_empty", 304 emptyText: "options_dialog_language_added_empty",
252 onClick: onToggleSubscriptionClick 305 onClick: toggleRemoveSubscription
253 }, 306 },
254 { 307 {
255 id: "blocking-languages-dialog-table", 308 id: "blocking-languages-dialog-table",
256 emptyText: "options_dialog_language_added_empty" 309 emptyText: "options_dialog_language_added_empty"
257 } 310 }
258 ]); 311 ]);
259 collections.allLangs = new Collection( 312 collections.allLangs = new Collection(
260 [ 313 [
261 { 314 {
262 id: "all-lang-table", 315 id: "all-lang-table",
263 emptyText: "options_dialog_language_other_empty", 316 emptyText: "options_dialog_language_other_empty",
264 onClick: onAddLanguageSubscriptionClick 317 onClick: onAddLanguageSubscriptionClick
265 } 318 }
266 ]); 319 ]);
267 collections.acceptableAds = new Collection( 320 collections.acceptableAds = new Collection(
268 [ 321 [
269 { 322 {
270 id: "acceptableads-table", 323 id: "acceptableads-table",
271 onClick: onToggleSubscriptionClick 324 onClick: toggleRemoveSubscription
272 } 325 }
273 ]); 326 ]);
274 collections.custom = new Collection( 327 collections.custom = new Collection(
275 [ 328 [
276 { 329 {
277 id: "custom-list-table", 330 id: "custom-list-table",
278 onClick: onToggleSubscriptionClick 331 onClick: toggleRemoveSubscription
279 } 332 }
280 ]); 333 ]);
281 collections.whitelist = new Collection( 334 collections.whitelist = new Collection(
282 [ 335 [
283 { 336 {
284 id: "whitelisting-table", 337 id: "whitelisting-table",
285 emptyText: "options_whitelisted_empty", 338 emptyText: "options_whitelisted_empty",
286 onClick: onRemoveFilterClick 339 onClick: onRemoveFilterClick
287 } 340 }
288 ]); 341 ]);
289 collections.customFilters = new Collection( 342 collections.customFilters = new Collection(
290 [ 343 [
291 { 344 {
292 id: "custom-filters-table", 345 id: "custom-filters-table",
293 emptyText: "options_customFilters_empty" 346 emptyText: "options_customFilters_empty"
294 } 347 }
295 ]); 348 ]);
296 collections.blockingLists = new Collection( 349 collections.filterLists = new Collection(
297 [ 350 [
298 { 351 {
299 id: "blocking-lists-table", 352 id: "all-filter-lists-table",
300 onClick: onToggleSubscriptionStateClick 353 onClick: toggleDisableSubscription
301 } 354 }
302 ]); 355 ]);
303 356
304 function observeSubscription(subscription) 357 function observeSubscription(subscription)
305 { 358 {
306 function onObjectChanged(change) 359 function onObjectChanged(change)
307 { 360 {
308 for (var i = 0; i < change.length; i++) 361 for (var i = 0; i < change.length; i++)
309 { 362 {
310 var property = change[i].name; 363 if (change[i].name == "disabled")
311 if (property == "disabled") 364 {
312 { 365 var recommendation = recommendationsMap[subscription.url];
313 var access = (subscription.url || 366 if (recommendation && recommendation.type == "ads")
314 subscription.text).replace(/'/g, "\\'"); 367 {
315 var elements = document.querySelectorAll("[data-access='" + access + " ']"); 368 if (subscription.disabled == false)
316 for (var i = 0; i < elements.length; i++)
317 {
318 var element = elements[i];
319 var tableId = element.parentElement ? element.parentElement.id : "";
320 var control = element.querySelector(".control");
321 if (control && control.localName == "input")
322 control.checked = subscription.disabled == false;
323 if (subscription.url in recommendationsMap)
324 { 369 {
325 var recommendation = recommendationsMap[subscription.url]; 370 collections.allLangs.removeItem(subscription);
326 var langids = collections.langs.getTableIds(); 371 collections.langs.addItems(subscription);
327 var allLangIds = collections.allLangs.getTableIds();
328 if (recommendation.type == "ads" &&
329 langids.concat(allLangIds).indexOf(tableId) != -1)
330 {
331 if (subscription.disabled == false)
332 {
333 collections.allLangs.removeItem(subscription);
334 collections.langs.addItems(subscription);
335 }
336 else
337 {
338 collections.allLangs.addItems(subscription);
339 collections.langs.removeItem(subscription);
340 }
341 }
342 } 372 }
373 else
374 {
375 collections.allLangs.addItems(subscription);
376 collections.langs.removeItem(subscription);
377 }
343 } 378 }
344 } 379 }
345 else 380 for (var i in collections)
346 { 381 collections[i].updateItem(subscription);
347 var blockingListId = collections.blockingLists.details[0].id;
348 var blockingList = document.getElementById(blockingListId);
349 var listItem = blockingList.querySelector("[data-access='" +
350 subscription.url + "']");
351 updateTimeDate(listItem, subscription);
352 }
353 } 382 }
354 } 383 }
355 384
356 if (!Object.observe) 385 if (!Object.observe)
357 { 386 {
358 ["disabled", "lastDownload"].forEach(function(property) 387 ["disabled", "lastDownload"].forEach(function(property)
359 { 388 {
360 subscription["$" + property] = subscription[property]; 389 subscription["$" + property] = subscription[property];
361 Object.defineProperty(subscription, property, 390 Object.defineProperty(subscription, property,
362 { 391 {
363 get: function() 392 get: function()
364 { 393 {
365 return this["$" + property]; 394 return this["$" + property];
366 }, 395 },
367 set: function(value) 396 set: function(newValue)
368 { 397 {
369 this["$" + property] = value; 398 var oldValue = this["$" + property];
370 var change = Object.create(null); 399 if (oldValue != newValue)
371 change.name = property; 400 {
372 onObjectChanged([change]); 401 this["$" + property] = newValue;
402 var change = Object.create(null);
403 change.name = property;
404 onObjectChanged([change]);
405 }
373 } 406 }
374 }); 407 });
375 }); 408 });
376 } 409 }
377 else 410 else
378 { 411 {
379 Object.observe(subscription, onObjectChanged); 412 Object.observe(subscription, onObjectChanged);
380 } 413 }
381 } 414 }
382 415
383 function updateSubscription(subscription) 416 function updateSubscription(subscription)
384 { 417 {
385 var subscriptionUrl = subscription.url; 418 var subscriptionUrl = subscription.url;
386 var knownSubscription = subscriptionsMap[subscriptionUrl]; 419 var knownSubscription = subscriptionsMap[subscriptionUrl];
387 if (knownSubscription) 420 if (knownSubscription)
388 { 421 {
389 knownSubscription.disabled = subscription.disabled; 422 for (var property in subscription)
390 knownSubscription.lastDownload = subscription.lastDownload; 423 if (property != "title")
424 knownSubscription[property] = subscription[property];
391 } 425 }
392 else 426 else
393 { 427 {
394 observeSubscription(subscription); 428 observeSubscription(subscription);
395 getAcceptableAdsURL(function(acceptableAdsUrl) 429 getAcceptableAdsURL(function(acceptableAdsUrl)
396 { 430 {
397 var collection = null; 431 var collection = null;
398 if (subscriptionUrl in recommendationsMap) 432 if (subscriptionUrl in recommendationsMap)
399 { 433 {
400 var recommendation = recommendationsMap[subscriptionUrl]; 434 var recommendation = recommendationsMap[subscriptionUrl];
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
444 var elements = doc.documentElement.getElementsByTagName("subscription"); 478 var elements = doc.documentElement.getElementsByTagName("subscription");
445 for (var i = 0; i < elements.length; i++) 479 for (var i = 0; i < elements.length; i++)
446 { 480 {
447 var element = elements[i]; 481 var element = elements[i];
448 var subscription = Object.create(null); 482 var subscription = Object.create(null);
449 subscription.title = element.getAttribute("title"); 483 subscription.title = element.getAttribute("title");
450 subscription.url = element.getAttribute("url"); 484 subscription.url = element.getAttribute("url");
451 subscription.disabled = null; 485 subscription.disabled = null;
452 subscription.downloadStatus = null; 486 subscription.downloadStatus = null;
453 subscription.homepage = null; 487 subscription.homepage = null;
454 subscription.lastSuccess = null;
455 var recommendation = Object.create(null); 488 var recommendation = Object.create(null);
456 recommendation.type = element.getAttribute("type"); 489 recommendation.type = element.getAttribute("type");
457 var prefix = element.getAttribute("prefixes"); 490 var prefix = element.getAttribute("prefixes");
458 if (prefix) 491 if (prefix)
459 { 492 {
460 prefix = prefix.replace(/\W/g, "_"); 493 prefix = prefix.replace(/\W/g, "_");
461 subscription.title = ext.i18n.getMessage("options_language_" + prefi x); 494 subscription.title = getMessage("options_language_" + prefix);
462 } 495 }
463 else 496 else
464 { 497 {
465 var type = recommendation.type.replace(/\W/g, "_"); 498 var type = recommendation.type.replace(/\W/g, "_");
466 subscription.title = ext.i18n.getMessage("common_feature_" + type + "_title"); 499 subscription.title = getMessage("common_feature_" + type + "_title") ;
467 } 500 }
468 501
469 recommendationsMap[subscription.url] = recommendation; 502 recommendationsMap[subscription.url] = recommendation;
470 updateSubscription(subscription); 503 updateSubscription(subscription);
471 } 504 }
472 }); 505 });
473 } 506 }
474 507
508 function findParentData(element, dataName, returnElement)
509 {
510 while (element)
511 {
512 if (element.hasAttribute("data-" + dataName))
513 return returnElement ? element : element.getAttribute("data-" + dataName );
514
515 element = element.parentElement;
516 }
517 return null;
518 }
519
475 function onClick(e) 520 function onClick(e)
476 { 521 {
522 var context = document.querySelector(".show-context-menu");
523 if (context)
524 context.classList.remove("show-context-menu");
525
477 var element = e.target; 526 var element = e.target;
478 while (true) 527 while (true)
479 { 528 {
480 if (!element) 529 if (!element)
481 return; 530 return;
482 531
483 if (element.hasAttribute("data-action")) 532 if (element.hasAttribute("data-action"))
484 break; 533 break;
485 534
486 element = element.parentElement; 535 element = element.parentElement;
487 }
488
489 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.
490 {
491 var elementCopy = element;
492 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.
493 elementCopy = elementCopy.parentNode;
494
495 return elementCopy;
496 } 536 }
497 537
498 var actions = element.getAttribute("data-action").split(","); 538 var actions = element.getAttribute("data-action").split(",");
499 for (var i = 0; i < actions.length; i++) 539 for (var i = 0; i < actions.length; i++)
500 { 540 {
501 switch (actions[i]) 541 switch (actions[i])
502 { 542 {
503 case "add-domain-exception": 543 case "add-domain-exception":
504 addWhitelistedDomain(); 544 addWhitelistedDomain();
505 break; 545 break;
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
544 }); 584 });
545 E("custom-filters").classList.remove("mode-edit"); 585 E("custom-filters").classList.remove("mode-edit");
546 break; 586 break;
547 case "switch-tab": 587 case "switch-tab":
548 document.body.setAttribute("data-tab", 588 document.body.setAttribute("data-tab",
549 element.getAttribute("data-tab")); 589 element.getAttribute("data-tab"));
550 break; 590 break;
551 case "update-all-subscriptions": 591 case "update-all-subscriptions":
552 ext.backgroundPage.sendMessage( 592 ext.backgroundPage.sendMessage(
553 { 593 {
554 type: "subscriptions.updateAll" 594 type: "subscriptions.update"
555 }); 595 });
556 break; 596 break;
557 case "open-context-menu": 597 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.
558 var listItem = getParentAccessElement(); 598 var listItem = findParentData(element, "access", true);
559 var contextMenu = listItem.querySelector(".content"); 599 if (listItem != context)
560 listItem.classList.add("context"); 600 listItem.classList.add("show-context-menu");
561 601 break;
562 function mouseover() 602 case "update-subscription":
563 {
564 contextMenu.addEventListener("mouseout", mouseout, false);
565 contextMenu.removeEventListener("mouseover", mouseover);
566 }
567 function mouseout(event)
568 {
569 if (event.target.parentElement != contextMenu)
570 {
571 var relatedTarget = event.relatedTarget;
572 if (relatedTarget.parentNode == this || relatedTarget == this)
573 return;
574 listItem.classList.remove("context");
575 contextMenu.removeEventListener("mouseout", mouseout);
576 }
577 }
578
579 contextMenu.addEventListener("mouseover", mouseover, false);
580 break;
581 case "update-now":
582 ext.backgroundPage.sendMessage( 603 ext.backgroundPage.sendMessage(
583 { 604 {
584 type: "subscriptions.update", 605 type: "subscriptions.update",
585 url: getParentAccessElement().dataset.access 606 url: findParentData(element, "access", false)
586 }); 607 });
587 break; 608 break;
588 case "website": 609 case "remove-subscription":
589 ext.backgroundPage.sendMessage( 610 ext.backgroundPage.sendMessage(
590 { 611 {
591 type: "subscriptions.website", 612 type: "subscriptions.remove",
592 url: getParentAccessElement().dataset.access 613 url: findParentData(element, "access", false)
593 }); 614 });
594 break; 615 break;
595 case "source":
596 window.open(getParentAccessElement().dataset.access);
597 break;
598 case "delete":
599 ext.backgroundPage.sendMessage(
600 {
601 type: "subscriptions.remove",
602 url: getParentAccessElement().dataset.access
603 });
604 break;
605 } 616 }
606 } 617 }
607 } 618 }
608 619
609 function onDOMLoaded() 620 function onDOMLoaded()
610 { 621 {
611 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.
612 {
613 template.content.querySelector(selector).
614 textContent = ext.i18n.getMessage(messageId);
615 }
616
617 var template = document.querySelector("#recommend-list-table template");
618 updateTemplate(template, ".popular", "options_popular");
619
620 template = document.querySelector("#all-lang-table template");
621 updateTemplate(template, ".button-add span", "options_button_add");
622
623 template = document.querySelector("#blocking-lists-table template");
624 updateTemplate(template, ".update-now", "options_blockingList_update_now");
625 updateTemplate(template, ".website", "options_blockingList_website");
626 updateTemplate(template, ".source", "options_blockingList_source");
627 updateTemplate(template, ".delete", "options_blockingList_delete");
628
629 populateLists(); 622 populateLists();
630
631 function onFindLanguageKeyUp() 623 function onFindLanguageKeyUp()
632 { 624 {
633 var searchStyle = E("search-style"); 625 var searchStyle = E("search-style");
634 if (!this.value) 626 if (!this.value)
635 searchStyle.innerHTML = ""; 627 searchStyle.innerHTML = "";
636 else 628 else
637 searchStyle.innerHTML = "#all-lang-table li:not([data-search*=\"" + this .value.toLowerCase() + "\"]) { display: none; }"; 629 searchStyle.innerHTML = "#all-lang-table li:not([data-search*=\"" + this .value.toLowerCase() + "\"]) { display: none; }";
638 } 630 }
639 631
640 function getKey(e) 632 function getKey(e)
(...skipping 26 matching lines...) Expand all
667 659
668 getDocLink("contribute", function(link) 660 getDocLink("contribute", function(link)
669 { 661 {
670 document.querySelector("#tab-contribute a").setAttribute("href", link); 662 document.querySelector("#tab-contribute a").setAttribute("href", link);
671 }); 663 });
672 664
673 updateShareLink(); 665 updateShareLink();
674 666
675 // Initialize interactive UI elements 667 // Initialize interactive UI elements
676 document.body.addEventListener("click", onClick, false); 668 document.body.addEventListener("click", onClick, false);
677 var placeholderValue = ext.i18n.getMessage("options_dialog_language_find"); 669 var placeholderValue = getMessage("options_dialog_language_find");
678 E("find-language").setAttribute("placeholder", placeholderValue); 670 E("find-language").setAttribute("placeholder", placeholderValue);
679 E("find-language").addEventListener("keyup", onFindLanguageKeyUp, false); 671 E("find-language").addEventListener("keyup", onFindLanguageKeyUp, false);
680 E("whitelisting-textbox").addEventListener("keypress", function(e) 672 E("whitelisting-textbox").addEventListener("keypress", function(e)
681 { 673 {
682 if (getKey(e) == "Enter") 674 if (getKey(e) == "Enter")
683 addWhitelistedDomain(); 675 addWhitelistedDomain();
684 }, false); 676 }, false);
685 677
686 // Advanced tab 678 // Advanced tab
687 var filterTextbox = document.querySelector("#custom-filters-add input"); 679 var filterTextbox = document.querySelector("#custom-filters-add input");
688 placeholderValue = ext.i18n.getMessage("options_customFilters_textbox_placeh older"); 680 placeholderValue = getMessage("options_customFilters_textbox_placeholder");
689 filterTextbox.setAttribute("placeholder", placeholderValue); 681 filterTextbox.setAttribute("placeholder", placeholderValue);
690 function addCustomFilters() 682 function addCustomFilters()
691 { 683 {
692 var filterText = filterTextbox.value; 684 var filterText = filterTextbox.value;
693 ext.backgroundPage.sendMessage( 685 ext.backgroundPage.sendMessage(
694 { 686 {
695 type: "filters.add", 687 type: "filters.add",
696 text: filterText 688 text: filterText
697 }); 689 });
698 filterTextbox.value = ""; 690 filterTextbox.value = "";
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
786 updateFilter(filters[i]); 778 updateFilter(filters[i]);
787 }); 779 });
788 } 780 }
789 }); 781 });
790 loadRecommendations(); 782 loadRecommendations();
791 getAcceptableAdsURL(function(acceptableAdsUrl) 783 getAcceptableAdsURL(function(acceptableAdsUrl)
792 { 784 {
793 var subscription = Object.create(null); 785 var subscription = Object.create(null);
794 subscription.url = acceptableAdsUrl; 786 subscription.url = acceptableAdsUrl;
795 subscription.disabled = true; 787 subscription.disabled = true;
796 subscription.title = ext.i18n.getMessage("options_acceptableAds_descriptio n"); 788 subscription.title = getMessage("options_acceptableAds_description");
797 updateSubscription(subscription); 789 updateSubscription(subscription);
798 790
799 // Load user subscriptions 791 // Load user subscriptions
800 ext.backgroundPage.sendMessage( 792 ext.backgroundPage.sendMessage(
801 { 793 {
802 type: "subscriptions.get", 794 type: "subscriptions.get",
803 downloadable: true 795 downloadable: true
804 }, 796 },
805 function(subscriptions) 797 function(subscriptions)
806 { 798 {
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
897 function onSubscriptionMessage(action, subscription) 889 function onSubscriptionMessage(action, subscription)
898 { 890 {
899 switch (action) 891 switch (action)
900 { 892 {
901 case "added": 893 case "added":
902 updateSubscription(subscription); 894 updateSubscription(subscription);
903 updateShareLink(); 895 updateShareLink();
904 896
905 var knownSubscription = subscriptionsMap[subscription.url]; 897 var knownSubscription = subscriptionsMap[subscription.url];
906 if (knownSubscription) 898 if (knownSubscription)
907 collections.blockingLists.addItems(knownSubscription); 899 collections.filterLists.addItems(knownSubscription);
908 else 900 else
909 collections.blockingLists.addItems(subscription); 901 collections.filterLists.addItems(subscription);
910 break; 902 break;
911 case "disabled": 903 case "disabled":
912 updateSubscription(subscription); 904 updateSubscription(subscription);
913 updateShareLink(); 905 updateShareLink();
914 break; 906 break;
915 case "updated": 907 case "lastDownload":
916 updateSubscription(subscription); 908 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.
917 break; 909 break;
918 case "homepage": 910 case "homepage":
919 // TODO: NYI 911 // TODO: NYI
920 break; 912 break;
921 case "removed": 913 case "removed":
922 var knownSubscription = subscriptionsMap[subscription.url]; 914 var knownSubscription = subscriptionsMap[subscription.url];
923 getAcceptableAdsURL(function(acceptableAdsUrl) 915 getAcceptableAdsURL(function(acceptableAdsUrl)
924 { 916 {
925 if (subscription.url == acceptableAdsUrl) 917 if (subscription.url == acceptableAdsUrl)
926 { 918 {
927 subscription.disabled = true; 919 subscription.disabled = true;
928 updateSubscription(subscription); 920 updateSubscription(subscription);
929 } 921 }
930 else 922 else
931 { 923 {
932 if (subscription.url in recommendationsMap) 924 if (subscription.url in recommendationsMap)
933 knownSubscription.disabled = true; 925 knownSubscription.disabled = true;
934 else 926 else
935 { 927 {
936 collections.custom.removeItem(knownSubscription); 928 collections.custom.removeItem(knownSubscription);
937 delete subscriptionsMap[subscription.url]; 929 delete subscriptionsMap[subscription.url];
938 } 930 }
939 } 931 }
940 updateShareLink(); 932 updateShareLink();
933 collections.filterLists.removeItem(knownSubscription);
941 }); 934 });
942 collections.blockingLists.removeItem(knownSubscription);
943 break; 935 break;
944 case "title": 936 case "title":
945 // TODO: NYI 937 // TODO: NYI
946 break; 938 break;
947 } 939 }
948 } 940 }
949 941
950 function onShareLinkClick(e) 942 function onShareLinkClick(e)
951 { 943 {
952 e.preventDefault(); 944 e.preventDefault();
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
1021 filter: ["addSubscription", "error"] 1013 filter: ["addSubscription", "error"]
1022 }); 1014 });
1023 ext.backgroundPage.sendMessage( 1015 ext.backgroundPage.sendMessage(
1024 { 1016 {
1025 type: "filters.listen", 1017 type: "filters.listen",
1026 filter: ["added", "loaded", "removed"] 1018 filter: ["added", "loaded", "removed"]
1027 }); 1019 });
1028 ext.backgroundPage.sendMessage( 1020 ext.backgroundPage.sendMessage(
1029 { 1021 {
1030 type: "subscriptions.listen", 1022 type: "subscriptions.listen",
1031 filter: ["added", "disabled", "updated", "homepage", "removed", "title"] 1023 filter: ["added", "disabled", "homepage", "lastDownload", "removed", "title" ]
1032 }); 1024 });
1033 1025
1034 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); 1026 window.addEventListener("DOMContentLoaded", onDOMLoaded, false);
1035 })(); 1027 })();
LEFTRIGHT

Powered by Google App Engine
This is Rietveld