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: Adderessed Felix initial comments Created June 9, 2015, 3:22 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
« no previous file with comments | « options.html ('k') | skin/options.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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)
28 {
29 this.details = details;
30 }
31
32 Collection.prototype = Object.create(Array.prototype);
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 return aValue.localeCompare(bValue);
44 });
45
46 for (var j = 0; j < this.details.length; j++)
47 {
48 var table = E(this.details[j].id);
49 var template = table.querySelector("template");
50 for (var i = 0; i < arguments.length; i++)
51 {
52 var item = arguments[i];
53 var text = item.title || item.url || item.text;
54 var listItem = document.createElement("li");
55 listItem.appendChild(document.importNode(template.content, true));
56 listItem.dataset.access = item.url || item.text;
57 listItem.querySelector(".display").textContent = text;
58 if (text)
59 listItem.dataset.search = text.toLowerCase();
60
61 var control = listItem.querySelector(".control");
62 if (control)
63 {
64 control.addEventListener("click", this.details[j].onClick, false);
65 control.checked = item.disabled == false;
66 }
67
68 if (table.hasChildNodes())
69 table.insertBefore(listItem, table.childNodes[this.indexOf(item)]);
70 else
71 table.appendChild(listItem);
72 }
73 }
74 return length;
75 };
76
77 Collection.prototype.removeItem = function(item)
78 {
79 var index = this.indexOf(item);
80 if (index == -1)
81 return;
82
83 this.splice(index, 1);
84 var access = (item.url || item.text).replace(/'/g, "\\'");
85 for (var i = 0; i < this.details.length; i++)
86 {
87 var table = E(this.details[i].id);
88 var element = table.querySelector("[data-access='" + access + "']");
89 element.parentElement.removeChild(element);
90 }
91 };
92
93 Collection.prototype.clearAll = function()
94 {
95 for (var i = 0; i < this.details.length; i++)
96 {
97 var table = E(this.details[i].id);
98 var template = table.querySelector("template");
99 table.innerHTML = "";
100 table.appendChild(template);
101 }
102 this.length = 0;
103 };
104
105 function onToggleSubscriptionClick(e)
106 {
107 e.preventDefault();
108 var subscriptionUrl = e.target.parentNode.dataset.access;
109 if (!e.target.checked)
110 removeSubscription(subscriptionUrl);
111 else
112 addEnableSubscription(subscriptionUrl);
113 }
114
115 function onAddLanguageSubscriptionClick(e)
116 {
117 e.preventDefault();
118 var url = this.parentNode.dataset.access;
119 addEnableSubscription(url);
120 }
121
122 function onRemoveFilterClick()
123 {
124 var filter = this.parentNode.dataset.access;
125 removeFilter(filter);
126 }
127
128 collections.popular = new Collection(
129 [
130 {
131 id: "recommend-list-table",
132 onClick: onToggleSubscriptionClick
133 }
134 ]);
135 collections.langs = new Collection(
136 [
137 {
138 id: "blocking-languages-table",
139 onClick: onToggleSubscriptionClick
140 },
141 {
142 id: "blocking-languages-dialog-table"
143 }
144 ]);
145 collections.allLangs = new Collection(
146 [
147 {
148 id: "all-lang-table",
149 onClick: onAddLanguageSubscriptionClick
150 }
151 ]);
152 collections.acceptableAds = new Collection(
153 [
154 {
155 id: "acceptableads-table",
156 onClick: onToggleSubscriptionClick
157 }
158 ]);
159 collections.custom = new Collection(
160 [
161 {
162 id: "custom-list-table",
163 onClick: onToggleSubscriptionClick
164 }
165 ]);
166 collections.whitelist = new Collection(
167 [
168 {
169 id: "whitelisting-table",
170 onClick: onRemoveFilterClick
171 }
172 ]);
173
174 function updateSubscription(subscription)
175 {
176 var subscriptionUrl = subscription.url;
177 var knownSubscription = subscriptionsMap[subscriptionUrl];
178 if (knownSubscription)
179 knownSubscription.disabled = subscription.disabled;
180 else
181 {
182 getAcceptableAdsURL(function(acceptableAdsUrl)
183 {
184 function onObjectChanged()
185 {
186 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' ");
187 var elements = document.querySelectorAll("[data-access='" + access + " ']");
188 for (var i = 0; i < elements.length; i++)
189 {
190 var element = elements[i];
191 var control = element.querySelector(".control");
192 if (control.localName == "input")
193 control.checked = subscription.disabled == false;
194 if (subscriptionUrl in recommendationsMap)
195 {
196 var recommendation = recommendationsMap[subscriptionUrl];
197 if (recommendation.isAdsType)
198 {
199 if (subscription.disabled == false)
200 {
201 collections.allLangs.removeItem(subscription);
202 collections.langs.addItems(subscription);
203 }
204 else
205 {
206 collections.allLangs.addItems(subscription);
207 collections.langs.removeItem(subscription);
208 }
209 }
210 }
211 }
212 }
213
214 if (!Object.observe)
215 {
216 // Currently only "disabled" property of subscription used for observa tion
217 // but with Advanced tab implementation we should also add more proper ties.
218 ["disabled"].forEach(function(property)
219 {
220 subscription["$" + property] = subscription[property];
221 Object.defineProperty(subscription, property,
222 {
223 get: function()
224 {
225 return this["$" + property];
226 },
227 set: function(value)
228 {
229 this["$" + property] = value;
230 onObjectChanged();
231 }
232 });
233 });
234 }
235 else
236 {
237 Object.observe(subscription, onObjectChanged);
238 }
239
240 var collection = null;
241 if (subscriptionUrl in recommendationsMap)
242 {
243 var recommendation = recommendationsMap[subscriptionUrl];
244 if (recommendation.isPopular)
245 collection = collections.popular;
246 else if (recommendation.isAdsType && subscription.disabled == false)
247 collection = collections.langs;
248 else
249 collection = collections.allLangs;
250 }
251 else if (subscriptionUrl == acceptableAdsUrl)
252 collection = collections.acceptableAds;
253 else
254 collection = collections.custom;
255
256 collection.addItems(subscription);
257 subscriptionsMap[subscriptionUrl] = subscription;
258 });
259 }
260 }
261
262 function updateFilter(filter)
263 {
264 var match = filter.text.match(/^@@\|\|([^\/:]+)\^\$document$/);
265 if (match && !filtersMap[filter.text])
266 {
267 filter.title = match[1];
268 collections.whitelist.addItems(filter);
269 filtersMap[filter.text] = filter
270 }
271 else
272 {
273 // TODO: add `filters[i].text` to list of custom filters
274 }
275 }
276
277 function loadRecommendations()
278 {
279 var request = new XMLHttpRequest();
280 request.open("GET", "subscriptions.xml", false);
281 request.addEventListener("load", function()
282 {
283 var list = document.getElementById("subscriptionSelector");
284 var docElem = request.responseXML.documentElement;
285 var elements = docElem.getElementsByTagName("subscription");
286 for (var i = 0; i < elements.length; i++)
287 {
288 var element = elements[i];
289 var subscription = Object.create(null);
290 subscription.title = element.getAttribute("title");
291 subscription.url = element.getAttribute("url");
292 subscription.disabled = null;
293 subscription.downloadStatus = null;
294 subscription.homepage = null;
295 subscription.lastSuccess = null;
296 var recommendation = Object.create(null);
297 recommendation.isAdsType = false;
298 recommendation.isPopular = false;
299 var prefix = element.getAttribute("prefixes");
300 if (prefix)
301 {
302 var prefix = element.getAttribute("prefixes").replace(/,/g, "_");
303 subscription.title = ext.i18n.getMessage("options_language_" + prefix) ;
304 recommendation.isAdsType = true;
305 }
306 else
307 subscription.title = element.getAttribute("specialization");
308
309 if (element.getAttribute("popular"))
310 recommendation.isPopular = true;
311
312 recommendationsMap[subscription.url] = recommendation;
313 updateSubscription(subscription);
314 }
315 }, false);
316 request.send(null);
317 }
318
319 function onDOMLoaded()
320 {
321 var recommendationTemplate = document.querySelector("#recommend-list-table t emplate");
322 var popularText = ext.i18n.getMessage("options_popular");
323 recommendationTemplate.content.querySelector(".popular").textContent = popul arText;
324 var languagesTemplate = document.querySelector("#all-lang-table template");
325 var buttonText = ext.i18n.getMessage("options_button_add");
326 languagesTemplate.content.querySelector(".button-add span").textContent = bu ttonText;
327
328 updateShareLink();
329 populateLists();
330
331 var tabList = document.querySelectorAll("#main-navigation-tabs li");
332 for (var i = 0; i < tabList.length; i++)
333 {
334 tabList[i].addEventListener("click", function(e)
335 {
336 document.body.dataset.tab = e.currentTarget.dataset.show;
337 }, false);
338 }
339
340 function onFindLanguageKeyUp()
341 {
342 var searchStyle = E("search-style");
343 if (!this.value)
344 searchStyle.innerHTML = "";
345 else
346 searchStyle.innerHTML = "#all-lang-table li:not([data-search*=\"" + this .value.toLowerCase() + "\"]) { display: none; }";
347 }
348
349 // Update version number in navigation sidebar
350 ext.backgroundPage.sendMessage(
351 {
352 method: "app.get",
353 what: "addonVersion"
354 },
355 function(addonVersion)
356 {
357 E("abp-version").textContent = addonVersion;
358 });
359
360 var placeholderValue = ext.i18n.getMessage("options_dialog_language_find");
361 E("find-language").setAttribute("placeholder", placeholderValue);
362 E("add-blocking-list").addEventListener("click", function()
363 {
364 openDialog("customlist");
365 }, false);
366 E("add-website-language").addEventListener("click", function()
367 {
368 openDialog("language");
369 }, false);
370 E("dialog-close").addEventListener("click", function()
371 {
372 delete document.body.dataset.dialog;
373 }, false);
374 E("edit-ownBlockingList-button").addEventListener("click", editCustomFilters , false);
375 E("find-language").addEventListener("keyup", onFindLanguageKeyUp, false);
376 E("whitelisting").addEventListener("click", function(e)
377 {
378 var id = e.target.id;
379 if (id == "whitelisting-add-icon" || id == "whitelisting-enter-icon")
380 addWhitelistedDomain();
381 else if (id == "whitelisting-cancel-button")
382 E("whitelisting-textbox").value = "";
383 }, false);
384 E("whitelisting-add-button").addEventListener("click", addWhitelistedDomain, false);
385 E("whitelisting-textbox").addEventListener("keypress", function(e)
386 {
387 // e.keyCode has been deprecated so we attempt to use e.key
388 // keyCode "13" corresponds to "Enter"
389 if ((e.key && e.key == "Enter") || (!e.key && e.keyCode == 13))
390 addWhitelistedDomain();
391 }, false);
392 E("import-blockingList-button").addEventListener("click", function()
393 {
394 var url = E("blockingList-textbox").value;
395 addEnableSubscription(url);
396 delete document.body.dataset.dialog;
397 }, false);
398 }
399
400 function openDialog(name)
401 {
402 document.body.dataset.dialog = name;
403 }
404
405 function populateLists()
406 {
407 subscriptionsMap = Object.create(null);
408 filtersMap = Object.create(null);
409 recommendationsMap = Object.create(null);
410
411 // Empty collections and lists
412 for (var property in collections)
413 collections[property].clearAll();
414
415 ext.backgroundPage.sendMessage(
416 {
417 type: "subscriptions.get",
418 special: true
419 },
420 function(subscriptions)
421 {
422 // Load filters
423 for (var i = 0; i < subscriptions.length; i++)
424 {
425 ext.backgroundPage.sendMessage(
426 {
427 type: "filters.get",
428 subscriptionUrl: subscriptions[i].url
429 },
430 function(filters)
431 {
432 for (var i = 0; i < filters.length; i++)
433 updateFilter(filters[i]);
434 });
435 }
436 });
437 loadRecommendations();
438 getAcceptableAdsURL(function(acceptableAdsUrl)
439 {
440 var subscription = Object.create(null);
441 subscription.url = acceptableAdsUrl;
442 subscription.disabled = true;
443 subscription.title = ext.i18n.getMessage("options_acceptableAds_descriptio n");
444 updateSubscription(subscription);
445
446 // Load user subscriptions
447 ext.backgroundPage.sendMessage(
448 {
449 type: "subscriptions.get",
450 downloadable: true
451 },
452 function(subscriptions)
453 {
454 for (var i = 0; i < subscriptions.length; i++)
455 onSubscriptionMessage("added", subscriptions[i]);
456 });
457 });
458 }
459
460 function addWhitelistedDomain()
461 {
462 var domain = E("whitelisting-textbox");
463 if (domain.value)
464 {
465 ext.backgroundPage.sendMessage(
466 {
467 type: "filters.add",
468 text: "@@||" + domain.value.toLowerCase() + "^$document"
469 });
470 }
471
472 domain.value = "";
473 }
474
475 function editCustomFilters()
476 {
477 //TODO: NYI
478 }
479
480 function getAcceptableAdsURL(callback)
481 {
482 ext.backgroundPage.sendMessage(
483 {
484 type: "prefs.get",
485 key: "subscriptions_exceptionsurl"
486 },
487 function(value)
488 {
489 getAcceptableAdsURL = function(callback)
490 {
491 callback(value);
492 }
493 getAcceptableAdsURL(callback);
494 });
495 }
496
497 function addEnableSubscription(url, title, homepage)
498 {
499 var messageType = null;
500 var knownSubscription = subscriptionsMap[url];
501 if (knownSubscription && knownSubscription.disabled == true)
502 messageType = "subscriptions.toggle"
503 else
504 messageType = "subscriptions.add"
505
506 var message = {
507 type: messageType,
508 url: url
509 };
510 if (title)
511 message.title = title;
512 if (homepage)
513 message.homepage = homepage;
514
515 ext.backgroundPage.sendMessage(message);
516 }
517
518 function removeSubscription(url)
519 {
520 ext.backgroundPage.sendMessage(
521 {
522 type: "subscriptions.remove",
523 url: url
524 });
525 }
526
527 function removeFilter(filter)
528 {
529 ext.backgroundPage.sendMessage(
530 {
531 type: "filters.remove",
532 text: filter
533 });
534 }
535
536 function onFilterMessage(action, filter)
537 {
538 switch (action)
539 {
540 case "added":
541 updateFilter(filter);
542 updateShareLink();
543 break;
544 case "loaded":
545 populateLists();
546 break;
547 case "removed":
548 var knownFilter = filtersMap[filter.text];
549 collections.whitelist.removeItem(knownFilter);
550 delete filtersMap[filter.text];
551 updateShareLink();
552 break;
553 }
554 }
555
556 function onSubscriptionMessage(action, subscription)
557 {
558 switch (action)
559 {
560 case "added":
561 case "disabled":
562 updateSubscription(subscription);
563 updateShareLink();
564 break;
565 case "homepage":
566 // TODO: NYI
567 break;
568 case "removed":
569 getAcceptableAdsURL(function(acceptableAdsUrl)
570 {
571 if (subscription.url == acceptableAdsUrl)
572 {
573 subscription.disabled = true;
574 updateSubscription(subscription);
575 }
576 else
577 {
578 var knownSubscription = subscriptionsMap[subscription.url];
579 if (subscription.url in recommendationsMap)
580 knownSubscription.disabled = true;
581 else
582 {
583 collections.custom.removeItem(knownSubscription);
584 delete subscriptionsMap[subscription.url];
585 }
586 }
587 updateShareLink();
588 });
589 break;
590 case "title":
591 // TODO: NYI
592 break;
593 }
594 }
595
596 function showAddSubscriptionDialog(subscription)
597 {
598 E("blockingList-textbox").value = subscription.url;
599 openDialog("customlist");
600 }
601
602 function updateShareLink()
603 {
604 ext.backgroundPage.sendMessage(
605 {
606 type: "filters.blocked",
607 url: "https://platform.twitter.com/widgets/",
608 requestType: "SCRIPT",
609 docDomain: "adblockplus.org",
610 thirdParty: true
611 },
612 function(blocked)
613 {
614 // TODO: modify "share" link accordingly
615 });
616 }
617
618 function E(id)
619 {
620 return document.getElementById(id);
621 }
622
623 ext.onMessage.addListener(function(message)
624 {
625 switch (message.type)
626 {
627 case "app.listen":
628 if (message.action == "addSubscription")
629 showAddSubscriptionDialog(message.args[0]);
630 break;
631 case "filters.listen":
632 onFilterMessage(message.action, message.args[0]);
633 break;
634 case "subscriptions.listen":
635 onSubscriptionMessage(message.action, message.args[0]);
636 break;
637 }
638 });
639
640 ext.backgroundPage.sendMessage(
641 {
642 type: "app.listen",
643 filter: ["addSubscription"]
644 });
645 ext.backgroundPage.sendMessage(
646 {
647 type: "filters.listen",
648 filter: ["added", "loaded", "removed"]
649 });
650 ext.backgroundPage.sendMessage(
651 {
652 type: "subscriptions.listen",
653 filter: ["added", "disabled", "homepage", "removed", "title"]
654 });
655
656 window.addEventListener("DOMContentLoaded", onDOMLoaded, false);
657 })();
OLDNEW
« no previous file with comments | « options.html ('k') | skin/options.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld