Imgur Upload Posts Grid Layout

Arrange Imgur upload posts in a grid layout.

  1. // ==UserScript==
  2. // @name Imgur Upload Posts Grid Layout
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Arrange Imgur upload posts in a grid layout.
  6. // @author Byakuran
  7. // @match https://imgur.com/a/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=imgur.com
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Create and inject CSS
  16. const style = document.createElement('style');
  17. style.textContent = `
  18. /* Toggle button styles */
  19. #gridLayoutToggle {
  20. position: fixed;
  21. top: 20px;
  22. right: 20px;
  23. z-index: 9999;
  24. padding: 8px 16px;
  25. background: #1bb76e;
  26. color: white;
  27. border: none;
  28. border-radius: 4px;
  29. cursor: pointer;
  30. font-size: 14px;
  31. transition: background 0.3s;
  32. }
  33. #gridLayoutToggle:hover {
  34. background: #159157;
  35. }
  36.  
  37. /* Grid layout styles */
  38. .has-min-posts .UploadPost {
  39. width: auto !important;
  40. margin-left: 20px !important;
  41. }
  42. .has-min-posts .Upload-container > :nth-child(2):not(.UploadPost) {
  43. margin-right: 40px !important;
  44. }
  45. .has-min-posts .UploadPost-files > .PostContent.UploadPost-file {
  46. flex: 0 0 auto;
  47. width: calc(33.33% - 10px);
  48. min-width: 200px;
  49. margin: 0 !important;
  50. }
  51. .has-min-posts .UploadPost-files {
  52. display: flex;
  53. flex-wrap: wrap;
  54. gap: 10px;
  55. padding: 10px;
  56. }
  57. .has-min-posts .UploadPost-files > :first-child:not(.PostContent.UploadPost-file),
  58. .has-min-posts .UploadPost-files > :last-child:not(.PostContent.UploadPost-file) {
  59. width: 100% !important;
  60. flex: none !important;
  61. }
  62. .ImageDescription {
  63. max-height: 2.4em;
  64. overflow: hidden;
  65. position: relative;
  66. cursor: pointer;
  67. transition: max-height 0.3s ease-out;
  68. padding-right: 25px;
  69. }
  70. .ImageDescription.expanded {
  71. max-height: 1000px;
  72. }
  73. .ImageDescription::after {
  74. content: "▼";
  75. position: absolute;
  76. bottom: 0;
  77. right: 0;
  78. background: linear-gradient(90deg, transparent, #1a3c6e 20%);
  79. padding: 0 5px;
  80. color: white;
  81. }
  82. .ImageDescription.expanded::after {
  83. content: "▲";
  84. background: linear-gradient(90deg, transparent, #1a3c6e 20%);
  85. color: white;
  86. }
  87. `;
  88. document.head.appendChild(style);
  89.  
  90. // Create toggle button
  91. const toggleButton = document.createElement('button');
  92. toggleButton.id = 'gridLayoutToggle';
  93. toggleButton.textContent = 'Disable Grid Layout';
  94. document.body.appendChild(toggleButton);
  95.  
  96. // Get layout preference from localStorage
  97. let isGridEnabled = localStorage.getItem('imgurGridLayout') !== 'disabled';
  98.  
  99. // Function to reset grid application state
  100. function resetGridState() {
  101. const containers = document.querySelectorAll('.UploadPost-files');
  102. containers.forEach(container => {
  103. container.removeAttribute('data-grid-applied');
  104. });
  105. }
  106.  
  107. // Update button text and layout based on current state
  108. function updateLayoutState() {
  109. toggleButton.textContent = isGridEnabled ? 'Disable Grid Layout' : 'Enable Grid Layout';
  110. document.body.classList.remove('has-min-posts');
  111. resetGridState();
  112. if (isGridEnabled) {
  113. applyChanges();
  114. }
  115. }
  116.  
  117. // Toggle button click handler
  118. toggleButton.addEventListener('click', () => {
  119. isGridEnabled = !isGridEnabled;
  120. localStorage.setItem('imgurGridLayout', isGridEnabled ? 'enabled' : 'disabled');
  121. updateLayoutState();
  122. });
  123.  
  124. function makeDescriptionsExpandable() {
  125. const descriptions = document.querySelectorAll('.ImageDescription:not([data-expandable])');
  126. descriptions.forEach(desc => {
  127. desc.setAttribute('data-expandable', 'true');
  128. desc.addEventListener('click', function() {
  129. this.classList.toggle('expanded');
  130. });
  131. });
  132. }
  133.  
  134. // Function to apply layout and make descriptions expandable
  135. function applyChanges() {
  136. if (!isGridEnabled) return;
  137.  
  138. const containers = document.querySelectorAll('.UploadPost-files');
  139. containers.forEach(container => {
  140. if (!container.dataset.gridApplied) {
  141. const posts = container.querySelectorAll(':scope > .PostContent.UploadPost-file');
  142. if (posts.length >= 3) {
  143. container.dataset.gridApplied = 'true';
  144. document.body.classList.add('has-min-posts');
  145. }
  146. }
  147. });
  148. makeDescriptionsExpandable();
  149. }
  150.  
  151. // Initial application
  152. updateLayoutState();
  153.  
  154. // Monitor for dynamic content
  155. const observer = new MutationObserver((mutations) => {
  156. mutations.forEach((mutation) => {
  157. if (mutation.addedNodes.length) {
  158. applyChanges();
  159. }
  160. });
  161. });
  162. observer.observe(document.body, {
  163. childList: true,
  164. subtree: true
  165. });
  166. })();