Large

Google Reader New Style Minimalistic by DemianGod v4.1

By DemianGod Last update Dec 27, 2011 — Installed 36,104 times.

There are 28 previous versions of this script.

// ==UserScript==
// @author              DemianGod - demiangod@gmail.com
// @name                Google Reader New Style Minimalistic by DemianGod v4.1
// @namespace           http://userscripts.org/scripts/show/30242
// @description         Removes all the junk from New Google+ Reader and just gives you the search and collapsable subscriptions + favicons. 
// @include             http*://*.google.com*/reader/*
// ==/UserScript==
// Google Reader New Style Minimalistic By DemianGod v4.1
// DemianGod http://userscripts.org/scripts/show/30242

const EXPORT_URL = '/reader/subscriptions/export';

const ICON_XPATH = "/html/body/div[6]/div/div[2]/div/div[5]/div[4]//div[starts-with(@class,'icon sub-icon')]";
const ICON_XPATH_PREFIX = '/opml/body//outline[@text="';
const ICON_OPML_XPATH_PREFIX = '/opml/body//outline';
const YQL_BASE_URL = 'http://query.yahooapis.com/v1/public/yql?q=';
const YQL_GET_HTML = 'select%20*%20from%20html%20where';	// URI-encoded
const YQL_HTML_QUERY = YQL_BASE_URL+YQL_GET_HTML;
const FAVICON_XPATH = "/html/head/link[@rel='icon'] | /html/head/link[@rel='ICON'] | /html/head/link[@rel='shortcut icon'] | /html/head/link[@rel='SHORTCUT ICON']";
const URL_PATTERN = "^http://"; // "http://" at the start of the string
const CHANGE_MSG_XPATH = "/html/body/div[2]/div/div[@id='message-area']";
const NAV_BAR_XPATH = "/html/body/div[6]/div";
const FEED_ADD_MSG_XPATH = "//div[@id='quick-add-success']";

// The OPML file is the file you get when you try to export your Google Reader subscriptions.
// It contains (amongst other things) the mapping between each site's URL and feed URL.
function fetchOPML(url, callback)
{
	var xhr = new XMLHttpRequest();
	xhr.open('get', url);
	xhr.onload = function(){callback(xhr.responseXML)};
	xhr.send(null);
}

function drawFavicon(iconNode, imgNode)
{
	var parent = iconNode.parentNode;
	parent.replaceChild(imgNode, iconNode);
}

