| Index: options.js |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/options.js |
| @@ -0,0 +1,586 @@ |
| +var backgroundPage = opera.extension.bgProcess; |
| +var imports = ["FilterStorage", "FilterNotifier", "Subscription", "SpecialSubscription", |
| + "DownloadableSubscription", "Filter", "WhitelistFilter", |
| + "Synchronizer", "Prefs", "Utils", "require"]; |
| +for (var i = 0; i < imports.length; i++) |
| + window[imports[i]] = backgroundPage[imports[i]]; |
| + |
| +// Loads options from localStorage and sets UI elements accordingly |
| +function loadOptions() |
| +{ |
| + // Set page title to i18n version of "Adblock Plus Options" |
| + document.title = i18n.getMessage("options"); |
| + |
| + // Set links |
| + $("#acceptableAdsLink").attr("href", Prefs.subscriptions_exceptionsurl); |
| + $("#acceptableAdsDocs").attr("href", Prefs.documentation_link.replace(/%LINK%/g, "acceptable_ads").replace(/%LANG%/g, Utils.appLocale)); |
| + |
| + // Add event listeners |
| + window.addEventListener("unload", unloadOptions, false); |
| + $("#updateFilterLists").click(updateFilterLists); |
| + $("#startSubscriptionSelection").click(startSubscriptionSelection); |
| + $("#subscriptionSelector").change(updateSubscriptionSelection); |
| + $("#addSubscription").click(addSubscription); |
| + $("#acceptableAds").click(allowAcceptableAds); |
| + $("#whitelistForm").submit(addWhitelistDomain); |
| + $("#removeWhitelist").click(removeSelectedExcludedDomain); |
| + $("#customFilterForm").submit(addTypedFilter); |
| + $("#removeCustomFilter").click(removeSelectedFilters); |
| + $("#rawFiltersButton").click(toggleFiltersInRawFormat); |
| + $("#importRawFilters").click(importRawFiltersText); |
| + FilterNotifier.addListener(onFilterChange); |
| + |
| + // Display jQuery UI elements |
| + $("#tabs").tabs(); |
| + $("button").button(); |
| + $(".refreshButton").button("option", "icons", {primary: "ui-icon-refresh"}); |
| + $(".addButton").button("option", "icons", {primary: "ui-icon-plus"}); |
| + $(".removeButton").button("option", "icons", {primary: "ui-icon-minus"}); |
| + |
| + // Popuplate option checkboxes |
| + initCheckbox("shouldShowIcon"); |
| + initCheckbox("shouldShowBlockElementMenu"); |
| + initCheckbox("disableInlineTextAds"); |
| + initCheckbox("hidePlaceholders"); |
| + |
| + // Load recommended subscriptions |
| + loadRecommendations(); |
| + |
| + // Show user's filters |
| + reloadFilters(); |
| +} |
| +$(loadOptions); |
| + |
| +// Reloads the displayed subscriptions and filters |
| +function reloadFilters() |
| +{ |
| + // Load user filter URLs |
| + var container = document.getElementById("filterLists"); |
| + while (container.lastChild) |
| + container.removeChild(container.lastChild); |
| + |
| + var hasAcceptable = false; |
| + for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| + { |
| + var subscription = FilterStorage.subscriptions[i]; |
| + if (subscription instanceof SpecialSubscription) |
| + continue; |
| + |
| + if (subscription.url == Prefs.subscriptions_exceptionsurl) |
| + { |
| + hasAcceptable = true; |
| + continue; |
| + } |
| + |
| + addSubscriptionEntry(subscription); |
| + } |
| + |
| + $("#acceptableAds").prop("checked", hasAcceptable); |
| + |
| + // User-entered filters |
| + showUserFilters(); |
| +} |
| + |
| +// Cleans up when the options window is closed |
| +function unloadOptions() |
| +{ |
| + FilterNotifier.removeListener(onFilterChange); |
| +} |
| + |
| +function initCheckbox(id) |
| +{ |
| + var checkbox = document.getElementById(id); |
| + checkbox.checked = typeof localStorage[id] == "undefined" ? true : localStorage[id] == "true"; |
| + checkbox.addEventListener("click", function() |
| + { |
| + localStorage[id] = checkbox.checked; |
| + }, false); |
| +} |
| + |
| +function showUserFilters() |
| +{ |
| + var filters = []; |
| + var exceptions = []; |
| + for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| + { |
| + var subscription = FilterStorage.subscriptions[i]; |
| + if (!(subscription instanceof SpecialSubscription)) |
| + continue; |
| + |
| + for (var j = 0; j < subscription.filters.length; j++) |
| + { |
| + var filter = subscription.filters[j]; |
| + if (filter instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(filter.text)) |
| + exceptions.push(RegExp.$1) |
| + else |
| + filters.push(filter.text); |
| + } |
| + } |
| + |
| + populateList("userFiltersBox", filters); |
| + populateList("excludedDomainsBox", exceptions); |
| +} |
| + |
| +var delayedSubscriptionSelection = null; |
| + |
| +function loadRecommendations() |
| +{ |
| + var request = new XMLHttpRequest(); |
| + request.open("GET", "subscriptions.xml"); |
| + request.onload = function() |
| + { |
| + var selectedIndex = 0; |
| + var selectedPrefix = null; |
| + var matchCount = 0; |
| + |
| + var list = document.getElementById("subscriptionSelector"); |
| + var elements = request.responseXML.documentElement.getElementsByTagName("subscription"); |
| + for (var i = 0; i < elements.length; i++) |
| + { |
| + var element = elements[i]; |
| + var option = document.createElement("option"); |
| + option.text = element.getAttribute("title") + " (" + element.getAttribute("specialization") + ")"; |
| + option._data = { |
| + title: element.getAttribute("title"), |
| + url: element.getAttribute("url"), |
| + homepage: element.getAttribute("homepage") |
| + }; |
| + |
| + var prefix = require("utils").Utils.checkLocalePrefixMatch(element.getAttribute("prefixes")); |
| + if (prefix) |
| + { |
| + option.style.fontWeight = "bold"; |
| + option.style.backgroundColor = "#E0FFE0"; |
| + option.style.color = "#000000"; |
| + if (!selectedPrefix || selectedPrefix.length < prefix.length) |
| + { |
| + selectedIndex = i; |
| + selectedPrefix = prefix; |
| + matchCount = 1; |
| + } |
| + else if (selectedPrefix && selectedPrefix.length == prefix.length) |
| + { |
| + matchCount++; |
| + |
| + // If multiple items have a matching prefix of the same length: |
| + // Select one of the items randomly, probability should be the same |
| + // for all items. So we replace the previous match here with |
| + // probability 1/N (N being the number of matches). |
| + if (Math.random() * matchCount < 1) |
| + { |
| + selectedIndex = i; |
| + selectedPrefix = prefix; |
| + } |
| + } |
| + } |
| + list.appendChild(option); |
| + } |
| + |
| + var option = document.createElement("option"); |
| + option.text = i18n.getMessage("filters_addSubscriptionOther_label") + "\u2026"; |
| + option._data = null; |
| + list.appendChild(option); |
| + |
| + list.selectedIndex = selectedIndex; |
| + |
| + if (delayedSubscriptionSelection) |
| + startSubscriptionSelection.apply(null, delayedSubscriptionSelection); |
| + }; |
| + request.send(null); |
| +} |
| + |
| +function startSubscriptionSelection(title, url) |
| +{ |
| + var list = document.getElementById("subscriptionSelector"); |
| + if (list.length == 0) |
| + { |
| + delayedSubscriptionSelection = [title, url]; |
| + return; |
| + } |
| + |
| + $('#tabs').tabs('select', 0); |
| + $("#addSubscriptionContainer").show(); |
| + $("#addSubscriptionButton").hide(); |
| + $("#subscriptionSelector").focus(); |
| + if (typeof url != "undefined") |
| + { |
| + list.selectedIndex = list.length - 1; |
| + document.getElementById("customSubscriptionTitle").value = title; |
| + document.getElementById("customSubscriptionLocation").value = url; |
| + } |
| + updateSubscriptionSelection(); |
| + document.getElementById("addSubscriptionContainer").scrollIntoView(true); |
| +} |
| + |
| +function updateSubscriptionSelection() |
| +{ |
| + var list = document.getElementById("subscriptionSelector"); |
| + var data = list.options[list.selectedIndex]._data; |
| + if (data) |
| + $("#customSubscriptionContainer").hide(); |
| + else |
| + { |
| + $("#customSubscriptionContainer").show(); |
| + $("#customSubscriptionTitle").focus(); |
| + } |
| +} |
| + |
| +function addSubscription() |
| +{ |
| + var list = document.getElementById("subscriptionSelector"); |
| + var data = list.options[list.selectedIndex]._data; |
| + if (data) |
| + doAddSubscription(data.url, data.title, data.homepage); |
| + else |
| + { |
| + var url = document.getElementById("customSubscriptionLocation").value.replace(/^\s+/, "").replace(/\s+$/, ""); |
| + if (!/^https?:/i.test(url)) |
| + { |
| + alert(i18n.getMessage("global_subscription_invalid_location")); |
| + $("#customSubscriptionLocation").focus(); |
| + return; |
| + } |
| + |
| + var title = document.getElementById("customSubscriptionTitle").value.replace(/^\s+/, "").replace(/\s+$/, ""); |
| + if (!title) |
| + title = url; |
| + |
| + doAddSubscription(url, title, null); |
| + } |
| + |
| + $("#addSubscriptionContainer").hide(); |
| + $("#customSubscriptionContainer").hide(); |
| + $("#addSubscriptionButton").show(); |
| +} |
| + |
| +function doAddSubscription(url, title, homepage) |
| +{ |
| + if (url in FilterStorage.knownSubscriptions) |
| + return; |
| + |
| + var subscription = Subscription.fromURL(url); |
| + if (!subscription) |
| + return; |
| + |
| + subscription.title = title; |
| + if (homepage) |
| + subscription.homepage = homepage; |
| + FilterStorage.addSubscription(subscription); |
| + |
| + if (subscription instanceof DownloadableSubscription && !subscription.lastDownload) |
| + Synchronizer.execute(subscription); |
| +} |
| + |
| +function allowAcceptableAds(event) |
| +{ |
| + var subscription = Subscription.fromURL(Prefs.subscriptions_exceptionsurl); |
| + if (!subscription) |
| + return; |
| + |
| + subscription.disabled = false; |
| + subscription.title = "Allow non-intrusive advertising"; |
| + if ($("#acceptableAds").prop("checked")) |
| + { |
| + FilterStorage.addSubscription(subscription); |
| + if (subscription instanceof DownloadableSubscription && !subscription.lastDownload) |
| + Synchronizer.execute(subscription); |
| + } |
| + else |
| + FilterStorage.removeSubscription(subscription); |
| +} |
| + |
| +function findSubscriptionElement(subscription) |
| +{ |
| + var children = document.getElementById("filterLists").childNodes; |
| + for (var i = 0; i < children.length; i++) |
| + if (children[i]._subscription == subscription) |
| + return children[i]; |
| + return null; |
| +} |
| + |
| +function updateSubscriptionInfo(element) |
| +{ |
| + var subscription = element._subscription; |
| + |
| + var title = element.getElementsByClassName("subscriptionTitle")[0]; |
| + title.textContent = subscription.title; |
| + title.setAttribute("title", subscription.url); |
| + if (subscription.homepage) |
| + title.href = subscription.homepage; |
| + else |
| + title.href = subscription.url; |
| + |
| + var enabled = element.getElementsByClassName("subscriptionEnabled")[0]; |
| + enabled.checked = !subscription.disabled; |
| + |
| + var lastUpdate = element.getElementsByClassName("subscriptionUpdate")[0]; |
| + lastUpdate.classList.remove("error"); |
| + if (Synchronizer.isExecuting(subscription.url)) |
| + lastUpdate.textContent = i18n.getMessage("filters_subscription_lastDownload_inProgress"); |
| + else if (subscription.downloadStatus && subscription.downloadStatus != "synchronize_ok") |
| + { |
| + var map = |
| + { |
| + "synchronize_invalid_url": "filters_subscription_lastDownload_invalidURL", |
| + "synchronize_connection_error": "filters_subscription_lastDownload_connectionError", |
| + "synchronize_invalid_data": "filters_subscription_lastDownload_invalidData", |
| + "synchronize_checksum_mismatch": "filters_subscription_lastDownload_checksumMismatch" |
| + }; |
| + if (subscription.downloadStatus in map) |
| + lastUpdate.textContent = i18n.getMessage(map[subscription.downloadStatus]); |
| + else |
| + lastUpdate.textContent = subscription.downloadStatus; |
| + lastUpdate.classList.add("error"); |
| + } |
| + else if (subscription.lastDownload > 0) |
| + { |
| + var timeDate = i18n_timeDateStrings(subscription.lastDownload * 1000); |
| + var messageID = (timeDate[1] ? "last_updated_at" : "last_updated_at_today"); |
| + lastUpdate.textContent = i18n.getMessage(messageID, timeDate); |
| + } |
| +} |
| + |
| +function onFilterChange(action, item, param1, param2) |
| +{ |
| + switch (action) |
| + { |
| + case "load": |
| + reloadFilters(); |
| + break; |
| + case "subscription.title": |
| + case "subscription.disabled": |
| + case "subscription.homepage": |
| + case "subscription.lastDownload": |
| + case "subscription.downloadStatus": |
| + var element = findSubscriptionElement(item); |
| + if (element) |
| + updateSubscriptionInfo(element); |
| + break; |
| + case "subscription.added": |
| + if (!(item instanceof SpecialSubscription) && !findSubscriptionElement(item)) |
| + { |
| + if (item.url == Prefs.subscriptions_exceptionsurl) |
| + $("#acceptableAds").prop("checked", true); |
| + else |
| + addSubscriptionEntry(item); |
| + } |
| + break; |
| + case "subscription.removed": |
| + if (item.url == Prefs.subscriptions_exceptionsurl) |
| + $("#acceptableAds").prop("checked", false); |
| + else |
| + { |
| + var element = findSubscriptionElement(item); |
| + if (element) |
| + element.parentNode.removeChild(element); |
| + } |
| + break; |
| + case "filter.added": |
| + if (item instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(item.text)) |
| + appendToListBox("excludedDomainsBox", RegExp.$1); |
| + else |
| + appendToListBox("userFiltersBox", item.text); |
| + break; |
| + case "filter.removed": |
| + if (item instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(item.text)) |
| + removeFromListBox("excludedDomainsBox", RegExp.$1); |
| + else |
| + removeFromListBox("userFiltersBox", item.text); |
| + break; |
| + } |
| +} |
| + |
| +// Populates a list box with a number of entries |
| +function populateList(id, entries) |
| +{ |
| + var list = document.getElementById(id); |
| + while (list.lastChild) |
| + list.removeChild(list.lastChild); |
| + |
| + entries.sort(); |
| + for (var i = 0; i < entries.length; i++) |
| + { |
| + var option = document.createElement("option"); |
| + option.text = entries[i]; |
| + option.value = entries[i]; |
| + list.appendChild(option); |
| + } |
| +} |
| + |
| +// Add a filter string to the list box. |
| +function appendToListBox(boxId, text) |
| +{ |
| + var elt = document.createElement("option"); |
| + elt.text = text; |
| + elt.value = text; |
| + document.getElementById(boxId).appendChild(elt); |
| +} |
| + |
| +// Remove a filter string from a list box. |
| +function removeFromListBox(boxId, text) |
| +{ |
| + var elt = document.createElement("option"); |
| + elt.text = text; |
| + elt.value = text; |
| + var list = document.getElementById(boxId); |
| + for (var i = 0; i < list.length; i++) |
| + if (list.options[i].value == text) |
| + list.remove(i--); |
| +} |
| + |
| +function addWhitelistDomain(event) |
| +{ |
| + event.preventDefault(); |
| + |
| + var domain = document.getElementById("newWhitelistDomain").value.replace(/\s/g, ""); |
| + document.getElementById("newWhitelistDomain").value = ""; |
| + if (!domain) |
| + return; |
| + |
| + var filterText = "@@||" + domain + "^$document"; |
| + FilterStorage.addFilter(Filter.fromText(filterText)); |
| +} |
| + |
| +// Adds filter text that user typed to the selection box |
| +function addTypedFilter(event) |
| +{ |
| + event.preventDefault(); |
| + |
| + var filterText = Filter.normalize(document.getElementById("newFilter").value); |
| + document.getElementById("newFilter").value = ""; |
| + if (!filterText) |
| + return; |
| + |
| + FilterStorage.addFilter(Filter.fromText(filterText)); |
| +} |
| + |
| +// Removes currently selected whitelisted domains |
| +function removeSelectedExcludedDomain() |
| +{ |
| + var excludedDomainsBox = document.getElementById("excludedDomainsBox"); |
| + var remove = []; |
| + for (var i = 0; i < excludedDomainsBox.length; i++) |
| + if (excludedDomainsBox.options[i].selected) |
| + remove.push(excludedDomainsBox.options[i].value); |
| + if (!remove.length) |
| + return; |
| + |
| + for (var i = 0; i < remove.length; i++) |
| + FilterStorage.removeFilter(Filter.fromText("@@||" + remove[i] + "^$document")); |
| +} |
| + |
| +// Removes all currently selected filters |
| +function removeSelectedFilters() |
| +{ |
| + var userFiltersBox = document.getElementById("userFiltersBox"); |
| + var remove = []; |
| + for (var i = 0; i < userFiltersBox.length; i++) |
| + if (userFiltersBox.options[i].selected) |
| + remove.push(userFiltersBox.options[i].value); |
| + if (!remove.length) |
| + return; |
| + |
| + for (var i = 0; i < remove.length; i++) |
| + FilterStorage.removeFilter(Filter.fromText(remove[i])); |
| +} |
| + |
| +// Shows raw filters box and fills it with the current user filters |
| +function toggleFiltersInRawFormat(event) |
| +{ |
| + event.preventDefault(); |
| + |
| + $("#rawFilters").toggle(); |
| + if ($("#rawFilters").is(":visible")) |
| + { |
| + var userFiltersBox = document.getElementById("userFiltersBox"); |
| + var text = ""; |
| + for (var i = 0; i < userFiltersBox.length; i++) |
| + text += userFiltersBox.options[i].value + "\n"; |
| + document.getElementById("rawFiltersText").value = text; |
| + } |
| +} |
| + |
| +// Imports filters in the raw text box |
| +function importRawFiltersText() |
| +{ |
| + $("#rawFilters").hide(); |
| + var filters = document.getElementById("rawFiltersText").value.split("\n"); |
| + var seenFilter = {__proto__: null}; |
| + for (var i = 0; i < filters.length; i++) |
| + { |
| + var text = Filter.normalize(filters[i]); |
| + if (!text) |
| + continue; |
| + |
| + // Don't import filter list header |
| + if (/^\[/.test(text)) |
| + continue; |
| + |
| + FilterStorage.addFilter(Filter.fromText(text)); |
| + seenFilter[text] = true; |
| + } |
| + |
| + var remove = []; |
| + for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| + { |
| + var subscription = FilterStorage.subscriptions[i]; |
| + if (!(subscription instanceof SpecialSubscription)) |
| + continue; |
| + |
| + for (var j = 0; j < subscription.filters.length; j++) |
| + { |
| + var filter = subscription.filters[j]; |
| + if (filter instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(filter.text)) |
| + continue; |
| + |
| + if (!(filter.text in seenFilter)) |
| + remove.push(filter); |
| + } |
| + } |
| + for (var i = 0; i < remove.length; i++) |
| + FilterStorage.removeFilter(remove[i]); |
| +} |
| + |
| +// Called when user explicitly requests filter list updates |
| +function updateFilterLists() |
| +{ |
| + for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| + { |
| + var subscription = FilterStorage.subscriptions[i]; |
| + if (subscription instanceof DownloadableSubscription) |
| + Synchronizer.execute(subscription, true, true); |
| + } |
| +} |
| + |
| +// Adds a subscription entry to the UI. |
| +function addSubscriptionEntry(subscription) |
| +{ |
| + var template = document.getElementById("subscriptionTemplate"); |
| + var element = template.cloneNode(true); |
| + element.removeAttribute("id"); |
| + element._subscription = subscription; |
| + |
| + var removeButton = element.getElementsByClassName("subscriptionRemoveButton")[0]; |
| + removeButton.setAttribute("title", removeButton.textContent); |
| + removeButton.textContent = "\xD7"; |
| + removeButton.addEventListener("click", function() |
| + { |
| + if (!confirm(i18n.getMessage("global_remove_subscription_warning"))) |
| + return; |
| + |
| + FilterStorage.removeSubscription(subscription); |
| + }, false); |
| + |
| + var enabled = element.getElementsByClassName("subscriptionEnabled")[0]; |
| + enabled.addEventListener("click", function() |
| + { |
| + if (subscription.disabled == !enabled.checked) |
| + return; |
| + |
| + subscription.disabled = !enabled.checked; |
| + }, false); |
| + |
| + updateSubscriptionInfo(element); |
| + |
| + document.getElementById("filterLists").appendChild(element); |
| +} |