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

Side by Side Diff: options.js

Issue 6088024630755328: issue 1526 - Implement new options page design for Chrome/Opera/Safari (Closed)
Patch Set: Created March 12, 2015, 2:01 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2015 Eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 "use strict";
19
20 (function()
21 {
22 var subscriptionsMap = Object.create(null);
23 var recommendationsMap = Object.create(null);
24 var filtersMap = Object.create(null);
25 var collections = Object.create(null);
26
27 function Collection(details)
Felix Dahlke 2015/06/07 21:09:59 Does details have to be an array? From what I've s
saroyanm 2015/06/09 15:29:13 on line 139 I'm passing array from 2 elements. "Ad
Felix Dahlke 2015/06/11 14:40:20 True, fair enough.
28 {
29 this.details = details;
30 }
31
32 Collection.prototype = Object.create(Array.prototype);
Felix Dahlke 2015/06/07 21:09:59 From what I've seen, the only member functions use
saroyanm 2015/06/09 15:29:13 I would like to understand why it's more preferabl
Felix Dahlke 2015/06/11 14:40:20 It's a pretty common principle in OOP (http://en.w
saroyanm 2015/06/12 10:51:18 Done.
33 Collection.prototype.addItems = function()
34 {
35 var length = Array.prototype.push.apply(this, arguments);
36 if (length == 0)
37 return;
38
39 this.sort(function(a, b)
40 {
41 var aValue = (a.title || a.url || a.text).toLowerCase();
42 var bValue = (b.title || b.url || a.text).toLowerCase();
43 if (aValue < bValue)
Felix Dahlke 2015/06/07 21:09:59 Nit: Would be shorter using localeCompare: `return
saroyanm 2015/06/09 15:29:13 I like this :) Done.
44 return -1;
45 if (aValue > bValue)
46 return 1;
47 return 0;
48 });
49
50 for (var j = 0; j < this.details.length; j++)
51 {
52 var table = E(this.details[j].id);
53 var template = table.querySelector("template");
54 for (var i = 0; i < arguments.length; i++)
55 {
56 var item = arguments[i];
57 var text = item.title || item.url || item.text;
58 var listItem = document.createElement("li");
59 listItem.appendChild(document.importNode(template.content, true));
60 listItem.dataset.access = item.url || item.text;
61 listItem.querySelector(".display").textContent = text;
62 if (text)
63 listItem.dataset.search = text.toLowerCase();
64
65 var control = listItem.querySelector(".control");
66 if (control)
67 {
68 control.addEventListener("click", this.details[j].onClick, false);
69 control.checked = item.disabled == false;
Felix Dahlke 2015/06/07 21:09:59 Nit: "Do not compare x == true or x == false. Use
Felix Dahlke 2015/06/07 21:15:24 Sorry, wrong link, should have been: https://devel
saroyanm 2015/06/09 15:29:13 In this case is bit complicated, while the design
Felix Dahlke 2015/06/11 14:40:20 Oh I didn't realise that. No then it's fine.
70 }
71
72 if (table.hasChildNodes)
Felix Dahlke 2015/06/07 21:09:59 Shouldn't this be `table.hasChildNodes()`?
saroyanm 2015/06/09 15:29:13 Done.
73 table.insertBefore(listItem, table.childNodes[this.indexOf(item)]);
74 else
75 table.appendChild(listItem);
76 }
77 }
78 return length;
79 };
80
81 Collection.prototype.removeItem = function(item)
82 {
83 var index = this.indexOf(item);
84 if (index == -1)
85 return;
86
87 this.splice(index, 1);
88 var access = (item.url || item.text).replace(/'/g, "\\'");
89 for (var i = 0; i < this.details.length; i++)
90 {
91 var table = E(this.details[i].id);
92 var element = table.querySelector("[data-access='" + access + "']");
93 element.parentElement.removeChild(element);
94 }
95 };
96
97 Collection.prototype.clearAll = function()
98 {
99 for (var i = 0; i < this.details.length; i++)
100 {
101 var table = E(this.details[i].id);
102 var template = table.querySelector("template");
103 table.innerHTML = "";
104 table.appendChild(template);
105 }
106 this.length = 0;
107 };
108
109 function toggleSubscription(e)
Felix Dahlke 2015/06/07 21:09:59 Nit: Wouldn't insist on it, but being an event han
saroyanm 2015/06/09 15:29:13 Done.
110 {
111 e.preventDefault();
112 var subscriptionUrl = e.target.parentNode.dataset.access;
113 if (!e.target.checked)
114 removeSubscription(subscriptionUrl);
115 else
116 addEnableSubscription(subscriptionUrl);
117 }
118
119 function addLanguageSubscription(e)
120 {
121 e.preventDefault();
122 var url = this.parentNode.dataset.access;
123 addEnableSubscription(url);
124 }
125
126 function triggerRemoveFilter()
127 {
128 var filter = this.parentNode.dataset.access;
129 removeFilter(filter);
130 }
131
132 collections.popular = new Collection(
133 [
134 {
135 id: "recommend-list-table",
136 onClick: toggleSubscription
137 }
138 ]);
139 collections.langs = new Collection(
140 [
141 {
142 id: "blocking-languages-table",
143 onClick: toggleSubscription
144 },
145 {
146 id: "blocking-languages-modal-table"
147 }
148 ]);
149 collections.allLangs = new Collection(
150 [
151 {
152 id: "all-lang-table",
153 onClick: addLanguageSubscription
154 }
155 ]);
156 collections.acceptableAds = new Collection(
157 [
158 {
159 id: "acceptableads-table",
160 onClick: toggleSubscription
161 }
162 ]);
163 collections.custom = new Collection(
164 [
165 {
166 id: "custom-list-table",
167 onClick: toggleSubscription
168 }
169 ]);
170 collections.whitelist = new Collection(
171 [
172 {
173 id: "whitelisting-table",
174 onClick: triggerRemoveFilter
175 }
176 ]);
177
178 function updateSubscription(subscription)
179 {
180 var subscriptionUrl = subscription.url;
181 var knownSubscription = subscriptionsMap[subscriptionUrl];
182 if (knownSubscription)
183 knownSubscription.disabled = subscription.disabled;
184 else
185 {
186 getAcceptableAdsURL(function(acceptableAdsUrl)
187 {
188 function onObjectChanged()
189 {
190 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' ");
191 var elements = document.querySelectorAll("[data-access='" + access + " ']");
192 for (var i = 0; i < elements.length; i++)
193 {
194 var element = elements[i];
195 var control = element.querySelector(".control");
196 if (control.localName == "input")
197 control.checked = subscription.disabled == false;
198 if (subscriptionUrl in recommendationsMap)
199 {
200 var recommendation = recommendationsMap[subscriptionUrl];
201 if (recommendation.isAdsType)
202 {
203 if (subscription.disabled == false)
204 {
205 collections.allLangs.removeItem(subscription);
206 collections.langs.addItems(subscription);
207 }
208 else
209 {
210 collections.allLangs.addItems(subscription);
211 collections.langs.removeItem(subscription);
212 }
213 }
214 }
215 }
216 }
217
218 if (!Object.observe)
219 {
220 ["disabled"].forEach(function(property)
Felix Dahlke 2015/06/07 21:09:59 This could be simplified :D
saroyanm 2015/06/09 15:29:13 For future we are going to not only listen to "dis
Felix Dahlke 2015/06/11 14:40:20 If we're absolutely sure we'll need it, I'm OK wit
221 {
222 subscription["$" + property] = subscription[property];
223 Object.defineProperty(subscription, property,
224 {
225 get: function()
226 {
227 return this["$" + property];
228 },
229 set: function(value)
230 {
231 this["$" + property] = value;
232 onObjectChanged();
233 }
234 });
235 });
236 }
237 else
238 {
239 Object.observe(subscription, function(changes)
Felix Dahlke 2015/06/07 21:09:59 Nit: Why not just `Object.observe(subscription, on
saroyanm 2015/06/09 15:29:13 Done.
240 {
241 onObjectChanged();
242 });
243 }
244
245 var collection = null;
246 if (subscriptionUrl in recommendationsMap)
247 {
248 var recommendation = recommendationsMap[subscriptionUrl];
249 if (recommendation.isPopular)
250 collection = collections.popular;
251 else if (recommendation.isAdsType && subscription.disabled == false)
252 collection = collections.langs;
253 else
254 collection = collections.allLangs;
255 }
256 else if (subscriptionUrl == acceptableAdsUrl)
257 collection = collections.acceptableAds;
258 else
259 collection = collections.custom;
260
261 collection.addItems(subscription);
262 subscriptionsMap[subscriptionUrl] = subscription;
263 });
264 }
265 }
266
267 function updateFilter(filter)
268 {
269 var match = filter.text.match(/^@@\|\|([^\/:]+)\^\$document$/);
270 if (match && !filtersMap[filter.text])
271 {
272 filter.title = match[1];
273 collections.whitelist.addItems(filter);
274 filtersMap[filter.text] = filter
275 }
276 else
277 {
278 // TODO: add `filters[i].text` to list of custom filters
279 }
280 }
281
282 function loadRecommendations()
283 {
284 var request = new XMLHttpRequest();
285 request.open("GET", "subscriptions.xml", false);
286 request.onload = function()
Felix Dahlke 2015/06/07 21:09:59 Nit: Use addEventListener()?
saroyanm 2015/06/09 15:29:13 Done.
287 {
288 var list = document.getElementById("subscriptionSelector");
289 var docElem = request.responseXML.documentElement;
290 var elements = docElem.getElementsByTagName("subscription");
291 for (var i = 0; i < elements.length; i++)
292 {
293 var element = elements[i];
294 var subscription = Object.create(null);
295 subscription.title = element.getAttribute("title");
296 subscription.url = element.getAttribute("url");
297 subscription.disabled = null;
298 subscription.downloadStatus = null;
299 subscription.homepage = null;
300 subscription.lastSuccess = null;
301 var recommendation = Object.create(null);
302 recommendation.isAdsType = false;
303 recommendation.isPopular = false;
304 var prefix = element.getAttribute("prefixes");
305 if (prefix)
306 {
307 var prefix = element.getAttribute("prefixes").replace(/,/g, "_");
308 subscription.title = ext.i18n.getMessage("options_language_" + prefix) ;
309 recommendation.isAdsType = true;
310 }
311 else
312 subscription.title = element.getAttribute("specialization");
313
314 if (element.getAttribute("popular"))
315 recommendation.isPopular = true;
316
317 recommendationsMap[subscription.url] = recommendation;
318 updateSubscription(subscription);
319 }
320 };
321 request.send(null);
322 }
323
324 function onDOMLoaded()
325 {
326 var recommendationTemplate = document.querySelector("#recommend-list-table t emplate");
327 var popularText = ext.i18n.getMessage("options_popular");
328 recommendationTemplate.content.querySelector(".popular").textContent = popul arText;
329 var languagesTemplate = document.querySelector("#all-lang-table template");
330 var buttonText = ext.i18n.getMessage("options_button_add");
331 languagesTemplate.content.querySelector(".button-add span").textContent = bu ttonText;
332
333 updateShareLink();
334 populateLists();
335
336 var tabList = document.querySelectorAll("#main-navigation-tabs li");
337 for (var i = 0; i < tabList.length; i++)
338 {
339 tabList[i].addEventListener("click", function(e)
340 {
341 document.body.dataset.tab = e.currentTarget.dataset.show;
342 }, false);
343 }
344
345 function searchLanguage()
Felix Dahlke 2015/06/07 21:09:59 Nit: Similar to above, I'd find this easier to und
saroyanm 2015/06/09 15:29:13 Done.
346 {
347 var searchStyle = E("search-style");
348 if (!this.value)
349 searchStyle.innerHTML = "";
350 else
351 searchStyle.innerHTML = "#all-lang-table li:not([data-search*=\"" + this .value.toLowerCase() + "\"]) { display: none; }";
352 }
353
354 // Update version number in navigation sidebar
355 ext.backgroundPage.sendMessage(
356 {
357 method: "app.get",
358 what: "addonVersion"
359 },
360 function(addonVersion)
361 {
362 E("abp-version").textContent = addonVersion;
363 });
364
365 var placeholderValue = ext.i18n.getMessage("options_modal_language_find");
366 E("find-language").setAttribute("placeholder", placeholderValue);
367 E("add-blocking-list").addEventListener("click", function()
368 {
369 openModal("customlist");
370 }, false);
371 E("add-website-language").addEventListener("click", function()
372 {
373 openModal("language");
374 }, false);
375 E("modal-close").addEventListener("click", function()
376 {
377 delete document.body.dataset.modal;
378 }, false);
379 E("edit-ownBlockingList-button").addEventListener("click", editCustomFilters , false);
380 E("find-language").addEventListener("keyup", searchLanguage, false);
381 E("whitelisting").addEventListener("click", function(e)
382 {
383 var id = e.target.id;
384 if (id == "whitelisting-add-icon" || id == "whitelisting-enter-icon")
385 addWhitelistedDomain();
386 else if (id == "whitelisting-cancel-button")
387 E("whitelisting-textbox").value = "";
388 }, false);
389 E("whitelisting-add-button").addEventListener("click", addWhitelistedDomain, false);
390 E("whitelisting-textbox").addEventListener("keypress", function(e)
391 {
392 // e.keyCode has been deprecated so we use e.key instead
Felix Dahlke 2015/06/07 21:09:59 Nit: "so we attempt to use e.key"? Since we're act
saroyanm 2015/06/09 15:29:13 Done.
393 if (e.key && e.key == "Enter")
394 addWhitelistedDomain();
395 else if (!e.key && e.keyCode == 13) //keyCode "13" corresponds to "Enter"
396 addWhitelistedDomain();
Felix Dahlke 2015/06/07 21:09:59 Nit: Seeing how the else body is identical to the
saroyanm 2015/06/09 15:29:13 Done.
397 }, false);
398 E("import-blockingList-button").addEventListener("click", function()
399 {
400 var url = E("blockingList-textbox").value;
401 addEnableSubscription(url);
402 delete document.body.dataset.modal;
403 }, false);
404 }
405
406 function openModal(name)
407 {
408 document.body.dataset.modal = name;
409 }
410
411 function populateLists()
412 {
413 subscriptionsMap = Object.create(null);
414 filtersMap = Object.create(null);
415 recommendationsMap = Object.create(null);
416
417 // Empty collections and lists
418 for (var property in collections)
419 collections[property].clearAll();
420
421 ext.backgroundPage.sendMessage(
422 {
423 type: "subscriptions.get",
424 special: true
425 },
426 function(subscriptions)
427 {
428 // Load filters
429 for (var i = 0; i < subscriptions.length; i++)
430 {
431 ext.backgroundPage.sendMessage(
432 {
433 type: "filters.get",
434 subscriptionUrl: subscriptions[i].url
435 },
436 function(filters)
437 {
438 for (var i = 0; i < filters.length; i++)
439 updateFilter(filters[i]);
440 });
441 }
442 });
443 loadRecommendations();
444 getAcceptableAdsURL(function(acceptableAdsUrl)
445 {
446 var subscription = Object.create(null);
447 subscription.url = acceptableAdsUrl;
448 subscription.disabled = true;
449 subscription.title = ext.i18n.getMessage("options_acceptableAds_descriptio n");
450 updateSubscription(subscription);
451
452 // Load user subscriptions
453 ext.backgroundPage.sendMessage(
454 {
455 type: "subscriptions.get",
456 downloadable: true
457 },
458 function(subscriptions)
459 {
460 for (var i = 0; i < subscriptions.length; i++)
461 onSubscriptionMessage("added", subscriptions[i]);
462 });
463 });
464 }
465
466 function addWhitelistedDomain()
467 {
468 var domain = E("whitelisting-textbox");
469 if (domain.value)
470 {
471 ext.backgroundPage.sendMessage(
472 {
473 type: "filters.add",
474 text: "@@||" + domain.value.toLowerCase() + "^$document"
475 });
476 }
477
478 domain.value = "";
479 }
480
481 function editCustomFilters()
482 {
483
Felix Dahlke 2015/06/07 21:09:59 Nit: Seeing how we have a bunch of TODO comments h
saroyanm 2015/06/09 15:29:13 Done.
484 }
485
486 function getAcceptableAdsURL(callback)
487 {
488 ext.backgroundPage.sendMessage(
489 {
490 type: "prefs.get",
491 key: "subscriptions_exceptionsurl"
492 },
493 function(value)
494 {
495 getAcceptableAdsURL = function(callback)
496 {
497 callback(value);
498 }
499 getAcceptableAdsURL(callback);
500 });
501 }
502
503 function addEnableSubscription(url, title, homepage)
504 {
505 var messageType = null;
506 var knownSubscription = subscriptionsMap[url];
507 if (knownSubscription && knownSubscription.disabled == true)
508 messageType = "subscriptions.toggle"
509 else
510 messageType = "subscriptions.add"
511
512 var message = {
513 type: messageType,
514 url: url
515 };
516 if (title)
517 message.title = title;
518 if (homepage)
519 message.homepage = homepage;
520
521 ext.backgroundPage.sendMessage(message);
522 }
523
524 function removeSubscription(url)
525 {
526 ext.backgroundPage.sendMessage(
527 {
528 type: "subscriptions.remove",
529 url: url
530 });
531 }
532
533 function removeFilter(filter)
534 {
535 ext.backgroundPage.sendMessage(
536 {
537 type: "filters.remove",
538 text: filter
539 });
540 }
541
542 function onFilterMessage(action, filter)
543 {
544 switch (action)
545 {
546 case "added":
547 updateFilter(filter);
548 updateShareLink();
549 break;
550 case "loaded":
551 populateLists();
552 break;
553 case "removed":
554 var knownFilter = filtersMap[filter.text];
555 collections.whitelist.removeItem(knownFilter);
556 delete filtersMap[filter.text];
557 updateShareLink();
558 break;
559 }
560 }
561
562 function onSubscriptionMessage(action, subscription)
563 {
564 switch (action)
565 {
566 case "added":
567 updateSubscription(subscription);
568 updateShareLink();
569 break;
570 case "disabled":
Felix Dahlke 2015/06/07 21:09:59 Merge with case "added" above?
saroyanm 2015/06/09 15:29:13 Done.
571 updateSubscription(subscription);
572 updateShareLink();
573 break;
574 case "homepage":
575 // TODO: NYI
576 break;
577 case "removed":
578 getAcceptableAdsURL(function(acceptableAdsUrl)
579 {
580 if (subscription.url == acceptableAdsUrl)
581 {
582 subscription.disabled = true;
583 updateSubscription(subscription);
584 }
585 else
586 {
587 var knownSubscription = subscriptionsMap[subscription.url];
588 if (subscription.url in recommendationsMap)
589 knownSubscription.disabled = true;
590 else
591 {
592 collections.custom.removeItem(knownSubscription);
593 delete subscriptionsMap[subscription.url];
594 }
595 }
596 updateShareLink();
597 });
598 break;
599 case "title":
600 // TODO: NYI
601 break;
602 }
603 }
604
605 function showAddSubscriptionDialog(subscription)
606 {
607 E("blockingList-textbox").value = subscription.url;
608 openModal("customlist");
609 }
610
611 function updateShareLink()
612 {
613 ext.backgroundPage.sendMessage(
614 {
615 type: "filters.blocked",
616 url: "https://platform.twitter.com/widgets/",
617 requestType: "SCRIPT",
618 docDomain: "adblockplus.org",
619 thirdParty: true
620 },
621 function(blocked)
622 {
623 // TODO: modify "share" link accordingly
624 });
625 }
626
627 function E(id)
Felix Dahlke 2015/06/07 21:09:59 Not a fan of this function, made things harder to
Felix Dahlke 2015/06/08 19:44:51 Just realised that we have E() as well in the Fire
628 {
629 return document.getElementById(id);
630 }
631
632 ext.onMessage.addListener(function(message)
633 {
634 switch (message.type)
635 {
636 case "app.listen":
637 if (message.action == "addSubscription")
638 showAddSubscriptionDialog(message.args[0]);
639 break;
640 case "filters.listen":
641 onFilterMessage(message.action, message.args[0]);
642 break;
643 case "subscriptions.listen":
644 onSubscriptionMessage(message.action, message.args[0]);
645 break;
646 }
647 });
648
649 ext.backgroundPage.sendMessage(
650 {
651 type: "app.listen",
652 filter: ["addSubscription"]
653 });
654 ext.backgroundPage.sendMessage(
655 {
656 type: "filters.listen",
657 filter: ["added", "loaded", "removed"]
658 });
659 ext.backgroundPage.sendMessage(
660 {
661 type: "subscriptions.listen",
662 filter: ["added", "disabled", "homepage", "removed", "title"]
663 });
664
665 window.addEventListener("DOMContentLoaded", onDOMLoaded, false);
666 })();
OLDNEW

Powered by Google App Engine
This is Rietveld