Source for "Google Reader Filter"

By Elad Ossadon
Has no other scripts.


// Author: Elad Ossadon (www.devign.co.il / elado7@gmail.com)

// ==UserScript==
// @name           Google Reader Filter
// @namespace      devign.co.il
// @description    Filters duplicated entries and unwanted content or highlight chosen content based on keywords (with regex support).
// @include        http://reader.google.com/reader/*
// @include        http://www.google.co.jp/reader/*
// @include        https://www.google.co.jp/reader/*
// @include        http://www.google.com/reader/*
// @include        https://www.google.com/reader/*
// ==/UserScript==
/*

v0.53 - Apr 20, 08
* Fixed minification regexp (added a few characters)

v0.52 - Apr 2, 08
* Fixed some design issues - excluded/highlighted entries doesn't affect the content of the entry (thanks Paul Irish!)


v0.51 - Mar 31, 08
* Fixed minification regexp (old one omitted numbers)


v0.5 - Mar 30, 08
* Fixed some bugs
* Enhanced filter
* UTF-8 characters support


v0.4 - Mar 26, 08
* Added the ability to hide duplicates/excluded entries, new checkboxes added in settings window.


v0.3 - Mar 19, 08
* Duplicates also marked as excluded. Duplicate is an entry whose url or title were already printed.


v0.2 - Mar 16, 08
* Added "Quick Add" window, which lets you add new exclude/highlight to the list from within an entry ([#] button is added to each entry on its right)
* RegExps are compiled only at initialization and after saving, not like before


v0.1 - Mar 8, 08
* First Release

*/

if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}if(typeof value.toJSON==='function'){return stringify(value.toJSON());}a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}return'['+a.join(',')+']';}if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}return'{'+a.join(',')+'}';}}return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}else{delete v[i];}}}}return filter(k,v);}if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}throw new SyntaxError('parseJSON');}};}();}

function addStyleSheet(css) {
	var link=document.createElement("link");
	link.rel="stylesheet";
	link.href="data:text/css,"+escape(css);
	document.getElementsByTagName("head")[0].appendChild(link);
}
function findPosition(element) {
	var point={x:0,y:0};
	var parent=element;
	while (parent) {
		point.x+=parent.offsetLeft;
		point.y+=parent.offsetTop;
		parent=parent.offsetParent;
	}
	return point;
}
function trim(s) {
	return s ? s.replace(/^\s+|\s+$/g,"") : "";
}

