Something Awful Image Fixes

Smarter image handling on the Something Awful forums.

  1. // ==UserScript==
  2. // @name Something Awful Image Fixes
  3. // @namespace SA
  4. // @description Smarter image handling on the Something Awful forums.
  5. // @include http://forums.somethingawful.com/*
  6. // @version 1.2.0
  7. // @grant GM_openInTab
  8. // @run-at document-end
  9. // @icon http://forums.somethingawful.com/favicon.ico
  10. // ==/UserScript==
  11.  
  12. var Util = {
  13. assetsLoaded: false,
  14. assetsLoading: 0,
  15.  
  16. /**
  17. * Initialise the page, strip out any assets that we will load.
  18. */
  19. initialise: function(target) {
  20. // Remove content images:
  21. var images = document.querySelectorAll('td.postbody img');
  22.  
  23. for (var index in images) {
  24. var image = images[index];
  25.  
  26. if (typeof image !== 'object') continue;
  27.  
  28. var src = image.getAttribute('src');
  29.  
  30. // Exclude smilies:
  31. if (!/somethingawful[.]com[/](images[/]smilies|forumsystem[/]emoticons)[/]/.test(src)) {
  32. var placeholder = document.createElement('span');
  33.  
  34. placeholder.setAttribute('data-saif-pending', 'yes');
  35. placeholder.saifCreate = src;
  36.  
  37. image.parentNode.replaceChild(placeholder, image);
  38. }
  39. }
  40.  
  41. // Remove other images:
  42. var images = document.querySelectorAll('img');
  43.  
  44. for (var index in images) {
  45. var image = images[index];
  46.  
  47. if (!image.parentNode) continue;
  48.  
  49. var placeholder = document.createElement('span');
  50.  
  51. placeholder.setAttribute('data-saif-pending', 'yes');
  52. placeholder.saifClone = image;
  53.  
  54. image.parentNode.replaceChild(placeholder, image);
  55. }
  56.  
  57. // Remove embedded videos:
  58. var iframes = document.querySelectorAll('td.postbody iframe');
  59.  
  60. for (var index in iframes) {
  61. var iframe = iframes[index];
  62.  
  63. if (!iframe.parentNode) continue;
  64.  
  65. var placeholder = document.createElement('span');
  66.  
  67. placeholder.setAttribute('data-saif-pending', 'yes');
  68. placeholder.saifClone = iframe;
  69.  
  70. iframe.parentNode.replaceChild(placeholder, iframe);
  71. }
  72.  
  73. // Fix post table styles:
  74. var posts = document.querySelectorAll('table.post');
  75.  
  76. for (var index in posts) {
  77. var post = posts[index];
  78.  
  79. if (typeof post !== 'object') continue;
  80.  
  81. post.style.tableLayout = 'fixed';
  82. }
  83.  
  84. Util.beginLoading(target);
  85. },
  86.  
  87. /**
  88. * Begin loading assets from the start of the document
  89. * until and including the windows viewport.
  90. */
  91. beginLoading: function(target) {
  92. var offset = window.scrollY + window.innerHeight;
  93.  
  94. if (!!target) {
  95. offset = Util.getElementOffset(target) + window.innerHeight
  96. }
  97.  
  98. // Initialise all elements up until the offset:
  99. var placeholders = document.querySelectorAll('[data-saif-pending]'),
  100. queue = [];
  101.  
  102. for (var index in placeholders) {
  103. var placeholder = placeholders[index];
  104.  
  105. if (typeof placeholder !== 'object') continue;
  106.  
  107. if (Util.getElementOffset(placeholder) < offset) {
  108. queue.push(placeholder);
  109. }
  110. }
  111.  
  112. for (var index in queue) {
  113. Util.createElement(queue[index]);
  114. }
  115.  
  116. // Wait until everything initialised thus far is loaded:
  117. if (!!target) {
  118. Util.waitForReady(function() {
  119. // Scroll to the target element:
  120. window.scrollTo(0, Util.getElementOffset(target));
  121.  
  122. // Resume loading of images and videos:
  123. Util.resumeLoading();
  124. });
  125. }
  126.  
  127. // No reason to wait:
  128. else {
  129. Util.resumeLoading();
  130. }
  131. },
  132.  
  133. /**
  134. * Resume loading assets not handled by `Util.beginLoading`
  135. * as they become visible in the windows viewport.
  136. */
  137. resumeLoading: function() {
  138. var placeholders = document.querySelectorAll('[data-saif-pending]');
  139.  
  140. for (var index in placeholders) {
  141. var placeholder = placeholders[index];
  142.  
  143. Util.waitForVisibility(placeholder, function(placeholder) {
  144. Util.createElement(placeholder);
  145. });
  146. }
  147. },
  148.  
  149. /**
  150. * Create an asset element from a placeholder.
  151. */
  152. createElement: function(placeholder) {
  153. if (!placeholder.parentNode) return;
  154.  
  155. // No processing needs to be done:
  156. if (!!placeholder.saifClone) {
  157. var element = placeholder.saifClone.cloneNode(true);
  158.  
  159. // Track the loading of this image:
  160. if (element instanceof HTMLImageElement) {
  161. Util.trackLoadState(element, ['load']);
  162. }
  163.  
  164. placeholder.parentNode.replaceChild(element, placeholder);
  165. }
  166.  
  167. // Process the source of this image:
  168. else if (!!placeholder.saifCreate) {
  169. var src = placeholder.saifCreate;
  170.  
  171. if (/i\.imgur\.com/.test(src)) {
  172. Util.createImgur(placeholder, src);
  173. }
  174.  
  175. else if (/staticflickr\.com\//.test(src)) {
  176. Util.createFlickr(placeholder, src);
  177. }
  178.  
  179. else {
  180. Util.createImage(placeholder, src);
  181. }
  182. }
  183. },
  184.  
  185. /**
  186. * Create an empty element indicating of failure.
  187. */
  188. createEmpty: function(placeholder) {
  189. var span = document.createElement('span');
  190.  
  191. span.setAttribute('data-saif-empty', 'yes');
  192.  
  193. placeholder.parentNode.replaceChild(span, placeholder);
  194. },
  195.  
  196. /**
  197. * Create a simple image element from a given source URL.
  198. */
  199. createImage: function(placeholder, src, href) {
  200. var wrapper = document.createElement('span');
  201.  
  202. wrapper.setAttribute('class', 'saif-wrapper');
  203.  
  204. // Create image element:
  205. var image = document.createElement('img');
  206.  
  207. // Track the loading of this image:
  208. Util.trackLoadState(image, ['load']);
  209.  
  210. // Append the image to the page when it is loaded:
  211. image.addEventListener('load', function() {
  212. if (!!href) {
  213. var link = document.createElement('a');
  214.  
  215. link.setAttribute('href', href);
  216. link.appendChild(image);
  217. wrapper.appendChild(link);
  218. }
  219.  
  220. else {
  221. wrapper.appendChild(image);
  222. }
  223.  
  224. if (!!placeholder.parentNode) {
  225. placeholder.parentNode.replaceChild(wrapper, placeholder);
  226. }
  227. });
  228.  
  229. // Set image source:
  230. image.setAttribute('src', src);
  231. },
  232.  
  233. /**
  234. * Create a video element from a list of source URLs with media types.
  235. */
  236. createVideo: function(placeholder, src, href, sources) {
  237. var wrapper = document.createElement('span');
  238.  
  239. wrapper.setAttribute('class', 'saif-wrapper');
  240.  
  241. // Create video element:
  242. var video = document.createElement('video');
  243.  
  244. // Set attributes to ensure gif style playback:
  245. video.setAttribute('preload', 'auto');
  246. video.setAttribute('autoplay', 'autoplay');
  247. video.setAttribute('muted', 'muted');
  248. video.setAttribute('loop', 'loop');
  249. video.setAttribute('webkit-playsinline', 'webkit-playsinline');
  250.  
  251. var action = document.createElement('a');
  252.  
  253. action.setAttribute('class', 'saif-link');
  254. action.setAttribute('target', '_blank');
  255. action.textContent = 'See original';
  256.  
  257. action.addEventListener('click', function(event) {
  258. event.stopPropagation();
  259. event.preventDefault();
  260.  
  261. wrapper.setAttribute('class', 'saif-wrapper hide');
  262. wrapper.removeChild(action);
  263. video.pause();
  264.  
  265. Util.createImage(wrapper, src, href);
  266. });
  267.  
  268. wrapper.appendChild(action);
  269.  
  270. // Video has loaded, insert it or a fallback:
  271. video.addEventListener('loadeddata', function() {
  272. if (
  273. (video.videoWidth < 75 && video.videoHeight < 100)
  274. || (video.videoHeight < 75 && video.videoWidth < 100)
  275. ) {
  276. video.pause();
  277. Util.createImage(placeholder, src, href);
  278. }
  279.  
  280. else {
  281. var link = document.createElement('a');
  282.  
  283. link.setAttribute('href', href);
  284. link.appendChild(video);
  285. wrapper.appendChild(link);
  286.  
  287. if (!!placeholder.parentNode) {
  288. placeholder.parentNode.replaceChild(wrapper, placeholder);
  289. }
  290. }
  291. });
  292.  
  293. // Track the loading of this video:
  294. Util.trackLoadState(video, ['loadeddata', 'error']);
  295.  
  296. // Add media sources:
  297. for (var index in sources) {
  298. var source = document.createElement('source');
  299.  
  300. source.setAttribute('src', sources[index][0]);
  301. source.setAttribute('type', sources[index][1]);
  302.  
  303. video.appendChild(source);
  304. }
  305. },
  306.  
  307. createStyle: function(css) {
  308. var head = document.querySelectorAll('head')[0],
  309. style = document.createElement('style');
  310.  
  311. style.textContent = css;
  312. head.appendChild(style);
  313. },
  314.  
  315. /**
  316. * Create an imgur image or video from a given source URL.
  317. */
  318. createImgur: function(placeholder, src) {
  319. var bits = /\/(.{5}|.{7})[hls]?\.(jpg|png|gif)/i.exec(src);
  320.  
  321. // Could not parse the image:
  322. if (bits) {
  323. var identity = bits[1],
  324. extension = bits[2].toLowerCase();
  325.  
  326. // Is a video:
  327. if ('gif' === extension) {
  328. Util.createVideo(
  329. placeholder,
  330. '//i.imgur.com/' + identity + '.' + extension,
  331. '//i.imgur.com/' + identity + '.' + extension,
  332. [
  333. ['//i.imgur.com/' + identity + '.webm', 'video/webm'],
  334. ['//i.imgur.com/' + identity + '.mp4', 'video/mp4']
  335. ]
  336. );
  337. }
  338.  
  339. // Is an image:
  340. else {
  341. Util.createImage(
  342. placeholder,
  343. '//i.imgur.com/' + identity + 'h.' + extension,
  344. '//i.imgur.com/' + identity + '.' + extension
  345. );
  346. }
  347. }
  348.  
  349. // The source was invalid:
  350. else {
  351. Util.createEmpty(placeholder);
  352. }
  353. },
  354.  
  355. /**
  356. * Create a flickr image from a given source URL.
  357. */
  358. createFlickr: function(placeholder, src) {
  359. var bits = /^(.+?\.com\/.+?\/.+?_.+?)(_[omstzb])?\.(.+?)$/.exec(src),
  360. location,
  361. extension;
  362.  
  363. // Create an image:
  364. if (bits) {
  365. var location = bits[1],
  366. extension = bits[3].toLowerCase();
  367.  
  368. Util.createImage(
  369. placeholder,
  370. location + '_b.' + extension,
  371. location + '_b.' + extension
  372. );
  373. }
  374.  
  375. // The source was invalid:
  376. else {
  377. Util.createEmpty(placeholder);
  378. }
  379. },
  380.  
  381. /**
  382. * Calculate the offset from the top of the page to the
  383. * top of the given element.
  384. */
  385. getElementOffset: function(element) {
  386. var offset = 0;
  387.  
  388. while (element.offsetParent) {
  389. offset += element.offsetTop;
  390. element = element.offsetParent;
  391. }
  392.  
  393. return offset;
  394. },
  395.  
  396. /**
  397. * Style an element so that it cannot break out of the post table.
  398. */
  399. setElementStyles: function(element) {
  400. element.style.display = 'inline-block';
  401. element.style.marginBottom = '5px';
  402. element.style.marginTop = '5px';
  403. element.style.maxWidth = '100%';
  404. },
  405.  
  406. /**
  407. * Attach events to count the number of currently loading assets.
  408. */
  409. trackLoadState: function(element, eventNames) {
  410. if (Util.assetsLoaded) return;
  411.  
  412. Util.assetsLoading++;
  413.  
  414. for (var index in eventNames) {
  415. element.addEventListener(eventNames[index], Util.trackReadyState);
  416. }
  417. },
  418.  
  419. /**
  420. * The attached event handler for `Util.trackLoadState`.
  421. */
  422. trackReadyState: function(event) {
  423. if (Util.assetsLoaded) return;
  424.  
  425. Util.assetsLoading--;
  426.  
  427. if (0 === Util.assetsLoading) {
  428. Util.assetsLoaded = true;
  429. }
  430. },
  431.  
  432. /**
  433. * Wait for all of the assets loaded in `Util.beginLoading`
  434. * to complete.
  435. */
  436. waitForReady: function(callback) {
  437. var wait = setInterval(function() {
  438. if (Util.assetsLoaded) {
  439. clearInterval(wait);
  440. callback();
  441. }
  442. }, 1);
  443. },
  444.  
  445. /**
  446. * Wait for the user to scroll within two pages of an element
  447. * and then call the callback.
  448. */
  449. waitForVisibility: function(element, callback) {
  450. var chromeSucks = false;
  451. var scroll = function() {
  452. if (chromeSucks) return;
  453.  
  454. var offset = Util.getElementOffset(element),
  455. max = window.scrollY + (window.innerHeight * 2);
  456.  
  457. if (max > offset) {
  458. chromeSucks = true;
  459. window.removeEventListener('scroll', scroll);
  460. callback(element);
  461. }
  462. };
  463.  
  464. scroll();
  465. window.addEventListener('scroll', scroll);
  466. }
  467. };
  468.  
  469. try {
  470. var offset = window.outerHeight;
  471.  
  472. Util.createStyle("\
  473. @-webkit-keyframes saifProgressSlider {\
  474. 0% { background: #3b3b3b; }\
  475. 100% { background: #3b3b3b; }\
  476. }\
  477. @keyframes saifProgressSlider {\
  478. 0% { background-position: 0px 0px; }\
  479. 100% { background-position: 16px 0px; }\
  480. }\
  481. span.saif-wrapper {\
  482. display: inline-block;\
  483. position: relative;\
  484. margin: 5px 0;\
  485. max-width: 100%;\
  486. }\
  487. span.saif-wrapper img,\
  488. span.saif-wrapper video {\
  489. max-width: 100%;\
  490. opacity: 1;\
  491. vertical-align: bottom;\
  492. }\
  493. span.saif-wrapper.hide {\
  494. background: repeating-linear-gradient(45deg, #444444 0px, #444444 8px, #3b3b3b 8px, #3b3b3b 16px) scroll 0% 0% / 300% 300%;\
  495. -webkit-animation: saifProgressSlider 60s linear infinite;\
  496. animation: saifProgressSlider 1s linear infinite;\
  497. }\
  498. span.saif-wrapper.hide video {\
  499. transition: opacity 0.5s ease;\
  500. opacity: 0;\
  501. }\
  502. span.saif-wrapper a.saif-link {\
  503. background: hsla(0, 0%, 10%, 0.7);\
  504. color: #ffffff;\
  505. cursor: pointer;\
  506. display: none;\
  507. font-size: 0.75em;\
  508. left: 0;\
  509. line-height: 1;\
  510. padding: 5px;\
  511. position: absolute;\
  512. right: 0;\
  513. text-decoration: none;\
  514. top: 0;\
  515. z-index: 1;\
  516. }\
  517. span.saif-wrapper:hover a.saif-link {\
  518. display: block;\
  519. }\
  520. ");
  521.  
  522. // Prevent images from loading:
  523. window.stop();
  524.  
  525. // Redirect the page:
  526. if (document.querySelectorAll('meta[http-equiv=refresh]').length) {
  527. var rule = document.querySelectorAll('meta[http-equiv=refresh]')[0].getAttribute('content');
  528.  
  529. if (/URL=(.+)$/.test(rule)) {
  530. window.location = /URL=(.+)$/.exec(rule)[1];
  531. }
  532. }
  533.  
  534. // Jump to appropriate place on page:
  535. else if (!!window.location.hash && document.querySelectorAll(window.location.hash).length) {
  536. Util.initialise(document.querySelectorAll(window.location.hash)[0]);
  537. }
  538.  
  539. // Load the page normally:
  540. else {
  541. Util.initialise();
  542. }
  543. }
  544.  
  545. catch (e) {
  546. console.log("Exception: " + e.name + " Message: " + e.message);
  547. }

QingJ © 2025

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