// ==UserScript== // @name GitHub Image Preview // @version 2.0.8 // @description A userscript that adds clickable image thumbnails // @license MIT // @author Rob Garrison // @namespace https://github.com/Mottie // @match https://github.com/* // @run-at document-idle // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @connect github.com // @connect githubusercontent.com // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=1108163 // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-image-preview.user.js // @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-image-preview.user.js // @supportURL https://github.com/Mottie/GitHub-userscripts/issues // ==/UserScript== (() => { "use strict"; GM_addStyle(` .ghip-wrapper .ghip-content { display:none; } .ghip-wrapper.ghip-show-previews .ghip-content { display:flex; width:100%; } .ghip-wrapper.ghip-show-previews .Box-row { border:0 !important; background-color:transparent !important; } .ghip-show-previews .Box-row:not(.ghsc-header):not(.hidden) > div[role] { display:none !important; } .ghip-wrapper.ghip-show-previews svg.ghip-non-image, .ghip-wrapper.ghip-show-previews img.ghip-non-image { height:80px; width:80px; margin-top:15px; } .ghip-wrapper.ghip-show-previews .image { width:100%; position:relative; overflow:hidden; text-align:center; } .ghip-wrapper.ghip-tiled .Box-row:not(.ghsc-header):not(.hidden) { width:24.5%; max-width:24.5%; justify-content:center; overflow:hidden; display:inline-flex !important; padding:8px !important; } .ghip-wrapper.ghip-tiled .image { height:180px; margin:12px !important; } .ghip-wrapper.ghip-tiled .image img, .ghip-wrapper svg { max-height:130px; max-width:90%; } /* zoom doesn't work in Firefox, but "-moz-transform:scale(3);" doesn't limit the size of the image, so it overflows */ .ghip-wrapper.ghip-tiled .image:hover img:not(.ghip-non-image) { zoom:3; } .ghip-wrapper.ghip-fullw .image { height:unset; padding-bottom:0; } .ghip-wrapper .image span { display:block; position:relative; } .ghip-wrapper .ghip-folder { margin-bottom:2em; } .image .ghip-file-type { font-size:40px; top:-2em; left:0; z-index:2; position:relative; text-shadow:1px 1px 1px #fff, -1px 1px 1px #fff, 1px -1px 1px #fff, -1px -1px 1px #fff; } .ghip-wrapper h4 { overflow:hidden; white-space:nowrap; text-overflow:ellipsis; margin:0 12px 5px; } .ghip-wrapper img, .ghip-wrapper svg { max-width:95%; } .ghip-wrapper img.error { border:5px solid red; border-radius:32px; } .btn.ghip-tiled > *, .btn.ghip-fullw > *, .ghip-wrapper iframe { pointer-events:none; vertical-align:baseline; } .ghip-content span.exploregrid-item .ghip-file-name { cursor:default; } /* override GitHub-Dark styles */ .ghip-wrapper img[src*='octocat-spinner'], img[src='/images/spinner.gif'] { width:auto !important; height:auto !important; } .ghip-wrapper td .simplified-path { color:#888 !important; } `); // supported img types const imgExt = /(png|jpg|jpeg|gif|tif|tiff|bmp|webp)$/i; const svgExt = /svg$/i; const spinner = "https://github.githubassets.com/images/spinners/octocat-spinner-32.gif"; const folderIconClasses = ` .octicon-file-directory, .octicon-file-symlink-directory, .octicon-file-submodule`; const tiled = ` `; const fullWidth = ` `; const imgTemplate = [ // not using backticks here; we need to minimize extra whitespace everywhere "", "${content}", "" ].join(""); const spanTemplate = [ "", "${content}", "" ].join(""); const contentWrap = document.createElement("div"); contentWrap.className = "ghip-content"; function setupWraper() { // set up wrapper const grid = $("div[role='grid']", $("#files").parentElement); if (grid) { grid.parentElement.classList.add("ghip-wrapper"); } } function addToggles() { if ($(".gh-img-preview") || !$(".file-navigation")) { return; } const div = document.createElement("div"); const btn = `btn BtnGroup-item tooltipped tooltipped-n" aria-label="Show`; div.className = "BtnGroup ml-2 gh-img-preview"; div.innerHTML = ` `; $(".file-navigation").appendChild(div); $(".ghip-tiled", div).addEventListener("click", event => { openView("tiled", event); }); $(".ghip-fullw", div).addEventListener("click", event => { openView("fullw", event); }); } function setInitState() { const state = GM_getValue("gh-image-preview"); if (state) { openView(state); } } function openView(name, event) { setupWraper(); const wrap = $(".ghip-wrapper"); if (!wrap) { return; } const el = $(".ghip-" + name); if (el) { if (event) { el.classList.toggle("selected"); if (!el.classList.contains("selected")) { return showList(); } } showPreview(name); } } function showPreview(name) { buildPreviews(); const wrap = $(".ghip-wrapper"); const selected = "ghip-" + name; const notSelected = "ghip-" + (name === "fullw" ? "tiled" : "fullw"); wrap.classList.add("ghip-show-previews", selected); $(".btn." + selected).classList.add("selected"); wrap.classList.remove(notSelected); $(".btn." + notSelected).classList.remove("selected"); GM_setValue("gh-image-preview", name); } function showList() { const wrap = $(".ghip-wrapper"); wrap.classList.remove("ghip-show-previews", "ghip-tiled", "ghip-fullw"); $(".btn.ghip-tiled").classList.remove("selected"); $(".btn.ghip-fullw").classList.remove("selected"); GM_setValue("gh-image-preview", ""); } function buildPreviews() { const wrap = $(".ghip-wrapper"); if (!wrap) { return; } $$(".Box-row", wrap).forEach(row => { let content = ""; // not every submodule includes a link; reference examples from // see https://github.com/electron/electron/tree/v1.1.1/vendor const el = $("div[role='rowheader'] a, div[role='rowheader'] span[title]", row); const url = el && el.nodeName === "A" ? el.href : ""; // use innerHTML because some links include path - see "third_party/lss" const fileName = el && el.textContent.trim() || ""; // add link color const title = (type = "file-name") => `