GitHub Image Resizer

Convert GitHub markdown image uploads to HTML <img> tags with customizable width; supports drag-drop, paste, and attachment button uploads.

  1. // ==UserScript==
  2. // @name GitHub Image Resizer
  3. // @namespace http://miked49er.github.io/
  4. // @version 1.3
  5. // @description Convert GitHub markdown image uploads to HTML <img> tags with customizable width; supports drag-drop, paste, and attachment button uploads.
  6. // @author Mike Deiters
  7. // @match https://github.com/*
  8. // @license MIT
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_registerMenuCommand
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const DEFAULT_WIDTH = 300;
  18.  
  19. function getWidth() {
  20. return GM_getValue('imageWidth', DEFAULT_WIDTH);
  21. }
  22.  
  23. function setWidth() {
  24. const current = getWidth();
  25. const input = prompt('Enter desired image width in px (number only):', current);
  26. const value = parseInt(input, 10);
  27. if (!isNaN(value) && value > 0) {
  28. GM_setValue('imageWidth', value);
  29. alert(`Image width set to ${value}px`);
  30. } else {
  31. alert('Invalid width.');
  32. }
  33. }
  34.  
  35. GM_registerMenuCommand('Set Image Width', setWidth);
  36.  
  37. const imageMarkdownRegex = /!\[(.*?)\]\((https:\/\/(?:user-images\.githubusercontent\.com|github\.com\/user-attachments\/assets)\/[^\)]+)\)/g;
  38.  
  39. function replaceImagesInTextarea(textarea) {
  40. let content = textarea.value;
  41. let modified = false;
  42.  
  43. const width = getWidth();
  44.  
  45. content = content.replace(imageMarkdownRegex, (match, alt, url) => {
  46. modified = true;
  47. return `<img src="${url}" alt="${alt}" width="${width}">`;
  48. });
  49.  
  50. if (modified) {
  51. textarea.value = content
  52. }
  53. }
  54.  
  55. function monitorTextarea(textarea) {
  56. if (textarea.dataset.monitored) return;
  57. textarea.dataset.monitored = "true";
  58.  
  59. // Check initial content
  60. replaceImagesInTextarea(textarea);
  61.  
  62. // MutationObserver for DOM changes (not .value though!)
  63. const observer = new MutationObserver(() => {
  64. replaceImagesInTextarea(textarea);
  65. });
  66.  
  67. observer.observe(textarea, { characterData: true, childList: true, subtree: true });
  68.  
  69. // Input still useful for typing/paste
  70. textarea.addEventListener('input', () => replaceImagesInTextarea(textarea));
  71.  
  72. // 🧠 Polling loop to detect programmatic .value changes (used by drag-drop)
  73. let lastValue = textarea.value;
  74. setInterval(() => {
  75. if (textarea.value !== lastValue) {
  76. lastValue = textarea.value;
  77. replaceImagesInTextarea(textarea);
  78. }
  79. }, 800);
  80. }
  81.  
  82.  
  83. function observeDynamicTextareas() {
  84. const observer = new MutationObserver((mutations) => {
  85. mutations.forEach(mutation => {
  86. mutation.addedNodes.forEach(node => {
  87. if (node.nodeType === 1) {
  88. const textareas = node.querySelectorAll('textarea');
  89. textareas.forEach(monitorTextarea);
  90. }
  91. });
  92. });
  93. });
  94.  
  95. observer.observe(document.body, { childList: true, subtree: true });
  96.  
  97. // Initial load
  98. document.querySelectorAll('textarea').forEach(monitorTextarea);
  99. }
  100.  
  101. function hookAttachmentButton() {
  102. // Handle pasted content
  103. document.body.addEventListener('paste', () => {
  104. setTimeout(() => {
  105. document.querySelectorAll('textarea').forEach(replaceImagesInTextarea);
  106. }, 500);
  107. });
  108.  
  109. // Handle manual typing or JS-inserted content
  110. document.body.addEventListener('input', (e) => {
  111. if (e.target.tagName === 'TEXTAREA') {
  112. setTimeout(() => replaceImagesInTextarea(e.target), 500);
  113. }
  114. });
  115.  
  116. // Handle drag-and-drop image uploads
  117. document.body.addEventListener('drop', (e) => {
  118. const textarea = e.target.closest('textarea');
  119. if (textarea) {
  120. setTimeout(() => {
  121. replaceImagesInTextarea(textarea);
  122. }, 1500); // Wait for upload + Markdown injection
  123. }
  124. });
  125.  
  126. // Handle file input via attachment button
  127. document.body.addEventListener('change', (e) => {
  128. if (e.target.type === 'file') {
  129. setTimeout(() => {
  130. document.querySelectorAll('textarea').forEach(replaceImagesInTextarea);
  131. }, 1000);
  132. }
  133. });
  134. }
  135.  
  136. observeDynamicTextareas();
  137. hookAttachmentButton();
  138. })();

QingJ © 2025

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