TweetDeck lightboxes

Shows an inline lightbox for images instead of opening a new tab/etc.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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);
})();