OLD | NEW |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
3 * Copyright (C) 2006-present eyeo GmbH | 3 * Copyright (C) 2006-present 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 /* global $, i18n, i18nTimeDateStrings */ | |
19 | |
20 "use strict"; | 18 "use strict"; |
21 | 19 |
22 /** | 20 const {require} = chrome.extension.getBackgroundPage(); |
23 * Creates a wrapping function used to conveniently send a type of message. | 21 |
24 * | 22 let iframe = document.getElementById("content"); |
25 * @param {Object} baseMessage The part of the message that's always sent | 23 |
26 * @param {...string} paramKeys Any message keys that have dynamic values. The | 24 iframe.onload = () => |
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 { | 25 { |
38 return function(...paramValues /* , callback */) | 26 document.title = iframe.contentDocument.title; |
39 { | 27 }; |
40 let message = Object.assign(Object.create(null), baseMessage); | |
41 let callback; | |
42 | 28 |
43 if (paramValues.length > 0) | 29 // Load the mobile version of the options page on Firefox for Android. |
44 { | 30 iframe.src = iframe.getAttribute("data-src-" + require("info").application) || |
45 let lastArg = paramValues[paramValues.length - 1]; | 31 iframe.getAttribute("data-src"); |
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 ext.backgroundPage.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 = 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 ext.backgroundPage.sendMessage({ | |
165 type: "app.listen", | |
166 filter: ["addSubscription", "focusSection"] | |
167 }); | |
168 ext.backgroundPage.sendMessage({ | |
169 type: "filters.listen", | |
170 filter: ["added", "loaded", "removed"] | |
171 }); | |
172 ext.backgroundPage.sendMessage({ | |
173 type: "prefs.listen", | |
174 filter: ["notifications_ignoredcategories", "notifications_showui", | |
175 "show_devtools_panel", "shouldShowBlockElementMenu"] | |
176 }); | |
177 ext.backgroundPage.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 $(loadOptions); | |
190 | |
191 function convertSpecialSubscription(subscription) | |
192 { | |
193 for (let filter of subscription.filters) | |
194 { | |
195 if (whitelistedDomainRegexp.test(filter.text)) | |
196 appendToListBox("excludedDomainsBox", RegExp.$1); | |
197 else | |
198 appendToListBox("userFiltersBox", filter.text); | |
199 } | |
200 } | |
201 | |
202 // Reloads the displayed subscriptions and filters | |
203 function reloadFilters() | |
204 { | |
205 // Load user filter URLs | |
206 let container = document.getElementById("filterLists"); | |
207 while (container.lastChild) | |
208 container.removeChild(container.lastChild); | |
209 | |
210 getSubscriptions(true, false, subscriptions => | |
211 { | |
212 for (let subscription of subscriptions) | |
213 { | |
214 if (subscription.url == acceptableAdsUrl) | |
215 $("#acceptableAds").prop("checked", !subscription.disabled); | |
216 else | |
217 addSubscriptionEntry(subscription); | |
218 } | |
219 }); | |
220 | |
221 // User-entered filters | |
222 getSubscriptions(false, true, subscriptions => | |
223 { | |
224 document.getElementById("userFiltersBox").innerHTML = ""; | |
225 document.getElementById("excludedDomainsBox").innerHTML = ""; | |
226 | |
227 for (let subscription of subscriptions) | |
228 convertSpecialSubscription(subscription); | |
229 }); | |
230 } | |
231 | |
232 function initCheckbox(id, key) | |
233 { | |
234 key = key || id; | |
235 let checkbox = document.getElementById(id); | |
236 | |
237 getPref(key, value => | |
238 { | |
239 onPrefMessage(key, value); | |
240 }); | |
241 | |
242 checkbox.addEventListener("click", () => | |
243 { | |
244 togglePref(key); | |
245 }, false); | |
246 } | |
247 | |
248 function loadRecommendations() | |
249 { | |
250 fetch("subscriptions.xml") | |
251 .then(response => | |
252 { | |
253 return response.text(); | |
254 }) | |
255 .then(text => | |
256 { | |
257 let selectedIndex = 0; | |
258 let selectedPrefix = null; | |
259 let matchCount = 0; | |
260 | |
261 let list = document.getElementById("subscriptionSelector"); | |
262 let doc = new DOMParser().parseFromString(text, "application/xml"); | |
263 let elements = doc.documentElement.getElementsByTagName("subscription"); | |
264 | |
265 for (let i = 0; i < elements.length; i++) | |
266 { | |
267 let element = elements[i]; | |
268 let option = new Option(); | |
269 option.text = element.getAttribute("title") + " (" + | |
270 element.getAttribute("specialization") + ")"; | |
271 option._data = { | |
272 title: element.getAttribute("title"), | |
273 url: element.getAttribute("url"), | |
274 homepage: element.getAttribute("homepage") | |
275 }; | |
276 | |
277 let prefix = element.getAttribute("prefixes"); | |
278 if (prefix) | |
279 { | |
280 prefix = prefix.replace(/\W/g, "_"); | |
281 option.style.fontWeight = "bold"; | |
282 option.style.backgroundColor = "#E0FFE0"; | |
283 option.style.color = "#000000"; | |
284 if (!selectedPrefix || selectedPrefix.length < prefix.length) | |
285 { | |
286 selectedIndex = i; | |
287 selectedPrefix = prefix; | |
288 matchCount = 1; | |
289 } | |
290 else if (selectedPrefix && selectedPrefix.length == prefix.length) | |
291 { | |
292 matchCount++; | |
293 | |
294 // If multiple items have a matching prefix of the same length: | |
295 // Select one of the items randomly, probability should be the same | |
296 // for all items. So we replace the previous match here with | |
297 // probability 1/N (N being the number of matches). | |
298 if (Math.random() * matchCount < 1) | |
299 { | |
300 selectedIndex = i; | |
301 selectedPrefix = prefix; | |
302 } | |
303 } | |
304 } | |
305 list.appendChild(option); | |
306 } | |
307 | |
308 let option = new Option(); | |
309 let label = i18n.getMessage("filters_addSubscriptionOther_label"); | |
310 option.text = label + "\u2026"; | |
311 option._data = null; | |
312 list.appendChild(option); | |
313 | |
314 list.selectedIndex = selectedIndex; | |
315 | |
316 if (delayedSubscriptionSelection) | |
317 startSubscriptionSelection(...delayedSubscriptionSelection); | |
318 }); | |
319 } | |
320 | |
321 function startSubscriptionSelection(title, url) | |
322 { | |
323 let list = document.getElementById("subscriptionSelector"); | |
324 if (list.length == 0) | |
325 { | |
326 delayedSubscriptionSelection = [title, url]; | |
327 return; | |
328 } | |
329 | |
330 $("#tabs").tabs("option", "active", 0); | |
331 $("#addSubscriptionContainer").show(); | |
332 $("#addSubscriptionButton").hide(); | |
333 $("#subscriptionSelector").focus(); | |
334 if (typeof url != "undefined") | |
335 { | |
336 list.selectedIndex = list.length - 1; | |
337 document.getElementById("customSubscriptionTitle").value = title; | |
338 document.getElementById("customSubscriptionLocation").value = url; | |
339 } | |
340 updateSubscriptionSelection(); | |
341 document.getElementById("addSubscriptionContainer").scrollIntoView(true); | |
342 } | |
343 | |
344 function updateSubscriptionSelection() | |
345 { | |
346 let list = document.getElementById("subscriptionSelector"); | |
347 let data = list.options[list.selectedIndex]._data; | |
348 if (data) | |
349 $("#customSubscriptionContainer").hide(); | |
350 else | |
351 { | |
352 $("#customSubscriptionContainer").show(); | |
353 $("#customSubscriptionTitle").focus(); | |
354 } | |
355 } | |
356 | |
357 function addSubscriptionClicked() | |
358 { | |
359 let list = document.getElementById("subscriptionSelector"); | |
360 let data = list.options[list.selectedIndex]._data; | |
361 if (data) | |
362 addSubscription(data.url, data.title, data.homepage); | |
363 else | |
364 { | |
365 let url = document.getElementById("customSubscriptionLocation") | |
366 .value.trim(); | |
367 if (!/^https?:/i.test(url)) | |
368 { | |
369 alert(i18n.getMessage("global_subscription_invalid_location")); | |
370 $("#customSubscriptionLocation").focus(); | |
371 return; | |
372 } | |
373 | |
374 let title = document.getElementById("customSubscriptionTitle").value.trim(); | |
375 if (!title) | |
376 title = url; | |
377 | |
378 addSubscription(url, title, null); | |
379 } | |
380 | |
381 $("#addSubscriptionContainer").hide(); | |
382 $("#customSubscriptionContainer").hide(); | |
383 $("#addSubscriptionButton").show(); | |
384 } | |
385 | |
386 function toggleAcceptableAds() | |
387 { | |
388 toggleSubscription(acceptableAdsUrl, true); | |
389 } | |
390 | |
391 function findSubscriptionElement(subscription) | |
392 { | |
393 for (let child of document.getElementById("filterLists").childNodes) | |
394 { | |
395 if (child._subscription.url == subscription.url) | |
396 return child; | |
397 } | |
398 return null; | |
399 } | |
400 | |
401 function updateSubscriptionInfo(element, subscription) | |
402 { | |
403 if (subscription) | |
404 element._subscription = subscription; | |
405 else | |
406 subscription = element._subscription; | |
407 | |
408 let title = element.getElementsByClassName("subscriptionTitle")[0]; | |
409 title.textContent = subscription.title; | |
410 title.setAttribute("title", subscription.url); | |
411 if (subscription.homepage) | |
412 title.href = subscription.homepage; | |
413 else | |
414 title.href = subscription.url; | |
415 | |
416 let enabled = element.getElementsByClassName("subscriptionEnabled")[0]; | |
417 enabled.checked = !subscription.disabled; | |
418 | |
419 let lastUpdate = element.getElementsByClassName("subscriptionUpdate")[0]; | |
420 lastUpdate.classList.remove("error"); | |
421 | |
422 let {downloadStatus} = subscription; | |
423 if (subscription.isDownloading) | |
424 { | |
425 lastUpdate.textContent = i18n.getMessage( | |
426 "filters_subscription_lastDownload_inProgress" | |
427 ); | |
428 } | |
429 else if (downloadStatus && downloadStatus != "synchronize_ok") | |
430 { | |
431 if (statusMessages.has(downloadStatus)) | |
432 { | |
433 lastUpdate.textContent = i18n.getMessage( | |
434 statusMessages.get(downloadStatus) | |
435 ); | |
436 } | |
437 else | |
438 lastUpdate.textContent = downloadStatus; | |
439 lastUpdate.classList.add("error"); | |
440 } | |
441 else if (subscription.lastDownload > 0) | |
442 { | |
443 let timeDate = i18nTimeDateStrings(subscription.lastDownload * 1000); | |
444 let messageID = (timeDate[1] ? "last_updated_at" : "last_updated_at_today"); | |
445 lastUpdate.textContent = i18n.getMessage(messageID, timeDate); | |
446 } | |
447 } | |
448 | |
449 function onSubscriptionMessage(action, subscription) | |
450 { | |
451 let element = findSubscriptionElement(subscription); | |
452 | |
453 switch (action) | |
454 { | |
455 case "disabled": | |
456 case "downloading": | |
457 case "downloadStatus": | |
458 case "homepage": | |
459 case "lastDownload": | |
460 case "title": | |
461 if (element) | |
462 updateSubscriptionInfo(element, subscription); | |
463 break; | |
464 case "added": | |
465 if (subscription.url.indexOf("~user") == 0) | |
466 convertSpecialSubscription(subscription); | |
467 else if (subscription.url == acceptableAdsUrl) | |
468 $("#acceptableAds").prop("checked", true); | |
469 else if (!element) | |
470 addSubscriptionEntry(subscription); | |
471 break; | |
472 case "removed": | |
473 if (subscription.url == acceptableAdsUrl) | |
474 $("#acceptableAds").prop("checked", false); | |
475 else if (element) | |
476 element.parentNode.removeChild(element); | |
477 break; | |
478 } | |
479 } | |
480 | |
481 function onPrefMessage(key, value) | |
482 { | |
483 switch (key) | |
484 { | |
485 case "notifications_showui": | |
486 document.getElementById( | |
487 "shouldShowNotificationsContainer" | |
488 ).hidden = !value; | |
489 return; | |
490 case "notifications_ignoredcategories": | |
491 key = "shouldShowNotifications"; | |
492 value = value.indexOf("*") == -1; | |
493 break; | |
494 } | |
495 let checkbox = document.getElementById(key); | |
496 if (checkbox) | |
497 checkbox.checked = value; | |
498 } | |
499 | |
500 function onFilterMessage(action, filter) | |
501 { | |
502 switch (action) | |
503 { | |
504 case "loaded": | |
505 reloadFilters(); | |
506 break; | |
507 case "added": | |
508 if (whitelistedDomainRegexp.test(filter.text)) | |
509 appendToListBox("excludedDomainsBox", RegExp.$1); | |
510 else | |
511 appendToListBox("userFiltersBox", filter.text); | |
512 break; | |
513 case "removed": | |
514 if (whitelistedDomainRegexp.test(filter.text)) | |
515 removeFromListBox("excludedDomainsBox", RegExp.$1); | |
516 else | |
517 removeFromListBox("userFiltersBox", filter.text); | |
518 break; | |
519 } | |
520 } | |
521 | |
522 // Add a filter string to the list box. | |
523 function appendToListBox(boxId, text) | |
524 { | |
525 // Note: document.createElement("option") is unreliable in Opera | |
526 let elt = new Option(); | |
527 elt.text = text; | |
528 elt.value = text; | |
529 document.getElementById(boxId).appendChild(elt); | |
530 } | |
531 | |
532 // Remove a filter string from a list box. | |
533 function removeFromListBox(boxId, text) | |
534 { | |
535 let list = document.getElementById(boxId); | |
536 // Edge does not support CSS.escape yet: | |
537 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101410
/ | |
538 quoteCSS(text, escapedCSS => | |
539 { | |
540 let selector = "option[value=" + escapedCSS + "]"; | |
541 for (let option of list.querySelectorAll(selector)) | |
542 list.removeChild(option); | |
543 }); | |
544 } | |
545 | |
546 function addWhitelistDomain(event) | |
547 { | |
548 event.preventDefault(); | |
549 | |
550 let domain = document.getElementById( | |
551 "newWhitelistDomain" | |
552 ).value.replace(/\s/g, ""); | |
553 document.getElementById("newWhitelistDomain").value = ""; | |
554 if (!domain) | |
555 return; | |
556 | |
557 let filterText = "@@||" + domain + "^$document"; | |
558 addFilter(filterText); | |
559 } | |
560 | |
561 // Adds filter text that user typed to the selection box | |
562 function addTypedFilter(event) | |
563 { | |
564 event.preventDefault(); | |
565 | |
566 let element = document.getElementById("newFilter"); | |
567 addFilter(element.value, errors => | |
568 { | |
569 if (errors.length > 0) | |
570 alert(errors.join("\n")); | |
571 else | |
572 element.value = ""; | |
573 }); | |
574 } | |
575 | |
576 // Removes currently selected whitelisted domains | |
577 function removeSelectedExcludedDomain(event) | |
578 { | |
579 event.preventDefault(); | |
580 let remove = []; | |
581 for (let option of document.getElementById("excludedDomainsBox").options) | |
582 { | |
583 if (option.selected) | |
584 remove.push(option.value); | |
585 } | |
586 if (!remove.length) | |
587 return; | |
588 | |
589 for (let domain of remove) | |
590 removeFilter("@@||" + domain + "^$document"); | |
591 } | |
592 | |
593 // Removes all currently selected filters | |
594 function removeSelectedFilters(event) | |
595 { | |
596 event.preventDefault(); | |
597 let options = document.querySelectorAll("#userFiltersBox > option:checked"); | |
598 for (let option of options) | |
599 removeFilter(option.value); | |
600 } | |
601 | |
602 // Shows raw filters box and fills it with the current user filters | |
603 function toggleFiltersInRawFormat(event) | |
604 { | |
605 event.preventDefault(); | |
606 | |
607 let rawFilters = document.getElementById("rawFilters"); | |
608 let filters = []; | |
609 | |
610 if (rawFilters.style.display != "table-row") | |
611 { | |
612 rawFilters.style.display = "table-row"; | |
613 for (let option of document.getElementById("userFiltersBox").options) | |
614 filters.push(option.value); | |
615 } | |
616 else | |
617 { | |
618 rawFilters.style.display = "none"; | |
619 } | |
620 | |
621 document.getElementById("rawFiltersText").value = filters.join("\n"); | |
622 } | |
623 | |
624 // Imports filters in the raw text box | |
625 function importRawFiltersText() | |
626 { | |
627 let text = document.getElementById("rawFiltersText").value; | |
628 | |
629 importRawFilters(text, true, errors => | |
630 { | |
631 if (errors.length > 0) | |
632 alert(errors.join("\n")); | |
633 else | |
634 $("#rawFilters").hide(); | |
635 }); | |
636 } | |
637 | |
638 // Called when user explicitly requests filter list updates | |
639 function updateFilterLists() | |
640 { | |
641 // Without the URL parameter this will update all subscriptions | |
642 updateSubscription(); | |
643 } | |
644 | |
645 // Adds a subscription entry to the UI. | |
646 function addSubscriptionEntry(subscription) | |
647 { | |
648 let template = document.getElementById("subscriptionTemplate"); | |
649 let element = template.cloneNode(true); | |
650 element.removeAttribute("id"); | |
651 element._subscription = subscription; | |
652 | |
653 let removeButton = element.getElementsByClassName( | |
654 "subscriptionRemoveButton" | |
655 )[0]; | |
656 removeButton.setAttribute("title", removeButton.textContent); | |
657 removeButton.textContent = "\xD7"; | |
658 removeButton.addEventListener("click", () => | |
659 { | |
660 if (!confirm(i18n.getMessage("global_remove_subscription_warning"))) | |
661 return; | |
662 | |
663 removeSubscription(subscription.url); | |
664 }, false); | |
665 | |
666 getPref("additional_subscriptions", additionalSubscriptions => | |
667 { | |
668 if (additionalSubscriptions.includes(subscription.url)) | |
669 removeButton.style.visibility = "hidden"; | |
670 }); | |
671 | |
672 let enabled = element.getElementsByClassName("subscriptionEnabled")[0]; | |
673 enabled.addEventListener("click", () => | |
674 { | |
675 subscription.disabled = !subscription.disabled; | |
676 toggleSubscription(subscription.url, true); | |
677 }, false); | |
678 | |
679 updateSubscriptionInfo(element); | |
680 | |
681 document.getElementById("filterLists").appendChild(element); | |
682 } | |
683 | |
684 function setLinks(id, ...args) | |
685 { | |
686 let element = document.getElementById(id); | |
687 if (!element) | |
688 return; | |
689 | |
690 let links = element.getElementsByTagName("a"); | |
691 for (let i = 0; i < links.length; i++) | |
692 { | |
693 if (typeof args[i] == "string") | |
694 { | |
695 links[i].href = args[i]; | |
696 links[i].setAttribute("target", "_blank"); | |
697 } | |
698 else if (typeof args[i] == "function") | |
699 { | |
700 links[i].href = "javascript:void(0);"; | |
701 links[i].addEventListener("click", args[i], false); | |
702 } | |
703 } | |
704 } | |
705 | |
706 ext.onMessage.addListener(message => | |
707 { | |
708 switch (message.type) | |
709 { | |
710 case "app.respond": | |
711 switch (message.action) | |
712 { | |
713 case "addSubscription": | |
714 let subscription = message.args[0]; | |
715 startSubscriptionSelection(subscription.title, subscription.url); | |
716 break; | |
717 case "focusSection": | |
718 for (let tab of document.getElementsByClassName("ui-tabs-panel")) | |
719 { | |
720 let found = tab.querySelector( | |
721 "[data-section='" + message.args[0] + "']" | |
722 ); | |
723 if (!found) | |
724 continue; | |
725 | |
726 let previous = document.getElementsByClassName("focused"); | |
727 if (previous.length > 0) | |
728 previous[0].classList.remove("focused"); | |
729 | |
730 let index = $("[href='#" + tab.id + "']").parent().index(); | |
731 $("#tabs").tabs("option", "active", index); | |
732 found.classList.add("focused"); | |
733 } | |
734 break; | |
735 } | |
736 break; | |
737 case "filters.respond": | |
738 onFilterMessage(message.action, message.args[0]); | |
739 break; | |
740 case "prefs.respond": | |
741 onPrefMessage(message.action, message.args[0]); | |
742 break; | |
743 case "subscriptions.respond": | |
744 onSubscriptionMessage(message.action, message.args[0]); | |
745 break; | |
746 } | |
747 }); | |
OLD | NEW |