GitHub Image Preview

A userscript that adds clickable image thumbnails

当前为 2016-07-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Image Preview
  3. // @version 1.0.9
  4. // @description A userscript that adds clickable image thumbnails
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace http://github.com/Mottie
  7. // @include https://github.com/*
  8. // @run-at document-idle
  9. // @grant GM_addStyle
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_xmlhttpRequest
  13. // @connect github.com
  14. // @connect githubusercontent.com
  15. // @author Rob Garrison
  16. // ==/UserScript==
  17. /* global GM_addStyle, GM_getValue, GM_setValue, GM_xmlhttpRequest */
  18. /*jshint unused:true, esnext:true */
  19. (() => {
  20. "use strict";
  21.  
  22. GM_addStyle(`
  23. table.files tr.ghip-image-previews, table.files.ghip-show-previews tbody tr.js-navigation-item { display:none; }
  24. table.files.ghip-show-previews tr.ghip-image-previews { display:table-row; }
  25. table.files.ghip-show-previews .ghip-non-image { height:80px; margin-top:15px; opacity:.2; }
  26. table.files.ghip-show-previews .image { position:relative; overflow:hidden; text-align:center; }
  27. .ghip-image-previews .image { padding:10px; }
  28. table.files.ghip-tiled .image { width:21.9%; }
  29. table.files.ghip-tiled .image .border-wrap img, .ghip-image-previews .border-wrap svg { max-height:130px; }
  30. table.files.ghip-fullw .image { width:97%; height:auto; }
  31. /* zoom doesn't work in Firefox, but "-moz-transform:scale(3);" doesn't limit the size of the image, so it overflows */
  32. table.files.ghip-tiled .image:hover img:not(.ghip-non-image) { zoom:3; }
  33. .ghip-image-previews .border-wrap img, .ghip-image-previews .border-wrap svg { max-width:95%; }
  34. .ghip-image-previews .border-wrap h4 { white-space:nowrap; text-overflow:ellipsis; margin-bottom:5px; }
  35. .ghip-image-previews .border-wrap h4.ghip-file-name { overflow:hidden; }
  36. .btn.ghip-tiled > *, .btn.ghip-fullw > *, .ghip-image-previews iframe { pointer-events:none; vertical-align:baseline; }
  37. .image .ghip-file-type { font-size:30px; top:-1.8em; position:relative; z-index:2; }
  38. .ghip-content span.exploregrid-item .ghip-file-name { cursor:default; }
  39. /* override GitHub-Dark styles */
  40. table.files img[src*='octocat-spinner'], img[src='/images/spinner.gif'] { width:auto !important; height:auto !important; }
  41. table.files td .simplified-path { color:#888 !important; }
  42. `);
  43.  
  44. // timer needed for file list to update?
  45. let timer,
  46. busy = false;
  47.  
  48. // supported img types
  49. const imgExt = /(png|jpg|jpeg|gif|tif|tiff|bmp|webp)$/i,
  50. svgExt = /svg$/i,
  51.  
  52. tiled = `
  53. <svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16">
  54. <path d="M0 0h7v7H0zM9 9h7v7H9zM9 0h7v7H9zM0 9h7v7H0z"/>
  55. </svg>
  56. `,
  57.  
  58. fullWidth = `
  59. <svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16">
  60. <path d="M0 0h16v7H0zM0 9h16v7H0z"/>
  61. </svg>
  62. `,
  63.  
  64. imgTemplate = [
  65. "<a href='${url}' class='exploregrid-item image js-navigation-open' rel='nofollow'>",
  66. "<span class='border-wrap'>${image}</span>",
  67. "</a>"
  68. ].join(""),
  69.  
  70. spanTemplate = [
  71. "<span class='exploregrid-item image'>",
  72. "<span class='border-wrap'>${image}</span>",
  73. "</span>"
  74. ].join("");
  75.  
  76. function addToggles() {
  77. if ($(".gh-img-preview")) { return; }
  78. busy = true;
  79. let div = document.createElement("div"),
  80. btn = `btn btn-sm tooltipped tooltipped-n" aria-label="Show`;
  81. div.className = "btn-group right gh-img-preview";
  82. div.innerHTML = `
  83. <div class="ghip-tiled ${btn} tiled files with image preview">${tiled}</div>
  84. <div class="ghip-fullw ${btn} full width files with image preview">${fullWidth}</div>
  85. `;
  86. $(".file-navigation").appendChild(div);
  87.  
  88. $(".ghip-tiled", div).addEventListener("click", function() {
  89. openView("tiled");
  90. });
  91. $(".ghip-fullw", div).addEventListener("click", function() {
  92. openView("fullw");
  93. });
  94. busy = false;
  95. }
  96.  
  97. function setInitState() {
  98. let view = GM_getValue("gh-image-preview");
  99. if (view) {
  100. openView(view);
  101. }
  102. }
  103.  
  104. function openView(name) {
  105. let el = $(".ghip-" + name);
  106. if (el) {
  107. el.classList.toggle("selected");
  108. if (el.classList.contains("selected")) {
  109. GM_setValue("gh-image-preview", name);
  110. showPreview(name);
  111. } else {
  112. GM_setValue("gh-image-preview", "");
  113. showList();
  114. }
  115. }
  116. }
  117.  
  118. function showPreview(size) {
  119. buildPreviews();
  120. let table = $("table.files"),
  121. btn1 = "ghip-" + size,
  122. btn2 = "ghip-" + (size === "fullw" ? "tiled" : "fullw");
  123. table.classList.add(...["ghip-show-previews", btn1]);
  124. $(".btn." + btn1).classList.add("selected");
  125. table.classList.remove(btn2);
  126. $(".btn." + btn2).classList.remove("selected");
  127. }
  128.  
  129. function showList() {
  130. $("table.files").classList.remove(...["ghip-show-previews", "ghip-tiled", "ghip-fullw"]);
  131. $(".btn.ghip-tiled").classList.remove("selected");
  132. $(".btn.ghip-fullw").classList.remove("selected");
  133. }
  134.  
  135. function buildPreviews() {
  136. busy = true;
  137. let template, url, temp, noExt,
  138. indx = 0,
  139. row = document.createElement("tr"),
  140. imgs = "<td colspan='4' class='ghip-content'>",
  141. table = $("table.files tbody:last-child"),
  142. files = $$("tr.js-navigation-item"),
  143. len = files.length;
  144. row.className = "ghip-image-previews";
  145. if ($(".ghip-image-previews")) {
  146. temp = $(".ghip-image-previews");
  147. temp.parentNode.removeChild(temp);
  148. }
  149. if (table) {
  150. for (indx = 0; indx < len; indx++) {
  151. // not every submodule includes a link; reference examples from
  152. // see https://github.com/electron/electron/tree/v1.1.1/vendor
  153. temp = $("td.content a", files[indx]) || $("td.content span span", files[indx]);
  154. // use innerHTML because some links include path - see "third_party/lss"
  155. template = temp ? temp.innerHTML.trim() + "</h4>" : "";
  156. // temp = temp && $("a", temp);
  157. url = temp && temp.nodeName === "A" ? temp.href : "";
  158. // add link color
  159. template = "<h4 class='ghip-file-name" + (url ? " text-blue" : "") + "'>" + template;
  160. if (imgExt.test(url)) {
  161. // *** image preview ***
  162. template += "<img src='" + url + "?raw=true'/>";
  163. imgs += imgTemplate.replace("${url}", url).replace("${image}", template);
  164. } else if (svgExt.test(url)) {
  165. // *** svg preview ***
  166. // loaded & encoded because GitHub sets content-type headers as a string
  167. temp = url.substring(url.lastIndexOf("/") + 1, url.length);
  168. template += "<img data-svg-holder='" + temp + "' alt='" + temp + "' />";
  169. imgs += updateTemplate(url, template);
  170. getSVG(url + "?raw=true");
  171. } else {
  172. // *** non-images (file/folder icons) ***
  173. temp = $("td.icon svg", files[indx]);
  174. if (temp) {
  175. // non-files svg class: "octicon-file-directory" or "octicon-file-submodule"
  176. noExt = temp.classList.contains("octicon-file-directory") ||
  177. temp.classList.contains("octicon-file-submodule");
  178. // add xmlns otherwise the svg won't work inside an img
  179. // GitHub doesn't include this attribute on any svg octicons
  180. temp = temp.outerHTML.replace("<svg", "<svg xmlns='http://www.w3.org/2000/svg'");
  181. // include "leaflet-tile-container" to invert icon for GitHub-Dark
  182. template += "<span class='leaflet-tile-container'>" +
  183. "<img class='ghip-non-image' src='data:image/svg+xml;base64," + window.btoa(temp) + "'/>" +
  184. "</span>";
  185. // get file name + extension
  186. temp = url.substring(url.lastIndexOf("/") + 1, url.length);
  187. // don't include extension for folders, or files with no extension, or
  188. // files starting with a "." (e.g. ".gitignore")
  189. if (!noExt && temp.indexOf(".") > 0) {
  190. template += "<h4 class='ghip-file-type'>" +
  191. temp.substring(temp.lastIndexOf(".") + 1, temp.length).toUpperCase() + "</h4>";
  192. }
  193. if (url) {
  194. imgs += updateTemplate(url, template);
  195. } else {
  196. // empty url; use non-link template
  197. // see "depot_tools @ 4fa73b8" at https://github.com/electron/electron/tree/v1.1.1/vendor
  198. imgs += updateTemplate(url, template, spanTemplate);
  199. }
  200. } else if (files[indx].classList.contains("up-tree")) {
  201. // Up tree link
  202. temp = $("td:nth-child(2) a", files[indx]);
  203. url = temp ? temp.href : "";
  204. if (url) {
  205. imgs += updateTemplate(url, "<h4 class='text-blue'>&middot;&middot</h4>");
  206. }
  207. }
  208. }
  209. }
  210. row.innerHTML = imgs + "</td>";
  211. table.appendChild(row);
  212. }
  213. busy = false;
  214. }
  215.  
  216. function updateTemplate(url, img, tmpl) {
  217. return (tmpl || imgTemplate)
  218. .replace("${url}", url)
  219. .replace("${image}", img);
  220. }
  221.  
  222. function getSVG(url) {
  223. GM_xmlhttpRequest({
  224. method: "GET",
  225. url: url,
  226. onload : function(response) {
  227. busy = true;
  228. let encoded,
  229. url = response.finalUrl,
  230. file = url.substring(url.lastIndexOf("/") + 1, url.length),
  231. target = $("[data-svg-holder='" + file+ "']");
  232. if (target) {
  233. encoded = window.btoa(response.responseText);
  234. target.src = "data:image/svg+xml;base64," + encoded;
  235. }
  236. busy = false;
  237. }
  238. });
  239. }
  240.  
  241. function $(selector, el) {
  242. return (el || document).querySelector(selector);
  243. }
  244. function $$(selector, el) {
  245. return Array.from((el || document).querySelectorAll(selector));
  246. }
  247.  
  248. function init() {
  249. if ($("table.files")) {
  250. addToggles();
  251. setInitState();
  252. }
  253. }
  254.  
  255. // DOM targets - to detect GitHub dynamic ajax page loading
  256. $$("#js-repo-pjax-container, .context-loader-container, [data-pjax-container]").forEach((target) => {
  257. new MutationObserver((mutations) => {
  258. mutations.forEach((mutation) => {
  259. // preform checks before adding code wrap to minimize function calls
  260. if (!busy && mutation.target === target) {
  261. clearTimeout(timer);
  262. timer = setTimeout(init, 200);
  263. }
  264. });
  265. }).observe(target, {
  266. childList: true,
  267. subtree: true
  268. });
  269. });
  270.  
  271. init();
  272.  
  273. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址