// ==UserScript== // @name eSIT: eSix Informative Thumbnails // @namespace prplbst // @version 1.2.0 // @description Gives each Video, Flash, and Blacklisted thumbnail on e621.net a unique appearance while also adding helpful info overlays to them. // @author purple.beastie // @updateURL https://raw.githubusercontent.com/purple-beastie/eSIT/master/eSIT.meta.js // @match https://e621.net/* // @match https://e926.net/* // @exclude *.json // @exclude *.xml // @grant none // @require https://cdn.jsdelivr.net/npm/jdenticon@1.6.0/dist/jdenticon.min.js // ==/UserScript== /* * License Information * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ (function() { 'use strict'; var userPrefUseBinaryUnits = -1; // 0 for KiB, MiB, etc.; 1 for kB, MB, etc.; any other value for auto select var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0, useBinaryUnits; switch (userPrefUseBinaryUnits) { case 0: useBinaryUnits = true; break; case 1: useBinaryUnits = false; break; default: useBinaryUnits = !isMac; } var iconSize = 114; var thumbCount = 0, thumbLimit = 1000, // maximum number of thumbs to process placeholderCount = 0, allPlaceholders = [], allBlacklistOverlays, allBlacklistLabels, multiOverlayFlag = false, thumbFocused = false, thumbHovered = false; var propertiesToCopy = ["border-top-style", "border-top-width", "border-top-color", "border-right-style", "border-right-width", "border-right-color", "border-bottom-style", "border-bottom-width", "border-bottom-color", "border-left-style", "border-left-width", "border-left-color", "border-top-left-radius", "border-top-right-radius", "border-bottom-left-radius", "border-bottom-right-radius", "box-shadow"]; var css = [ '.esit-container {display:block;position:relative;z-index:5;width:150px;height:150px;overflow:hidden;margin:0 auto;vertical-align:middle;}', '.thumb .esit-container {display:block}', '.thumb_dtext .esit-container, .thumb_avatar .esit-container {display: inline-block}', '.thumb_avatar .esit-container {margin-bottom:3px}', '.esit-btn {width:100%;height:100%;border:0;padding:0;background:none;cursor:pointer;background:#000}', '.esit-container img.esit-img {position:static;display: inline;padding:' + (150 - iconSize) / 2 + 'px;}', '.esit-placeholder {position:absolute;z-index:10;top:0;left:0;padding:0;pointer-events:none}', // Text '.esit-text {color:white;transition: opacity 0.3s ease;width: 100%; height: 100%;z-index: 1;display:flex;justify-content:center;align-items:center;position:absolute;top:0;}', '.esit-label {transition:opacity 0.35s ease 0.08s;position:relative;font-size: 18px;font-weight: bold;letter-spacing:0.5px;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;}', '.esit-file-size {position: absolute;bottom:2px;left:4px;font-size:14px;}', '.esit-dimensions {position: absolute;bottom:2px;right:4px;font-size:14px;}', // Tag Icons '.esit-tag-icon-box {position:absolute;top:2px;right:4px;height:14px;width:14px;}', //'.esit-tag-icon {}', '.esit-tag-icon-sound {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAM1BMVEUAAAD///////////////////////////////////////////////////////////////+3leKCAAAAEHRSTlMAEB8gUGBwoK+wz9Df4O/wLoylUwAAAE1JREFUeF51zoEKgDAIBFC3yrVd1f3/12YolUDHgTwQVDxLzBCzbmKUkJOrCGk1YtST+pJtZn84cbN+uFv/loF6UNOhnt5AcYpSUlrgAhTzBRSiExLiAAAAAElFTkSuQmCC")}', '.esit-tag-icon-nosound {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOBAMAAADtZjDiAAAAHlBMVEUAAAD///////////////////////////////////8kfJuVAAAACXRSTlMAEGBwoM/Q0tPwH5urAAAAQ0lEQVQIW2NgYGBQYAADpgkQWhNCM80E0zM5Z05IFQwF05nl08C0xMxGMC0+sxAi3jEVTKcJpsH1wc1hYIbSDA5ADAAC1hcHYm+i5AAAAABJRU5ErkJggg==")}', '.esit-tag-icon-soundwarning {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOBAMAAADtZjDiAAAAD1BMVEUAAAD///////////////+PQt5oAAAABHRSTlMAYHCgAq13pAAAAC5JREFUCFtjYGBgEGAAA0YHCC3iwOAC4rqAaRcWImgGBA3TBzeHgQkizsBgAMQAEcEJXtpC/XAAAAAASUVORK5CYII=")}', '.esit-tag-icon-inconsistentsound {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOBAMAAADtZjDiAAAAMFBMVEUAAAD///////////////////////////////////////////////////////////87TQQwAAAAD3RSTlMAEDBgcH+AkKDAz9/g7/BQA5AnAAAATElEQVQIW2NgYGAwYAAD5g8Q2v4D0/5FQO7/DzzPXjP85///gb9gPphmM7wJphk4H0FofwcIPZ8BQoONAdKmEHNA4gwMLFBzGRqAGAANYSMjbRkjVAAAAABJRU5ErkJggg==")}', // Outer text stroke workaround for labels '.esit-label::before {position:absolute;-webkit-text-stroke: 4px #000;text-stroke: 4px #000;left:0;z-index:-1;}', '.esit-label-blacklist::before {content:"Blacklisted";}', '.esit-label-video::before {content:"Video";}', '.esit-label-flash::before {content:"Flash";}', '.esit-label-deleted::before {content:"Deleted";}', // Label fades '.esit-container:hover .esit-label-fade {opacity:0}', '.esit-btn:focus .esit-label-fade, a:focus .esit-label-fade {opacity:0}', '.thumb_avatar .esit-label {opacity:0}', '.thumb_avatar .esit-container:hover .esit-label-fade {opacity:1}', '.thumb_avatar .esit-btn:focus .esit-label-fade, .thumb_avatar a:focus .esit-label-fade {opacity:1}', // Blacklist overlay '.esit-blacklist-items {opacity:0;transition: opacity 0.35s ease 0.18s;width: 140px; height: 140px;position: absolute;top:0;left:0;background: rgba(0,0,0,0.8);z-index:1;text-align:left;padding:5px;}', '.esit-blacklist-items p {color:white;font-size:11px;overflow:hidden;}', '.esit-bl-tag {white-space: nowrap;}', // Blacklist overlay fades '.esit-container:hover .esit-blacklist-items {opacity:1}', '.esit-btn:focus .esit-blacklist-items, a:focus .esit-blacklist-items {opacity:1}', '.thumb_avatar .esit-container:hover .esit-blacklist-items {transition-delay: 1s}', '.thumb_avatar .esit-btn:focus .esit-blacklist-items, .thumb_avatar a:focus .esit-blacklist-items {transition-delay: 1s}', // Blacklist link '.esit-blacklist-link {width:100%;height:30px;position:absolute;bottom:0;left:0;z-index:2;background:#333;display:flex;justify-content:center;align-items:center;font-size:15px;transition:opacity 0.35s ease 0.2s,background-color 0.3s ease;opacity:0;border-top-left-radius:inherit;border-top-right-radius:inherit;}', 'a:link .esit-blacklist-link, a:visited .esit-blacklist-link, a:focus .esit-blacklist-link {color:white}', '.esit-blacklist-link:hover, a:focus .esit-blacklist-link {opacity:1}', '.esit-blacklist-link:active, a:focus .esit-blacklist-link:hover {background:#555}', '.esit-preview-bg {background:hsla(0,0%,100%,0.12) !important}', '.esit-preview-border {width: 100%;height: 100%;box-sizing: border-box;border: 1px #B00 solid;position: absolute;z-index:3;left: 0;top: 0;border-radius: inherit;pointer-events:none;}', '.esit-fader {transition: opacity 0.7s ease}', '.esit-fade {opacity: 0}', '.esit-hide {display:none !important;}', '.esit-opaque {opacity: 1 !important;}', 'span.esit-container>button.esit-btn>img.esit-img-reset {' + propertiesToCopy.join(":initial !important;") + ':initial !important;}', // Adjust main CSS 'div#recent-user {min-width: 338px;width:338px;}', '#userpage .thumbs-user:last-child {margin-left:0}', '#userpage div.userpage_right {margin-left: 172px}', '.thumbs-user.section, #userpage .thumb {width: 156px;}', '#userpage .thumb_avatar :not(.esit-container) img {margin-bottom:3px}', '#userpage .level-blocked {padding-left: 5px; padding-right: 5px;}', 'span.thumb a {display: inline;}' ].join("\n"); var style = document.createElement("style"); style.type = 'text/css'; style.id = 'esit-informative-thumb-stylesheet'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); function formatBytes(nBytes, binaryUnits) { var units, oneKay; if (binaryUnits) { units = ["B", "KiB", "MiB", "GiB"]; oneKay = 1024; } else { units = ["B", "kB", "MB", "GB"]; oneKay = 1000; } var magnitude = Math.floor(Math.log(nBytes) / Math.log(oneKay)), decimals = Math.max(magnitude - 1, 0); return parseFloat((nBytes / Math.pow(oneKay, magnitude)).toFixed(decimals)) + units[magnitude]; // TB+ files will break this. Oh no! } function timer(lap){ if(lap) console.log(lap, 'in:', (performance.now()-timer.prev).toFixed(3) + 'ms'); timer.prev = performance.now(); } // public domain toBlob polyfill if (!HTMLCanvasElement.prototype.toBlob) { Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { value: function (callback, type, quality) { var binStr = atob( this.toDataURL(type, quality).split(',')[1] ), len = binStr.length, arr = new Uint8Array(len); for (var i = 0; i < len; i++ ) { arr[i] = binStr.charCodeAt(i); } callback( new Blob( [arr], {type: type || 'image/png'} ) ); } }); } function hoverAllBlacklisted(event) { if (event.ctrlKey) { for (var overlay of allBlacklistOverlays) { overlay.classList.add('esit-opaque'); } for (var label of allBlacklistLabels) { label.classList.add('esit-fade'); } multiOverlayFlag = true; document.removeEventListener("keydown", hoverAllBlacklisted); } } function unhoverAllBlacklisted(event) { if (multiOverlayFlag) { for (var overlay of allBlacklistOverlays) { overlay.classList.remove('esit-opaque'); } for (var label of allBlacklistLabels) { label.classList.remove('esit-fade'); } multiOverlayFlag = false; } } function checkCtrlKey(event) { if (!event.ctrlKey) { unhoverAllBlacklisted(event); document.addEventListener("keydown", hoverAllBlacklisted); } } function addKeyListener(event) { document.addEventListener("keydown", hoverAllBlacklisted); document.addEventListener("keyup", checkCtrlKey); } function removeKeyListener(event) { if(!thumbFocused && !thumbHovered) { document.removeEventListener("keydown", hoverAllBlacklisted); document.removeEventListener("keyup", checkCtrlKey); } } function createInfoThumb(pair) { if (thumbCount > thumbLimit) throw $break; var thumbs = $$("#p" + pair.key); if (thumbs.length === 0) return; var post = pair.value; // generate identicon (or black fill for deleted posts) var icon = document.createElement("canvas"), ctx = icon.getContext('2d'); icon.width = icon.height = iconSize; if (post.md5) { jdenticon.drawIcon(ctx, post.md5, iconSize); } else { ctx.fillStyle = "#000"; ctx.fillRect(0, 0, icon.width, icon.height); } var needsTypeThumb = post.file_ext === 'webm' || post.file_ext === 'swf' || post.status === 'deleted', needsBlacklistThumb = Post.blacklist_options.replace && post.blacklisted.length > 0; thumbs.each(function(thumb) { if (thumbCount > thumbLimit) throw $break; if (needsTypeThumb || needsBlacklistThumb) { thumbCount++; var esitContainer = document.createElement("span"), esitButton = document.createElement("button"), esitText = document.createElement("div"); esitContainer.className = 'esit-container'; esitButton.className = 'esit-btn'; esitText.className = 'esit-text'; esitContainer.appendChild(esitButton); esitButton.appendChild(esitText); var anchor = thumb.down('a'), score = thumb.down('.post-score'), originalImg = anchor.down('img'), img = originalImg.cloneNode(false), imgStyle = window.getComputedStyle(originalImg, null); if (score) { score.style.setProperty('font-size', '100%'); var scoreWidth = 150, blw = parseFloat(imgStyle.getPropertyValue('border-left-width')), brw = parseFloat(imgStyle.getPropertyValue('border-right-width')); scoreWidth += blw + brw; score.style.setProperty('width', scoreWidth + 'px'); } // copy certain properties from the img for (var pi=0; pi < propertiesToCopy.length; pi++) { var property = propertiesToCopy[pi], propertyValue = imgStyle.getPropertyValue(property); esitContainer.style.setProperty(property, propertyValue); } // now safe to reset those properties on the img img.className ='esit-img esit-img-reset'; var isVisible = thumb.offsetWidth > 0 || thumb.offsetHeight > 0; if (isVisible) { esitText.classList.add('esit-fade'); var placeholder = img.cloneNode(false); placeholderCount++; esitContainer.appendChild(placeholder); placeholder.className = 'esit-placeholder esit-fader'; placeholder.addEventListener("transitionend", function (event) { event.currentTarget.parentNode.removeChild(placeholder); esitText.classList.remove('esit-fade'); }); } anchor.replaceChild(esitContainer, originalImg); img.width = img.height = iconSize; icon.toBlob(function(blob) { var url = URL.createObjectURL(blob); img.addEventListener("load", function() { // Because Firefox fires a load event twice per img, fade transition occurs before the thumbnails are ready URL.revokeObjectURL(url); if (placeholder) { allPlaceholders.push(placeholder); if (allPlaceholders.length === placeholderCount) { for (var t=0; t < placeholderCount; t++) { allPlaceholders[t].classList.add('esit-fade'); } } } }); img.src = url; }); if (needsTypeThumb) { var typeLabel = document.createElement("span"); typeLabel.className = 'esit-label'; esitText.appendChild(typeLabel); esitText.title = img.title; if (post.status === 'deleted') { if (needsBlacklistThumb) typeLabel.classList.toggle('esit-hide'); typeLabel.textContent = 'Deleted'; typeLabel.classList.add('esit-label-deleted'); } else { var postFileSize = document.createElement("span"), postDimensions = document.createElement("span"), tagIconBox = document.createElement("span"); postFileSize.className = 'esit-file-size'; postDimensions.className = 'esit-dimensions'; tagIconBox.className = 'esit-tag-icon-box'; var possibleSoundTags = ['sound', 'no_sound', 'sound_warning'], applicableSoundTags = []; for(var tag of post.tags) { if (possibleSoundTags.indexOf(tag) > -1) applicableSoundTags.push(tag); } if (applicableSoundTags.indexOf('sound') > -1 && applicableSoundTags.indexOf('no_sound') > -1) { tagIconBox.classList.add('esit-tag-icon-inconsistentsound'); } else if (applicableSoundTags.indexOf('sound_warning') > -1) { tagIconBox.classList.add('esit-tag-icon-soundwarning'); } else if (applicableSoundTags.indexOf('sound') > -1) { tagIconBox.classList.add('esit-tag-icon-sound'); } else if (applicableSoundTags.indexOf('no_sound') > -1) { tagIconBox.classList.add('esit-tag-icon-nosound'); } if (needsBlacklistThumb) { typeLabel.classList.add('esit-hide'); postFileSize.classList.add('esit-hide'); postDimensions.classList.add('esit-hide'); tagIconBox.classList.add('esit-hide'); } else { typeLabel.classList.add('esit-label-fade'); } esitText.appendChild(postFileSize); esitText.appendChild(postDimensions); esitText.appendChild(tagIconBox); postFileSize.textContent = formatBytes(post.file_size, useBinaryUnits); postDimensions.textContent = post.width + "x" + post.height; switch (post.file_ext) { case 'swf': typeLabel.textContent = 'Flash'; typeLabel.classList.add('esit-label-flash'); break; default: typeLabel.textContent = 'Video'; typeLabel.classList.add('esit-label-video'); } } } if (needsBlacklistThumb) { var link = document.createElement("span"); link.textContent = 'Link'; link.classList.add('esit-blacklist-link'); esitContainer.appendChild(link); link.title = img.title; var previewBorder = document.createElement("span"); previewBorder.className = 'esit-preview-border esit-hide'; var containerStyle = window.getComputedStyle(esitContainer, null), noBTS = containerStyle.getPropertyValue('border-top-style') === 'none', noBBS = containerStyle.getPropertyValue('border-bottom-style') === 'none', topR = noBTS ? 'inherit' : '0', bottomR = noBBS ? 'inherit' : '0'; previewBorder.style.setProperty('border-radius', [topR, topR, bottomR, bottomR].join(' ')); esitContainer.appendChild(previewBorder); var blacklistLabel = document.createElement("span"); blacklistLabel.className = 'esit-label esit-label-blacklist esit-label-fade'; blacklistLabel.textContent = 'Blacklisted'; esitText.appendChild(blacklistLabel); var blacklistedFor = document.createElement("div"); blacklistedFor.className = 'esit-blacklist-items'; for (var i=0; i < post.blacklisted.length; i++) { var blItem = document.createElement("p"); blItem.innerHTML = "" + post.blacklisted[i].tags.join(" ") + ""; blacklistedFor.appendChild(blItem); } esitText.removeAttribute("title"); esitText.appendChild(blacklistedFor); esitButton.addEventListener("click", function (event) { if (event.button !== 0) { return; } event.preventDefault(); var togglePreview = function (post, previewThumbs) { previewThumbs.each(function(thumb) { var thumbButton = thumb.down('.esit-btn'); if (!thumbButton) return; var img = thumb.down('img.esit-img'), toToggle = thumb.querySelectorAll('.esit-label, .esit-file-size, .esit-dimensions, .esit-tag-icon-box, .esit-blacklist-items, .esit-preview-border'); if (post.file_ext !== 'webm' && post.file_ext !== 'swf' && post.status !== 'deleted') { var preview = thumb.down('.esit-preview'); if (!preview) { preview = document.createElement("img"); preview.width = post.preview_width; preview.height = post.preview_height; preview.src = post.preview_url; preview.alt = ""; preview.className = 'esit-preview esit-hide'; var thumbText = thumb.down('.esit-text'); thumbButton.insertBefore(preview, thumbText); } var swap = img.alt; img.alt = preview.alt; preview.alt = swap; img.classList.toggle('esit-hide'); preview.classList.toggle('esit-hide'); thumbButton.classList.toggle('esit-preview-bg'); } else { var typeLabel = thumb.querySelector('.esit-label:not(.esit-label-blacklist)'); if (typeLabel.classList.contains('esit-label-fade')) { typeLabel.classList.remove('esit-label-fade'); } else { setTimeout(function(){typeLabel.classList.add('esit-label-fade');}, 1000); } } esitText.title = esitText.title ? "" : link.title; for (var i=0; i < toToggle.length; i++) { toToggle[i].classList.toggle('esit-hide'); } }); }; if(event.ctrlKey) { var targetThumbPreviewState = blacklistLabel.classList.contains('esit-hide'); Post.posts.each(function(pair) { var post = pair.value; if (post.blacklisted.length === 0) return; var postThumbs = $$("#p" + pair.key); if (postThumbs.length === 0) return; var firstThumbBlacklistLabel = postThumbs[0].down('.esit-label-blacklist'), previewState = firstThumbBlacklistLabel.classList.contains('esit-hide'); if(targetThumbPreviewState == previewState) { togglePreview(post, postThumbs); } }); } else { togglePreview(post, thumbs); } }); esitContainer.addEventListener("mouseenter", function(e) { thumbHovered = true; addKeyListener(e); }); anchor.addEventListener("focusin", function(e) { thumbFocused = true; addKeyListener(e); }); esitContainer.addEventListener("mouseleave", function(e) { thumbHovered = false; if(!thumbHovered && !thumbFocused) unhoverAllBlacklisted(e); }); anchor.addEventListener("focusout", function(e) { thumbFocused = false; if(!thumbHovered && !thumbFocused) unhoverAllBlacklisted(e); }); esitContainer.addEventListener("mouseleave", function(e) { thumbHovered = false; removeKeyListener(e); }); anchor.addEventListener("focusout", function(e) { thumbFocused = false; removeKeyListener(e); }); if (thumb.classList.contains('thumb_avatar')) { blacklistLabel.classList.remove('esit-label-fade'); var fadeOutTimer; var fadeInOut = function (event) { blacklistLabel.classList.add('esit-label-fade'); // 900ms = avatar overlay transition delay - (normal overlay transition delay - normal label delay) fadeOutTimer = setTimeout(function(){blacklistLabel.classList.remove('esit-label-fade');}, 900); }; esitContainer.addEventListener("mouseenter", fadeInOut); anchor.addEventListener("focusin", fadeInOut); esitContainer.addEventListener("mouseleave", function(){clearTimeout(fadeOutTimer);}); anchor.addEventListener("focusout", function(){clearTimeout(fadeOutTimer);}); } } anchor.addEventListener("mouseup", function (event) { event.currentTarget.blur(); }); esitButton.addEventListener("mouseup", function (event) { event.currentTarget.blur(); }); esitButton.appendChild(img); } }); } //timer(); Post.posts.each(createInfoThumb); allBlacklistOverlays = document.querySelectorAll('.esit-blacklist-items'); allBlacklistLabels = document.querySelectorAll('.esit-label-blacklist'); //timer('All thumbs processed'); })();