GitHub Image Previewer

Previews various image formats, including JPG, PNG, GIF, BMP, TIFF, WebP, SVG, and ICO.

当前为 2024-12-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Image Previewer
  3. // @description Previews various image formats, including JPG, PNG, GIF, BMP, TIFF, WebP, SVG, and ICO.
  4. // @icon https://github.githubassets.com/favicons/favicon-dark.svg
  5. // @version 1.0
  6. // @author afkarxyz
  7. // @namespace https://github.com/afkarxyz/misc-scripts/
  8. // @supportURL https://github.com/afkarxyz/misc-scripts/issues
  9. // @license MIT
  10. // @match https://github.com/*
  11. // @grant none
  12. // @run-at document-end
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. const GitHubImagePreviewer = {
  19. supportedImageFormats: /\.(jpe?g|png|gif|bmp|ico|tiff?|webp|svg)$/i,
  20. imagePreviewContainer: null,
  21. imagePreviewElement: null,
  22. imagePreviewTitle: null,
  23. imagePreviewMetadata: null,
  24.  
  25. init() {
  26. this.createImagePreviewElements();
  27. this.attachImagePreviewListeners();
  28. this.disableGitHubTooltips();
  29. },
  30.  
  31. createImagePreviewElements() {
  32. this.imagePreviewContainer = document.createElement('div');
  33. this.imagePreviewContainer.style.cssText = `
  34. position: fixed;
  35. z-index: 9999;
  36. background: white;
  37. border: 1px solid #d1d5da;
  38. border-radius: 3px;
  39. box-shadow: 0 1px 5px rgba(27,31,35,0.15);
  40. display: none;
  41. padding: 10px;
  42. max-width: 300px;
  43. max-height: 300px;
  44. color: #24292e;
  45. `;
  46.  
  47. this.imagePreviewTitle = document.createElement('div');
  48. this.imagePreviewTitle.style.cssText = `
  49. font-weight: bold;
  50. margin-bottom: 5px;
  51. white-space: nowrap;
  52. overflow: hidden;
  53. text-overflow: ellipsis;
  54. `;
  55.  
  56. this.imagePreviewElement = document.createElement('img');
  57. this.imagePreviewElement.style.cssText = `
  58. max-width: 100%;
  59. max-height: 200px;
  60. display: block;
  61. margin: 0 auto;
  62. background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
  63. linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
  64. linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
  65. linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
  66. background-size: 20px 20px;
  67. background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
  68. `;
  69.  
  70. this.imagePreviewMetadata = document.createElement('div');
  71. this.imagePreviewMetadata.style.cssText = `
  72. font-size: 12px;
  73. color: #586069;
  74. margin-top: 5px;
  75. text-align: center;
  76. `;
  77.  
  78. this.imagePreviewContainer.appendChild(this.imagePreviewTitle);
  79. this.imagePreviewContainer.appendChild(this.imagePreviewElement);
  80. this.imagePreviewContainer.appendChild(this.imagePreviewMetadata);
  81. document.body.appendChild(this.imagePreviewContainer);
  82. },
  83.  
  84. attachImagePreviewListeners() {
  85. document.addEventListener('mouseover', this.handleImageMouseOver.bind(this));
  86. document.addEventListener('mouseout', this.handleImageMouseOut.bind(this));
  87. document.addEventListener('mousemove', this.handleImageMouseMove.bind(this));
  88. },
  89.  
  90. disableGitHubTooltips() {
  91. const style = document.createElement('style');
  92. style.textContent = `
  93. .tooltipped::before, .tooltipped::after {
  94. display: none !important;
  95. }
  96. `;
  97. document.head.appendChild(style);
  98. },
  99.  
  100. handleImageMouseOver(event) {
  101. const target = event.target.closest('a');
  102. if (target && this.supportedImageFormats.test(target.href)) {
  103. if (target.hasAttribute('title')) {
  104. target.dataset.originalTitle = target.getAttribute('title');
  105. target.removeAttribute('title');
  106. }
  107. this.showImagePreview(target);
  108. }
  109. },
  110.  
  111. handleImageMouseOut() {
  112. this.hideImagePreview();
  113. },
  114.  
  115. handleImageMouseMove(event) {
  116. if (this.imagePreviewContainer.style.display !== 'none') {
  117. const x = event.pageX + 15;
  118. const y = event.pageY + 15;
  119. this.imagePreviewContainer.style.left = `${x}px`;
  120. this.imagePreviewContainer.style.top = `${y}px`;
  121. }
  122. },
  123.  
  124. showImagePreview(target) {
  125. const imageUrl = target.href.replace('/blob/', '/raw/');
  126. this.imagePreviewTitle.textContent = target.dataset.originalTitle || target.getAttribute('title') || target.textContent;
  127. this.imagePreviewElement.src = imageUrl;
  128. this.imagePreviewContainer.style.display = 'block';
  129.  
  130. this.imagePreviewElement.onload = this.updateImageMetadata.bind(this);
  131. },
  132.  
  133. hideImagePreview() {
  134. this.imagePreviewContainer.style.display = 'none';
  135. },
  136.  
  137. updateImageMetadata() {
  138. const width = this.imagePreviewElement.naturalWidth;
  139. const height = this.imagePreviewElement.naturalHeight;
  140. this.imagePreviewMetadata.textContent = `${width} x ${height} px`;
  141. },
  142. };
  143.  
  144. GitHubImagePreviewer.init();
  145. })();