// ==UserScript==
// @name TweetDeck lightboxes
// @namespace https://yal.cc/
// @version 1.0
// @description Shows an inline lightbox for images instead of opening a new tab/etc.
// @author YellowAfterlife
// @match https://tweetdeck.twitter.com/*
// @grant none
// ==/UserScript==
/* jshint eqnull:true */
/* jshint esversion:6 */
(function() {
'use strict';
let css = document.createElement("style");
css.type = "text/css"; css.innerHTML = `
.prf-header .imgxis-badge {
position: absolute;
left: 4px;
top: 4px;
width: 16px;
height: 16px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.7);
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
}
.imgxis-panner {
background: rgba(41,47,51,0.9);
position: absolute;
left: 0; width: 100%;
top: 0; height: 100%;
z-index: 350;
}
.imgxis-panner, .imgxis-panner img {
cursor: move;
}
.imgxis-panner.zoomed, .imgxis-panner.zoomed img {
-ms-interpolation-mode: nearest-neighbor;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: -o-crisp-edges;
image-rendering: pixelated;
}
.imgxis-panner img, .imgxis-panner video {
position: absolute;
transform-origin: top left !important;
margin: 0;
background: none !important;
}
.imgxis-panner::after {
content: attr(zoom);
color: white;
display: inline-block;
padding: 1px 2px;
background: rgba(0, 0, 0, 0.4);
position: absolute; top: 0; left: 0;
}
.imgxis-panner.odd-zoom::after {
color: #ffe040;
}
.imgxis-panner iframe {
position: absolute;
top: 0; bottom: 0;
left: 50px;
width: calc(100% - 100px);
height: 100%;
height: 100vh;
border: 0;
}
`;
document.body.appendChild(css);
//
let img0 = document.createElement("img"); // original
let img0failed = false;
img0.onerror = (_) => { img0failed = true };
//
let img1 = document.createElement("img"); // full-sized
let img1failed = false;
img1.onerror = (_) => { img1failed = true };
//
let video = document.createElement("video");
video.loop = true;
video.controls = true;
video.autoplay = true;
let videoLoaded = false;
video.oncanplay = (_) => { videoLoaded = true };
let isVideo = false;
//
let iframe = document.createElement("iframe");
iframe.setAttribute
//
let panner = document.createElement("div");
panner.className = "imgxis-panner";
panner.appendChild(img0);
panner.appendChild(img1);
panner.appendChild(video);
panner.appendChild(iframe);
let panX = 0, panY = 0, panZ = 0, panM = 1;
let panWidth = 0, panHeight = 0;
let panIdle = false; // whether nothing happened to it yet
let zoomed = false;
//
function panUpdate() {
let pz = (panM >= 1);
if (pz != zoomed) {
zoomed = pz;
let cl = panner.classList;
if (pz) cl.add("zoomed"); else cl.remove("zoomed");
}
panner.setAttribute("zoom", `${panM*100|0}%`);
let tf = `matrix(${panM},0,0,${panM},${-panX|0},${-panY|0})`;
img0.style.transform = tf;
img1.style.transform = tf;
video.style.transform = tf;
}
//
function panWheel(e) {
panIdle = false;
panner.classList.remove("odd-zoom");
let d = e.deltaY;
d = (d < 0 ? 1 : d > 0 ? -1 : 0) * 0.5;
let zx = e.pageX, zy = e.pageY;
let prev = panM;
if (Math.abs(panZ - Math.round(panZ * 2) / 2) > 0.001) {
panZ = d > 0 ? Math.ceil(panZ * 2) / 2 : Math.floor(panZ * 2) / 2;
} else panZ = Math.round((panZ + d) * 2) / 2;
panM = Math.pow(2, panZ);
let f = panM / prev;
panX = (zx + panX) * f - zx;
panY = (zy + panY) * f - zy;
panUpdate();
}
var mouseX = 0, mouseY = 0, mouseDown = false;
function panMove(e) {
let lastX = mouseX; mouseX = e.pageX;
let lastY = mouseY; mouseY = e.pageY;
if (mouseDown) {
panX -= (mouseX - lastX);
panY -= (mouseY - lastY);
panUpdate();
}
}
function panPress(e) {
panIdle = false;
panMove(e);
if (e.target == panner) {
e.preventDefault();
setTimeout(() => panHide(), 1);
} else if (e.which != 3) {
e.preventDefault();
mouseDown = true;
}
}
function panRelease(e) {
panMove(e);
mouseDown = false;
}
function panKeyDown(e) {
if (e.keyCode == 27/* ESC */) {
e.preventDefault();
e.stopPropagation();
panHide();
return false;
}
}
panner.addEventListener("mousemove", panMove);
panner.addEventListener("mousedown", panPress);
panner.addEventListener("mouseup", panRelease);
panner.addEventListener("wheel", panWheel);
//
function panFit(lw, lh) {
let iw = window.innerWidth, ih = window.innerHeight;
panZ = 0;
if (lw < iw && lh < ih) {
// zoom in (up to 800%)
for (let k = 0; k < 3; k++) {
if (lw * 2 < iw && lh * 2 < ih) {
panZ += 1; lw *= 2; lh *= 2;
}
}
} else {
while (lw > iw || lh > ih) { // zoom out until fits
panZ -= 1; lw /= 2; lh /= 2;
}
}
panM = Math.pow(2, panZ);
panX = -(iw - lw) / 2;
panY = -(ih - lh) / 2;
console.log(iw, ih, lw, lh, panX, panY, panM);
}
//
let panCheckInt2 = null;
function panCheck2() {
if (isVideo) {
let lw = video.offsetWidth, lh = video.offsetHeight;
if (!videoLoaded) return;
clearInterval(panCheckInt2); panCheckInt2 = null;
panFit(lw, lh);
console.log(lw, lh, panX, panY);
} else {
let lw = img1.width, lh = img1.height;
if (lw <= 0 || lh <= 0) return;
clearInterval(panCheckInt2); panCheckInt2 = null;
//
if (img1failed) return;
img1.style.visibility = "";
if (/*panIdle*/true) { // it makes sense to rescale to original if idle, but looks odd
panZ -= Math.log2(Math.max(lw / img0.width, lh / img0.height));
panM = Math.pow(2, panZ);
if (Math.abs((panZ * 2) % 1) > 0.001) {
panner.classList.add("odd-zoom");
}
} else panFit(lw, lh);
img0.width = lw;
img0.height = lh;
}
panUpdate();
}
//
let panCheckInt = null;
function panCheck() {
let lw = img0.width, lh = img0.height;
if (lw <= 0 || lh <= 0) return;
if (img0failed) return;
//console.log(lw, lh, img0failed);
clearInterval(panCheckInt); panCheckInt = null;
panFit(lw, lh);
img0.style.visibility = "";
panUpdate();
if (img1.src) {
panCheckInt2 = setInterval(panCheck2, 25);
}
}
//
var panTickInt = null;
function panTick() {
let lastWidth = panWidth; panWidth = window.innerWidth;
let lastHeight = panHeight; panHeight = window.innerHeight;
if (panWidth != lastWidth || panHeight != lastHeight) {
panX -= (panWidth - lastWidth) / 2;
panY -= (panHeight - lastHeight) / 2;
panUpdate();
}
}
function panShow(url, orig, mode) {
isVideo = mode == 1;
img1.style.display = img0.style.display = (mode == 0 ? "" : "none");
video.style.display = mode == 1 ? "" : "none";
iframe.style.display = mode == 2 ? "" : "none";
if (mode == 2) {
iframe.src = url;
} else if (mode == 1) {
video.src = url;
videoLoaded = false;
} else {
img0.removeAttribute("width");
img0.removeAttribute("height");
img1.src = url; img0failed = false;
img0.src = orig; img1failed = false;
img1.style.visibility = "hidden";
img0.style.visibility = "hidden";
}
document.querySelector(".application").appendChild(panner);
document.addEventListener("keydown", panKeyDown);
panWidth = window.innerWidth;
panHeight = window.innerHeight;
if (mode == 2) return;
panTickInt = setInterval(panTick, 100);
if (isVideo) {
panCheckInt2 = setInterval(panCheck2, 25);
} else {
panCheckInt = setInterval(panCheck, 25);
}
}
function panHide() {
video.src = "";
img0.src = "";
img1.src = "";
iframe.src = "";
panner.parentElement.removeChild(panner);
document.removeEventListener("keydown", panKeyDown);
clearInterval(panTickInt); panTickInt = null;
if (panCheckInt != null) { clearInterval(panCheckInt); panCheckInt = null; }
if (panCheckInt2 != null) { clearInterval(panCheckInt2); panCheckInt2 = null; }
}
//
function panGetShow(url, orig, mode) {
if (mode == null) mode = 0;
return (e) => {
e.preventDefault();
e.stopPropagation();
panShow(url, orig, mode);
};
}
//
function getBackgroundUrl(el) {
let url = el.style.backgroundImage;
if (url == null) return url;
return url.slice(4, -1).replace(/"/g, "");
}
setInterval(() => {
// pictures:
for (let query of [
`.js-media-preview-container:not(.is-video):not(.is-gif) .js-media-image-link:not(.imgxis-link)`,
`.media-image-container .js-media-image-link:not(.imgxis-link)`,
]) for (let el of document.querySelectorAll(query)) {
el.classList.add("imgxis-link");
let url, orig;
if (/(?:.jpg|.png|.jpeg|.gif)$/g.test(el.href)) {
url = el.href;
orig = el.getAttribute("data-original-url") || (url + ":small");
} else {
let img = el.querySelector("img");
if (img == null) {
orig = getBackgroundUrl(el);
if (orig == null) continue;
} else {
orig = img.src;
}
url = orig.replace(/(?:\:small|\:large|\?format=.+)$/g, ":orig");
}
el.addEventListener("click", panGetShow(url, orig));
}
// profile backgrounds:
for (let el of document.querySelectorAll(`.prf-header:not(.imgxis-link`)) {
el.classList.add("imgxis-link");
let orig = getBackgroundUrl(el);
if (orig == null) continue;
let url = orig.replace(/\/web$/g, "/1500x500");
let a = document.createElement("a");
a.className = "imgxis-badge";
a.href = "#";
a.title = "View profile background";
a.addEventListener("click", panGetShow(url, orig));
el.appendChild(a);
}
// avatars:
for (let el of document.querySelectorAll(`.prf-img img.avatar:not(.imgxis-link)`)) {
el.classList.add("imgxis-link");
let orig = el.src;
let url = orig.replace(/(?:_bigger|_normal)\./g, ".");
el.addEventListener("click", panGetShow(url, orig));
}
// gifs:
for (let el of document.querySelectorAll(`.media-item-gif:not(.imgxis-link)`)) {
el.classList.add("imgxis-link");
let url = el.src;
el.parentElement.addEventListener("click", panGetShow(url, url, 1));
}
// videos:
for (let el of document.querySelectorAll(`.media-preview-container.is-video:not(.imgxis-link)`)) {
el.classList.add("imgxis-link");
let link = el.querySelector("a");
let par = el.parentElement;
let url = link && link.href;
if (url) url = url.replace("https://www.", "https://");
if (!url) {
//
} else if (url.startsWith("https://t.co") || url.startsWith("https://twitter.com")) {
url = null;
} else if (url.startsWith("https://youtube.com/")) {
var mt = /v=([\w-]+)/.exec(url);
if (mt) url = `https://www.youtube.com/embed/${mt[1]}?autoplay=1`;
}
if (!url) while (par) {
if (par.tagName == "ARTICLE" || par.classList.contains("quoted-tweet")) {
url = par.getAttribute("data-tweet-id");
if (url) url = `https://twitter.com/i/videos/tweet/${url}?auto_buffer=1&autoplay=1`;
break;
} else par = par.parentElement;
}
if (url) el.parentElement.addEventListener("click", panGetShow(url, url, 2));
}
}, 250);
})();