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

Side by Side Diff: desktop-options.js

Issue 29738601: Issue 6431 - Removed old options page (Closed)
Patch Set: Created March 31, 2018, 7:30 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 | « desktop-options.html ('k') | lib/filterComposer.js » ('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-present 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 /* global $, i18nTimeDateStrings */
19
20 "use strict";
21
22 /**
23 * Creates a wrapping function used to conveniently send a type of message.
24 *
25 * @param {Object} baseMessage The part of the message that's always sent
26 * @param {...string} paramKeys Any message keys that have dynamic values. The
27 * returned function will take the corresponding
28 * values as arguments.
29 * @return {function} The generated messaging function, optionally
30 * taking any values as specified by the paramKeys
31 * and finally an optional callback. (Although the
32 * value arguments are optional their index must be
33 * maintained. E.g. if you omit the first value you
34 * must omit the second too.)
35 */
36 function wrapper(baseMessage, ...paramKeys)
37 {
38 return function(...paramValues /* , callback */)
39 {
40 let message = Object.assign(Object.create(null), baseMessage);
41 let callback;
42
43 if (paramValues.length > 0)
44 {
45 let lastArg = paramValues[paramValues.length - 1];
46 if (typeof lastArg == "function")
47 callback = lastArg;
48
49 for (let i = 0; i < paramValues.length - (callback ? 1 : 0); i++)
50 message[paramKeys[i]] = paramValues[i];
51 }
52
53 browser.runtime.sendMessage(message, callback);
54 };
55 }
56
57 const getDocLink = wrapper({type: "app.get", what: "doclink"}, "link");
58 const getInfo = wrapper({type: "app.get"}, "what");
59 const getPref = wrapper({type: "prefs.get"}, "key");
60 const togglePref = wrapper({type: "prefs.toggle"}, "key");
61 const getSubscriptions = wrapper({type: "subscriptions.get"},
62 "downloadable", "special");
63 const removeSubscription = wrapper({type: "subscriptions.remove"}, "url");
64 const addSubscription = wrapper({type: "subscriptions.add"},
65 "url", "title", "homepage");
66 const toggleSubscription = wrapper({type: "subscriptions.toggle"},
67 "url", "keepInstalled");
68 const updateSubscription = wrapper({type: "subscriptions.update"}, "url");
69 const importRawFilters = wrapper({type: "filters.importRaw"},
70 "text", "removeExisting");
71 const addFilter = wrapper({type: "filters.add"}, "text");
72 const removeFilter = wrapper({type: "filters.remove"}, "text");
73 const quoteCSS = wrapper({type: "composer.quoteCSS"}, "CSS");
74
75 const whitelistedDomainRegexp = /^@@\|\|([^/:]+)\^\$document$/;
76 const statusMessages = new Map([
77 ["synchronize_invalid_url",
78 "filters_subscription_lastDownload_invalidURL"],
79 ["synchronize_connection_error",
80 "filters_subscription_lastDownload_connectionError"],
81 ["synchronize_invalid_data",
82 "filters_subscription_lastDownload_invalidData"],
83 ["synchronize_checksum_mismatch",
84 "filters_subscription_lastDownload_checksumMismatch"]
85 ]);
86
87 let delayedSubscriptionSelection = null;
88 let acceptableAdsUrl;
89
90 // Loads options from localStorage and sets UI elements accordingly
91 function loadOptions()
92 {
93 // Set page title to i18n version of "Adblock Plus Options"
94 document.title = browser.i18n.getMessage("options");
95
96 // Set links
97 getPref("subscriptions_exceptionsurl", url =>
98 {
99 acceptableAdsUrl = url;
100 $("#acceptableAdsLink").attr("href", acceptableAdsUrl);
101 });
102 getDocLink("acceptable_ads", url =>
103 {
104 $("#acceptableAdsDocs").attr("href", url);
105 });
106 getDocLink("filterdoc", url =>
107 {
108 setLinks("filter-must-follow-syntax", url);
109 });
110 getInfo("application", application =>
111 {
112 getInfo("platform", platform =>
113 {
114 if (platform == "chromium" && application != "opera")
115 application = "chrome";
116
117 getDocLink(application + "_support", url =>
118 {
119 setLinks("found-a-bug", url);
120 });
121
122 if (platform == "gecko")
123 $("#firefox-warning").removeAttr("hidden");
124 });
125 });
126
127 // Add event listeners
128 $("#updateFilterLists").click(updateFilterLists);
129 $("#startSubscriptionSelection").click(startSubscriptionSelection);
130 $("#subscriptionSelector").change(updateSubscriptionSelection);
131 $("#addSubscription").click(addSubscriptionClicked);
132 $("#acceptableAds").click(toggleAcceptableAds);
133 $("#whitelistForm").submit(addWhitelistDomain);
134 $("#removeWhitelist").click(removeSelectedExcludedDomain);
135 $("#customFilterForm").submit(addTypedFilter);
136 $("#removeCustomFilter").click(removeSelectedFilters);
137 $("#rawFiltersButton").click(toggleFiltersInRawFormat);
138 $("#importRawFilters").click(importRawFiltersText);
139
140 // Display jQuery UI elements
141 $("#tabs").tabs();
142 $("button:not(.subscriptionRemoveButton)").button();
143 $(".refreshButton").button("option", "icons", {primary: "ui-icon-refresh"});
144 $(".addButton").button("option", "icons", {primary: "ui-icon-plus"});
145 $(".removeButton").button("option", "icons", {primary: "ui-icon-minus"});
146
147 // Popuplate option checkboxes
148 initCheckbox("shouldShowBlockElementMenu");
149 initCheckbox("show_devtools_panel");
150 initCheckbox("shouldShowNotifications", "notifications_ignoredcategories");
151
152 getInfo("features", features =>
153 {
154 if (!features.devToolsPanel)
155 document.getElementById("showDevtoolsPanelContainer").hidden = true;
156 });
157 getPref("notifications_showui", showNotificationsUI =>
158 {
159 if (!showNotificationsUI)
160 document.getElementById("shouldShowNotificationsContainer").hidden = true;
161 });
162
163 // Register listeners in the background message responder
164 browser.runtime.sendMessage({
165 type: "app.listen",
166 filter: ["addSubscription", "focusSection"]
167 });
168 browser.runtime.sendMessage({
169 type: "filters.listen",
170 filter: ["added", "loaded", "removed"]
171 });
172 browser.runtime.sendMessage({
173 type: "prefs.listen",
174 filter: ["notifications_ignoredcategories", "notifications_showui",
175 "show_devtools_panel", "shouldShowBlockElementMenu"]
176 });
177 browser.runtime.sendMessage({
178 type: "subscriptions.listen",
179 filter: ["added", "disabled", "homepage", "lastDownload", "removed",
180 "title", "downloadStatus", "downloading"]
181 });
182
183 // Load recommended subscriptions
184 loadRecommendations();
185
186 // Show user's filters
187 reloadFilters();
188 }
189
190 function convertSpecialSubscription(subscription)
191 {
192 for (let filter of subscription.filters)
193 {
194 if (whitelistedDomainRegexp.test(filter.text))
195 appendToListBox("excludedDomainsBox", RegExp.$1);
196 else
197 appendToListBox("userFiltersBox", filter.text);
198 }
199 }
200
201 // Reloads the displayed subscriptions and filters
202 function reloadFilters()
203 {
204 // Load user filter URLs
205 let container = document.getElementById("filterLists");
206 while (container.lastChild)
207 container.removeChild(container.lastChild);
208
209 getSubscriptions(true, false, subscriptions =>
210 {
211 for (let subscription of subscriptions)
212 {
213 if (subscription.url == acceptableAdsUrl)
214 $("#acceptableAds").prop("checked", !subscription.disabled);
215 else
216 addSubscriptionEntry(subscription);
217 }
218 });
219
220 // User-entered filters
221 getSubscriptions(false, true, subscriptions =>
222 {
223 document.getElementById("userFiltersBox").innerHTML = "";
224 document.getElementById("excludedDomainsBox").innerHTML = "";
225
226 for (let subscription of subscriptions)
227 convertSpecialSubscription(subscription);
228 });
229 }
230
231 function initCheckbox(id, key)
232 {
233 key = key || id;
234 let checkbox = document.getElementById(id);
235
236 getPref(key, value =>
237 {
238 onPrefMessage(key, value);
239 });
240
241 checkbox.addEventListener("click", () =>
242 {
243 togglePref(key);
244 }, false);
245 }
246
247 function loadRecommendations()
248 {
249 fetch("subscriptions.xml")
250 .then(response =>
251 {
252 return response.text();
253 })
254 .then(text =>
255 {
256 let selectedIndex = 0;
257 let selectedPrefix = null;
258 let matchCount = 0;
259
260 let list = document.getElementById("subscriptionSelector");
261 let doc = new DOMParser().parseFromString(text, "application/xml");
262 let elements = doc.documentElement.getElementsByTagName("subscription");
263
264 for (let i = 0; i < elements.length; i++)
265 {
266 let element = elements[i];
267 let option = new Option();
268 option.text = element.getAttribute("title") + " (" +
269 element.getAttribute("specialization") + ")";
270 option._data = {
271 title: element.getAttribute("title"),
272 url: element.getAttribute("url"),
273 homepage: element.getAttribute("homepage")
274 };
275
276 let prefix = element.getAttribute("prefixes");
277 if (prefix)
278 {
279 prefix = prefix.replace(/\W/g, "_");
280 option.style.fontWeight = "bold";
281 option.style.backgroundColor = "#E0FFE0";
282 option.style.color = "#000000";
283 if (!selectedPrefix || selectedPrefix.length < prefix.length)
284 {
285 selectedIndex = i;
286 selectedPrefix = prefix;
287 matchCount = 1;
288 }
289 else if (selectedPrefix && selectedPrefix.length == prefix.length)
290 {
291 matchCount++;
292
293 // If multiple items have a matching prefix of the same length:
294 // Select one of the items randomly, probability should be the same
295 // for all items. So we replace the previous match here with
296 // probability 1/N (N being the number of matches).
297 if (Math.random() * matchCount < 1)
298 {
299 selectedIndex = i;
300 selectedPrefix = prefix;
301 }
302 }
303 }
304 list.appendChild(option);
305 }
306
307 let option = new Option();
308 let label = browser.i18n.getMessage("filters_addSubscriptionOther_label");
309 option.text = label + "\u2026";
310 option._data = null;
311 list.appendChild(option);
312
313 list.selectedIndex = selectedIndex;
314
315 if (delayedSubscriptionSelection)
316 startSubscriptionSelection(...delayedSubscriptionSelection);
317 });
318 }
319
320 function startSubscriptionSelection(title, url)
321 {
322 let list = document.getElementById("subscriptionSelector");
323 if (list.length == 0)
324 {
325 delayedSubscriptionSelection = [title, url];
326 return;
327 }
328
329 $("#tabs").tabs("option", "active", 0);
330 $("#addSubscriptionContainer").show();
331 $("#addSubscriptionButton").hide();
332 $("#subscriptionSelector").focus();
333 if (typeof url != "undefined")
334 {
335 list.selectedIndex = list.length - 1;
336 document.getElementById("customSubscriptionTitle").value = title;
337 document.getElementById("customSubscriptionLocation").value = url;
338 }
339 updateSubscriptionSelection();
340 document.getElementById("addSubscriptionContainer").scrollIntoView(true);
341 }
342
343 function updateSubscriptionSelection()
344 {
345 let list = document.getElementById("subscriptionSelector");
346 let data = list.options[list.selectedIndex]._data;
347 if (data)
348 $("#customSubscriptionContainer").hide();
349 else
350 {
351 $("#customSubscriptionContainer").show();
352 $("#customSubscriptionTitle").focus();
353 }
354 }
355
356 function addSubscriptionClicked()
357 {
358 let list = document.getElementById("subscriptionSelector");
359 let data = list.options[list.selectedIndex]._data;
360 if (data)
361 addSubscription(data.url, data.title, data.homepage);
362 else
363 {
364 let url = document.getElementById("customSubscriptionLocation")
365 .value.trim();
366 if (!/^https?:/i.test(url))
367 {
368 alert(browser.i18n.getMessage("global_subscription_invalid_location"));
369 $("#customSubscriptionLocation").focus();
370 return;
371 }
372
373 let title = document.getElementById("customSubscriptionTitle").value.trim();
374 if (!title)
375 title = url;
376
377 addSubscription(url, title, null);
378 }
379
380 $("#addSubscriptionContainer").hide();
381 $("#customSubscriptionContainer").hide();
382 $("#addSubscriptionButton").show();
383 }
384
385 function toggleAcceptableAds()
386 {
387 toggleSubscription(acceptableAdsUrl, true);
388 }
389
390 function findSubscriptionElement(subscription)
391 {
392 for (let child of document.getElementById("filterLists").childNodes)
393 {
394 if (child._subscription.url == subscription.url)
395 return child;
396 }
397 return null;
398 }
399
400 function updateSubscriptionInfo(element, subscription)
401 {
402 if (subscription)
403 element._subscription = subscription;
404 else
405 subscription = element._subscription;
406
407 let title = element.getElementsByClassName("subscriptionTitle")[0];
408 title.textContent = subscription.title;
409 title.setAttribute("title", subscription.url);
410 if (subscription.homepage)
411 title.href = subscription.homepage;
412 else
413 title.href = subscription.url;
414
415 let enabled = element.getElementsByClassName("subscriptionEnabled")[0];
416 enabled.checked = !subscription.disabled;
417
418 let lastUpdate = element.getElementsByClassName("subscriptionUpdate")[0];
419 lastUpdate.classList.remove("error");
420
421 let {downloadStatus} = subscription;
422 if (subscription.isDownloading)
423 {
424 lastUpdate.textContent = browser.i18n.getMessage(
425 "filters_subscription_lastDownload_inProgress"
426 );
427 }
428 else if (downloadStatus && downloadStatus != "synchronize_ok")
429 {
430 if (statusMessages.has(downloadStatus))
431 {
432 lastUpdate.textContent = browser.i18n.getMessage(
433 statusMessages.get(downloadStatus)
434 );
435 }
436 else
437 lastUpdate.textContent = downloadStatus;
438 lastUpdate.classList.add("error");
439 }
440 else if (subscription.lastDownload > 0)
441 {
442 let timeDate = i18nTimeDateStrings(subscription.lastDownload * 1000);
443 let messageID = (timeDate[1] ? "last_updated_at" : "last_updated_at_today");
444 lastUpdate.textContent = browser.i18n.getMessage(messageID, timeDate);
445 }
446 }
447
448 function onSubscriptionMessage(action, subscription)
449 {
450 let element = findSubscriptionElement(subscription);
451
452 switch (action)
453 {
454 case "disabled":
455 case "downloading":
456 case "downloadStatus":
457 case "homepage":
458 case "lastDownload":
459 case "title":
460 if (element)
461 updateSubscriptionInfo(element, subscription);
462 break;
463 case "added":
464 if (subscription.url.indexOf("~user") == 0)
465 convertSpecialSubscription(subscription);
466 else if (subscription.url == acceptableAdsUrl)
467 $("#acceptableAds").prop("checked", true);
468 else if (!element)
469 addSubscriptionEntry(subscription);
470 break;
471 case "removed":
472 if (subscription.url == acceptableAdsUrl)
473 $("#acceptableAds").prop("checked", false);
474 else if (element)
475 element.parentNode.removeChild(element);
476 break;
477 }
478 }
479
480 function onPrefMessage(key, value)
481 {
482 switch (key)
483 {
484 case "notifications_showui":
485 document.getElementById(
486 "shouldShowNotificationsContainer"
487 ).hidden = !value;
488 return;
489 case "notifications_ignoredcategories":
490 key = "shouldShowNotifications";
491 value = value.indexOf("*") == -1;
492 break;
493 }
494 let checkbox = document.getElementById(key);
495 if (checkbox)
496 checkbox.checked = value;
497 }
498
499 function onFilterMessage(action, filter)
500 {
501 switch (action)
502 {
503 case "loaded":
504 reloadFilters();
505 break;
506 case "added":
507 if (whitelistedDomainRegexp.test(filter.text))
508 appendToListBox("excludedDomainsBox", RegExp.$1);
509 else
510 appendToListBox("userFiltersBox", filter.text);
511 break;
512 case "removed":
513 if (whitelistedDomainRegexp.test(filter.text))
514 removeFromListBox("excludedDomainsBox", RegExp.$1);
515 else
516 removeFromListBox("userFiltersBox", filter.text);
517 break;
518 }
519 }
520
521 // Add a filter string to the list box.
522 function appendToListBox(boxId, text)
523 {
524 // Note: document.createElement("option") is unreliable in Opera
525 let elt = new Option();
526 elt.text = text;
527 elt.value = text;
528 document.getElementById(boxId).appendChild(elt);
529 }
530
531 // Remove a filter string from a list box.
532 function removeFromListBox(boxId, text)
533 {
534 let list = document.getElementById(boxId);
535 // Edge does not support CSS.escape yet:
536 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101410 /
537 quoteCSS(text, escapedCSS =>
538 {
539 let selector = "option[value=" + escapedCSS + "]";
540 for (let option of list.querySelectorAll(selector))
541 list.removeChild(option);
542 });
543 }
544
545 function addWhitelistDomain(event)
546 {
547 event.preventDefault();
548
549 let domain = document.getElementById(
550 "newWhitelistDomain"
551 ).value.replace(/\s/g, "");
552 document.getElementById("newWhitelistDomain").value = "";
553 if (!domain)
554 return;
555
556 let filterText = "@@||" + domain + "^$document";
557 addFilter(filterText);
558 }
559
560 // Adds filter text that user typed to the selection box
561 function addTypedFilter(event)
562 {
563 event.preventDefault();
564
565 let element = document.getElementById("newFilter");
566 addFilter(element.value, errors =>
567 {
568 if (errors.length > 0)
569 alert(errors.join("\n"));
570 else
571 element.value = "";
572 });
573 }
574
575 // Removes currently selected whitelisted domains
576 function removeSelectedExcludedDomain(event)
577 {
578 event.preventDefault();
579 let remove = [];
580 for (let option of document.getElementById("excludedDomainsBox").options)
581 {
582 if (option.selected)
583 remove.push(option.value);
584 }
585 if (!remove.length)
586 return;
587
588 for (let domain of remove)
589 removeFilter("@@||" + domain + "^$document");
590 }
591
592 // Removes all currently selected filters
593 function removeSelectedFilters(event)
594 {
595 event.preventDefault();
596 let options = document.querySelectorAll("#userFiltersBox > option:checked");
597 for (let option of options)
598 removeFilter(option.value);
599 }
600
601 // Shows raw filters box and fills it with the current user filters
602 function toggleFiltersInRawFormat(event)
603 {
604 event.preventDefault();
605
606 let rawFilters = document.getElementById("rawFilters");
607 let filters = [];
608
609 if (rawFilters.style.display != "table-row")
610 {
611 rawFilters.style.display = "table-row";
612 for (let option of document.getElementById("userFiltersBox").options)
613 filters.push(option.value);
614 }
615 else
616 {
617 rawFilters.style.display = "none";
618 }
619
620 document.getElementById("rawFiltersText").value = filters.join("\n");
621 }
622
623 // Imports filters in the raw text box
624 function importRawFiltersText()
625 {
626 let text = document.getElementById("rawFiltersText").value;
627
628 importRawFilters(text, true, errors =>
629 {
630 if (errors.length > 0)
631 alert(errors.join("\n"));
632 else
633 $("#rawFilters").hide();
634 });
635 }
636
637 // Called when user explicitly requests filter list updates
638 function updateFilterLists()
639 {
640 // Without the URL parameter this will update all subscriptions
641 updateSubscription();
642 }
643
644 // Adds a subscription entry to the UI.
645 function addSubscriptionEntry(subscription)
646 {
647 let template = document.getElementById("subscriptionTemplate");
648 let element = template.cloneNode(true);
649 element.removeAttribute("id");
650 element._subscription = subscription;
651
652 let removeButton = element.getElementsByClassName(
653 "subscriptionRemoveButton"
654 )[0];
655 removeButton.setAttribute("title", removeButton.textContent);
656 removeButton.textContent = "\xD7";
657 removeButton.addEventListener("click", () =>
658 {
659 if (!confirm(browser.i18n.getMessage("global_remove_subscription_warning")))
660 return;
661
662 removeSubscription(subscription.url);
663 }, false);
664
665 getPref("additional_subscriptions", additionalSubscriptions =>
666 {
667 if (additionalSubscriptions.includes(subscription.url))
668 removeButton.style.visibility = "hidden";
669 });
670
671 let enabled = element.getElementsByClassName("subscriptionEnabled")[0];
672 enabled.addEventListener("click", () =>
673 {
674 subscription.disabled = !subscription.disabled;
675 toggleSubscription(subscription.url, true);
676 }, false);
677
678 updateSubscriptionInfo(element);
679
680 document.getElementById("filterLists").appendChild(element);
681 }
682
683 function setLinks(id, ...args)
684 {
685 let element = document.getElementById(id);
686 if (!element)
687 return;
688
689 let links = element.getElementsByTagName("a");
690 for (let i = 0; i < links.length; i++)
691 {
692 if (typeof args[i] == "string")
693 {
694 links[i].href = args[i];
695 links[i].setAttribute("target", "_blank");
696 }
697 else if (typeof args[i] == "function")
698 {
699 links[i].href = "javascript:void(0);";
700 links[i].addEventListener("click", args[i], false);
701 }
702 }
703 }
704
705 document.addEventListener("DOMContentLoaded", loadOptions);
706
707 ext.onMessage.addListener(message =>
708 {
709 switch (message.type)
710 {
711 case "app.respond":
712 switch (message.action)
713 {
714 case "addSubscription":
715 let subscription = message.args[0];
716 startSubscriptionSelection(subscription.title, subscription.url);
717 break;
718 case "focusSection":
719 for (let tab of document.getElementsByClassName("ui-tabs-panel"))
720 {
721 let found = tab.querySelector(
722 "[data-section='" + message.args[0] + "']"
723 );
724 if (!found)
725 continue;
726
727 let previous = document.getElementsByClassName("focused");
728 if (previous.length > 0)
729 previous[0].classList.remove("focused");
730
731 let index = $("[href='#" + tab.id + "']").parent().index();
732 $("#tabs").tabs("option", "active", index);
733 found.classList.add("focused");
734 }
735 break;
736 }
737 break;
738 case "filters.respond":
739 onFilterMessage(message.action, message.args[0]);
740 break;
741 case "prefs.respond":
742 onPrefMessage(message.action, message.args[0]);
743 break;
744 case "subscriptions.respond":
745 onSubscriptionMessage(message.action, message.args[0]);
746 break;
747 }
748 });
OLDNEW
« no previous file with comments | « desktop-options.html ('k') | lib/filterComposer.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld