Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save botmtl/b87806daec94c6c99a1082b6a4d4a3b2 to your computer and use it in GitHub Desktop.
Save botmtl/b87806daec94c6c99a1082b6a4d4a3b2 to your computer and use it in GitHub Desktop.
google search filter plus
// ==UserScript==
// @name Google Search Filter Plus
// @description Filters google search results
// @namespace smk
// @license MPL 1.1; http://www.mozilla.org/MPL/MPL-1.1.html
// @include http://www.google.tld/
// @include http://www.google.tld/?*
// @include http://www.google.tld/#*&q=*
// @include http://www.google.tld/#q=*
// @include http://www.google.tld/cse?*
// @include http://www.google.tld/custom?*
// @include http://www.google.tld/search?*
// @include https://encrypted.google.com/
// @include https://encrypted.google.com/#*&q=*
// @include https://encrypted.google.com/search?*
// @include https://www.google.tld/
// @include https://www.google.tld/?*
// @include https://www.google.tld/#*&q=*
// @include https://www.google.tld/#q=*
// @include https://www.google.tld/cse?*
// @include https://www.google.tld/custom?*
// @include https://www.google.tld/search?*
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_getResourceURL
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @require https://cdn.jsdelivr.net/jquery/2.1.3/jquery.min.js
// @require https://cdn.jsdelivr.net/jquery.ui/1.11.3/jquery-ui.min.js
// @require https://cdn.jsdelivr.net/jquery.event.drag/2.2/jquery.event.drag.min.js
// @require https://rawgit.com/mleibman/SlickGrid/2.1.0/slick.core.js
// @require https://rawgit.com/mleibman/SlickGrid/2.1.0/slick.editors.js
// @require https://rawgit.com/mleibman/SlickGrid/2.1.0/slick.grid.js
// @resource jquery-ui-css https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/jquery-ui.min.css
// @resource jquery-ui-css/images/ui-bg_glass_55_fbf9ee_1x400.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
// @resource jquery-ui-css/images/ui-bg_glass_65_ffffff_1x400.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
// @resource jquery-ui-css/images/ui-bg_glass_75_dadada_1x400.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-bg_glass_75_dadada_1x400.png
// @resource jquery-ui-css/images/ui-bg_glass_75_e6e6e6_1x400.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
// @resource jquery-ui-css/images/ui-bg_glass_95_fef1ec_1x400.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
// @resource jquery-ui-css/images/ui-bg_highlight-soft_75_cccccc_1x100.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
// @resource jquery-ui-css/images/ui-icons_222222_256x240.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-icons_222222_256x240.png
// @resource jquery-ui-css/images/ui-icons_2e83ff_256x240.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-icons_2e83ff_256x240.png
// @resource jquery-ui-css/images/ui-icons_454545_256x240.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-icons_454545_256x240.png
// @resource jquery-ui-css/images/ui-icons_888888_256x240.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-icons_888888_256x240.png
// @resource jquery-ui-css/images/ui-icons_cd0a0a_256x240.png https://cdn.jsdelivr.net/jquery.ui/1.11.3/themes/smoothness/images/ui-icons_cd0a0a_256x240.png
// @resource slickgrid-css https://rawgit.com/mleibman/SlickGrid/2.1.0/slick.grid.css
// @resource slickgrid-css/images/sort-asc.gif https://rawgit.com/mleibman/SlickGrid/2.1.0/images/sort-asc.gif
// @resource slickgrid-css/images/sort-desc.gif https://rawgit.com/mleibman/SlickGrid/2.1.0/images/sort-desc.gif
// ==/UserScript==
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var LogTime = __webpack_require__(2).LogTime;
function main() {
LogTime.start();
__webpack_require__(3);
let config = __webpack_require__(4);
let SearchGui = __webpack_require__(5).SearchGui;
let searchGui;
if (SearchGui.isSearchPage()) {
searchGui = new SearchGui();
searchGui.filterResults(SearchGui.getResults());
}
for (let pluginName of config.plugins) __webpack_require__(1)("./" + pluginName.toLowerCase())(searchGui);
LogTime.snap("Total init time");
}
main();
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
var map = {
"./customsearch": 6,
"./customsearch.js": 6,
"./instant": 7,
"./instant.js": 7
};
function webpackContext(req) {
return __webpack_require__(webpackContextResolve(req));
};
function webpackContextResolve(req) {
return map[req] || (function() { throw new Error("Cannot find module '" + req + "'.") }());
};
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = 1;
/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
let Logger = exports.Logger = {
msg: (function (_msg) {
var _msgWrapper = function msg(_x) {
return _msg.apply(this, arguments);
};
_msgWrapper.toString = function () {
return _msg.toString();
};
return _msgWrapper;
})(function (msg) {
console.log(msg);
}),
error: function error(msg) {
console.log("Error: " + msg);
}
};
let LogTime = exports.LogTime = {
curTime: null,
start: function start() {
this.curTime = new Date().getTime();
},
snap: function snap(msg) {
console.log("" + msg + ": " + (new Date().getTime() - this.curTime) + "ms");
}
};
Object.defineProperty(exports, "__esModule", {
value: true
});
/***/ },
/* 3 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };
var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
/* global $, GM_addStyle, GM_registerMenuCommand, Slick*/
var config = _interopRequire(__webpack_require__(4));
var prefStyle = _interopRequire(__webpack_require__(12));
var prefHTML = _interopRequire(__webpack_require__(11));
var CombinedMultiMatcher = __webpack_require__(8).CombinedMultiMatcher;
var _gfpUtils = __webpack_require__(9);
var addStyleResolve = _gfpUtils.addStyleResolve;
var pad = _gfpUtils.pad;
let Pref = (function () {
function Pref() {
_classCallCheck(this, Pref);
let dialog = null;
let resourcesAdded = false;
GM_registerMenuCommand("Google Search Filter +", () => {
if (!resourcesAdded) this.addResources();
if (dialog) return;
dialog = new PrefDialog().dialog.on("dialogclose", () => dialog = null);
}, null);
}
_prototypeProperties(Pref, null, {
addResources: {
value: function addResources() {
addStyleResolve("jquery-ui-css");
addStyleResolve("slickgrid-css");
GM_addStyle(prefStyle.toString());
},
writable: true,
configurable: true
}
});
return Pref;
})();
let PrefDialog = (function () {
function PrefDialog() {
_classCallCheck(this, PrefDialog);
this.dialog = $(prefHTML).dialog(Object.assign({ title: "Google Search Filter +" }, this.dialogConfig));
this.grid = this.dialog.find(".grid");
this.bindImport();
this.bindExport();
this.addGrid();
}
_prototypeProperties(PrefDialog, null, {
dialogConfig: {
get: function () {
return {
width: $(window).width() * 0.5,
height: $(window).height() * 0.5,
close: function close() {
$(this).remove();
}
};
},
configurable: true
},
bindImport: {
value: function bindImport() {
this.dialog.find(".import").click(e => {
$("<textarea></textarea>").dialog(Object.assign({
title: "Import",
buttons: [{
text: "OK",
click: function click() {
config.filtersObject = JSON.parse($(this).val());
config.flushFilters();
config.constructor.call(config);
$(this).dialog("close");
}
}, { text: "Cancel", click: function click() {
$(this).dialog("close");
} }],
create: function create() {
setTimeout(() => this.select(), 0);
}
}, this.dialogConfig));
return false;
});
},
writable: true,
configurable: true
},
bindExport: {
value: function bindExport() {
this.dialog.find(".export").click(e => {
$("<textarea></textarea>").attr("readonly", "readonly").val(JSON.stringify(config.filtersObject, null, 2)).dialog(Object.assign({
title: "Export",
buttons: [{ text: "Close", click: function click() {
$(this).dialog("close");
} }],
create: function create() {
setTimeout(() => {
this.focus();this.setSelectionRange(0, this.value.length, "backward");
}, 0);
}
}, this.dialogConfig));
return false;
});
},
writable: true,
configurable: true
},
addGrid: {
value: function addGrid() {
let data = [];
for (let filter of config.filters) {
data.push({
text: filter.text,
slow: CombinedMultiMatcher.isSlowFilter(filter),
enabled: !filter.disabled,
hitCount: filter.hitCount,
lastHit: filter.lastHit });
}
let slickGrid = new Slick.Grid(this.grid, data, [{
id: "text", field: "text", name: "Filter rule", width: 300, sortable: true, editor: Slick.Editors.Text
}, {
id: "slow", field: "slow", name: "!", width: 1, sortable: true
}, {
id: "enabled", field: "enabled", name: "Enabled", width: 40, sortable: true,
formatter: Slick.Formatters.Checkmark, editor: Slick.Editors.Checkbox
}, {
id: "hitCount", field: "hitCount", name: "Hits", width: 1, sortable: true, editor: Slick.Editors.Text
}, {
id: "lastHit", field: "lastHit", name: "Last hit", width: 110, sortable: true,
formatter: (row, cell, value, columnDef, dataContext) => {
let date = new Date(value);
return dataContext.hitCount > 0 ? "" + date.getFullYear() + "-" + pad(date.getMonth() + 1, 2) + "-" + pad(date.getDate(), 2) + " " + ("" + pad(date.getHours() + 1, 2) + ":" + pad(date.getMinutes(), 2) + ":" + pad(date.getSeconds(), 2) + ":") + ("" + pad(date.getMilliseconds(), 3)) : "";
}, editor: Slick.Editors.Text
}], {
enableCellNavigation: true,
enableColumnReorder: false,
forceFitColumns: true });
slickGrid.onSort.subscribe((e, args) => {
let field = args.sortCol.field;
let res = args.sortAsc ? 1 : -1;
data.sort((x, y) => x[field] > y[field] ? res : x[field] < y[field] ? -res : 0);
slickGrid.invalidateAllRows();
slickGrid.render();
});
let height = this.grid.height();
this.dialog.on("dialogresize", (e, ui) => {
this.grid.css("height", "" + (height + (ui.size.height - ui.originalSize.height)) + "px");
slickGrid.resizeCanvas();
});
},
writable: true,
configurable: true
}
});
return PrefDialog;
})();
module.exports = new Pref();
/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
/* globals GM_getValue, GM_setValue */
var Filter = __webpack_require__(10).Filter;
let Filters = (function () {
function Filters(filters) {
_classCallCheck(this, Filters);
this._filters = filters;
this._callbacks = [];
}
_prototypeProperties(Filters, null, (function () {
var _prototypeProperties2 = {
get: {
value: function get(i) {
return this._filters[i];
},
writable: true,
configurable: true
},
trigger: {
value: function trigger(type, value) {
for (let cb of this._callbacks) cb(type, value);
},
writable: true,
configurable: true
},
push: {
value: function push(filter) {
this._filters.push(filter);
this.trigger("push", filter);
},
writable: true,
configurable: true
},
remove: {
value: function remove(filter) {
this._filters.pop(this._filters.indexOf(filter));
this.trigger("remove", filter);
},
writable: true,
configurable: true
},
update: {
value: function update(filter) {
this.trigger("update", filter);
},
writable: true,
configurable: true
},
observe: {
value: function observe(cb) {
this._callbacks.push(cb);
},
writable: true,
configurable: true
},
unobserve: {
value: function unobserve(cb) {
this._callbacks.pop(this._callbacks.indexOf(cb));
},
writable: true,
configurable: true
}
};
_prototypeProperties2[Symbol.iterator] = {
value: function () {
return this._filters[Symbol.iterator]();
},
writable: true,
configurable: true
};
return _prototypeProperties2;
})());
return Filters;
})();
let Config = (function () {
function Config() {
_classCallCheck(this, Config);
this.plugins = ["customSearch", "instant"];
this.allowHidden = GM_getValue("allowHidden", true);
this.filtersObject = JSON.parse(GM_getValue("filters", "{}"));
let filters = [];
for (let key in this.filtersObject) filters.push(Filter.fromObject(key, this.filtersObject[key]));
this.filters = new Filters(filters);
this.filters.observe((type, value) => {
switch (type) {
case "push":
this.filtersObject[value.text] = value.toObject();break;
case "remove":
delete this.filtersObject[value.text];break;
case "update":
this.filtersObject[value.text] = value.toObject();break;
}
});
}
_prototypeProperties(Config, null, {
flushAllowHidden: {
value: function flushAllowHidden() {
GM_setValue("allowHidden", this.allowHidden);
},
writable: true,
configurable: true
},
flushFilters: {
value: function flushFilters() {
GM_setValue("filters", JSON.stringify(this.filtersObject));
},
writable: true,
configurable: true
}
});
return Config;
})();
module.exports = new Config();
/***/ },
/* 5 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var _defaults = function (obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; };
var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };
var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) _defaults(subClass, superClass); };
var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
/* globals GM_addStyle */
var config = _interopRequire(__webpack_require__(4));
var guiStyle = _interopRequire(__webpack_require__(13));
var _gfpFilter = __webpack_require__(10);
var BlockingFilter = _gfpFilter.BlockingFilter;
var MultiRegExpFilter = _gfpFilter.MultiRegExpFilter;
var CombinedMultiMatcher = __webpack_require__(8).CombinedMultiMatcher;
var cache = __webpack_require__(9).cache;
let NodeData = exports.NodeData = (function () {
function NodeData(node) {
_classCallCheck(this, NodeData);
this.node = node;
}
_prototypeProperties(NodeData, null, {
act: {
value: function act(action, filter) {
if (this.action == action) {
this.redo(filter);
return true;
} else if (this.action !== null) {
this.undo();
delete this.redo;
delete this.undo;
}
this.action = action;
},
writable: true,
configurable: true
},
getChildren: {
value: function* getChildren() {},
writable: true,
configurable: true
},
redo: {
value: function redo() {},
writable: true,
configurable: true
},
undo: {
value: function undo() {},
writable: true,
configurable: true
}
});
return NodeData;
})();
NodeData.attrs = ["url", "title", "summary"];
NodeData.prototype.linkArea = null;
NodeData.prototype.url = null;
NodeData.prototype.title = null;
NodeData.prototype.summary = null;
NodeData.prototype.action = null;
let ResultsData = exports.ResultsData = (function (NodeData) {
function ResultsData() {
_classCallCheck(this, ResultsData);
if (NodeData != null) {
NodeData.apply(this, arguments);
}
}
_inherits(ResultsData, NodeData);
_prototypeProperties(ResultsData, null, {
getChildren: {
value: function* getChildren() {
for (let child of this.node.querySelectorAll("li.g")) {
if (child.id == "imagebox_bigimages") {
yield new ImageContainerData(child);
} else if (child.id == "lclbox") {
yield new MapContainerData(child);
} else if (child.classList.contains("mnr-c")) {
yield new KnowledgeData(child);
} else if (child.classList.contains("card-section")) {
yield new NewsData(child);
} else if (!child.classList.contains("obcontainer")) {
yield new TextData(child);
}
}
},
writable: true,
configurable: true
}
});
return ResultsData;
})(NodeData);
let CommonData = (function (NodeData) {
function CommonData() {
_classCallCheck(this, CommonData);
if (NodeData != null) {
NodeData.apply(this, arguments);
}
}
_inherits(CommonData, NodeData);
_prototypeProperties(CommonData, null, {
linkArea: {
get: function () {
return cache(this, "linkArea", this.node.querySelector("cite").parentNode);
},
configurable: true
},
url: {
get: function () {
return cache(this, "url", this.node.querySelector("h3.r>a").href);
},
configurable: true
},
title: {
get: function () {
return cache(this, "title", this.node.querySelector("h2.r, h3.r").textContent);
},
configurable: true
}
});
return CommonData;
})(NodeData);
let KnowledgeData = (function (CommonData) {
function KnowledgeData() {
_classCallCheck(this, KnowledgeData);
if (CommonData != null) {
CommonData.apply(this, arguments);
}
}
_inherits(KnowledgeData, CommonData);
return KnowledgeData;
})(CommonData);
let TextData = (function (CommonData) {
function TextData() {
_classCallCheck(this, TextData);
if (CommonData != null) {
CommonData.apply(this, arguments);
}
}
_inherits(TextData, CommonData);
_prototypeProperties(TextData, null, {
summary: {
get: function () {
return cache(this, "summary", this.node.querySelector("div.s").textContent);
},
configurable: true
}
});
return TextData;
})(CommonData);
let NewsData = (function (NodeData) {
function NewsData() {
_classCallCheck(this, NewsData);
if (NodeData != null) {
NodeData.apply(this, arguments);
}
}
_inherits(NewsData, NodeData);
_prototypeProperties(NewsData, null, {
linkArea: {
get: function () {
return cache(this, "linkArea", this.node.querySelector("cite").parentNode);
},
configurable: true
},
url: {
get: function () {
return cache(this, "url", this.node.querySelector("a").href);
},
configurable: true
},
title: {
get: function () {
return cache(this, "title", this.node.querySelector("a").textContent);
},
configurable: true
},
summary: {
get: function () {
let node = this.node.querySelector("span.s");
return cache(this, "summary", node ? node.textContent : null);
},
configurable: true
}
});
return NewsData;
})(NodeData);
let MapContainerData = (function (NodeData) {
function MapContainerData() {
_classCallCheck(this, MapContainerData);
if (NodeData != null) {
NodeData.apply(this, arguments);
}
}
_inherits(MapContainerData, NodeData);
_prototypeProperties(MapContainerData, null, {
getChildren: {
value: function* getChildren() {
for (let child of this.node.querySelectorAll("div.g")) yield new MapData(child);
},
writable: true,
configurable: true
}
});
return MapContainerData;
})(NodeData);
let MapData = (function (CommonData) {
function MapData() {
_classCallCheck(this, MapData);
if (CommonData != null) {
CommonData.apply(this, arguments);
}
}
_inherits(MapData, CommonData);
return MapData;
})(CommonData);
let ImageContainerData = (function (NodeData) {
function ImageContainerData() {
_classCallCheck(this, ImageContainerData);
if (NodeData != null) {
NodeData.apply(this, arguments);
}
}
_inherits(ImageContainerData, NodeData);
_prototypeProperties(ImageContainerData, null, {
getChildren: {
value: function* getChildren() {
for (let child of this.node.querySelectorAll(".bia")) yield new ImageData(child);
},
writable: true,
configurable: true
}
});
return ImageContainerData;
})(NodeData);
let ImageData = (function (NodeData) {
function ImageData() {
_classCallCheck(this, ImageData);
if (NodeData != null) {
NodeData.apply(this, arguments);
}
}
_inherits(ImageData, NodeData);
_prototypeProperties(ImageData, null, {
url: {
get: function () {
return cache(this, "url", this.node.href);
},
configurable: true
}
});
return ImageData;
})(NodeData);
let SearchGui = exports.SearchGui = (function () {
function SearchGui() {
_classCallCheck(this, SearchGui);
this.matcher = new CombinedMultiMatcher(3);
for (let filter of config.filters) this.matcher.add(filter);
config.filters.observe((type, value) => {
switch (type) {
case "push":
this.matcher.add(value);break;
case "remove":
this.matcher.remove(value);break;
}
});
this.nodeData = { children: [] };
this.createNodes();
GM_addStyle(guiStyle.toString());
}
_prototypeProperties(SearchGui, {
isSearchPage: {
value: function isSearchPage() {
return window.location.href.indexOf("/search?") > -1;
},
writable: true,
configurable: true
},
getResults: {
value: function getResults() {
return document.getElementById("ires");
},
writable: true,
configurable: true
}
}, {
createNodes: {
value: function createNodes() {
/**
Create once and clone to improve performance.
*/
// dash
this.dash = document.createElement("span");
this.dash.textContent = "-";
this.dash.classList.add("dash");
// add filter link
this.addLink = document.createElement("a");
this.addLink.textContent = "Filter";
this.addLink.setAttribute("href", "#");
this.addLink.classList.add("filter-add");
// hidden result title
this.showTitle = document.createElement("span");
this.showTitle.classList.add("show-title");
// hidden result 'show' link
this.showLink = document.createElement("a");
this.showLink.textContent = "show";
this.showLink.setAttribute("href", "#");
this.showLink.classList.add("show-link");
},
writable: true,
configurable: true
},
toggleResult: {
value: function toggleResult(nodeData, showTitle, showLink) {
let initial = arguments[3] === undefined ? false : arguments[3];
for (let child of nodeData.node.children) if (child != showTitle && child != showLink) child.classList.toggle("hide");
if (initial) {
return;
}if (showTitle) showTitle.classList.toggle("hide");
showLink.classList.toggle("hide");
showLink.textContent = showLink.classList.contains("hide") ? "hide" : "show";
},
writable: true,
configurable: true
},
hideResult: {
value: function hideResult(nodeData) {
let filter = arguments[1] === undefined ? null : arguments[1];
if (nodeData.act(this.hideResult, filter)) {
return;
}if (config.allowHidden && filter.collapse) {
nodeData.node.classList.add("hide");
nodeData.undo = () => nodeData.node.classList.remove("hide");
return;
}
let showTitle = null;
if (nodeData.title !== null) {
showTitle = this.showTitle.cloneNode(false);
showTitle.textContent = nodeData.title;
nodeData.node.appendChild(showTitle);
}
let showLink = this.showLink.cloneNode(true);
if (filter) showLink.title = filter.text;
nodeData.node.appendChild(showLink);
this.toggleResult(nodeData, showTitle, showLink, true);
showLink.onclick = () => {
this.toggleResult(nodeData, showTitle, showLink);
return false;
};
nodeData.redo = filter => {
if (filter) showLink.title = filter.text;
};
nodeData.undo = () => {
if (!showLink.classList.contains("hide")) this.toggleResult(nodeData, showTitle, showLink);
if (showTitle) nodeData.node.removeChild(showTitle);
nodeData.node.removeChild(showLink);
};
},
writable: true,
configurable: true
},
addFilterLink: {
value: function addFilterLink(nodeData) {
let filter = arguments[1] === undefined ? null : arguments[1];
if (nodeData.act(this.addFilterLink, filter)) {
return;
}if (!nodeData.linkArea) {
return;
}let dash = this.dash.cloneNode(true);
nodeData.linkArea.appendChild(dash);
let addLink = this.addLink.cloneNode(true);
if (filter) addLink.title = filter.text;
nodeData.linkArea.appendChild(addLink);
addLink.onclick = () => {
this.addFromResult(nodeData);
return false;
};
nodeData.redo = filter => {
if (filter) addLink.title = filter.text;
};
nodeData.undo = () => {
nodeData.linkArea.removeChild(dash);
nodeData.linkArea.removeChild(addLink);
};
},
writable: true,
configurable: true
},
addFromResult: {
value: function addFromResult(nodeData) {
let domainUrl = "||" + nodeData.url.replace(/^[\w\-]+:\/+(?:www\.)?/, "");
let text = prompt("Filter: ", domainUrl);
if (text === null) {
return;
}config.filters.push(MultiRegExpFilter.fromText(text));
this.filterResults();
},
writable: true,
configurable: true
},
_filterResults: {
value: function _filterResults(nodeData) {
let filter = this.matcher.matchesAny(nodeData, NodeData.attrs);
if (filter) {
filter.hitCount++;
filter.lastHit = new Date().getTime();
config.filters.update(filter);
if (filter instanceof BlockingFilter) {
this.hideResult(nodeData, filter);
return true;
} else {
this.addFilterLink(nodeData, filter);
return false;
}
}
if (nodeData.children === undefined) nodeData.children = Array.from(nodeData.getChildren());
let filtered = !!nodeData.children.length;
for (let childData of nodeData.children) {
if (!this._filterResults(childData)) filtered = false;
}
if (filtered) this.hideResult(nodeData);else if (NodeData.attrs.some(attr => nodeData[attr] !== null)) this.addFilterLink(nodeData);
return filtered;
},
writable: true,
configurable: true
},
filterResults: {
value: function filterResults() {
let node = arguments[0] === undefined ? null : arguments[0];
let matched = false;
let listener = (type, value) => {
if (type == "update") matched = true;
};
config.filters.observe(listener);
if (node) {
let nodeData = new ResultsData(node);
this.nodeData.children.push(nodeData);
this._filterResults(nodeData);
} else {
this._filterResults(this.nodeData);
}
if (matched) config.flushFilters();
config.filters.unobserve(listener);
},
writable: true,
configurable: true
}
});
return SearchGui;
})();
Object.defineProperty(exports, "__esModule", {
value: true
});
/***/ },
/* 6 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (searchGui) {};
// //check if url is a google custom search url
// let location = window.location.href;
// if(location.indexOf('/cse?') == -1 && location.indexOf('/custom?') == -1)
// return;
// //set searchGui first so searchGui.filterResults() produces no errors
// searchGui.getResultType=function() null;
// //wait when ajax loads search results
// let cse=document.getElementById('cse');
// let resultsCallback=function(e){
// searchGui.getResults=function() document.querySelector('.gsc-results');
// searchGui.r={
// res: {
// getResults: function(node) node.querySelectorAll('.gsc-table-result'),
// getLinkArea: function() null,
// getUrl: function() null,
// getTitle: function() null,
// getSummary: function() null,
// },
// text: {
// getResults: function() null,
// getLinkArea: function(node) node.querySelector('.gsc-url-bottom'),
// getUrl: function(node) node.querySelector('a.gs-title').href,
// getTitle: function(node) node.querySelector('a.gs-title').textContent,
// getSummary: function(node) node.querySelector('.gs-snippet').textContent,
// },
// }
// searchGui.getResultType=function(node,filterClass){
// if(node.classList.contains('gsc-results')){
// return searchGui.r.res;
// }else if(node.classList.contains('gsc-table-result')){
// return searchGui.r.text;
// }
// }
// //google custom search layout
// prefLink.createLink=function(){
// let linkParent=searchGui.getResults();
// let link=document.createElement('a');
// link.setAttribute('style','float:right; margin-top:10px; color:#0000CC; font-size:14px; text-decoration:none;');
// link.setAttribute('href','javascript:void(0);');
// link.appendChild(document.createTextNode('Config Filters'));
// linkParent.insertBefore(document.createElement('br'),linkParent.firstElementChild);
// linkParent.insertBefore(link,linkParent.firstElementChild);
// return link;
// }
// prefLink.init();
// GM_addStyle('.gs-visibleUrl-long{display: inline !important}');
// //there are no previous results, filter directly
// searchGui.filterResults();
// };
// //hook draw
// CustomSearchControl=unsafeWindow.google.search.CustomSearchControl;
// let o_draw=CustomSearchControl.prototype.draw;
// CustomSearchControl.prototype.draw=function(){
// //privileged code
// o_draw.apply(this,arguments);
// let cse=document.querySelector('#cse');
// let o_resultsTable=null;
// //observer callback after loading the first set of results
// let loadNextCallback=function(mutations){
// /**
// returns:
// whether new results have loaded
// */
// let resultsTable=cse.querySelector('.gsc-table-result');
// if(resultsTable==null || o_resultsTable==resultsTable)
// return false;
// o_resultsTable=resultsTable;
// window.dispatchEvent(new CustomEvent('results'));
// return true;
// }
// let loadNextObserver=new MutationObserver(loadNextCallback);
// //observer callback when loading the first set of results
// let loadFirstCallback=function(mutations){
// //wait until the first results have loaded before disconnecting
// if(!loadNextCallback())
// return false;
// loadFirstObserver.disconnect();
// let results=cse.querySelector('.gsc-results');
// if(results==null)
// return;
// loadNextObserver.observe(results,{attributes: true});
// }
// let loadFirstObserver=new MutationObserver(loadFirstCallback);
// //wait until the first set of results have loaded
// loadFirstObserver.observe(cse,{subtree: true, childList: true});
// }
// //resultsCallback needs privileged functions
// window.addEventListener('results',resultsCallback,false);
/***/ },
/* 7 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (searchGui) {};
// //initialize searchGui if we haven't already (e.g. on the home page)
// if(!searchGui.initialized)
// searchGui.init();
// //results parent node
// let resultsNode;
// //pref link
// let _prefLink;
// let resultsObserver=new MutationObserver(function(mutations){
// mutations.forEach(function(mutation) {
// //filter nodes whenever they are added, instead of doing batch filters
// for(let i=0;i<mutation.addedNodes.length;i++){
// addedNode=mutation.addedNodes[i];
// if(addedNode.id=='ires'){
// //update prefLink (google instant inserts a new one every time)
// if(!_prefLink || !isInDom(_prefLink))
// _prefLink=prefLink.createLinkSettings();
// //we have a new query, google only adds this node with all results added (to test this properly, disable all other userscripts)
// searchGui.filterResults();
// prefMeta.isUpdated=false;
// //other extensions might use this
// window.dispatchEvent(new CustomEvent('instantResults'));
// }
// };
// });
// });
// let mainNode=document.getElementById('main');
// if(!mainNode)
// return false;
// resultsObserver.observe(mainNode,{subtree: true, childList: true});
/***/ },
/* 8 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var _defaults = function (obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; };
var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } };
var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) _defaults(subClass, superClass); };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var WhitelistFilter = __webpack_require__(10).WhitelistFilter;
var Matcher = __webpack_require__(14).Matcher;
let SubMatcher = exports.SubMatcher = (function (Matcher) {
function SubMatcher() {
_classCallCheck(this, SubMatcher);
if (Matcher != null) {
Matcher.apply(this, arguments);
}
}
_inherits(SubMatcher, Matcher);
_prototypeProperties(SubMatcher, {
_findCandidates: {
value: function _findCandidates(filter) {
if (filter.regexpSource === null) {
return null;
}return filter.regexpSource.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
},
writable: true,
configurable: true
},
isSlowFilter: {
value: function isSlowFilter(filter) {
return !this._findCandidates(filter);
},
writable: true,
configurable: true
}
}, {
findKeyword: {
value: function findKeyword(filter) {
let res = "";
let candidates = this.constructor._findCandidates(filter);
if (candidates === null) {
return res;
}let resCount = 16777215;
let resLength = 0;
for (let candidate of candidates) {
candidate = candidate.substr(1);
let count = this.filterByKeyword.has(candidate) ? this.filterByKeyword.get(candidate).length : 0;
if (count < resCount || count == resCount && candidate.length > resLength) {
res = candidate;
resCount = count;
resLength = candidate.length;
}
}
return res;
},
writable: true,
configurable: true
},
add: {
value: function add(filter) {
// Duplicates are rare and not checked for efficiency, otherwise we need to store sub filter text maps, and multiple
// parents per sub filter.
let keyword = this.findKeyword(filter);
let prevEntry = this.filterByKeyword.get(keyword);
if (prevEntry === undefined) {
this.filterByKeyword.set(keyword, filter);
} else if (prevEntry.length == 1) {
this.filterByKeyword.set(keyword, [prevEntry, filter]);
} else {
prevEntry.push(filter);
}
},
writable: true,
configurable: true
},
remove: {
value: function remove(filter) {
// only used by pref, doesn't need to be efficient
let candidates = this.constructor._findCandidates(filter);
if (candidates === null) candidates = [""];
for (let candidate of candidates) {
candidate = candidate.substr(1);
let prevEntry = this.filterByKeyword.get(candidate);
if (prevEntry === undefined) {} else if (prevEntry.length == 1) {
if (prevEntry == filter) {
this.filterByKeyword["delete"](candidate);
break;
}
} else {
let i = prevEntry.indexOf(filter);
if (i > -1) {
if (prevEntry.length == 2) this.filterByKeyword.set(candidate, prevEntry[1 - i]);else prevEntry.pop(i);
break;
}
}
}
},
writable: true,
configurable: true
},
clear: {
value: function clear() {
this.filterByKeyword = new Map();
},
writable: true,
configurable: true
},
hasFilter: {
value: function hasFilter() {
throw "not implemented";
},
writable: true,
configurable: true
},
getKeywordForFilter: {
value: function getKeywordForFilter() {
throw "not implemented";
},
writable: true,
configurable: true
},
_iterMatches: {
value: function* _iterMatches(filters, data, parents) {
for (let filter of filters) {
let parent = filter.parent;
if ((filter.dataIndex === 0 || parents.has(parent)) && filter.matches(data)) yield filter;
}
},
writable: true,
configurable: true
},
iterMatches: {
value: function* iterMatches(data, parents) {
/**
Args:
parents: All parent filters matched so far, excluding the ones whose subFilters have so far been null.
*/
if (data === null) {
return;
}let candidates = data.toLowerCase().match(/[a-z0-9%]{3,}/g);
if (candidates === null) candidates = [];
candidates.push("");
for (let keyword of candidates) {
let filters = this.filterByKeyword.get(keyword);
if (filters) {
for (let res of this._iterMatches(filters, data, parents)) yield res;
}
}
},
writable: true,
configurable: true
}
});
return SubMatcher;
})(Matcher);
let MultiMatcher = exports.MultiMatcher = (function () {
function MultiMatcher(n) {
_classCallCheck(this, MultiMatcher);
this.n = n;
this.matchers = [];
for (let i = 0; i < n; i++) this.matchers.push(new SubMatcher());
}
_prototypeProperties(MultiMatcher, {
isSlowFilter: {
value: function isSlowFilter(filter) {
for (let subFilter of filter.filters) {
if (SubMatcher.isSlowFilter(subFilter)) {
return true;
}
}
return false;
},
writable: true,
configurable: true
}
}, {
add: {
value: function add(filter) {
for (let subFilter of filter.filters) this.matchers[subFilter.index].add(subFilter);
},
writable: true,
configurable: true
},
remove: {
value: function remove(filter) {
for (let subFilter of filter.filters) this.matchers[subFilter.index].remove(subFilter);
},
writable: true,
configurable: true
},
clear: {
value: function clear() {
for (let matcher of this.matchers) matcher.clear();
},
writable: true,
configurable: true
},
matchesAny: {
value: function matchesAny(data, attrs) {
// {filter: nextNullNum}
let prevFilters = new Map();
let curFilters = new Map();
for (let i = 0; i < this.n; i++) {
for (let subFilter of this.matchers[i].iterMatches(data[attrs[i]], prevFilters)) {
if (subFilter.dataIndex == subFilter.parent.filters.length - 1) {
return subFilter.parent;
}curFilters.set(subFilter.parent, subFilter.parent.filters[subFilter.dataIndex + 1].index - i);
}
if (i != this.n - 1) {
// include null subFilters whose parents have so far matched
for (let _ref of prevFilters.entries()) {
var _ref2 = _slicedToArray(_ref, 2);
let filter = _ref2[0];
let nextNullNum = _ref2[1];
if (nextNullNum > 0) curFilters.set(filter, nextNullNum - 1);
}
var _ref3 = [curFilters, new Map()];
var _ref32 = _slicedToArray(_ref3, 2);
prevFilters = _ref32[0];
curFilters = _ref32[1];
}
}
return null;
},
writable: true,
configurable: true
}
});
return MultiMatcher;
})();
let CombinedMultiMatcher = exports.CombinedMultiMatcher = (function () {
function CombinedMultiMatcher(n) {
_classCallCheck(this, CombinedMultiMatcher);
// caching is not required since we have one instance per tab, and past filter results are easily accessible
this.blacklist = new MultiMatcher(n);
this.whitelist = new MultiMatcher(n);
}
_prototypeProperties(CombinedMultiMatcher, {
isSlowFilter: {
value: function isSlowFilter(filter) {
return MultiMatcher.isSlowFilter(filter);
},
writable: true,
configurable: true
}
}, {
add: {
value: function add(filter) {
if (filter instanceof WhitelistFilter) {
this.whitelist.add(filter);
} else {
this.blacklist.add(filter);
}
},
writable: true,
configurable: true
},
remove: {
value: function remove(filter) {
if (filter instanceof WhitelistFilter) {
this.whitelist.remove(filter);
} else {
this.blacklist.remove(filter);
}
},
writable: true,
configurable: true
},
clear: {
value: function clear() {
this.blacklist.clear();
this.whitelist.clear();
},
writable: true,
configurable: true
},
matchesAny: {
value: function matchesAny(data, attrs) {
return this.whitelist.matchesAny(data, attrs) || this.blacklist.matchesAny(data, attrs);
},
writable: true,
configurable: true
}
});
return CombinedMultiMatcher;
})();
Object.defineProperty(exports, "__esModule", {
value: true
});
/***/ },
/* 9 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
/* global GM_addStyle, GM_getResourceText, GM_getResourceURL */
exports.cache = cache;
exports.pad = pad;
exports.addStyleResolve = addStyleResolve;
function cache(obj, prop, value) {
Object.defineProperty(obj, prop, { value: value });
return value;
}
function pad(num, size) {
return Array(Math.max(size - num.toString().length + 1, 0)).join(0) + num;
}
function addStyleResolve(name) {
GM_addStyle(GM_getResourceText(name).replace(/url\("?([^":]+)"?\)/g, (match, url) => "url(\"" + GM_getResourceURL("" + name + "/" + url) + "\")"));
}
Object.defineProperty(exports, "__esModule", {
value: true
});
/***/ },
/* 10 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var _defaults = function (obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; };
var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) _defaults(subClass, superClass); };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var _gfpLibFilterClasses = __webpack_require__(15);
var ActiveFilter = _gfpLibFilterClasses.ActiveFilter;
var Filter_ = _gfpLibFilterClasses.Filter;
var InvalidFilter = _gfpLibFilterClasses.InvalidFilter;
var RegExpFilter_ = _gfpLibFilterClasses.RegExpFilter;
let RegExpFilter = exports.RegExpFilter = (function (RegExpFilter_) {
function RegExpFilter(regexpSource, matchCase, collapse) {
_classCallCheck(this, RegExpFilter);
if (matchCase) this.matchCase = matchCase;
if (collapse) this.collapse = collapse;
// convert regex filters immediately to catch syntax errors, normal filters on-demand
if (regexpSource.length >= 2 && regexpSource.startsWith("/") && regexpSource.endsWith("/")) {
let regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
Object.defineProperty(this, "regexp", { value: regexp });
} else {
this.regexpSource = regexpSource;
}
}
_inherits(RegExpFilter, RegExpFilter_);
_prototypeProperties(RegExpFilter, {
fromParts: {
value: function fromParts(text) {
let optionsStr = arguments[1] === undefined ? "" : arguments[1];
// text is not stored to save memory
let matchCase = false;
let collapse = false;
let options = optionsStr.toUpperCase().split(",");
for (let option of options) {
switch (option) {
case "MATCH_CASE":
matchCase = true;break;
case "COLLAPSE":
collapse = true;break;
case "":
break;
default:
return new InvalidFilter("Unknown option: " + option);
}
}
return new RegExpFilter(text, matchCase, collapse);
},
writable: true,
configurable: true
}
}, (function () {
var _prototypeProperties2 = {
matches: {
value: function matches(data) {
return this.regexp.test(data);
},
writable: true,
configurable: true
}
};
_prototypeProperties2[Symbol.iterator] = {
value: function* () {
yield this;
},
writable: true,
configurable: true
};
return _prototypeProperties2;
})());
return RegExpFilter;
})(RegExpFilter_);
RegExpFilter.prototype.matchCase = false;
RegExpFilter.prototype.collapse = false;
RegExpFilter.prototype.index = 0;
RegExpFilter.prototype.dataIndex = 0;
let MultiRegExpFilter = exports.MultiRegExpFilter = (function (ActiveFilter) {
function MultiRegExpFilter(text, filters) {
_classCallCheck(this, MultiRegExpFilter);
this.text = text;
this.filters = filters;
for (let filter of filters) filter.parent = this;
}
_inherits(MultiRegExpFilter, ActiveFilter);
_prototypeProperties(MultiRegExpFilter, {
fromText: {
value: function fromText(text) {
let origText = text;
let filters = [];
let blocking = true;
if (text.indexOf("@@") === 0) {
blocking = false;
text = text.substr(2);
}
let parts = text.split(/\$([\w,]*?)(?:\$|$)/);
for (let i = 0; i < parts.length; i += 2) {
let part = parts[i];
let options = parts[i + 1];
if (!part) continue;
let filter = RegExpFilter.fromParts(part, options);
if (filter instanceof InvalidFilter) {
return new InvalidFilter(text);
}if (i > 0) filter.index = i / 2;
if (filters.length > 0) filter.dataIndex = filters.length;
filters.push(filter);
}
if (filters.length == 1) filters = filters[0];
return blocking ? new BlockingFilter(origText, filters) : new WhitelistFilter(origText, filters);
},
writable: true,
configurable: true
}
}, {
collapse: {
get: function () {
for (let filter of this.filters) if (filter.collapse) return true;
return false;
},
configurable: true
}
});
return MultiRegExpFilter;
})(ActiveFilter);
let Filter = exports.Filter = Filter_;
Filter.fromObject = function (text, obj) {
let res = Filter.fromText(text);
if (res instanceof ActiveFilter) {
if ("disabled" in obj) res._disabled = obj.disabled === true;
if ("hitCount" in obj) res._hitCount = parseInt(obj.hitCount) || 0;
if ("lastHit" in obj) res._lastHit = parseInt(obj.lastHit) || 0;
}
return res;
};
Filter.prototype.toObject = function () {
let res = {};
if (this instanceof ActiveFilter) {
if (this._disabled) res.disabled = true;
if (this._hitCount) res.hitCount = this._hitCount;
if (this._lastHit) res.lastHit = this._lastHit;
}
return res;
};
Filter.fromText = MultiRegExpFilter.fromText;
let BlockingFilter = exports.BlockingFilter = (function (MultiRegExpFilter) {
function BlockingFilter() {
_classCallCheck(this, BlockingFilter);
if (MultiRegExpFilter != null) {
MultiRegExpFilter.apply(this, arguments);
}
}
_inherits(BlockingFilter, MultiRegExpFilter);
return BlockingFilter;
})(MultiRegExpFilter);
let WhitelistFilter = exports.WhitelistFilter = (function (MultiRegExpFilter) {
function WhitelistFilter() {
_classCallCheck(this, WhitelistFilter);
if (MultiRegExpFilter != null) {
MultiRegExpFilter.apply(this, arguments);
}
}
_inherits(WhitelistFilter, MultiRegExpFilter);
return WhitelistFilter;
})(MultiRegExpFilter);
Object.defineProperty(exports, "__esModule", {
value: true
});
/***/ },
/* 11 */
/***/ function(module, exports, __webpack_require__) {
module.exports = "<div class=gfp><div class=row><a href=# class=add title=\"Add filter\"><img class=button-icon src=" + __webpack_require__(19) + "></a> <a href=# class=import title=\"Import filters\"><img class=button-icon src=" + __webpack_require__(20) + "></a> <a href=# class=export title=\"Export filters\"><img class=button-icon src=" + __webpack_require__(21) + "></a></div><div class=row><div class=grid></div></div></div>";
/***/ },
/* 12 */
/***/ function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(16)();
exports.push([module.id, ".ui-front{z-index:1000}.ui-dialog textarea{box-sizing:border-box;width:100%!important;resize:none}.slick-sort-indicator{float:none}.gfp{display:flex!important;flex-direction:column}.gfp a{outline:0}.gfp .button-icon{height:16px;width:16px}.gfp .row:last-child{flex-grow:1}.gfp .grid{height:100%}.gfp .slick-cell:first-child{white-space:pre}.gfp .slick-header-column:not(:first-child),.gfp .slick-cell:not(:first-child){text-align:right}", ""]);
/***/ },
/* 13 */
/***/ function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(16)();
exports.push([module.id, ".hide{display:none}.dash{padding:0 3px}.filter-add{color:#12C!important;font-size:90%;text-decoration:none}.filter-add:hover{text-decoration:underline}.show-title{padding-right:5px;color:#999;font-size:90%}.show-link{color:#999!important;font-size:90%;text-decoration:none}.show-link.hide{display:inline;color:#12C!important}", ""]);
/***/ },
/* 14 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-2015 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @fileOverview Matcher class implementing matching addresses against a list of filters.
*/
var _require = __webpack_require__(15);
let Filter = _require.Filter;
let RegExpFilter = _require.RegExpFilter;
let WhitelistFilter = _require.WhitelistFilter;
/**
* Blacklist/whitelist filter matching
* @constructor
*/
function Matcher() {
this.clear();
}
exports.Matcher = Matcher;
Matcher.prototype = {
/**
* Lookup table for filters by their associated keyword
* @type Object
*/
filterByKeyword: null,
/**
* Lookup table for keywords by the filter text
* @type Object
*/
keywordByFilter: null,
/**
* Removes all known filters
*/
clear: function clear() {
this.filterByKeyword = Object.create(null);
this.keywordByFilter = Object.create(null);
},
/**
* Adds a filter to the matcher
* @param {RegExpFilter} filter
*/
add: function add(filter) {
if (filter.text in this.keywordByFilter) {
return;
} // Look for a suitable keyword
let keyword = this.findKeyword(filter);
let oldEntry = this.filterByKeyword[keyword];
if (typeof oldEntry == "undefined") this.filterByKeyword[keyword] = filter;else if (oldEntry.length == 1) this.filterByKeyword[keyword] = [oldEntry, filter];else oldEntry.push(filter);
this.keywordByFilter[filter.text] = keyword;
},
/**
* Removes a filter from the matcher
* @param {RegExpFilter} filter
*/
remove: function remove(filter) {
if (!(filter.text in this.keywordByFilter)) {
return;
}let keyword = this.keywordByFilter[filter.text];
let list = this.filterByKeyword[keyword];
if (list.length <= 1) delete this.filterByKeyword[keyword];else {
let index = list.indexOf(filter);
if (index >= 0) {
list.splice(index, 1);
if (list.length == 1) this.filterByKeyword[keyword] = list[0];
}
}
delete this.keywordByFilter[filter.text];
},
/**
* Chooses a keyword to be associated with the filter
* @param {String} text text representation of the filter
* @return {String} keyword (might be empty string)
*/
findKeyword: function findKeyword(filter) {
let result = "";
let text = filter.text;
if (Filter.regexpRegExp.test(text)) {
return result;
} // Remove options
let match = Filter.optionsRegExp.exec(text);
if (match) text = match.input.substr(0, match.index);
// Remove whitelist marker
if (text.substr(0, 2) == "@@") text = text.substr(2);
let candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
if (!candidates) {
return result;
}let hash = this.filterByKeyword;
let resultCount = 16777215;
let resultLength = 0;
for (let i = 0, l = candidates.length; i < l; i++) {
let candidate = candidates[i].substr(1);
let count = candidate in hash ? hash[candidate].length : 0;
if (count < resultCount || count == resultCount && candidate.length > resultLength) {
result = candidate;
resultCount = count;
resultLength = candidate.length;
}
}
return result;
},
/**
* Checks whether a particular filter is being matched against.
*/
hasFilter: function hasFilter( /**RegExpFilter*/filter) /**Boolean*/
{
return filter.text in this.keywordByFilter;
},
/**
* Returns the keyword used for a filter, null for unknown filters.
*/
getKeywordForFilter: function getKeywordForFilter( /**RegExpFilter*/filter) /**String*/
{
if (filter.text in this.keywordByFilter) {
return this.keywordByFilter[filter.text];
} else {
return null;
}
},
/**
* Checks whether the entries for a particular keyword match a URL
*/
_checkEntryMatch: function _checkEntryMatch(keyword, location, contentType, docDomain, thirdParty, sitekey) {
let list = this.filterByKeyword[keyword];
for (let i = 0; i < list.length; i++) {
let filter = list[i];
if (filter.matches(location, contentType, docDomain, thirdParty, sitekey)) {
return filter;
}
}
return null;
},
/**
* Tests whether the URL matches any of the known filters
* @param {String} location URL to be tested
* @param {String} contentType content type identifier of the URL
* @param {String} docDomain domain name of the document that loads the URL
* @param {Boolean} thirdParty should be true if the URL is a third-party request
* @param {String} sitekey public key provided by the document
* @return {RegExpFilter} matching filter or null
*/
matchesAny: function matchesAny(location, contentType, docDomain, thirdParty, sitekey) {
let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
if (candidates === null) candidates = [];
candidates.push("");
for (let i = 0, l = candidates.length; i < l; i++) {
let substr = candidates[i];
if (substr in this.filterByKeyword) {
let result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
if (result) {
return result;
}
}
}
return null;
}
};
/**
* Combines a matcher for blocking and exception rules, automatically sorts
* rules into two Matcher instances.
* @constructor
*/
function CombinedMatcher() {
this.blacklist = new Matcher();
this.whitelist = new Matcher();
this.resultCache = Object.create(null);
}
exports.CombinedMatcher = CombinedMatcher;
/**
* Maximal number of matching cache entries to be kept
* @type Number
*/
CombinedMatcher.maxCacheEntries = 1000;
CombinedMatcher.prototype = {
/**
* Matcher for blocking rules.
* @type Matcher
*/
blacklist: null,
/**
* Matcher for exception rules.
* @type Matcher
*/
whitelist: null,
/**
* Lookup table of previous matchesAny results
* @type Object
*/
resultCache: null,
/**
* Number of entries in resultCache
* @type Number
*/
cacheEntries: 0,
/**
* @see Matcher#clear
*/
clear: function clear() {
this.blacklist.clear();
this.whitelist.clear();
this.resultCache = Object.create(null);
this.cacheEntries = 0;
},
/**
* @see Matcher#add
*/
add: function add(filter) {
if (filter instanceof WhitelistFilter) this.whitelist.add(filter);else this.blacklist.add(filter);
if (this.cacheEntries > 0) {
this.resultCache = Object.create(null);
this.cacheEntries = 0;
}
},
/**
* @see Matcher#remove
*/
remove: function remove(filter) {
if (filter instanceof WhitelistFilter) this.whitelist.remove(filter);else this.blacklist.remove(filter);
if (this.cacheEntries > 0) {
this.resultCache = Object.create(null);
this.cacheEntries = 0;
}
},
/**
* @see Matcher#findKeyword
*/
findKeyword: function findKeyword(filter) {
if (filter instanceof WhitelistFilter) {
return this.whitelist.findKeyword(filter);
} else {
return this.blacklist.findKeyword(filter);
}
},
/**
* @see Matcher#hasFilter
*/
hasFilter: function hasFilter(filter) {
if (filter instanceof WhitelistFilter) {
return this.whitelist.hasFilter(filter);
} else {
return this.blacklist.hasFilter(filter);
}
},
/**
* @see Matcher#getKeywordForFilter
*/
getKeywordForFilter: function getKeywordForFilter(filter) {
if (filter instanceof WhitelistFilter) {
return this.whitelist.getKeywordForFilter(filter);
} else {
return this.blacklist.getKeywordForFilter(filter);
}
},
/**
* Checks whether a particular filter is slow
*/
isSlowFilter: function isSlowFilter( /**RegExpFilter*/filter) /**Boolean*/
{
let matcher = filter instanceof WhitelistFilter ? this.whitelist : this.blacklist;
if (matcher.hasFilter(filter)) {
return !matcher.getKeywordForFilter(filter);
} else {
return !matcher.findKeyword(filter);
}
},
/**
* Optimized filter matching testing both whitelist and blacklist matchers
* simultaneously. For parameters see Matcher.matchesAny().
* @see Matcher#matchesAny
*/
matchesAnyInternal: function matchesAnyInternal(location, contentType, docDomain, thirdParty, sitekey) {
let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
if (candidates === null) candidates = [];
candidates.push("");
let blacklistHit = null;
for (let i = 0, l = candidates.length; i < l; i++) {
let substr = candidates[i];
if (substr in this.whitelist.filterByKeyword) {
let result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
if (result) {
return result;
}
}
if (substr in this.blacklist.filterByKeyword && blacklistHit === null) blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey);
}
return blacklistHit;
},
/**
* @see Matcher#matchesAny
*/
matchesAny: function matchesAny(location, contentType, docDomain, thirdParty, sitekey) {
let key = location + " " + contentType + " " + docDomain + " " + thirdParty + " " + sitekey;
if (key in this.resultCache) {
return this.resultCache[key];
}let result = this.matchesAnyInternal(location, contentType, docDomain, thirdParty, sitekey);
if (this.cacheEntries >= CombinedMatcher.maxCacheEntries) {
this.resultCache = Object.create(null);
this.cacheEntries = 0;
}
this.resultCache[key] = result;
this.cacheEntries++;
return result;
}
};
/**
* Shared CombinedMatcher instance that should usually be used.
* @type CombinedMatcher
*/
let defaultMatcher = exports.defaultMatcher = new CombinedMatcher();
/***/ },
/* 15 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } };
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-2015 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @fileOverview Definition of Filter class and its subclasses.
*/
var _require = __webpack_require__(17);
let FilterNotifier = _require.FilterNotifier;
/**
* Abstract base class for filters
*
* @param {String} text string representation of the filter
* @constructor
*/
function Filter(text) {
this.text = text;
this.subscriptions = [];
}
exports.Filter = Filter;
Filter.prototype = {
/**
* String representation of the filter
* @type String
*/
text: null,
/**
* Filter subscriptions the filter belongs to
* @type Array of Subscription
*/
subscriptions: null,
/**
* Serializes the filter to an array of strings for writing out on the disk.
* @param {Array of String} buffer buffer to push the serialization results into
*/
serialize: function serialize(buffer) {
buffer.push("[Filter]");
buffer.push("text=" + this.text);
},
toString: function toString() {
return this.text;
}
};
/**
* Cache for known filters, maps string representation to filter objects.
* @type Object
*/
Filter.knownFilters = Object.create(null);
/**
* Regular expression that element hiding filters should match
* @type RegExp
*/
Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/;
/**
* Regular expression that RegExp filters specified as RegExps should match
* @type RegExp
*/
Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/;
/**
* Regular expression that options on a RegExp filter should match
* @type RegExp
*/
Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/;
/**
* Creates a filter of correct type from its text representation - does the basic parsing and
* calls the right constructor then.
*
* @param {String} text as in Filter()
* @return {Filter}
*/
Filter.fromText = function (text) {
if (text in Filter.knownFilters) return Filter.knownFilters[text];
let ret;
let match = text.indexOf("#") >= 0 ? Filter.elemhideRegExp.exec(text) : null;
if (match) ret = ElemHideBase.fromText(text, match[1], match[2], match[3], match[4], match[5]);else if (text[0] == "!") ret = new CommentFilter(text);else ret = RegExpFilter.fromText(text);
Filter.knownFilters[ret.text] = ret;
return ret;
};
/**
* Deserializes a filter
*
* @param {Object} obj map of serialized properties and their values
* @return {Filter} filter or null if the filter couldn't be created
*/
Filter.fromObject = function (obj) {
let ret = Filter.fromText(obj.text);
if (ret instanceof ActiveFilter) {
if ("disabled" in obj) ret._disabled = obj.disabled == "true";
if ("hitCount" in obj) ret._hitCount = parseInt(obj.hitCount) || 0;
if ("lastHit" in obj) ret._lastHit = parseInt(obj.lastHit) || 0;
}
return ret;
};
/**
* Removes unnecessary whitespaces from filter text, will only return null if
* the input parameter is null.
*/
Filter.normalize = function ( /**String*/text) /**String*/
{
if (!text) return text;
// Remove line breaks and such
text = text.replace(/[^\S ]/g, "");
if (/^\s*!/.test(text)) {
// Don't remove spaces inside comments
return text.trim();
} else if (Filter.elemhideRegExp.test(text)) {
// Special treatment for element hiding filters, right side is allowed to contain spaces
var _$$exec = /^(.*?)(#\@?#?)(.*)$/.exec(text);
var _$$exec2 = _slicedToArray(_$$exec, 4);
let domain = _$$exec2[1];
let separator = _$$exec2[2];
let selector = _$$exec2[3];
return domain.replace(/\s/g, "") + separator + selector.trim();
} else return text.replace(/\s/g, "");
};
/**
* Class for invalid filters
* @param {String} text see Filter()
* @param {String} reason Reason why this filter is invalid
* @constructor
* @augments Filter
*/
function InvalidFilter(text, reason) {
Filter.call(this, text);
this.reason = reason;
}
exports.InvalidFilter = InvalidFilter;
InvalidFilter.prototype = {
__proto__: Filter.prototype,
/**
* Reason why this filter is invalid
* @type String
*/
reason: null,
/**
* See Filter.serialize()
*/
serialize: function serialize(buffer) {}
};
/**
* Class for comments
* @param {String} text see Filter()
* @constructor
* @augments Filter
*/
function CommentFilter(text) {
Filter.call(this, text);
}
exports.CommentFilter = CommentFilter;
CommentFilter.prototype = {
__proto__: Filter.prototype,
/**
* See Filter.serialize()
*/
serialize: function serialize(buffer) {}
};
/**
* Abstract base class for filters that can get hits
* @param {String} text see Filter()
* @param {String} [domains] Domains that the filter is restricted to separated by domainSeparator e.g. "foo.com|bar.com|~baz.com"
* @constructor
* @augments Filter
*/
function ActiveFilter(text, domains) {
Filter.call(this, text);
this.domainSource = domains;
}
exports.ActiveFilter = ActiveFilter;
ActiveFilter.prototype = Object.defineProperties({
__proto__: Filter.prototype,
_disabled: false,
_hitCount: 0,
_lastHit: 0,
/**
* String that the domains property should be generated from
* @type String
*/
domainSource: null,
/**
* Separator character used in domainSource property, must be overridden by subclasses
* @type String
*/
domainSeparator: null,
/**
* Determines whether the trailing dot in domain names isn't important and
* should be ignored, must be overridden by subclasses.
* @type Boolean
*/
ignoreTrailingDot: true,
/**
* Determines whether domainSource is already upper-case,
* can be overridden by subclasses.
* @type Boolean
*/
domainSourceIsUpperCase: false,
/**
* Array containing public keys of websites that this filter should apply to
* @type Array of String
*/
sitekeys: null,
/**
* Checks whether this filter is active on a domain.
* @param {String} docDomain domain name of the document that loads the URL
* @param {String} [sitekey] public key provided by the document
* @return {Boolean} true in case of the filter being active
*/
isActiveOnDomain: function isActiveOnDomain(docDomain, sitekey) {
// Sitekeys are case-sensitive so we shouldn't convert them to upper-case to avoid false
// positives here. Instead we need to change the way filter options are parsed.
if (this.sitekeys && (!sitekey || this.sitekeys.indexOf(sitekey.toUpperCase()) < 0)) {
return false;
} // If no domains are set the rule matches everywhere
if (!this.domains) {
return true;
} // If the document has no host name, match only if the filter isn't restricted to specific domains
if (!docDomain) {
return this.domains[""];
}if (this.ignoreTrailingDot) docDomain = docDomain.replace(/\.+$/, "");
docDomain = docDomain.toUpperCase();
while (true) {
if (docDomain in this.domains) {
return this.domains[docDomain];
}let nextDot = docDomain.indexOf(".");
if (nextDot < 0) break;
docDomain = docDomain.substr(nextDot + 1);
}
return this.domains[""];
},
/**
* Checks whether this filter is active only on a domain and its subdomains.
*/
isActiveOnlyOnDomain: function isActiveOnlyOnDomain( /**String*/docDomain) /**Boolean*/
{
if (!docDomain || !this.domains || this.domains[""]) {
return false;
}if (this.ignoreTrailingDot) docDomain = docDomain.replace(/\.+$/, "");
docDomain = docDomain.toUpperCase();
for (let domain in this.domains) if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1)) {
return false;
}return true;
},
/**
* See Filter.serialize()
*/
serialize: function serialize(buffer) {
if (this._disabled || this._hitCount || this._lastHit) {
Filter.prototype.serialize.call(this, buffer);
if (this._disabled) buffer.push("disabled=true");
if (this._hitCount) buffer.push("hitCount=" + this._hitCount);
if (this._lastHit) buffer.push("lastHit=" + this._lastHit);
}
}
}, {
disabled: {
get: function () {
return this._disabled;
},
set: function (value) {
if (value != this._disabled) {
let oldValue = this._disabled;
this._disabled = value;
FilterNotifier.triggerListeners("filter.disabled", this, value, oldValue);
}
return this._disabled;
},
enumerable: true,
configurable: true
},
hitCount: {
get: function () {
return this._hitCount;
},
set: function (value) {
if (value != this._hitCount) {
let oldValue = this._hitCount;
this._hitCount = value;
FilterNotifier.triggerListeners("filter.hitCount", this, value, oldValue);
}
return this._hitCount;
},
enumerable: true,
configurable: true
},
lastHit: {
get: function () {
return this._lastHit;
},
set: function (value) {
if (value != this._lastHit) {
let oldValue = this._lastHit;
this._lastHit = value;
FilterNotifier.triggerListeners("filter.lastHit", this, value, oldValue);
}
return this._lastHit;
},
enumerable: true,
configurable: true
},
domains: {
get: function () {
// Despite this property being cached, the getter is called
// several times on Safari, due to WebKit bug 132872
let prop = Object.getOwnPropertyDescriptor(this, "domains");
if (prop) return prop.value;
let domains = null;
if (this.domainSource) {
let source = this.domainSource;
if (!this.domainSourceIsUpperCase) {
// RegExpFilter already have uppercase domains
source = source.toUpperCase();
}
let list = source.split(this.domainSeparator);
if (list.length == 1 && list[0][0] != "~") {
// Fast track for the common one-domain scenario
domains = { __proto__: null, "": false };
if (this.ignoreTrailingDot) list[0] = list[0].replace(/\.+$/, "");
domains[list[0]] = true;
} else {
let hasIncludes = false;
for (let i = 0; i < list.length; i++) {
let domain = list[i];
if (this.ignoreTrailingDot) domain = domain.replace(/\.+$/, "");
if (domain == "") continue;
let include;
if (domain[0] == "~") {
include = false;
domain = domain.substr(1);
} else {
include = true;
hasIncludes = true;
}
if (!domains) domains = Object.create(null);
domains[domain] = include;
}
domains[""] = !hasIncludes;
}
this.domainSource = null;
}
Object.defineProperty(this, "domains", { value: domains, enumerable: true });
return this.domains;
},
enumerable: true,
configurable: true
}
});
/**
* Abstract base class for RegExp-based filters
* @param {String} text see Filter()
* @param {String} regexpSource filter part that the regular expression should be build from
* @param {Number} [contentType] Content types the filter applies to, combination of values from RegExpFilter.typeMap
* @param {Boolean} [matchCase] Defines whether the filter should distinguish between lower and upper case letters
* @param {String} [domains] Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com"
* @param {Boolean} [thirdParty] Defines whether the filter should apply to third-party or first-party content only
* @param {String} [sitekeys] Public keys of websites that this filter should apply to
* @constructor
* @augments ActiveFilter
*/
function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys) {
ActiveFilter.call(this, text, domains, sitekeys);
if (contentType != null) this.contentType = contentType;
if (matchCase) this.matchCase = matchCase;
if (thirdParty != null) this.thirdParty = thirdParty;
if (sitekeys != null) this.sitekeySource = sitekeys;
if (regexpSource.length >= 2 && regexpSource[0] == "/" && regexpSource[regexpSource.length - 1] == "/") {
// The filter is a regular expression - convert it immediately to catch syntax errors
let regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
Object.defineProperty(this, "regexp", { value: regexp });
} else {
// No need to convert this filter to regular expression yet, do it on demand
this.regexpSource = regexpSource;
}
}
exports.RegExpFilter = RegExpFilter;
RegExpFilter.prototype = Object.defineProperties({
__proto__: ActiveFilter.prototype,
/**
* @see ActiveFilter.domainSourceIsUpperCase
*/
domainSourceIsUpperCase: true,
/**
* Number of filters contained, will always be 1 (required to optimize Matcher).
* @type Integer
*/
length: 1,
/**
* @see ActiveFilter.domainSeparator
*/
domainSeparator: "|",
/**
* Expression from which a regular expression should be generated - for delayed creation of the regexp property
* @type String
*/
regexpSource: null,
/**
* Content types the filter applies to, combination of values from RegExpFilter.typeMap
* @type Number
*/
contentType: 2147483647,
/**
* Defines whether the filter should distinguish between lower and upper case letters
* @type Boolean
*/
matchCase: false,
/**
* Defines whether the filter should apply to third-party or first-party content only. Can be null (apply to all content).
* @type Boolean
*/
thirdParty: null,
/**
* String that the sitekey property should be generated from
* @type String
*/
sitekeySource: null,
/**
* Tests whether the URL matches this filter
* @param {String} location URL to be tested
* @param {String} contentType content type identifier of the URL
* @param {String} docDomain domain name of the document that loads the URL
* @param {Boolean} thirdParty should be true if the URL is a third-party request
* @param {String} sitekey public key provided by the document
* @return {Boolean} true in case of a match
*/
matches: function matches(location, contentType, docDomain, thirdParty, sitekey) {
if (this.regexp.test(location) && (RegExpFilter.typeMap[contentType] & this.contentType) != 0 && (this.thirdParty == null || this.thirdParty == thirdParty) && this.isActiveOnDomain(docDomain, sitekey)) {
return true;
}
return false;
}
}, {
regexp: {
get: function () {
// Despite this property being cached, the getter is called
// several times on Safari, due to WebKit bug 132872
let prop = Object.getOwnPropertyDescriptor(this, "regexp");
if (prop) return prop.value;
// Remove multiple wildcards
let source = this.regexpSource.replace(/\*+/g, "*") // remove multiple wildcards
.replace(/\^\|$/, "^") // remove anchors following separator placeholder
.replace(/\W/g, "\\$&") // escape special symbols
.replace(/\\\*/g, ".*") // replace wildcards by .*
// process separator placeholders (all ANSI characters but alphanumeric characters and _%.-)
.replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?") // process extended anchor at expression start
.replace(/^\\\|/, "^") // process anchor at expression start
.replace(/\\\|$/, "$") // process anchor at expression end
.replace(/^(\.\*)/, "") // remove leading wildcards
.replace(/(\.\*)$/, ""); // remove trailing wildcards
let regexp = new RegExp(source, this.matchCase ? "" : "i");
Object.defineProperty(this, "regexp", { value: regexp });
return regexp;
},
enumerable: true,
configurable: true
},
sitekeys: {
get: function () {
// Despite this property being cached, the getter is called
// several times on Safari, due to WebKit bug 132872
let prop = Object.getOwnPropertyDescriptor(this, "sitekeys");
if (prop) return prop.value;
let sitekeys = null;
if (this.sitekeySource) {
sitekeys = this.sitekeySource.split("|");
this.sitekeySource = null;
}
Object.defineProperty(this, "sitekeys", { value: sitekeys, enumerable: true });
return this.sitekeys;
},
enumerable: true,
configurable: true
}
});
// Required to optimize Matcher, see also RegExpFilter.prototype.length
Object.defineProperty(RegExpFilter.prototype, "0", {
get: function get() {
return this;
}
});
/**
* Creates a RegExp filter from its text representation
* @param {String} text same as in Filter()
*/
RegExpFilter.fromText = function (text) {
let blocking = true;
let origText = text;
if (text.indexOf("@@") == 0) {
blocking = false;
text = text.substr(2);
}
let contentType = null;
let matchCase = null;
let domains = null;
let sitekeys = null;
let thirdParty = null;
let collapse = null;
let options;
let match = text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null;
if (match) {
options = match[1].toUpperCase().split(",");
text = match.input.substr(0, match.index);
for (let option of options) {
let value = null;
let separatorIndex = option.indexOf("=");
if (separatorIndex >= 0) {
value = option.substr(separatorIndex + 1);
option = option.substr(0, separatorIndex);
}
option = option.replace(/-/, "_");
if (option in RegExpFilter.typeMap) {
if (contentType == null) contentType = 0;
contentType |= RegExpFilter.typeMap[option];
} else if (option[0] == "~" && option.substr(1) in RegExpFilter.typeMap) {
if (contentType == null) contentType = RegExpFilter.prototype.contentType;
contentType &= ~RegExpFilter.typeMap[option.substr(1)];
} else if (option == "MATCH_CASE") matchCase = true;else if (option == "~MATCH_CASE") matchCase = false;else if (option == "DOMAIN" && typeof value != "undefined") domains = value;else if (option == "THIRD_PARTY") thirdParty = true;else if (option == "~THIRD_PARTY") thirdParty = false;else if (option == "COLLAPSE") collapse = true;else if (option == "~COLLAPSE") collapse = false;else if (option == "SITEKEY" && typeof value != "undefined") sitekeys = value;else return new InvalidFilter(origText, "Unknown option " + option.toLowerCase());
}
}
if (!blocking && (contentType == null || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text)) {
// Exception filters shouldn't apply to pages by default unless they start with a protocol name
if (contentType == null) contentType = RegExpFilter.prototype.contentType;
contentType &= ~RegExpFilter.typeMap.DOCUMENT;
}
try {
if (blocking) return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys, collapse);else return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys);
} catch (e) {
return new InvalidFilter(origText, e);
}
};
/**
* Maps type strings like "SCRIPT" or "OBJECT" to bit masks
*/
RegExpFilter.typeMap = {
OTHER: 1,
SCRIPT: 2,
IMAGE: 4,
STYLESHEET: 8,
OBJECT: 16,
SUBDOCUMENT: 32,
DOCUMENT: 64,
XBL: 1,
PING: 1,
XMLHTTPREQUEST: 2048,
OBJECT_SUBREQUEST: 4096,
DTD: 1,
MEDIA: 16384,
FONT: 32768,
BACKGROUND: 4, // Backwards compat, same as IMAGE
POPUP: 268435456,
ELEMHIDE: 1073741824
};
// ELEMHIDE, POPUP option shouldn't be there by default
RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.POPUP);
/**
* Class for blocking filters
* @param {String} text see Filter()
* @param {String} regexpSource see RegExpFilter()
* @param {Number} contentType see RegExpFilter()
* @param {Boolean} matchCase see RegExpFilter()
* @param {String} domains see RegExpFilter()
* @param {Boolean} thirdParty see RegExpFilter()
* @param {String} sitekeys see RegExpFilter()
* @param {Boolean} collapse defines whether the filter should collapse blocked content, can be null
* @constructor
* @augments RegExpFilter
*/
function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys, collapse) {
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys);
this.collapse = collapse;
}
exports.BlockingFilter = BlockingFilter;
BlockingFilter.prototype = {
__proto__: RegExpFilter.prototype,
/**
* Defines whether the filter should collapse blocked content. Can be null (use the global preference).
* @type Boolean
*/
collapse: null
};
/**
* Class for whitelist filters
* @param {String} text see Filter()
* @param {String} regexpSource see RegExpFilter()
* @param {Number} contentType see RegExpFilter()
* @param {Boolean} matchCase see RegExpFilter()
* @param {String} domains see RegExpFilter()
* @param {Boolean} thirdParty see RegExpFilter()
* @param {String} sitekeys see RegExpFilter()
* @constructor
* @augments RegExpFilter
*/
function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys) {
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys);
}
exports.WhitelistFilter = WhitelistFilter;
WhitelistFilter.prototype = {
__proto__: RegExpFilter.prototype
};
/**
* Base class for element hiding filters
* @param {String} text see Filter()
* @param {String} [domains] Host names or domains the filter should be restricted to
* @param {String} selector CSS selector for the HTML elements that should be hidden
* @constructor
* @augments ActiveFilter
*/
function ElemHideBase(text, domains, selector) {
ActiveFilter.call(this, text, domains || null);
if (domains) this.selectorDomain = domains.replace(/,~[^,]+/g, "").replace(/^~[^,]+,?/, "").toLowerCase();
this.selector = selector;
}
exports.ElemHideBase = ElemHideBase;
ElemHideBase.prototype = {
__proto__: ActiveFilter.prototype,
/**
* @see ActiveFilter.domainSeparator
*/
domainSeparator: ",",
/**
* @see ActiveFilter.ignoreTrailingDot
*/
ignoreTrailingDot: false,
/**
* Host name or domain the filter should be restricted to (can be null for no restriction)
* @type String
*/
selectorDomain: null,
/**
* CSS selector for the HTML elements that should be hidden
* @type String
*/
selector: null
};
/**
* Creates an element hiding filter from a pre-parsed text representation
*
* @param {String} text same as in Filter()
* @param {String} domain domain part of the text representation (can be empty)
* @param {String} tagName tag name part (can be empty)
* @param {String} attrRules attribute matching rules (can be empty)
* @param {String} selector raw CSS selector (can be empty)
* @return {ElemHideFilter|ElemHideException|InvalidFilter}
*/
ElemHideBase.fromText = function (text, domain, isException, tagName, attrRules, selector) {
if (!selector) {
if (tagName == "*") tagName = "";
let id = null;
let additional = "";
if (attrRules) {
attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g);
for (let rule of attrRules) {
rule = rule.substr(1, rule.length - 2);
let separatorPos = rule.indexOf("=");
if (separatorPos > 0) {
rule = rule.replace(/=/, "=\"") + "\"";
additional += "[" + rule + "]";
} else {
if (id) {
var _require2 = __webpack_require__(18);
let Utils = _require2.Utils;
return new InvalidFilter(text, Utils.getString("filter_elemhide_duplicate_id"));
} else id = rule;
}
}
}
if (id) selector = tagName + "." + id + additional + "," + tagName + "#" + id + additional;else if (tagName || additional) selector = tagName + additional;else {
var _require3 = __webpack_require__(18);
let Utils = _require3.Utils;
return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria"));
}
}
if (isException) return new ElemHideException(text, domain, selector);else return new ElemHideFilter(text, domain, selector);
};
/**
* Class for element hiding filters
* @param {String} text see Filter()
* @param {String} domains see ElemHideBase()
* @param {String} selector see ElemHideBase()
* @constructor
* @augments ElemHideBase
*/
function ElemHideFilter(text, domains, selector) {
ElemHideBase.call(this, text, domains, selector);
}
exports.ElemHideFilter = ElemHideFilter;
ElemHideFilter.prototype = {
__proto__: ElemHideBase.prototype
};
/**
* Class for element hiding exceptions
* @param {String} text see Filter()
* @param {String} domains see ElemHideBase()
* @param {String} selector see ElemHideBase()
* @constructor
* @augments ElemHideBase
*/
function ElemHideException(text, domains, selector) {
ElemHideBase.call(this, text, domains, selector);
}
exports.ElemHideException = ElemHideException;
ElemHideException.prototype = {
__proto__: ElemHideBase.prototype
};
/**
* Defines whether the filter is disabled
* @type Boolean
*/
/**
* Number of hits on the filter since the last reset
* @type Number
*/
/**
* Last time the filter had a hit (in milliseconds since the beginning of the epoch)
* @type Number
*/
/**
* Map containing domains that this filter should match on/not match on or null if the filter should match on all domains
* @type Object
*/
/**
* Regular expression to be used when testing against this filter
* @type RegExp
*/
/**
* Array containing public keys of websites that this filter should apply to
* @type Array of String
*/
/***/ },
/* 16 */
/***/ function(module, exports, __webpack_require__) {
module.exports = function() {
var list = [];
list.toString = function toString() {
var result = [];
for(var i = 0; i < this.length; i++) {
var item = this[i];
if(item[2]) {
result.push("@media " + item[2] + "{" + item[1] + "}");
} else {
result.push(item[1]);
}
}
return result.join("");
};
return list;
}
/***/ },
/* 17 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-2015 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @fileOverview This component manages listeners and calls them to distributes
* messages about filter changes.
*/
/**
* List of registered listeners
* @type Array of function(action, item, newValue, oldValue)
*/
let listeners = [];
/**
* This class allows registering and triggering listeners for filter events.
* @class
*/
let FilterNotifier = exports.FilterNotifier = {
/**
* Adds a listener
*/
addListener: function addListener( /**function(action, item, newValue, oldValue)*/listener) {
if (listeners.indexOf(listener) >= 0) {
return;
}listeners.push(listener);
},
/**
* Removes a listener that was previosly added via addListener
*/
removeListener: function removeListener( /**function(action, item, newValue, oldValue)*/listener) {
let index = listeners.indexOf(listener);
if (index >= 0) listeners.splice(index, 1);
},
/**
* Notifies listeners about an event
* @param {String} action event code ("load", "save", "elemhideupdate",
* "subscription.added", "subscription.removed",
* "subscription.disabled", "subscription.title",
* "subscription.lastDownload", "subscription.downloadStatus",
* "subscription.homepage", "subscription.updated",
* "filter.added", "filter.removed", "filter.moved",
* "filter.disabled", "filter.hitCount", "filter.lastHit")
* @param {Subscription|Filter} item item that the change applies to
*/
triggerListeners: function triggerListeners(action, item, param1, param2, param3) {
let list = listeners.slice();
for (let listener of list) listener(action, item, param1, param2, param3);
}
};
/***/ },
/* 18 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
/***/ },
/* 19 */
/***/ function(module, exports, __webpack_require__) {
module.exports = "data:image/png;base64,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"
/***/ },
/* 20 */
/***/ function(module, exports, __webpack_require__) {
module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAACX5JREFUeNqkl3uMHWUZxn/fNzPnfna3Z/ec3W633bbbFlraUmhpqWgNhXK3MV5ivKCCGo1/oBaDJkIQNcYIXhKMBoLGGIgioFgvSIooUiq2Umm3rUtpu7Ts9rL3PdeZ+W7+cUqhyRYJvsmTmcxM5nve93veZ94RA4OnAMilAx743V4GRDe6ezEL2w0R0JWBSSXoyoCE5K5xkf1kH8VnR2jsnaByw1yqw3WUtY4FOceuEUfSg4sKgn3jBm0dgnOHz5vdlGAcODj/pWlWGSfWjIUUfzTAgtBQ9gRjeyfdcFmJv/dmOGgcg96brfZWCXgSEr7IDtfte546zoecYK12dKc9R84HpcGXAo3gxTGHde5LwxVO9QduW2vAg8Uk2z2BfVsEEoHHqZiNfxwUd/XX/XfmEpaSbyikIOdD0oOEBCkEAmjxwDiXDjXzx+riM8ete/9Yigf78uI7nuCEAnBwrn3wfU8CkE0FZNKB+M+e4duPTI3dVo4bOa8jh5g/BzOnE6MVjuZ+SgE+Aima77U4fA9SUhBpCiM1d8svD9kVazrEzUnJK+5NKiD+svMI2liGR6fF1r+9+sOndlZuyQSSWFWpV8eQXkjuolWq/V0bvUIuJWclmiJLeuALcSYz50A7R2wgtjAWOnzBE59aGnysJ89EQ5+jAq+cKGOs5b7HD3zh4NHkLS1tBcqNMrgw6u32HtmwcsGf1i9LDRzvrs7681Ti25XQrvOTAuNASpBSnubgkA4CHM5BewDHa/baPw3a2288P9hSygj0DKqQPZ15aqFZdexk+o5svp2qMqREbfTSpZkbL15WurHY2frL2T2lf3dm5NPXzpOfbUR6KFSWKDYobTHG4LQGY5HGIa3DcxZpDJ0p2H1S3Twwrt6R8x2BsGeQ9ByZAORkNW554vnx25RtK2ghcbpa3byh4/OljswjSju0tsSxphI7CimxpzfL/dMNjbGOONboWGO0xSiD1RqMQVgD1uJZS1fGa91+TN1SjsBYgTYCh2RkSrP3cA35zAvHr9h9sHF1KpumVq9w8WL/G3NK2Udj1TQArKa12E5PZ4FWadk4L3hIGDMiBMSxRSuD1hpjzBlgLMIatNIU04LjFXv1/lNx3+Ck4uiUZnAs5sCRCtF0iNRR2G3r5dawNsF5s+O7g0Dcs71/ktBlyGYCGuQ4PJ0nqiqWlwIuaPePtAWuHwTOWZTWaG3R2mC0wWiN0QaMwcQa6SzppGzbfjRaPVa3DFcNt/9miJ0vV8gkPfyNa3seaskFJ4NEUh0brf/h6ImyA4cvLb0Xrsb4WQ4ci3msf5SPb+rivStbWJgXLx6JxBXCOeLYIHyHlM2GcIBzDmsB44hCRT6VJO3pleu6g19vP1xjdDIkWWrBAX4mFUzN7W55LOF7DI03cM4S+JK+JYsg0YqHIRmAcY6fPTdCKQOdKbFrMHJIAVFs8FzznNNd6RxY58A4wkZEqS3LiXGz6v4dEzw1MI1zTQ8B8K1rCk0KiXNgrGPF0iUUSx1opRECPCmFL+iZiq24+y8j9d5CMuhaUHRhjNDK4AFSOMQbPME5cMahjSXpw8mGbj18rNJljfOcs4EnmZCC8llWbIxldmcHnaUOnDV4EnxPsO9o/dYgn96yoJgW2YSs+RKRyyRdWAmF0Ror3IzGb63DOIfnCfKt6RUG8UzGkmlpUan9w2H/6jn6Y/4bH27JpvjIhgvIZ5MYY/E8wdR03PbwruqtGzYv7ArrCl8KpBRgLbVyCMagz2H2FofVlunxGp0dudZCIdOqlSObC9i9Z+zyQ8O1D5whEMaa9Svn0NedJ9YGnIfvCTzrhIxVww8ku/YcpzxRpdiSIOEJfNGskHZmRpt1gLOOo4dHOHxohFpsmaxprrpqMdIYkgkXyzMfBQGZZNC0V9G0WQRkMv5kyYvv2ff8EJet7cEZS70WoyKFMQalDUoZjNLY+HVoZdDKNE1KG+JIUZ6us2xJOx6C6ZdO7Jyqm4clp1nm0knm97QxWQupxopqrCiHMcpp7vjE/PvV4KmttbJi9cU91GoRWhlUrFGRxiiDVg6l7RlobZrX46YhNeqKQluG5Stm88LTh6c3X9jyxcVzs5NnKtDMWKCMRb8ByliSSU+vW5T9yq4nBybmL+ygo5RjuhKh4tNZxhalNFqb1xFZdGyIY02joQgjzZpL57P/3ycpqdoPLltZ+Mei7lRzIBECQiUYGY8JlZlxdrhyTftArRzd+a9njty77rJFbH3kBWr1mFTCw8rmcCJea24HzgmssyjrmC5HLL1oHlJ6lPcd2/mtjy64J5eVWOuQr4klVCClIPAE/gxIBpLVF8y6v9E/9GR9OmLVJb1MToVEkSF+Lfv4tZIblFbEsaZcDsm0pFl58Vz+/tv+2oVdiS+fKuvavsEqB47WkM45Ak/Sns+AtUjnZoRRlmJbIr55U9eW3Y/vGVmytIvS7DYmJutNEnFTjE1o4lhTCxX1UHPZFedx4J+vsjanv7f5XbOf7WoN6C2m6C2mkNY5MqmArkKeQEDSFzMiENCe9Vm1vHDggqz9+gtPvMS7r16KBSq1mPi02LTWKGUIQ8PkZIOVq3tJSI+DT+5/9sbr5313zXlt9HVlWNzdhPj1tn7mFGdRbM03/ft/hJQQRUZ+7q5/bZ3zgbXXj09U+Osf99LWmibZVFSTVDWipT3P+z7xDv720+dr18z3r7ruink7WtL+Wev4UZjm6HDMq8cncG9pkndkUr595/JZX/3VL3auu+FrV3Yc3DvE8LFxWvJJpBDExhBFhsuvX8GB7UeY26h8/4PXrNrRiAxT5ejshHACrR2xMihl3wIctYai2Jndt66db764dT+b3nchvnFUKiGVUFEdrbFm/ULSSZ+hP+/buXRx23dDZXGAJ8VZkFYZnDY4bU8f/zdUaCi2BVy7ad5PRrYd+H19ImTDNcswo3XUSJVSMc8lG5ew/b4dU5++tmfLhzcvrCakJOV7pIKz4S+al+fthBAgpFBbblp6270P7Lj08ruuKx7cMcix3UNs+vImBp46RHpo9Ht9S/qek2/yu+ZXyzFvN4SA5SuLAyuefuXO53++68dX3rSek+tPIYTkxOO7/3HXbau/n0t5jI/WT7vNDAT27h/n/4kgkCzoKzzw8raB615d2HZD17IOdt29bfLKiwpfSWaCutYWa88tbz8I5P9FwPMEMvDUTR89/6aHH33uW4ceF/PmSnnveSsWPhuGhnTaPzMpzRT/HQBNqoL3BrQbdAAAAABJRU5ErkJggg=="
/***/ },
/* 21 */
/***/ function(module, exports, __webpack_require__) {
module.exports = "data:image/png;base64,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"
/***/ }
/******/ ]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment