joeytwiddle / Title Youtube Locations

// ==UserScript==
// @name           Title Youtube Locations
// @namespace      TYTLs
// @description    Puts the video title in the location bar of all YouTube video pages.  Now with extra features add scrollbars, animate thumbnails, reduce font sizes and un-float the header.
// @version        1.3.0
// @downstreamURL  http://userscripts.org/scripts/source/87416.user.js
// @include        http://*.youtube.*/*
// @include        http://youtube.*/*
// @include        https://*.youtube.*/*
// @include        https://youtube.*/*
//// YouTube thumbnails almost certainly appear if you do a video search:
// @include        https://www.google.co.uk/search?*&tbm=vid&*
//// YouTube thumbnails occasionally appear in normal Google search results:
// @include        https://www.google.co.uk/search*
// @grant          GM_addStyle
// @grant          GM_log
// ==/UserScript==

// TODO: In case for some reason YouTube or another script redirects us to
// another URL which has lost the title param we added, we will re-run forever.
// To avoid this we should have a fallback.  E.g. if the title we want to set
// is the same as the last one we did set (via GM_set/getValue) then do not try
// again.
// This has never happened so far.  :)

// If you are interested in YouTube images:
// Thumbnails url looks like http://img.youtube.com/vi/8aYQ_wjmriQ/2.jpg
// Youtube generates 4 thumbnails: 0.jpg at 320x240, and 1.jpg, 2.jpg, 3.jpg
// Also large: hqdefault.jpg (480x360) and sometimes but not always hq1.jpg, hq2.jpg, hq3.jpg



var addTitleToLocation = false; // Oh dear, the primary feature of this plugin has started causing annoying reloads.  TODO Perhaps we can do it with pushState instead...
var reduceFontSizes    = true;
var addScrollbars      = true;
var scrollDownToVideo  = false; // YouTube's header ("masthead") is now floating.  Setting this true will un-float it, then scroll down to hide it.  But scrolling containers should then be enlarged.
var animateThumbnails  = true;
var unfloatTheHeader   = true;



if (addTitleToLocation) {

	// NOTE: This code is deprecated, as I now use the userscripts URLs_Need_Titles

	setTimeout(function(){
		if (document.location.pathname == "/watch") {

			var title = document.title.replace(/ - YouTube$/,'')
				|| null;

			if (title)
				title = title.replace(/ /g,'_').replace(/^[\r\n_]*/,'').replace(/[\r\n_]*$/,''); // "_"s paste better into IRC, since " "s become "%20"s which are hard to read.  The second and third parts trim "_"s and newlines from the start and end of the string.

			if (title) {
				if (!document.location.hash) {
					document.location.replace(document.location.href + '#' + title); // Does not alter browser history
					// document.location.hash = title; // Crashes Chrome less often
				}
			}
		}
	},5000); // This is what really stops the crashing!

}



if (reduceFontSizes) {
	// == Reduce font size of thumbnail titles ==
	GM_addStyle(".yt-tile-default.video-list-item a .title, #watch-sidebar .video-list-item .title { font-size: 11px; line-height: 10px; }");
	// Defaults are font-size: 13px; and line-height: 15px; which show only two lines in my browser.
}



if (addScrollbars) {

	// == Scrollbars on comments and related vids, to keep the video in view. ==
	setTimeout(function(){
		// We could alternatively act on watch-panel but that includes the video navigation buttons!
		// BUG: Interferes with YouTube's lazy-loading of thumbnails for related video.
		if (document.location.href.indexOf("/all_comments?") >= 0) {
			return;   // Leave the full comments page alone.
		}

		// The top of watch7-content has become very large, so put a scrollbar on the whole lot.
		var watchDiscussion = /* document.getElementById("watch-discussion") || */ document.getElementById("watch7-content");
		if (watchDiscussion) {
			var toSubtract = 464;     // Small video screen
			//var toSubtract = 544;   // Medium size video screen
			var roomForComments = window.innerHeight - toSubtract;
			if (roomForComments < 200) {
				roomForComments = 200;
			}
			watchDiscussion.style.overflow = "auto";
			watchDiscussion.style.maxHeight = roomForComments+"px"; /* For a video height 360p */
			GM_addStyle(" #watch7-content { border: 1px solid; border-color: #c8c8c8 #dddddd #dddddd #c8c8c8; margin-top: 5px; } #watch-header { margin-top: 0px; margin-bottom: 0px; } ");
		}

		var watchSidebar = document.getElementById("watch-sidebar") || document.getElementById("watch7-sidebar");
		if (watchSidebar) {
			watchSidebar.style.overflow = "auto";
			watchSidebar.style.maxHeight = (window.innerHeight - 69)+"px";
			// May 2012 - fixes below no longer needed.
			// Now the text wraps because of the scrollbar, so we widen the element:
			// watchSidebar.style.width = (320+24)+"px";
			// watchSidebar.style.width = '300px';
			// And we must widen its container also:
			// TODO BUG: Why does this work in the console, but not from the userscript?
			// document.getElementById("watch-main").style.width = (960+24)+"px";
			GM_addStyle(" #watch-sidebar, #watch7-sidebar { border: 1px solid; border-color: #c8c8c8 #dddddd #dddddd #c8c8c8; } ");
			// Without this, the Google notifications dropdown will appear behind our sidebar.
			// But with it, the sidebar is not clickable!  :-(
			//GM_addStyle(" #watch-sidebar, #watch7-sidebar { z-index: -20; } ");
		}

		if (scrollDownToVideo) {
			// Un-float the header:
			GM_addStyle(" #masthead-positioner { position: initial; } #masthead-positioner-height-offset { height: 0px; } ");
			//// Title text
			// document.getElementById("eow-title").scrollIntoView();
			//// Uploader info and videolist popdown.
			// document.getElementById("watch-headline-user-info").scrollIntoView();
			//// The author's video list (was supposed to be a small gap above the video when collapsed, but it's not)
			// document.getElementById("watch-more-from-user").scrollIntoView();
			//// The video
			// document.getElementsByTagName("embed")[0].scrollIntoView();
			//// The video
			var watchVideo = document.getElementById("movie_player") || document.getElementById("player-api");
			if (watchVideo) {
				watchVideo.scrollIntoView();
			}
			//// Slight gap above the video (I prefer that)
			// var watchContainer = document.getElementById("watch7-container");
			// if (watchContainer) {
			// 	watchContainer.scrollIntoView();
			// }
		}

		// ~ October 2014
		// This feels like a bug in Chrome 38.  The element is given position:absolute but it does not move up when its parent does!  We can fix it anyway:
		GM_addStyle(" #watch8-secondary-actions { position: initial; } ");
	},1000);

}