var GRF={
	excludes:[],
	highlights:[],

	init:function () {
		GRF.excludes=JSON.parse(decodeURI(trim(GM_getValue("excludes",JSON.stringify(GRF.excludes)))));
		GRF.highlights=JSON.parse(decodeURI(trim(GM_getValue("highlights",JSON.stringify(GRF.highlights)))));

		GRF.hideExcluds=+GM_getValue("hideExcluds",0);
		GRF.hideDuplicates=+GM_getValue("hideDuplicates",0);

		GRF.excludes.sort(GRF._stringSort);
		GRF.highlights.sort(GRF._stringSort);

		GRF._initInterface();

		GRF._setRegExps();

		if (GRF.excludes.length || GRF.highlights.length) {
			document.body.addEventListener("DOMNodeInserted",GRF.filterEntries,false);
		}
	},

	_setRegExps:function () {
		if (GRF.excludes.length) GRF.rxExcludes=GRF._getRegExp(GRF.excludes);
		if (GRF.highlights.length) GRF.rxHighlights=GRF._getRegExp(GRF.highlights);
	},
	// (0) all until 0 (2f) , [0-9], above 9 (3a) to @ (40), [A-Z], above Z (5b) to a (60), [a-z], above z (7b) to european characters (bf), another unicode character
	// this regex contains all symbols between 0-255 char codes, such as ~!@#$%^&*()_+- etc in order to replace them with a single space
	// that way, both "hello: world!" and "hello.world" become "hello world" and than regex in exclude/highlight stays simple
	minifiyRx:/[\u0000-\u002f\u003a-\u0040\u005b-\u0060\u007b-\u00bf\u201d\u202b]+/g,
	_getRegExp:function (items) {
		return new RegExp("(^| )("+items.join("|")+")($| )","i");
	},

	_stringSort:function (a,b) { if (a>b)return 1;if (a<b)return -1;return 0; },

	_initInterface:function () {
		addStyleSheet("\
.filter-settings-button,.filter-settings-window *,.filter-settings-entry-window *{font-family:verdana;font-size:11px;color:#000;}\
.filter-settings-window textarea,.filter-settings-entry-window input[type=text]{font-family:'courier new';border:1px solid #669CB9;}\
\
.filter-settings-button{cursor:pointer;background-color:#ADCBDA;padding:4px;position:absolute;top:30px;right:10px;z-index:1000;}\
.filter-settings-button:hover{background-color:#B46B8F;}\
.filter-settings-window{background-color:#ADCBDA;border:1px solid #669CB9;padding:4px;position:absolute;top:30px;right:10px;z-index:1000;}\
.filter-settings-window label.ta{float:left;width:250px;}\
.filter-settings-window textarea{height:300px;width:250px;}\
\
.filter-settings-entry-window{width:300px;background-color:#ADCBDA;border:1px solid #669CB9;font-size:11px;padding:4px;position:fixed;top:30px;right:10px;z-index:1000;}\
.filter-settings-entry-window input[type=text]{width:300px;}\
\
.filter-settings-window button,.filter-settings-entry-window button{margin-top:5px;margin-right:5px;background-color:#669CB9;color:#fff;font-size:11px;border:1px solid #000;}\
\
.entry-filtered div.collapsed *{color:#BCBCBC!important;}\
.entry-filtered:hover div.collapsed *{color:#6A6A6A!important;}\
\
.entry-highlighted div.collapsed{background-color:#E6EFCF!important;}\
.entry-highlighted div.collapsed *{color:#000!important;background-color:#E6EFCF;}\
\
.entry-duplicate *{color:#C7D0D8!important;}\
\
.filter-configure-entry{background-color:#daa;color:#fff;padding:2px;cursor:pointer;position:absolute;top:0;right:90px}\
		");

		var div=document.createElement("div");
		div.innerHTML="Filter settings";
		div.className="filter-settings-button";
		div.addEventListener("click",GRF.openSettings,false);
		document.body.appendChild(div);
	},
	_formatListToTextArea:function (list) {
		return list.join("\n").replace(/(\r?\n){2,}/,"");
	},
	openSettings:function () {
		if (!GRF._settings) {
			var settings=document.createElement("div");
			settings.className="filter-settings-window";

			settings.innerHTML="<label class='ta'>Excludes</label><label class='ta'>Highlights</label><div style='clear:both'></div>";

			var excludeTextarea=document.createElement("textarea");
			settings.appendChild(excludeTextarea);

			var highlightTextarea=document.createElement("textarea");
			settings.appendChild(highlightTextarea);

			settings.appendChild(document.createElement("br"));

			// hide dups
			var hideDuplicates=document.createElement("input");
			hideDuplicates.type="checkbox";
			hideDuplicates.id="grf-hideDuplicates";
			settings.appendChild(hideDuplicates);

			var hideDuplicatesLabel=document.createElement("label");
			hideDuplicatesLabel.innerHTML="Hide Duplicates"
			hideDuplicatesLabel.setAttribute("for","grf-hideDuplicates");
			settings.appendChild(hideDuplicatesLabel);
			hideDuplicatesLabel.style.paddingRight="20px;";

			// hide excluded
			var hideExcluds=document.createElement("input");
			hideExcluds.id="grf-hideExcluds";
			hideExcluds.type="checkbox";
			settings.appendChild(hideExcluds);

			var hideExcludsLabel=document.createElement("label");
			hideExcludsLabel.setAttribute("for","grf-hideExcluds");
			hideExcludsLabel.innerHTML="Hide Excluds"
			settings.appendChild(hideExcludsLabel);


			settings.appendChild(document.createElement("br"));

			/// buttons
			var update=document.createElement("button");
			update.textContent="Update";
			update.addEventListener("click",function () {
				GRF.saveSettings(excludeTextarea.value,highlightTextarea.value);
				settings.style.display="none";
			},false);
			settings.appendChild(update);

			var close=document.createElement("button");
			close.textContent="Close";
			close.addEventListener("click",function () {
				settings.style.display="none";
			},false);
			settings.appendChild(close);

			document.body.appendChild(settings);

			// globalize elements
			GRF._settings=settings;
			GRF._excludeTextarea=excludeTextarea;
			GRF._highlightTextarea=highlightTextarea;
			GRF._hideDuplicates=hideDuplicates;
			GRF._hideExcluds=hideExcluds;
		}
		GRF._settings.style.display="";
		GRF._excludeTextarea.value=GRF._formatListToTextArea(GRF.excludes);
		GRF._highlightTextarea.value=GRF._formatListToTextArea(GRF.highlights);

		GRF._hideDuplicates.checked=!!GRF.hideDuplicates;
		GRF._hideExcluds.checked=!!GRF.hideExcluds;
	},
	openSettingsForEntry:function (relative,title) {
		if (!GRF._settingsForEntry) {
			var settingsForEntry=document.createElement("div");
			settingsForEntry.className="filter-settings-entry-window";
			settingsForEntry.innerHTML="<label>Quick Add</label><div style='clear:both'></div>";

			var input=document.createElement("input");
			input.type="text";
			settingsForEntry.appendChild(input);

			settingsForEntry.appendChild(document.createElement("br"));

			var excludeRadio=document.createElement("input");
			excludeRadio.name="ex"; excludeRadio.type="radio"; excludeRadio.value=1;
			excludeRadio.checked=true;
			settingsForEntry.appendChild(excludeRadio);
			settingsForEntry.appendChild(document.createTextNode("Exclude"));

			var highlightRadio=document.createElement("input");
			highlightRadio.name="ex"; highlightRadio.type="radio"; highlightRadio.value=2;
			settingsForEntry.appendChild(highlightRadio);
			settingsForEntry.appendChild(document.createTextNode("Highlight"));

			settingsForEntry.appendChild(document.createElement("br"));

			var update=document.createElement("button");
			update.textContent="Add";
			update.addEventListener("click",function () {
				var coll;
				coll=highlightRadio.checked ? GRF.highlights : GRF.excludes;
				coll.push(input.value);
				GRF.saveCollections();
				settingsForEntry.style.display="none";
			},false);
			settingsForEntry.appendChild(update);

			var close=document.createElement("button");
			close.textContent="Close";
			close.addEventListener("click",function () {
				settingsForEntry.style.display="none";
			},false);
			settingsForEntry.appendChild(close);

			document.body.appendChild(settingsForEntry);

			GRF._settingsForEntry=settingsForEntry;
			GRF._settingsForEntryInput=input;
		}
		GRF._settingsForEntry.style.display="";
		var point=findPosition(relative);
		GRF._settingsForEntry.style.left=(point.x-250)+"px";
		var y=point.y+21-document.getElementById("entries").scrollTop;
		if (y+GRF._settingsForEntry.offsetHeight>document.documentElement.offsetHeight) {
			GRF._settingsForEntry.style.top="auto";
			GRF._settingsForEntry.style.bottom=(document.documentElement.offsetHeight-y+10)+"px";
		}
		else {
			GRF._settingsForEntry.style.top=y+"px";
			GRF._settingsForEntry.style.bottom="auto";
		}
		GRF._settingsForEntryInput.value=title;
	},

	saveSettings:function (excludesString,highlightsString) {
		GRF.excludes=excludesString.replace(/\r/,"").split(/\n+/);
		GRF.highlights=highlightsString.replace(/\r/,"").split(/\n+/);
		GRF.hideExcluds=+GRF._hideExcluds.checked;
		GRF.hideDuplicates=+GRF._hideDuplicates.checked;
		GM_setValue("hideExcluds",GRF.hideExcluds ? 1 : "");
		GM_setValue("hideDuplicates",GRF.hideDuplicates ? 1 : "");
		GRF.saveCollections();
	},

	saveCollections:function () {
		GM_setValue("excludes",encodeURI(JSON.stringify(GRF.excludes)));
		GM_setValue("highlights",encodeURI(JSON.stringify(GRF.highlights)));
		GRF._setRegExps();
	},

	filterEntries:function (e) {
		var element=e.target;

		if (element.className!="entry") return;

		// reset the dups list when entries inserted again
		if (element.parentNode.id=="entries") {
			var l=document.getElementById("entries").childNodes.length;
			if (l<=2) GRF.list={};
		}

		var title=element.getElementsByTagName("h2")[0];
		var url=element.getElementsByTagName("a")[0].href;

		var minifiedTitle=GRF.minifyTitle(title.textContent);

		var configure=document.createElement("span");
		configure.className="filter-configure-entry";
		configure.textContent="#";
		element.appendChild(configure,element);
		configure.title=minifiedTitle;
		configure.addEventListener("click",GRF.configureCurrent,false);

		var escapedTitle=encodeURI(minifiedTitle);

		var b=false;
		if (GRF.list[escapedTitle]/* || GRF.list[url]*/) {
			element.className+=" entry-duplicate";
			if (GRF.hideDuplicates) element.parentNode.removeChild(element);//element.style.display="none";
			return;
		}
		if (GRF.excludes.length) {
			b=GRF.checkEntry(minifiedTitle,element,GRF.rxExcludes,"entry-filtered");
			if (GRF.hideExcluds && b) element.parentNode.removeChild(element);//element.style.display="none";
		}
		if (!b && GRF.highlights.length) {
			GRF.checkEntry(minifiedTitle,element,GRF.rxHighlights,"entry-highlighted");
		}
		GRF.list[escapedTitle]=/*GRF.list[url]=*/true;
	},
	minifyTitle:function (title) {
		return trim(title.replace(GRF.minifiyRx," ").replace(/ +/g," ").toLowerCase());
	},
	list:{},
	configureCurrent:function (e) {
		GRF.openSettingsForEntry(e.target,e.target.title);
		e.preventDefault();
		e.stopPropagation();
	},
	checkEntry:function (title,element,rx,className) {
		if (rx.test(title)) {
			element.className+=" "+className;
			return true;
		}
		return false;
	}
};

addEventListener("load",function () {
	GRF.init();
},false);