// ==UserScript==
// @name Bovverzoom
// @description My take of (pre)viewing images and video clips on Reddit by mouse hovering on their thumbnail or text link.
// @namespace raina
// @include /^https?:\/\/(\w+\.)?reddit\.com\//
// @version 1.3.2
// @done Run on subdomains for languages, no participation mode etc...
// @done Allow passive mixed content, so that images from hosts without HTTPS can load.
// @done Avoid mixed content issues by stripping out explicit protocol. (E.g. GFYCat videos don't load over HTTP from an HTTPS Reddit session)
// @done Handle Imgur ad hoc collection images
// @done Handle Imgur "gifv" clips.
// @todo Handle DOM mutations.
// @todo Handle Imgur albums and galleries somehow.
// @todo Keyboard navigation?
// @grant none
// ==/UserScript==
(function() {
"use strict";
var i, j;
var doc = document;
var thumbs = doc.getElementsByClassName("thumbnail");
var userText = doc.getElementsByClassName("usertext-body");
var show = function() {
this.style.display = "block";
};
var hide = function() {
this.style.display = "none";
};
var img = doc.createElement("img");
img.className = "bovverzoom";
img.cache = "";
img.show = show;
img.hide = hide;
var clip = doc.createElement("video");
clip.className = "bovverzoom";
clip.autoplay = "autoplay";
clip.muted = "muted";
clip.loop = "loop";
clip.cache = "";
clip.show = show;
clip.hide = hide;
var style = doc.createElement("style");
style.type = "text/css";
style.textContent = ".bovverzoom {" +
"background-color: rgba(0,0,0,.01);" +
"position: fixed;" +
"right: 0;" +
"top: 0;" +
"z-index: 100;" +
"max-width: 100%;" +
"max-height: 100%;" +
"pointer-events: none;" +
"border-radius: 3px;" +
"display: none;" +
"}"+
".glow {" +
"box-shadow: rgba(255, 255, 255, .5) 0 0 8px" +
"}";
var fetchGFY = function(url, e) {
img.hide();
if (url != clip.cache) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", showGFY, false);
xhr.open("GET", url, true);
xhr.send();
clip.cache = url;
} else {
clip.show();
}
};
var showGFY = function() {
clip.show();
var gfyItem = JSON.parse(this.responseText).gfyItem;
var newClip = clip.cloneNode(false);
var webmSrc = doc.createElement("source");
webmSrc.src = gfyItem.webmUrl.replace(/^https?:/, "");
webmSrc.type = "video/webm";
newClip.appendChild(webmSrc);
var mp4Src = doc.createElement("source");
mp4Src.src = gfyItem.mp4Url.replace(/^https?:/, "");
mp4Src.type = "video/mp4";
newClip.appendChild(mp4Src);
document.body.replaceChild(newClip, clip);
newClip.hide = clip.hide;
newClip.show = clip.show;
clip = newClip;
clip.show();
};
var showGIFV = function(url) {
clip.show();
var newClip = clip.cloneNode(false);
var webmSrc = doc.createElement("source");
webmSrc.src = url.replace(".gifv", ".webm").replace(/^https?:/, "");
webmSrc.type = "video/webm";
newClip.appendChild(webmSrc);
var mp4Src = doc.createElement("source");
mp4Src.src = url.replace(".gifv", ".mp4").replace(/^https?:/, "");
mp4Src.type = "video/mp4";
newClip.appendChild(mp4Src);
document.body.replaceChild(newClip, clip);
newClip.hide = clip.hide;
newClip.show = clip.show;
clip = newClip;
clip.show();
};
var imgMe = function(src, e) {
clip.hide();
if (src != img.cache) {
img.src = "";
img.src = src;
img.cache = src;
}
img.show();
};
var killMe = function() {
clip.hide();
img.hide();
};
var rigMe = function(src, el) {
el.addEventListener("mouseover", swapper, false);
el.addEventListener("mouseout", swapper, false);
el.className = el.className + " glow";
};
var clipFunc = rigMe;
var imgFunc = rigMe;
var swapper = function(e) {
var obj;
if (undefined !== this) {
obj = this;
clipFunc = obj.href.match(/gfycat/) ? fetchGFY : showGIFV;
imgFunc = imgMe;
} else {
obj = e;
}
if ("mouseover" === e.type || "" === e.type) {
if (obj.href.match(/^https?:\/\/(.*\.)?gfycat\.com\/.{1,}$/i)) { // gfycat.com, HTML5 video even for direct .gif links
clipFunc("//gfycat.com/cajax/get/" + obj.href.replace(/^.*\.com\//, ""), e);
} else if (obj.href.match(/^https?:\/\/((i|m|www)\.)?imgur\.com\/[^\/]{1,}\.gifv$/)) { // imgur.com HTML5 video
clipFunc(obj.href, e);
} else if (obj.href.match(/\.(bmp|gif|jpg|png|svg)(\?.*)?$/i)) { // images, any domain
imgFunc(obj.href, e);
} else if (obj.href.match(/^https?:\/\/((www|m)\.)?imgur\.com(\/r\/[^\/]*)?\/[^\/]{1,}#[0-9]{1,}$/)) { // imgur.com ad hoc collections
var imgList = obj.href.slice(obj.href.lastIndexOf("/") + 1, obj.href.lastIndexOf("#")).split(",");
var index = obj.href.slice(obj.href.lastIndexOf("#") + 1);
imgFunc("//i.imgur.com/" + imgList[index] + ".jpg", e);
} else if (obj.href.match(/^https?:\/\/((www|m)\.)?imgur\.com(\/r\/[^\/]*)?\/[^\/]{1,}$/)) { // imgur.com, nonhotlinked single images
imgFunc(obj.href.replace(/\/([^\/]*\.)?imgur\.com(\/r\/[^\/]*)?/, "/i.imgur.com") + ".jpg", e);
} else { // not a recognized image/clip link
killMe();
}
} else { // mouseout
killMe();
}
};
doc.head.appendChild(style);
doc.body.appendChild(clip);
doc.body.appendChild(img);
for (i = 0; i < thumbs.length; i++) {
swapper(thumbs[i]);
}
for (i = 0; i < userText.length; i++) {
var textLinks = userText[i].getElementsByClassName("md")[0].getElementsByTagName("a");
if (textLinks.length) {
for (j = 0; j < textLinks.length; j++) {
swapper(textLinks[j]);
}
}
}
}());