if (animateThumbnails) {

	// == Thumbnail animation ==
	// TODO: This is working fine on "related videos" thumbnails, but not on queue
	// thumbnails, even if I have the queue open when I load the page.
	// Perhaps we are responding to a mouseout event from a child element, because
	// we are not checking the event target like we should do.
	function initThumbnailAnimator() {
		// function createThumbnailAnimatorEvent(thumbImage) {
		var filenameRE = /\/([^/]*)\.(jpg|webp)(\?.*|$)/;
		var thumbImage    = null;
		var originalHref  = null;
		var timer  = null;
		//var frames = ["1.jpg","2.jpg","3.jpg"];   // "default.jpg",
		var frames = ["1","2","3"];
		var frameI = 0;
		function changeFrame() {
			frameI = (frameI + 1) % frames.length;
			var match = originalHref.match(filenameRE);
			var extension = match[2];
			var filename = frames[frameI] + '.' + extension;
			thumbImage.src = originalHref.replace(filenameRE, '/' + filename);
		}
		function startAnimation() {
			// Because there was a bug that the running animation would not stop!
			if (timer) {
				clearInterval(timer);
			}
			originalHref = thumbImage.src;
			if (originalHref.match(/^data:/)) {
				return;
			}
			// We make this check quite late, due to lazy loading
			if (originalHref.match(filenameRE)) {
				// logElem("Starting animation",thumbImage);
				timer = setInterval(changeFrame,600);
			}
		}
		function stopAnimation() {
			if (timer) {
				// logElem("Stopping animation",thumbImage);
				clearInterval(timer);
				timer = null;
				// This isn't really neccessary, except to ensure the check for default\.jpg above works next time!
				//thumbImage.src = thumbImage.src.replace(/\/[^/]*$/,'') + '/' + "default.jpg";
				thumbImage.src = originalHref;
			}
		}
		function logElem(name,elem) {
			report = "<"+elem.tagName+" id="+elem.id+" class="+elem.className+" src="+elem.src+" />";
			GM_log(name+" = "+report);
		}
		function check(fn) {
			return function(evt) {
				// logElem("["+evt.type+"] evt.target",evt.target);
				var elemToCheck = evt.target || evt.srcElement;
				if (elemToCheck.tagName == "IMG") {
					thumbImage = elemToCheck;
					return fn();
				} else if (elemToCheck.className=='screen') {
					var seekImg = elemToCheck.parentNode.getElementsByTagName("img")[0];
					if (seekImg) {
						thumbImage = seekImg;
						return fn();
					}
				// } else {
					// var imgCount = elemToCheck.getElementsByTagName("img").length;
					// if (imgCount == 1) {
						// thumbImage = elemToCheck.getElementsByTagName("img")[0];
						// // logElem("["+evt.type+"] checking sub-image",thumbImage);
						// logElem("Whilst checking",elemToCheck);
						// logElem("  Animating elem",thumbImage);
						// logElem("  with parent",thumbImage.parentNode);
						// logElem("  whilst currentTarget",evt.currentTarget);
						// logElem("  and srcElement",evt.srcElement);
						// return fn();
					// }
				}
			};
		}
		//// Unfortunately these do not fire on any HTMLImageElements when browsing the queue.
		document.body.addEventListener("mouseover",check(startAnimation),false);
		document.body.addEventListener("mouseout",check(stopAnimation),false);
		// var videoList = document.getElementById("watch-sidebar"); // or watch-module or watch-module-body or watch-related or watch-more-related
		// var videoList = document.getElementsByClassName("video-list")[0]; // can be 4 of these!
		// var thumbs = document.getElementsByTagName("img");
		// for (var i=0;i<thumbs.length;i++) {
			// createThumbnailAnimatorEvent(thumbs[i]);
		// }
	}
	setTimeout(initThumbnailAnimator,1000);

}



if (unfloatTheHeader) {
	GM_addStyle("#masthead-positioner { position: static; }");
	var gapElement = document.getElementById("masthead-positioner-height-offset");
	if (gapElement) {
		gapElement.parentNode.removeChild(gapElement);
	}
}