Previews various image formats, including JPG, PNG, GIF, BMP, TIFF, WebP, SVG, and ICO.
// ==UserScript== // @name GitHub Image Previewer // @description Previews various image formats, including JPG, PNG, GIF, BMP, TIFF, WebP, SVG, and ICO. // @icon https://github.githubassets.com/favicons/favicon-dark.svg // @version 1.1 // @author afkarxyz // @namespace https://github.com/afkarxyz/misc-scripts/ // @supportURL https://github.com/afkarxyz/misc-scripts/issues // @license MIT // @match https://github.com/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; const GitHubImagePreviewer = { supportedImageFormats: /\.(jpe?g|png|gif|bmp|ico|tiff?|webp|svg)$/i, imagePreviewContainer: null, imagePreviewElement: null, imagePreviewTitle: null, imagePreviewMetadata: null, init() { this.createImagePreviewElements(); this.attachImagePreviewListeners(); this.disableGitHubTooltips(); }, createImagePreviewElements() { this.imagePreviewContainer = document.createElement('div'); this.imagePreviewContainer.style.cssText = ` position: fixed; z-index: 9999; background: white; border: 1px solid #d1d5da; border-radius: 3px; box-shadow: 0 1px 5px rgba(27,31,35,0.15); display: none; padding: 10px; max-width: 300px; max-height: 300px; color: #24292e; `; this.imagePreviewTitle = document.createElement('div'); this.imagePreviewTitle.style.cssText = ` font-weight: bold; margin-bottom: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; this.imagePreviewElement = document.createElement('img'); this.imagePreviewElement.style.cssText = ` max-width: 100%; max-height: 200px; display: block; margin: 0 auto; background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; `; this.imagePreviewMetadata = document.createElement('div'); this.imagePreviewMetadata.style.cssText = ` font-size: 12px; color: #586069; margin-top: 5px; text-align: center; `; this.imagePreviewContainer.appendChild(this.imagePreviewTitle); this.imagePreviewContainer.appendChild(this.imagePreviewElement); this.imagePreviewContainer.appendChild(this.imagePreviewMetadata); document.body.appendChild(this.imagePreviewContainer); }, attachImagePreviewListeners() { document.addEventListener('mouseover', this.handleImageMouseOver.bind(this)); document.addEventListener('mouseout', this.handleImageMouseOut.bind(this)); document.addEventListener('mousemove', this.handleImageMouseMove.bind(this)); }, disableGitHubTooltips() { const style = document.createElement('style'); style.textContent = ` .tooltipped::before, .tooltipped::after { display: none !important; } `; document.head.appendChild(style); }, handleImageMouseOver(event) { const target = event.target.closest('a'); if (target && this.supportedImageFormats.test(target.href)) { if (target.hasAttribute('title')) { target.dataset.originalTitle = target.getAttribute('title'); target.removeAttribute('title'); } this.showImagePreview(target); } }, handleImageMouseOut() { this.hideImagePreview(); }, handleImageMouseMove(event) { if (this.imagePreviewContainer.style.display !== 'none') { const mouseX = event.clientX; const mouseY = event.clientY; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const previewWidth = this.imagePreviewContainer.offsetWidth; const previewHeight = this.imagePreviewContainer.offsetHeight; let x = mouseX + 15; let y = mouseY + 15; if (x + previewWidth > viewportWidth) { x = mouseX - previewWidth - 15; } if (y + previewHeight > viewportHeight) { y = mouseY - previewHeight - 15; } this.imagePreviewContainer.style.left = `${x}px`; this.imagePreviewContainer.style.top = `${y}px`; } }, showImagePreview(target) { const imageUrl = target.href.replace('/blob/', '/raw/'); this.imagePreviewTitle.textContent = target.dataset.originalTitle || target.getAttribute('title') || target.textContent; this.imagePreviewElement.src = imageUrl; this.imagePreviewContainer.style.display = 'block'; this.imagePreviewElement.onload = this.updateImageMetadata.bind(this); }, hideImagePreview() { this.imagePreviewContainer.style.display = 'none'; }, updateImageMetadata() { const width = this.imagePreviewElement.naturalWidth; const height = this.imagePreviewElement.naturalHeight; this.imagePreviewMetadata.textContent = `${width} x ${height} px`; }, }; GitHubImagePreviewer.init(); })();