function replaceAllFeedIcons(opml)
{
	function replaceIcons(iconNodes)
	{
		for (var i=0; i<iconNodes.snapshotLength; i++)
		{
			/* I need to match the icon node with its XML element in the OPML file.
			 * I'm going to match based on the title.
			 */
			var currIcon = iconNodes.snapshotItem(i);
			var feedTitle = currIcon.nextSibling.firstChild.textContent;

			var srcURLNode, srcURL;
			if (feedTitle.indexOf("\"") == -1)	//	common case - title doesn't contain double quotes
			{
				srcURLNode = opml.evaluate(ICON_XPATH_PREFIX+feedTitle+'"]/@htmlUrl', opml, null,
					XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
				
				if (srcURLNode == null)	//	shouldn't ever (or at least rarely) happen
				{
					GM_log("Unable to get the srcURLNode for feed "+feedTitle);
					continue;
				}

				srcURL = srcURLNode.textContent;
			}
			else	//	going to be expensive
			{
				var iconOPMLNodes = opml.evaluate(ICON_OPML_XPATH_PREFIX, opml, null,
					XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
				
				/* Unfortunately, XPath 1.0 doesn't allow for escaping single quotes (') and double quotes (").
				 * Therefore, I'm treating the "text" attribute as a string, and doing the comparison this way.
				 * XPath 2.0, whenever that gets widely supported, should render this method obsolete.
				 */
				for (var j=0; j<iconOPMLNodes.snapshotLength; j++)
				{
					currNode = iconOPMLNodes.snapshotItem(j);
					if (currNode.getAttribute("text") == feedTitle)
					{
						srcURL = currNode.getAttribute("htmlUrl");
						break;
					}
				}

				if (j == iconOPMLNodes.snapshotLength)
				{
					GM_log("Unable to get the srcURLNode for feed "+feedTitle);
					continue;
				}
			}
			
			srcURL = "http://"+srcURL.split("/", 3)[2];	// get the root URL
			
			/* create the img node used to display our icon */
			var imgNode = document.createElement("img");
			imgNode.style.borderWidth = "0px";
			imgNode.style.height = "16px";
			imgNode.style.width = "16px";
			imgNode.style.opacity = "1.0";
			imgNode.style.marginTop = "2px";

			/* By setting the class attribute to be the same, we can ensure that the same CSS styles get applied.
			 * You can remove the line to see what it looks like without those styles */
			imgNode.className = currIcon.className;
			// remove the default feed icon
			imgNode.style.backgroundImage = "none";
			imgNode.style.visibility = "hidden";
			imgNode.setAttribute("alreadysearchedpage", "false");
		
			var defaultFaviconURL;	// contains our first guess at the correct URL
			var faviconURL = GM_getValue(srcURL);
		
			if (faviconURL == undefined || faviconURL == "")	//	no user-specified value
				defaultFaviconURL = srcURL + "/favicon.ico";
			else
				defaultFaviconURL = faviconURL;
			
			imgNode.src = defaultFaviconURL;
			// if the image loads correctly, then the favicon is in the default location
			imgNode.addEventListener("load",
				(function(srcURL, defaultFaviconURL)
				{
					return function() 
						{
							this.style.visibility = "visible";
							GM_setValue(srcURL, defaultFaviconURL);
						};
				})(srcURL, defaultFaviconURL),
				true);
			// otherwise, its in a non-default location and we have to hunt for it
			imgNode.addEventListener("error",
				(function(imgNode, srcURL, currIcon)
				{
					return function()
						{
							if (this.getAttribute("alreadysearchedpage") == "true")
								return;
							
							/* make use of the YQL service from Yahoo! which will:
							 * 1) convert HTML into well-formed XML
							 * 2) lets us retrieve selective parts of a webpage, based on an XPath query -
							 *    the benefit is that we have to do less client-side processing
							 *    
							 * Here we are only requesting the part of the webpage which specifies the favicon location   
							 */
							var queryURL = YQL_HTML_QUERY+encodeURIComponent(' url="'+srcURL+'"'+' and xpath="'+FAVICON_XPATH+'"');
							GM_xmlhttpRequest({
								method: "GET",
								url: queryURL,
								onload:
									function(response)
									{
										if (response.status == '200')	//	response is OK
										{
											// remove XML opening declaration before passing to constructor (apparently required)
											var urlResultDoc = new XML(response.responseText.replace(/^<\?xml [^>]*>\s*/,''));
											var urlResult = urlResultDoc.results;
											var url, faviconURL;
											
											if (urlResult.link.length() == 0)	//	no favicons :(
											{
												/* therefore, go back to using the generic icon provided by Google Reader,
												 * so that the icon isn't just blank/broken
												 */
												drawFavicon(imgNode, currIcon);
												/* create the entry, so the user can manually fill it in (if desired) via about:config
												 * (e.g., with the URL of some other site's favicon)
												 */
												GM_setValue(srcURL, "");
												return;
											}
											else if (urlResult.link.length() == 1)
												url = urlResult.link.@href;
											else if (urlResult.link.length() == 2)
												/* this should rarely happen, but some websites will specify 2 link tags:
												 * one with rel="shortcut icon" and one with rel="icon".  I've arbitrarily
												 * chosen to select the former.
												 */
												url = urlResult.link.(@rel.toLowerCase() == "shortcut icon").@href;
											
											if (url.search(new RegExp(URL_PATTERN)) != -1) // absolute path
												faviconURL = url;
											else // relative path
											{
												/* originally I thought the following was sufficient to distinguish between relative and
												 * absolute, but then at sites like www.hdnumerique.com, I saw they specified their
												 * favicon url as "imgs/favicon.ico" :)
												 */
												if (url.charAt(0) == '/') 
													faviconURL = srcURL+url;
												else
													faviconURL = srcURL+'/'+url;
											}

											imgNode.setAttribute("alreadysearchedpage", "true");
											imgNode.src = faviconURL;
											drawFavicon(currIcon, imgNode);
											imgNode.style.visibility = "visible";
											GM_setValue(srcURL, faviconURL.toString());
										}
										else
											GM_log("YQL query for "+srcURL+" returned with error " + response.status);
											
									}
							});
						}
				})(imgNode, srcURL, currIcon),
				true);
			drawFavicon(currIcon, imgNode);
		}
	}

	var regIconNodes = document.evaluate(ICON_XPATH, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	replaceIcons(regIconNodes);
}

//	here we draw the favicons when our script first loads
fetchOPML(EXPORT_URL, replaceAllFeedIcons);

/*	But there are 3 times when we will have to redraw the favicons:
 *  
 *  1) When the user edits feeds (e.g., re-order, rename)
 *  2) When the user navigates to the Settings and back
 *  3) When the user adds a feed using the "Add a subscription" button
 *  
 *  This is because these operations don't reload the page (I think Google is using AJAX for them).
 *  Detection of these events is fairly crude, but Google doesn't provide notifications for them.
 */	

//	Editing ends when the "Saved changes to [feed-name]" message appears
var changeMsg = document.evaluate(CHANGE_MSG_XPATH, document, null,
				XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
	
changeMsg.addEventListener("DOMNodeInserted",
	function (event)
	{
		if (this.textContent.search("Saved changes") == 0 || this.textContent.search("You have") == 0)
			/*
			 * I don't actually need to fetch the opml file here, but the replaceAllFeedIcons() function
			 * makes calls to GM_getValue()/GM_setValue(), which cannot be made in the context of the
			 * webpage (a security error occurs when this happens).  Thus, I have to call the function
			 * in the context of a GreaseMonkey XmlHttpRequest.
			 */
			fetchOPML(EXPORT_URL, replaceAllFeedIcons);
	},
	true);
	
//	The user navigated back from the Settings page when the class attribute of the
//	left-hand side navigation bar is modified in a certain way
var navBar = document.evaluate(NAV_BAR_XPATH, document, null,
	XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

var unhide = false;

navBar.addEventListener("DOMAttrModified", 
	function(event) 
	{
//		if (event.attrName == "class")
//			console.log("attrName = "+event.attrName+"\nprevValue = "+event.prevValue+"\nnewValue = "+event.newValue);
		//	we're coming back from the Settings page 
		if (event.attrName == "class" && event.prevValue == "hidden" && event.newValue == "")
			unhide = true;

//		console.log("unhide = "+unhide+" attrName = "+event.attrName + " newValue = "+event.newValue);
		
		/*	I only re-draw the first time the class attribute is changed after coming back from the
		 *	Settings page.  I used the flag because this type of change to the class
		 *	attribute occurs often.  For example, it occurs every time you click on a feed/subscription
		 *	in the left panel.
		 */
		if (unhide && event.attrName == "class" && event.newValue == "link tree-link-selected")
		{
			fetchOPML(EXPORT_URL, replaceAllFeedIcons);
			unhide = false;
		}
	}, 
	true);


//	A new feed is added using the "Add a subscription" button when the class attribute
//	of the "success" message becomes visible
var feedAddMsg = document.evaluate(FEED_ADD_MSG_XPATH, document, null,
	XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

feedAddMsg.addEventListener("DOMAttrModified",
	function(event)
	{
		if (event.attrName == "class" && event.prevValue == "hidden" && event.newValue == "")
		{
			/*
			 * I don't actually need to fetch the opml file here, but the replaceAllFeedIcons() function
			 * makes calls to GM_getValue()/GM_setValue(), which cannot be made in the context of the
			 * webpage (a security error occurs when this happens).  Thus, I have to call the function
			 * in the context of a GreaseMonkey XmlHttpRequest.
			 */
			fetchOPML(EXPORT_URL, replaceAllFeedIcons);
		}
	},
	true);

(function() { var ids = ["gb","logo-container","sections-header","reading-list-selector","lhn-recommendations","sub-tree-header","lhn-add-subscription-section","lhn-selectors","home-section","viewer-refresh","viewer-view-options","mark-all-as-read-split-button","stream-prefs-menu","chrome-view-links","settings-button-container"];

//,"sections-header","viewer-header"

 function toggle_gr ()
 {
   var length = ids.length;
   var is_visible = document.getElementById(ids[0]).style.display != "none";

   for (var i=0; i<length; i++){
     if(document.getElementById(ids[i]) != null)
		document.getElementById(ids[i]).style.display = is_visible?"none":"block";
   }
   GM_addStyle(".gbh { display:none !important; }");  //Hide dividing line
   if(is_visible){


	//document.getElementById('main').style.marginTop = '-2.5em';
	document.getElementById('main').style.marginTop = '-2.9em';
	document.getElementById('main').style.fontSize = '8pt';
	document.getElementById('main').style.height = '150%';

	document.getElementById('lhn-subscriptions').style.height = '150%';
	document.getElementById('lhn-subscriptions').style.marginTop = '0.5em';

	document.getElementById('sub-tree').style.fontSize = '8pt';
	document.getElementById('sub-tree').style.marginTop = '0.5em';

	document.getElementById('lhn-subscriptions-minimize').style.marginTop = '-9em';

 	document.getElementById('search-input').style.width = '70%';
	document.getElementById('search-input').style.marginLeft = '-19em';


var overrideCSS = " \
#search { marginTop:'-5em' !important; height:'50%' !important; padding:1px !important } \
#viewer-header { padding:1px !important ; position:fixed; top:-2px; right:-30px; height:30px ; width:95px ; z-index:1 !important } \
#sections-header { height:45px !important; } \
//#viewer-header { height:45px !important; } \
#entries { padding:0 !important; } \
#entries.list .collapsed { line-height:2.8ex !important; padding:1px 0 !important; } \
#entries.list .entry .entry-secondary .snippet { color: #333 } \
#entries.list .entry.read .entry-secondary .snippet { color: inherit } \
#entries.list .entry .entry-main .entry-source-title \
{color:#333 !important} \
#entries.list .entry.read .entry-main .entry-source-title \
{color: #666 !important} \
#title-and-status-holder { padding:0.3ex 0 0 0.5em !important; } \
.entry-icons { top:0 !important } \
.entry-source-title { top:2px !important } \
.entry-secondary { top:2px !important } \
.entry-main .entry-original { top:4px !important } \
.section-minimize { left: 3px !important; top: 3px !important } \
#sub-tree-header \
{ padding-left: 15px !important; } \
.folder .folder .folder-toggle { margin-left:13px !important } \
.folder .sub-icon, .folder .folder>a>.icon { margin-left:27px !important } \
.folder .folder>ul .icon { margin-left:34px !important } \
.folder .folder .name-text { max-width:160px; !important } \
#recommendations-tree .sub-icon {background-position: -65px 0 !important;} \
.scroll-tree .icon, .scroll-tree .favicon {height: 15px !important; width: 15px !important;} \
.scroll-tree .folder-icon {background-position: -48px 0 !important;} \
.scroll-tree li a .name {color: #000000 !important;} \
.scroll-tree .tag-icon {background-position: -33px -32px !important;} \
";
GM_addStyle(overrideCSS);
	
   }
   else {
 	document.getElementById('search-input').style.width = '40%';
	document.getElementById('search-input').style.marginLeft = '0em';
   }
 }


 function GRT_key(event) {
   element = event.target;
   elementName = element.nodeName.toLowerCase();
   if (elementName == "input") {
     typing = (element.type == "text" || element.type == "password");
   } else {
     typing = (elementName == "textarea");
   }
   if (typing) return true;
   if (String.fromCharCode(event.which)=="W" && !event.ctrlKey && !event.metaKey) {
     toggle_gr();
     try {
       event.preventDefault();
     } catch (e) {
     }
     return false;
   }
   return true;
 }

 document.addEventListener("keydown", GRT_key, false);
 toggle_gr();

//var foo = document.getElementById('item-up-down-buttons');viewer-header
//var clonefoo = foo.cloneNode(true);
//var div2 = document.getElementById('search');
//div2.appendChild(clonefoo);

var accountname = document.getElementById('gbmpn');
document.title = document.title + " | " + accountname.innerHTML + " |";

})();