ImageFAQs

Converts image and webm/mp4 URLs into their embedded form

目前为 2019-11-02 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name ImageFAQs
  3. // @namespace FightingGames@gfaqs
  4. // @include http://www.gamefaqs.gamespot.com
  5. // @include http://www.gamefaqs.gamespot.com*
  6. // @include https://www.gamefaqs.gamespot.com
  7. // @include https://www.gamefaqs.gamespot.com*
  8. // @include http://gamefaqs.gamespot.com
  9. // @include http://gamefaqs.gamespot.com*
  10. // @include https://gamefaqs.gamespot.com
  11. // @include https://gamefaqs.gamespot.com*
  12. // @icon http://fightinggames.bitbucket.io/imagefaqs/icon.png
  13. // @description Converts image and webm/mp4 URLs into their embedded form
  14. // @license MIT
  15. // @supportURL https://gamefaqs.gamespot.com/boards/1063-customfaqs-scripts-and-styles
  16. // @homepageURL https://fightinggames.bitbucket.io/imagefaqs/
  17. // @version 1.22.25
  18. // @grant GM_addStyle
  19. // @grant GM_getResourceText
  20. // @grant GM_log
  21. // @grant GM_xmlhttpRequest
  22. // @grant GM.xmlHttpRequest
  23. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  24. // @require https://code.jquery.com/jquery-2.2.4.min.js
  25. // @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
  26. // @require https://cdnjs.cloudflare.com/ajax/libs/async/2.0.0/async.min.js
  27. // @resource jqueryuibaseCSS https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery.ui.base.css
  28. // @resource jqueryuithemeCSS https://code.jquery.com/ui/1.11.4/themes/cupertino/jquery-ui.css
  29. // ==/UserScript==
  30.  
  31.  
  32. /*
  33. The MIT License (MIT)
  34.  
  35. This greasemonkey script for GameFAQs converts image and webm/mp4
  36. links into their embedded form. It can be considered as a spiritual successor
  37. to text-to-image (TTI) for GameFAQs.
  38.  
  39. Copyright (c) 2018 FightingGames@gamefaqs <adrenalinebionicarm@gmail.com>
  40. Copyright (c) 2018 FeaturingDante@gamefaqs <featuringDante@gmail.com>
  41.  
  42. Permission is hereby granted, free of charge, to any person obtaining a copy
  43. of this software and associated documentation files (the "Software"), to deal
  44. in the Software without restriction, including without limitation the rights
  45. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  46. copies of the Software, and to permit persons to whom the Software is
  47. furnished to do so, subject to the following conditions:
  48.  
  49. The above copyright notice and this permission notice shall be included in
  50. all copies or substantial portions of the Software.
  51.  
  52. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  53. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  54. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  55. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  56. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  57. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  58. THE SOFTWARE.
  59. */
  60.  
  61. /*
  62. Reminder:
  63. GM_addStyle_from_file and GM_addStyle_from_string are currently being used to
  64. replace GM_addStyle.
  65.  
  66. Currently checks if imagefaqs has already been loaded after clicking back button
  67. See code immediately after print function.
  68.  
  69. Relying on hacks:
  70. // @grant GM.xmlHttpRequest
  71. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  72.  
  73. .embeddedImgAnchor > .embeddedImg (#video) behave differently between firefox and chrome
  74. - In firefox, anchor's href is disabled while video is expanded.
  75. - Left-click does not shrink video, needed [Close Video]
  76.  
  77. - In chrome:
  78. - Left-click will shrink video except when using video controls.
  79. */
  80.  
  81. SUPPORT_URL = "https://gamefaqs.gamespot.com/boards/1063-customfaqs-scripts-and-styles";
  82. HOMEPAGE_URL = "http://fightinggames.bitbucket.io/imagefaqs/";
  83. USER_URL = "https://gamefaqs.gamespot.com/user";
  84.  
  85.  
  86. var isDebug = false;
  87. var print = function(msg) {};
  88. if (unsafeWindow.console && isDebug)
  89. print = unsafeWindow.console.log;
  90.  
  91.  
  92. if ($("body").find('#imagefaqsLoaded').length !== 0) {
  93. print('Imagefaqs already loaded. Returning.')
  94. return;
  95. }
  96. else {
  97. $("body").append("<div id='imagefaqsLoaded'></div>");
  98. }
  99.  
  100.  
  101.  
  102.  
  103. /*
  104. Get number of keys in a primitive java object
  105. */
  106. Object.size = function(obj) {
  107. var size = 0;
  108. var key;
  109.  
  110. for (key in obj) {
  111. if (obj.hasOwnProperty(key))
  112. size++;
  113. }
  114. return size;
  115. };
  116.  
  117.  
  118. /*
  119. Below is a collection of functions to iterate through HTML objects (e.g. step from <b></b> to <img/>)
  120. https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Whitespace_in_the_DOM
  121. */
  122.  
  123.  
  124. /*
  125. * Determine whether a node's text content is entirely whitespace.
  126. *
  127. * @param nod A node implementing the |CharacterData| interface (i.e.,
  128. * a |Text|, |Comment|, or |CDATASection| node
  129. * @return True if all of the text content of |nod| is whitespace,
  130. * otherwise false.
  131. */
  132. function is_all_ws( nod )
  133. {
  134. // Use ECMA-262 Edition 3 String and RegExp features
  135. return !(/[^\t\n\r ]/.test(nod.textContent));
  136. }
  137.  
  138.  
  139. /*
  140. * Determine if a node should be ignored by the iterator functions.
  141. *
  142. * @param nod An object implementing the DOM1 |Node| interface.
  143. * @return true if the node is:
  144. * 1) A |Text| node that is all whitespace
  145. * 2) A |Comment| node
  146. * and otherwise false.
  147. */
  148.  
  149. function is_ignorable( nod )
  150. {
  151. return ( nod.nodeType == 8) || // A comment node
  152. ( (nod.nodeType == 3) && is_all_ws(nod) ); // a text node, all ws
  153. }
  154.  
  155. /*
  156. * Version of |previousSibling| that skips nodes that are entirely
  157. * whitespace or comments. (Normally |previousSibling| is a property
  158. * of all DOM nodes that gives the sibling node, the node that is
  159. * a child of the same parent, that occurs immediately before the
  160. * reference node.)
  161. *
  162. * @param sib The reference node.
  163. * @return Either:
  164. * 1) The closest previous sibling to |sib| that is not
  165. * ignorable according to |is_ignorable|, or
  166. * 2) null if no such node exists.
  167. */
  168. function node_before( sib )
  169. {
  170. while ((sib = sib.previousSibling)) {
  171. if (!is_ignorable(sib)) return sib;
  172. }
  173. return null;
  174. }
  175.  
  176. /*
  177. * Version of |nextSibling| that skips nodes that are entirely
  178. * whitespace or comments.
  179. *
  180. * @param sib The reference node.
  181. * @return Either:
  182. * 1) The closest next sibling to |sib| that is not
  183. * ignorable according to |is_ignorable|, or
  184. * 2) null if no such node exists.
  185. */
  186. function node_after( sib )
  187. {
  188. while ((sib = sib.nextSibling)) {
  189. if (!is_ignorable(sib)) return sib;
  190. }
  191. return null;
  192. }
  193.  
  194. /*
  195. * Version of |lastChild| that skips nodes that are entirely
  196. * whitespace or comments. (Normally |lastChild| is a property
  197. * of all DOM nodes that gives the last of the nodes contained
  198. * directly in the reference node.)
  199. *
  200. * @param sib The reference node.
  201. * @return Either:
  202. * 1) The last child of |sib| that is not
  203. * ignorable according to |is_ignorable|, or
  204. * 2) null if no such node exists.
  205. */
  206. function last_child( par )
  207. {
  208. var res=par.lastChild;
  209. while (res) {
  210. if (!is_ignorable(res)) return res;
  211. res = res.previousSibling;
  212. }
  213. return null;
  214. }
  215.  
  216. /*
  217. * Version of |firstChild| that skips nodes that are entirely
  218. * whitespace and comments.
  219. *
  220. * @param sib The reference node.
  221. * @return Either:
  222. * 1) The first child of |sib| that is not
  223. * ignorable according to |is_ignorable|, or
  224. * 2) null if no such node exists.
  225. */
  226. function first_child( par )
  227. {
  228. var res=par.firstChild;
  229. while (res) {
  230. if (!is_ignorable(res)) return res;
  231. res = res.nextSibling;
  232. }
  233. return null;
  234. }
  235.  
  236. /*
  237. * Version of |data| that doesn't include whitespace at the beginning
  238. * and end and normalizes all whitespace to a single space. (Normally
  239. * |data| is a property of text nodes that gives the text of the node.)
  240. *
  241. * @param txt The text node whose data should be returned
  242. * @return A string giving the contents of the text node with
  243. * whitespace collapsed.
  244. */
  245. function data_of( txt )
  246. {
  247. var data = txt.textContent;
  248. // Use ECMA-262 Edition 3 String and RegExp features
  249. data = data.replace(/[\t\n\r ]+/g, " ");
  250. if (data.charAt(0) == " ")
  251. data = data.substring(1, data.length);
  252. if (data.charAt(data.length - 1) == " ")
  253. data = data.substring(0, data.length - 1);
  254. return data;
  255. }
  256.  
  257. /*End of HTML object iterator functions*/
  258.  
  259.  
  260.  
  261.  
  262.  
  263.  
  264.  
  265. /*Get our own unique jQuery library instance*/
  266. this.$ = this.jQuery = jQuery.noConflict(true);
  267.  
  268.  
  269.  
  270.  
  271.  
  272.  
  273. /*
  274. Display a notification on the bottom-right of the browser.
  275.  
  276. @param msg :: string to put between <span></span>
  277. @param duration :: number of milliseconds to display message before it disappears. Default 1000
  278. */
  279. function showNotification(msg, duration)
  280. {
  281. var notificationBox = $("#imagefaqs_notificationBox");
  282. notificationBox.remove();
  283.  
  284. $("body").append(
  285. "<div id='imagefaqs_notificationBox'>" +
  286. "<span style='color: black'>" +
  287. msg +
  288. "</span>" +
  289. "</div>"
  290. );
  291.  
  292. notificationBox = $("#imagefaqs_notificationBox");
  293.  
  294. notificationBox.css({
  295. "position": "absolute",
  296. "background": "white",
  297. "top": ($(window).scrollTop() + $(window).height() - notificationBox.height())+"px",
  298. "left": ($(window).scrollLeft() + $(window).width() - notificationBox.find("span").width() - 1)+"px",
  299. });
  300.  
  301. // Note: Also look at $(window).scroll(function() { ... which controls scrolling of notification box.
  302.  
  303. if (duration === undefined)
  304. duration = 1000;
  305.  
  306. notificationBox.effect("highlight").delay(duration).fadeOut(500);
  307. }
  308.  
  309.  
  310.  
  311.  
  312. /*This top portion of the script handles the settings box and local storage*/
  313.  
  314. var thumbnailImgWidth = 0;
  315. var thumbnailImgHeight = 150;
  316. var thumbnailImgWidthSig = 0;
  317. var thumbnailImgHeightSig = 75;
  318. var settingsJSON = localStorage.getItem("imagefaqs");
  319. var settings; /*key-value array of JSON*/
  320. var sessionResizeHotkey;
  321. var sessionHideToggleHotkey;
  322. var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; /*osdep*/
  323. var isTampermonkey = GM_info.scriptHandler === "Tampermonkey";
  324.  
  325.  
  326. function getDefaultSettings() {
  327. return {
  328. enable_sigs: true,
  329. enable_floatingImg: true,
  330. enable_floatingImg_url: false,
  331. includeSigWhenExpanding: false,
  332. hideSigsWhenHideMode: false,
  333. legacySigDetection: true,
  334. show_imgURLspan: true,
  335. show_imgResolutionSpan: true,
  336. show_imgFilesizeSpan: false,
  337. show_imgGoogle: true,
  338. show_imgIqdb: true,
  339. show_blacklistToggle: true,
  340. show_imgAllResizeToggles: true,
  341. show_hideToggle: true,
  342. show_hideAllToggle: true,
  343. imgContainerStyle: "sideBySideSig",
  344. auto_scroll_when_thumbnail_clicked: false,
  345. thumbnailWidth: thumbnailImgWidth,
  346. thumbnailHeight: thumbnailImgHeight,
  347. thumbnailWidthSig: thumbnailImgWidthSig,
  348. thumbnailHeightSig: thumbnailImgHeightSig,
  349. expandedWidth: 0,
  350. expandedHeight: 0,
  351. imgContainerBackgroundColor: "#DFE6F7",
  352. isHideMode: false,
  353. hideToggleHotkey: {115: 115}, /*F4*/
  354. resizeHotkey: {113: 113}, /*F2*/
  355. blacklistStr: "",
  356. loopThumbnailVideo: true,
  357. loopExpandedVideo: false,
  358. };
  359. }
  360.  
  361. function setSettingsToDefault() {
  362. settings = getDefaultSettings();
  363. }
  364.  
  365.  
  366. /*if imageFAQs was freshly installed*/
  367. if (settingsJSON === null)
  368. {
  369. setSettingsToDefault();
  370.  
  371. localStorage.setItem("imagefaqs", JSON.stringify(settings)); /*save default settings*/
  372.  
  373. /*gfaqsdep for URL*/
  374. showNotification(`ImageFAQs ${GM_info.script.version} has successfully been installed.`+
  375. `<br/><br/>Settings can be accessed under 'Message Board Information' `+
  376. `at<br/><a target='_blank' href='${USER_URL}'>${USER_URL}</a>`, 10000);
  377. }
  378. else
  379. {
  380. settings = $.parseJSON(settingsJSON);
  381.  
  382. //For each default setting, apply it to current settings if current setting isn't set
  383. var defaultSettings = getDefaultSettings();
  384. var defaultKeys = Object.keys(defaultSettings);
  385. defaultKeys.forEach(function (defaultKey) {
  386. if (settings[defaultKey] === undefined) {
  387. settings[defaultKey] = defaultSettings[defaultKey];
  388. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  389. }
  390. });
  391. }
  392.  
  393.  
  394. print("ImageFAQs settings from local storage: " + JSON.stringify(settings));
  395.  
  396.  
  397. /*
  398. Convert an ASCII numeric representation of a keyboard character to its full string name
  399.  
  400. e.g. keyCodeToString(16) === "shift"
  401. */
  402. function keyCodeToString(keyCode)
  403. {
  404. keyCode = parseInt(keyCode);
  405.  
  406. if (keyCode === 16)
  407. {
  408. return "shift";
  409. }
  410. else if (keyCode === 17)
  411. {
  412. return "ctrl";
  413. }
  414. else if (keyCode === 18)
  415. {
  416. return "alt";
  417. }
  418. else if (keyCode >= 112 && keyCode <= 123) /*F keys*/
  419. {
  420. return "F" + (keyCode - 111);
  421. }
  422. else
  423. {
  424. return String.fromCharCode(keyCode);
  425. }
  426. }
  427.  
  428.  
  429. /*
  430. Convert an array of KV ASCII numeric representation of keyboard characters to its
  431. full string representation
  432.  
  433. e,g, keyCodeKVArrayToString([{68:68},{77:77}]) === "DM"
  434. */
  435. function keyCodeKVArrayToString(array)
  436. {
  437. var string = "";
  438.  
  439. for (var key in array)
  440. {
  441. string += keyCodeToString(key) + " ";
  442. }
  443.  
  444. return string;
  445. }
  446.  
  447.  
  448. /*
  449. Get a copy of an array of KV's using 1-level deep cloning.
  450. */
  451. function cloneKVArray(KVArray)
  452. {
  453. var newKVArray = {};
  454.  
  455. for (var key in KVArray)
  456. {
  457. newKVArray[key] = KVArray[key];
  458. }
  459.  
  460. return newKVArray;
  461. }
  462.  
  463.  
  464.  
  465. sessionHideToggleHotkey = cloneKVArray(settings.hideToggleHotkey);
  466. sessionResizeHotkey = cloneKVArray(settings.resizeHotkey);
  467.  
  468.  
  469.  
  470.  
  471. function applySettingsToPopup(settings, settingsPopup)
  472. {
  473. settingsPopup.find("#enable_sigs_checkbox").prop("checked", settings.enable_sigs);
  474. settingsPopup.find("#enable_floatingImg").prop("checked", settings.enable_floatingImg);
  475. settingsPopup.find("#enable_floatingImg_url").prop("checked", settings.enable_floatingImg_url);
  476.  
  477. settingsPopup.find("#includeSigWhenExpanding_checkbox").prop("checked", settings.includeSigWhenExpanding);
  478. settingsPopup.find("#hideSigsWhenHideMode_checkbox").prop("checked", settings.hideSigsWhenHideMode);
  479. settingsPopup.find("#legacySigDetection_checkbox").prop("checked", settings.legacySigDetection);
  480.  
  481. settingsPopup.find("#loopThumbnailVideo_checkbox").prop("checked", settings.loopThumbnailVideo);
  482. settingsPopup.find("#loopExpandedVideo_checkbox").prop("checked", settings.loopExpandedVideo);
  483.  
  484. settingsPopup.find("#show_ImgURLspan_checkbox").prop("checked", settings.show_imgURLspan);
  485. settingsPopup.find("#show_ImgResolutionSpan_checkbox").prop("checked", settings.show_imgResolutionSpan);
  486. settingsPopup.find("#show_imgFilesizeSpan_checkbox").prop("checked", settings.show_imgFilesizeSpan);
  487. settingsPopup.find("#show_imgGoogle_checkbox").prop("checked", settings.show_imgGoogle);
  488. settingsPopup.find("#show_blacklistToggle_checkbox").prop("checked", settings.show_blacklistToggle);
  489. settingsPopup.find("#show_imgIqdb_checkbox").prop("checked", settings.show_imgIqdb);
  490. settingsPopup.find("#show_imgAllResizeToggles_checkbox").prop("checked", settings.show_imgAllResizeToggles);
  491. settingsPopup.find("#show_hideToggle_checkbox").prop("checked", settings.show_hideToggle);
  492. settingsPopup.find("#show_hideAllToggle_checkbox").prop("checked", settings.show_hideAllToggle);
  493.  
  494. settingsPopup.find("input[value="+settings.imgContainerStyle+"]").prop("checked", true);
  495. settingsPopup.find("#auto_scroll_when_thumbnail_clicked_checkbox").prop("checked", settings.auto_scroll_when_thumbnail_clicked);
  496.  
  497. settingsPopup.find("#thumbnailImgWidth_text").val(settings.thumbnailWidth);
  498. settingsPopup.find("#thumbnailImgHeight_text").val(settings.thumbnailHeight);
  499. settingsPopup.find("#thumbnailImgWidthSig_text").val(settings.thumbnailWidthSig);
  500. settingsPopup.find("#thumbnailImgHeightSig_text").val(settings.thumbnailHeightSig);
  501. settingsPopup.find("#expandedWidth_text").val(settings.expandedWidth);
  502. settingsPopup.find("#expandedHeight_text").val(settings.expandedHeight);
  503.  
  504. settingsPopup.find("#imgContainerBackgroundColor_color").val(settings.imgContainerBackgroundColor);
  505. settingsPopup.find("#resizeHotkey_text").val( keyCodeKVArrayToString(settings.resizeHotkey) );
  506. settingsPopup.find("#hideToggleHotkey_text").val( keyCodeKVArrayToString(settings.hideToggleHotkey) );
  507. settingsPopup.find("#blacklist_text").val( settings.blacklistStr );
  508. settingsPopup.children("#responseSpan").html("&nbsp;");
  509. }
  510.  
  511.  
  512.  
  513.  
  514. /*Event where user opens up settings menu*/
  515. $("body").on("click", "#imagefaqs_settings_but", function(event) {
  516.  
  517. var settingsPopup = $("#imagefaqs_settings_popup");
  518. event.preventDefault();
  519.  
  520. if (settingsPopup.length === 0)
  521. {
  522. $("body").append(
  523. "<div id='imagefaqs_settings_popup' title='ImageFAQs Settings. Version "+GM_info.script.version+"'>" +
  524.  
  525. "Update history: <a target='_blank' href='"+HOMEPAGE_URL+"' style='outline: 0'>"+HOMEPAGE_URL+"</a>" +
  526. "<br/>" +
  527. "<span id='feedback_bugreport_info'>" +
  528. "Feedback and bug reporting: <a target='_blank' href='"+SUPPORT_URL+"'>"+SUPPORT_URL+"</a>" +
  529. "<br/>" +
  530. "</span>" +
  531.  
  532. "<fieldset>" +
  533. "<legend>Main</legend>" +
  534. "<label><input id='enable_floatingImg' type='checkbox'>Enable floating expanded image upon cursor hover over thumbnail</label>" +
  535. "<br/>" +
  536. "<label><input id='enable_floatingImg_url' type='checkbox'>Enable floating expanded image upon cursor hover over URL</label>" +
  537. "<br/>" +
  538. "<label><input id='auto_scroll_when_thumbnail_clicked_checkbox' type='checkbox'>Auto-scroll to top of image after toggling visibility or size</label>" +
  539. "</fieldset>" +
  540.  
  541. "<fieldset>" +
  542. "<legend>Signature-specific</legend>" +
  543. "<label><input id='enable_sigs_checkbox' type='checkbox'>Embed signature images</label>" +
  544. "<br/>" +
  545. "<label><input id='legacySigDetection_checkbox' type='checkbox'>Add legacy signature image detection on older posts</label>" +
  546. "<br/>" +
  547. "<label><input id='includeSigWhenExpanding_checkbox' type='checkbox'>Affect signature images when expanding and contracting all images</label>" +
  548. "<br/>" +
  549. "<label><input id='hideSigsWhenHideMode_checkbox' type='checkbox'>Affect signature images when toggling hide mode</label>" +
  550. "</fieldset>" +
  551.  
  552. "<fieldset>" +
  553. "<legend>Video-specific</legend>" +
  554. "<label><input id='loopThumbnailVideo_checkbox' type='checkbox'>Auto-play and loop thumbnail videos (muted)</label>" +
  555. "<br/>" +
  556. "<label><input id='loopExpandedVideo_checkbox' type='checkbox'>Loop expanded videos</label>" +
  557. "<br/>" +
  558. "</fieldset>" +
  559.  
  560. "<fieldset>" +
  561. "<legend>Image Container Toggles and Information Visibility</legend>" +
  562. "<label><input id='show_ImgURLspan_checkbox' type='checkbox'>Show image URL</label>" +
  563. "<br/>" +
  564. "<label><input id='show_ImgResolutionSpan_checkbox' type='checkbox'>Show image resolution</label>" +
  565. "<br/>" +
  566. "<label><input id='show_imgFilesizeSpan_checkbox' type='checkbox'>Show image filesize (Warning: Doubles load time)</label>" +
  567. "<br/>" +
  568. "<label><input id='show_imgGoogle_checkbox' type='checkbox'>Show google reverse image search button</label>" +
  569. "<br/>" +
  570. "<label><input id='show_imgIqdb_checkbox' type='checkbox'>Show iqdb reverse image search button</label>" +
  571. "<br/>" +
  572. "<label><input id='show_blacklistToggle_checkbox' type='checkbox'>Show blacklist toggle button</label>" +
  573. "<br/>" +
  574. "<label><input id='show_imgAllResizeToggles_checkbox' type='checkbox'>Show all-resize toggle buttons</label>" +
  575. "<br/>" +
  576. "<label><input id='show_hideAllToggle_checkbox' type='checkbox'>Show all-hide/expose toggle buttons</label>" +
  577. "<br/>" +
  578. "<label><input id='show_hideToggle_checkbox' type='checkbox'>Show individual hide/expose toggle button</label>" +
  579. "</fieldset>" +
  580.  
  581. "<fieldset>" +
  582. "<legend>Image Container Style</legend>" +
  583. "<label><input type='radio' name='imgContainerStyle' value='stacked' checked='checked'>Stacked and Verbose</label>" +
  584. "<br>" +
  585. "<label><input type='radio' name='imgContainerStyle' value='sideBySide'>Side-By-Side and Succinct</label>" +
  586. "<br>" +
  587. "<label><input type='radio' name='imgContainerStyle' value='sideBySideSig'>Side-By-Side and Succinct (Signatures Only)</label>" +
  588. "</fieldset>" +
  589.  
  590. "<label><input id='thumbnailImgWidth_text' type='text'>Thumbnail Max-Width (0 default)</label>" +
  591. "<br/>" +
  592. "<label><input id='thumbnailImgHeight_text' type='text'>Thumbnail Max-Height (150 default)</label>" +
  593. "<br/>" +
  594. "<label><input id='thumbnailImgWidthSig_text' type='text'>Signature Thumbnail Max-Width (0 default)</label>" +
  595. "<br/>" +
  596. "<label><input id='thumbnailImgHeightSig_text' type='text'>Signature Thumbnail Max-Height (75 default)</label>" +
  597. "<br/>" +
  598. "<label><input id='expandedWidth_text' type='text'>Expanded max-width (0 default)</label>" +
  599. "<br/>" +
  600. "<label><input id='expandedHeight_text' type='text'>Expanded max-height (0 default)</label>" +
  601. "<br/ style='margin-bottom: 5px'>" +
  602. "<span>Tip: Use 0 to specify no length restriction.</span>" +
  603. "<br/><br/>" +
  604.  
  605. "<input id='imgContainerBackgroundColor_color' type='color' value='#DFE6F7' size=4 ><label>Image container background color (RGB={223,230,247} default)</label>" +
  606. "<br/ style='margin-bottom: 5px'>" +
  607. "<span>Tip: Use RGB={0,0,0} for no background color.</span>" +
  608. "<br/><br/>" +
  609.  
  610. "<label>Newline separated list of blacklisted image URLs</label>" +
  611. "<br/>" +
  612. "<textarea id='blacklist_text'></textarea>" +
  613. "<br/ style='margin-bottom: 5px'>" +
  614. "<span>Tip: You can blacklist an entire domain (e.g. pbsrc.com).</span>" +
  615. "<br/><br/>" +
  616.  
  617. "<label><input id='resizeHotkey_text' class='hotkeyInput' type='text'>Toggle image size hotkey (F2 default)</label>" +
  618. "<br/>" +
  619. "<label><input id='hideToggleHotkey_text' class='hotkeyInput' type='text'>Toggle hide mode hotkey (F4 default)</label>" +
  620. "<br/ style='margin-bottom: 5px'>" +
  621. "<span>Tip: You can disable a hotkey with the backspace.</span>" +
  622. "<br/><br/>" +
  623.  
  624. "<button id='saveImagefaqsSettingsBut' type='button'>Save</button> " +
  625. "<button id='cancelImagefaqsSettingsBut' type='button'>Cancel</button> " +
  626. "<span id='responseSpan'>&nbsp;&nbsp;</span>" + /*for reporting success or failure*/
  627.  
  628. "<button id='defaultSettingsBut' type='button' style='float: right'>Reset Settings</button> " +
  629. "</div>"
  630. );
  631.  
  632. settingsPopup = $("#imagefaqs_settings_popup");
  633. }
  634.  
  635. /*Show user's custom settings*/
  636. applySettingsToPopup(settings, settingsPopup);
  637.  
  638. settingsPopup.dialog({
  639. width: 550,
  640. height: 800 /*adds a scrollbar*/
  641. });
  642.  
  643. settingsPopup.find("#blacklist_text").width(settingsPopup.width());
  644. });
  645.  
  646.  
  647.  
  648.  
  649. $("body").on("click", "#defaultSettingsBut", function(event) {
  650. event.preventDefault();
  651.  
  652. var settingsPopup = $("#imagefaqs_settings_popup");
  653.  
  654. setSettingsToDefault();
  655. applySettingsToPopup(settings, settingsPopup);
  656.  
  657. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  658. reportResponseMsgInImagefaqsSettingsPopupBox("Default settings saved.", settingsPopup);
  659. });
  660.  
  661.  
  662.  
  663. /*Event where users clicks on the "Save Settings" button*/
  664. $("body").on("click", "#saveImagefaqsSettingsBut", function(event) {
  665.  
  666. event.preventDefault();
  667.  
  668. var settingsPopup = $("#imagefaqs_settings_popup");
  669.  
  670. /*if valid thumbnail dimensions*/
  671. if (
  672. !isNaN($("#thumbnailImgWidth_text").val()) && $("#thumbnailImgWidth_text").val() >= 0 &&
  673. !isNaN($("#thumbnailImgHeight_text").val()) && $("#thumbnailImgHeight_text").val() >= 0 &&
  674. !isNaN($("#thumbnailImgWidthSig_text").val()) && $("#thumbnailImgWidthSig_text").val() >= 0 &&
  675. !isNaN($("#thumbnailImgHeightSig_text").val()) && $("#thumbnailImgHeightSig_text").val() >= 0)
  676. {
  677. settings.thumbnailWidth = $("#thumbnailImgWidth_text").val();
  678. settings.thumbnailHeight = $("#thumbnailImgHeight_text").val();
  679. settings.thumbnailWidthSig = $("#thumbnailImgWidthSig_text").val();
  680. settings.thumbnailHeightSig = $("#thumbnailImgHeightSig_text").val();
  681. }
  682. else
  683. {
  684. reportResponseMsgInImagefaqsSettingsPopupBox("Error: Invalid thumbnail dimensions. Use non-negative integers only.", settingsPopup);
  685. return;
  686. }
  687.  
  688. /*if valid expanded dimensions*/
  689. if (
  690. !isNaN($("#expandedWidth_text").val()) && $("#expandedWidth_text").val() >= 0 &&
  691. !isNaN($("#expandedHeight_text").val()) && $("#expandedHeight_text").val() >= 0)
  692. {
  693. settings.expandedWidth = $("#expandedWidth_text").val();
  694. settings.expandedHeight = $("#expandedHeight_text").val();
  695. }
  696. else
  697. {
  698. reportResponseMsgInImagefaqsSettingsPopupBox("Error: Invalid expanded dimensions. Use non-negative integers only.", settingsPopup);
  699. return;
  700. }
  701.  
  702. /*if valid image container background color*/
  703. if (settingsPopup.find("#imgContainerBackgroundColor_color").val().match(/^(#[a-zA-Z0-9]{6})|0$/i))
  704. {
  705. settings.imgContainerBackgroundColor = settingsPopup.find("#imgContainerBackgroundColor_color").val();
  706. }
  707. else
  708. {
  709. reportResponseMsgInImagefaqsSettingsPopupBox("Error: Invalid image container background color.", settingsPopup);
  710. return;
  711. }
  712.  
  713. settings.enable_sigs = settingsPopup.find("#enable_sigs_checkbox").is(":checked");
  714. settings.enable_floatingImg = settingsPopup.find("#enable_floatingImg").is(":checked");
  715. settings.enable_floatingImg_url = settingsPopup.find("#enable_floatingImg_url").is(":checked");
  716.  
  717. settings.loopThumbnailVideo = settingsPopup.find("#loopThumbnailVideo_checkbox").is(":checked");
  718. settings.loopExpandedVideo = settingsPopup.find("#loopExpandedVideo_checkbox").is(":checked");
  719.  
  720. settings.includeSigWhenExpanding = settingsPopup.find("#includeSigWhenExpanding_checkbox").is(":checked");
  721. settings.hideSigsWhenHideMode = settingsPopup.find("#hideSigsWhenHideMode_checkbox").is(":checked");
  722. settings.legacySigDetection = settingsPopup.find("#legacySigDetection_checkbox").is(":checked");
  723.  
  724. settings.show_imgURLspan = settingsPopup.find("#show_ImgURLspan_checkbox").is(":checked");
  725. settings.show_imgResolutionSpan = settingsPopup.find("#show_ImgResolutionSpan_checkbox").is(":checked");
  726. settings.show_imgFilesizeSpan = settingsPopup.find("#show_imgFilesizeSpan_checkbox").is(":checked");
  727. settings.show_imgGoogle = settingsPopup.find("#show_imgGoogle_checkbox").is(":checked");
  728. settings.show_imgIqdb = settingsPopup.find("#show_imgIqdb_checkbox").is(":checked");
  729. settings.show_blacklistToggle = settingsPopup.find("#show_blacklistToggle_checkbox").is(":checked");
  730. settings.show_imgAllResizeToggles = settingsPopup.find("#show_imgAllResizeToggles_checkbox").is(":checked");
  731. settings.show_hideToggle = settingsPopup.find("#show_hideToggle_checkbox").is(":checked");
  732. settings.show_hideAllToggle = settingsPopup.find("#show_hideAllToggle_checkbox").is(":checked");
  733.  
  734. settings.imgContainerStyle = settingsPopup.find("input[name=imgContainerStyle]:checked").val();
  735. settings.auto_scroll_when_thumbnail_clicked = settingsPopup.find("#auto_scroll_when_thumbnail_clicked_checkbox").is(":checked");
  736.  
  737. settings.resizeHotkey = cloneKVArray(sessionResizeHotkey);
  738. settings.hideToggleHotkey = cloneKVArray(sessionHideToggleHotkey);
  739. settings.blacklistStr = settingsPopup.find("#blacklist_text").val();
  740.  
  741. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  742.  
  743. reportResponseMsgInImagefaqsSettingsPopupBox("Settings saved.", settingsPopup);
  744. });
  745.  
  746.  
  747.  
  748. /*
  749. Display a notification in the settings window
  750.  
  751. @param msg :: message string to show to the user
  752. @param box :: $("#imagefaqs_settings_popup")
  753. */
  754. function reportResponseMsgInImagefaqsSettingsPopupBox(msg, box)
  755. {
  756. var msgBox = box.children("#responseSpan");
  757.  
  758. msgBox.html(msg);
  759. msgBox.effect("highlight");
  760. }
  761.  
  762.  
  763. /*Event when users clicks on the "Cancel" button the settings window.*/
  764. $("body").on("click", "#cancelImagefaqsSettingsBut", function(event) {
  765.  
  766. event.preventDefault();
  767. $("#imagefaqs_settings_popup").dialog("close");
  768. });
  769.  
  770.  
  771.  
  772.  
  773.  
  774.  
  775. /*Begin main scripting*/
  776. (function(){
  777.  
  778. print("Entered main()");
  779.  
  780. var isHideMode = settings.isHideMode;
  781. var enable_floatingImg = settings.enable_floatingImg;
  782. var enable_floatingImg_url = settings.enable_floatingImg_url;
  783.  
  784. var includeSigWhenExpanding = settings.includeSigWhenExpanding;
  785. var hideSigsWhenHideMode = settings.hideSigsWhenHideMode;
  786. var legacySigDetection = settings.legacySigDetection;
  787.  
  788. var loopThumbnailVideo = settings.loopThumbnailVideo;
  789. var loopExpandedVideo = settings.loopExpandedVideo;
  790.  
  791. var isShow = {
  792. URLSpan: settings.show_imgURLspan,
  793. resolutionSpan: settings.show_imgResolutionSpan,
  794. filesizeSpan: settings.show_imgFilesizeSpan,
  795. googleReverse: settings.show_imgGoogle,
  796. IQDBreverse: settings.show_imgIqdb,
  797. blacklistToggle: settings.show_blacklistToggle,
  798. resizeToggles: settings.show_imgAllResizeToggles,
  799. hideToggles: settings.show_hideAllToggle,
  800. hideToggle: settings.show_hideToggle,
  801. };
  802.  
  803. var imgContainerStyle = settings.imgContainerStyle;
  804. var autoScrollWhenThumbnailClicked = settings.auto_scroll_when_thumbnail_clicked;
  805.  
  806. var thumbnailImgWidth = Number(settings.thumbnailWidth);
  807. var thumbnailImgHeight = Number(settings.thumbnailHeight);
  808. var thumbnailImgWidthSig = Number(settings.thumbnailWidthSig);
  809. var thumbnailImgHeightSig = Number(settings.thumbnailHeightSig);
  810. var expandedImgWidth = Number(settings.expandedWidth);
  811. var expandedImgHeight = Number(settings.expandedHeight);
  812.  
  813. var imgContainerBackgroundColor = settings.imgContainerBackgroundColor;
  814. if (imgContainerBackgroundColor === "#000000") {
  815. imgContainerBackgroundColor = 0;
  816. }
  817.  
  818. var blacklist = settings.blacklistStr.split("\n"); /*array of URLs to block*/
  819. var currentURL;
  820. var pageType; /* "topic" or "postPreview" or "other" */
  821. var allowImgInSig = settings.enable_sigs;
  822. var floatingImgWidth;
  823. var floatingImgRightOffset = 50;
  824. var floatingImgBorder = 10;
  825. var currentHoveredThumbnail = null;
  826. var imgAnchorTags = [];
  827.  
  828. var expandedImgWidth_css = expandedImgWidth === 0 ? "" : expandedImgWidth+"px";
  829. var expandedImgHeight_css = expandedImgHeight === 0 ? "" : expandedImgHeight+"px";
  830.  
  831. print("Global settings applied");
  832.  
  833. var heldDownKeys = {};
  834. $("body").on("keydown", "#imagefaqs_settings_popup #hideToggleHotkey_text", function(event){
  835.  
  836. heldDownKeys[event.which] = event.which;
  837. sessionHideToggleHotkey = cloneKVArray(heldDownKeys);
  838.  
  839. $(this).val(
  840. keyCodeKVArrayToString(sessionHideToggleHotkey)
  841. );
  842.  
  843. return false;
  844. });
  845.  
  846. $("body").on("keydown", "#imagefaqs_settings_popup #resizeHotkey_text", function(event){
  847.  
  848. heldDownKeys[event.which] = event.which;
  849. sessionResizeHotkey = cloneKVArray(heldDownKeys);
  850.  
  851. $(this).val(
  852. keyCodeKVArrayToString(sessionResizeHotkey)
  853. );
  854.  
  855. return false;
  856. });
  857.  
  858.  
  859. $("body").on("keyup", "#imagefaqs_settings_popup .hotkeyInput", function(event){
  860. delete heldDownKeys[event.which];
  861. return false;
  862. });
  863.  
  864. $("body").on("keypress", "#imagefaqs_settings_popup .hotkeyInput", function(event){
  865. event.preventDefault();
  866. });
  867.  
  868.  
  869.  
  870. /*
  871. Toggle between "display" and "hide" images.
  872. */
  873. function toggleImageVisiblity(embeddedImgContainers, via)
  874. {
  875. if (via === "hotkey")
  876. isHideMode = !isHideMode;
  877.  
  878. /*If set images to be hidden...*/
  879. if ((via === "hotkey" && isHideMode) ||
  880. via === "closeAnchor")
  881. {
  882. if (pageType !== "other")
  883. {
  884. embeddedImgContainers.each(function(index, element) {
  885. if ($(this).hasClass("isHidable") && !$(this).hasClass("hidden")) {
  886. hideMedia($(this), false);
  887. }
  888. });
  889.  
  890. if (via === "hotkey")
  891. showNotification("ImageFAQs: All images hidden.");
  892. }
  893. else
  894. {
  895. if (via === "hotkey")
  896. showNotification("ImageFAQs: Images will be hidden.");
  897. }
  898. }
  899. else
  900. {
  901. if (pageType !== "other")
  902. {
  903. embeddedImgContainers.each(function(index, element) {
  904. if ($(this).hasClass("hidden")) {
  905. showMedia($(this), false);
  906. }
  907. });
  908.  
  909. if (via === "hotkey")
  910. showNotification("ImageFAQs: All images exposed.");
  911. }
  912. else
  913. {
  914. if (via === "hotkey")
  915. showNotification("ImageFAQs: Images will be exposed.");
  916. }
  917. }
  918.  
  919. settings.isHideMode = isHideMode;
  920. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  921. }
  922.  
  923.  
  924.  
  925.  
  926.  
  927. var nextImageSize = "expanded";
  928. $("body").keydown(function(event){
  929.  
  930. var key;
  931. var numMatchingPressedHotkeys;
  932. var desiredAction = "";
  933. heldDownKeys[event.which] = event.which;
  934.  
  935.  
  936. /*if backspace, ignore*/
  937. if (heldDownKeys[8] === 8)
  938. {
  939. return;
  940. }
  941.  
  942.  
  943. /*are every hide mode hotkey pressed?*/
  944. numMatchingPressedHotkeys = 0;
  945. for (key in settings.hideToggleHotkey)
  946. {
  947. if (settings.hideToggleHotkey[key] !== heldDownKeys[key])
  948. {
  949. break;
  950. }
  951.  
  952. numMatchingPressedHotkeys++;
  953. }
  954. if (numMatchingPressedHotkeys === Object.size(settings.hideToggleHotkey) && numMatchingPressedHotkeys > 0)
  955. {
  956. desiredAction = "toggleHideMode";
  957. }
  958.  
  959.  
  960.  
  961.  
  962. /*is every resize hotkey pressed?*/
  963. if (desiredAction === "")
  964. {
  965. numMatchingPressedHotkeys = 0;
  966. for (key in settings.resizeHotkey)
  967. {
  968. if (settings.resizeHotkey[key] !== heldDownKeys[key])
  969. {
  970. break;
  971. }
  972. numMatchingPressedHotkeys++;
  973. }
  974. if (numMatchingPressedHotkeys === Object.size(settings.resizeHotkey) && numMatchingPressedHotkeys > 0)
  975. {
  976. desiredAction = "toggleImageResize";
  977. }
  978. }
  979.  
  980.  
  981.  
  982. if (desiredAction === "toggleHideMode")
  983. {
  984. toggleImageVisiblity($(".embeddedImgContainer"), "hotkey");
  985. }
  986. else if (desiredAction === "toggleImageResize")
  987. {
  988. if (nextImageSize === "expanded")
  989. {
  990. toggleSizesOfImages($(".embeddedImg"), true);
  991. nextImageSize = "thumbnail";
  992. showNotification("ImageFAQs: Expanded all images.");
  993. }
  994. else
  995. {
  996. toggleSizesOfImages($(".embeddedImg.expandedImg"), false);
  997. nextImageSize = "expanded";
  998. showNotification("ImageFAQs: Shrunk all images.");
  999. }
  1000. }
  1001. });
  1002.  
  1003.  
  1004.  
  1005.  
  1006. $("body").keyup(function(event){
  1007. delete heldDownKeys[event.which];
  1008. });
  1009.  
  1010.  
  1011.  
  1012.  
  1013.  
  1014.  
  1015.  
  1016. if (blacklist.length === 1 && blacklist[0] === "")
  1017. {
  1018. blacklist = [];
  1019. }
  1020. else
  1021. {
  1022. /*trim white space*/
  1023. for (var i = 0; i < blacklist.length; i++)
  1024. {
  1025. blacklist[i].trim();
  1026. }
  1027. }
  1028.  
  1029.  
  1030.  
  1031.  
  1032.  
  1033.  
  1034.  
  1035. function isURLmatchBlacklistedURL(url, blacklistedURL)
  1036. {
  1037. blacklistedURL = blacklistedURL.trim();
  1038.  
  1039. /*escape everything*/
  1040. var blacklistedURLregex =
  1041. new RegExp( blacklistedURL.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") );
  1042.  
  1043. if (blacklistedURLregex.exec(url) !== null)
  1044. {
  1045. return true;
  1046. }
  1047. else
  1048. {
  1049. return false;
  1050. }
  1051. }
  1052.  
  1053.  
  1054. /*
  1055. Is the url string in the blacklist?
  1056.  
  1057. @url :: string of the url to check
  1058. @blacklist :: array of blacklisted urls
  1059.  
  1060. Returns true or false
  1061. */
  1062. function isURLinBlacklist(url, blacklist)
  1063. {
  1064. for (var i = 0; i < blacklist.length; i++)
  1065. {
  1066. if (isURLmatchBlacklistedURL(url, blacklist[i]))
  1067. {
  1068. return true;
  1069. }
  1070. }
  1071.  
  1072. return false;
  1073. }
  1074.  
  1075.  
  1076.  
  1077.  
  1078. $(window).scroll(function() {
  1079.  
  1080. var notificationBoxes = $("body").children("#imagefaqs_notificationBox");
  1081.  
  1082. notificationBoxes.each(function(index, element) {
  1083. $(this).css({
  1084. "top": ($(window).scrollTop() + $(window).height() - $(this).height())+"px",
  1085. "left": ($(window).scrollLeft() + $(window).width() - $(this).find("span").width() - 1)+"px"
  1086. });
  1087. });
  1088. });
  1089.  
  1090.  
  1091.  
  1092. print("Parsing URL")
  1093.  
  1094. currentURL = window.location.href;
  1095. if (currentURL.match(/.*gamefaqs\.gamespot\.com\/boards\/.*\/.*\/.*/i)) /*gfaqsdep*/
  1096. {
  1097. pageType = "messageDetails";
  1098. }
  1099. else if (currentURL.match(/.*gamefaqs\.gamespot\.com\/boards\/.*\/.*/i)) /*gfaqsdep*/
  1100. {
  1101. pageType = "topic";
  1102. }
  1103. else if (currentURL.match(/.*gamefaqs\.gamespot\.com\/boards\/post/i)) /*gfaqsdep*/
  1104. {
  1105. pageType = "postPreview";
  1106. }
  1107. else if (currentURL.match(/.*gamefaqs\.gamespot\.com\/user[^\/]*$/i)) /*gfaqsdep*/
  1108. {
  1109. /*add settings link*/ /*gfaqsdep*/
  1110. $("div.body tbody:eq(1)").append("<tr><td colspan='2'><a id='imagefaqs_settings_but' href='#'>ImageFAQs Options</a> - Change thumbnail size, enable signature embedding, and more.</td></tr>");
  1111. }
  1112. else
  1113. {
  1114. pageType = "other";
  1115. return;
  1116. }
  1117.  
  1118.  
  1119. function GM_addStyle_from_file(fileName) {
  1120.  
  1121. var head = document.head
  1122. , link = document.createElement("link")
  1123.  
  1124. link.type = "text/css"
  1125. link.rel = "stylesheet"
  1126. link.href = fileName
  1127.  
  1128. head.appendChild(link)
  1129. }
  1130.  
  1131. function GM_addStyle_from_string(str) {
  1132. var node = document.createElement('style');
  1133. node.innerHTML = str;
  1134. document.body.appendChild(node);
  1135. }
  1136.  
  1137. var jqueryuibaseCSS = "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery.ui.base.css"
  1138. var jqueryuithemeCSS = "https://code.jquery.com/ui/1.11.4/themes/cupertino/jquery-ui.css"
  1139.  
  1140. print("GM_addStyle_from_file(jqueryuibaseCSS)")
  1141. GM_addStyle_from_file (jqueryuibaseCSS);
  1142. print("GM_addStyle_from_file(jqueryuithemeCSS)")
  1143. GM_addStyle_from_file (jqueryuithemeCSS);
  1144.  
  1145.  
  1146.  
  1147.  
  1148.  
  1149. print("GM_addStyle_from_string(all)")
  1150. GM_addStyle_from_string (
  1151. ".embeddedImgContainer {" +
  1152. "background: "+imgContainerBackgroundColor+";" +
  1153. "}" +
  1154. ".embeddedImgAnchor {"+
  1155. "display: inline-block;"+
  1156. "}" +
  1157. ".thumbnailImg {"+
  1158. "display: inline-block;"+ /*prevents parent anchor from stretching horizontally*/
  1159. "vertical-align: bottom;"+ /*eliminates small gap between bottom of image and parent div*/
  1160. "}" +
  1161. ".expandedImg {" +
  1162. "display: inline-block;" +
  1163. "vertical-align: bottom;" +
  1164. "}" +
  1165. ".imgMenuButton {" +
  1166. "overflow: hidden;" +
  1167. "position: absolute;" +
  1168. "text-align: center;" +
  1169. "z-index: 90;" +
  1170. "background-color: #99FFCC;" +
  1171. "opacity: 0.5;" +
  1172. "pointer-events: none;" +
  1173. "}" +
  1174. ".imgMenuButton_symbol {" +
  1175. "display: table-cell;" +
  1176. "vertical-align: middle;" +
  1177. "}" +
  1178. ".imgMenuItem {" +
  1179. "display: block;" +
  1180. "}" +
  1181. ".imgMenuItemAnchor {" +
  1182. "display: block;" +
  1183. "}" +
  1184. ".imgMenu {" +
  1185. "background-color: #CCFFFF;" +
  1186. "opacity: 0.9;" +
  1187. "border-style: solid;" +
  1188. "border-width: 1px;" +
  1189. "}" +
  1190. ".ui-widget { " + /*shrink font size in the settings menu*/
  1191. "font-size:96%;" +
  1192. "}" +
  1193. "#imagefaqs_settings_popup input { " +
  1194. "margin-right: 5px;" +
  1195. "}" +
  1196. "#imagefaqs_settings_popup a { " +
  1197. "color: blue;" +
  1198. "}" +
  1199. ".widget { " +
  1200. "display: inline-block;"+ /*Allows wrapping of widget elements without expanding parent div (i.e. float left),*/
  1201. "}" /*and clears next element to newline (i.e. next element is not floating.*/
  1202. );
  1203.  
  1204.  
  1205. print("CSS applied");
  1206.  
  1207.  
  1208. $("body").css("position", "relative");
  1209. var posts = null;
  1210.  
  1211. posts = $("div.msg_body"); /*gfaqsdep*/
  1212.  
  1213.  
  1214. print("Retrieved posts");
  1215.  
  1216.  
  1217.  
  1218. function getMediaTyping(obj)
  1219. {
  1220. var url = "";
  1221. if (typeof obj == "string")
  1222. {
  1223. url = obj;
  1224. }
  1225. else
  1226. {
  1227. url = getMediaURL(obj);
  1228. }
  1229.  
  1230. var mediaTyping = {
  1231. type: undefined,
  1232. subtype: undefined,
  1233. };
  1234.  
  1235. if (url.match(/i\.imgtc\.com\/\w+\.gifm$/))
  1236. {
  1237. mediaTyping.type = "iframe";
  1238. mediaTyping.subtype = "imgtc";
  1239. }
  1240. else if (url.match(/https?:\/\/[^<>\s]*?\.(?:png|jpg|gif|jpeg|webm|gifv|mp4)/i))
  1241. {
  1242. var mediaExt = url.split(".").pop();
  1243. mediaTyping.subtype = mediaExt.match(/png|jpg|gifv|gif|jpeg|webm|mp4/i)[0];
  1244. mediaTyping.type = mediaTyping.subtype.match(/webm|mp4/i) ? "video" : "image" ;
  1245. }
  1246. else if (url.match(/https?:\/\/gfycat\.com\/\w+$/i))
  1247. {
  1248. /*will be converted to webm later*/
  1249.  
  1250. mediaTyping.type = "iframe";
  1251. mediaTyping.subtype = "gfycat";
  1252. }
  1253. else if (url.match(/https?:\/\/(.\.)?imgur\.com\/\w+$/i))
  1254. {
  1255. /*will be converted to webm later*/
  1256.  
  1257. mediaTyping.type = "iframe";
  1258. mediaTyping.subtype = "imgur";
  1259. }
  1260. else
  1261. {
  1262. mediaTyping = undefined;
  1263. }
  1264.  
  1265. return mediaTyping;
  1266. }
  1267.  
  1268. function getMediaURL(obj)
  1269. {
  1270. if (obj.hasClass("embeddedImg"))
  1271. {
  1272. /*original 'href' may be modified to stop anchor events*/
  1273. return obj.closest(".embeddedImgAnchor").attr("data-backup-href");
  1274. }
  1275. else if (obj.hasClass("floatingImg"))
  1276. {
  1277. return obj.embeddedImgRef.closest(".embeddedImgAnchor").attr("data-backup-href");
  1278. }
  1279. else if (obj.hasClass(".embeddedImgContainer"))
  1280. {
  1281. return obj.find(".embeddedImgAnchor").attr("data-backup-href");
  1282. }
  1283. else
  1284. {
  1285. var container = getEmbeddedImgContainer(obj);
  1286. return container.find(".embeddedImgAnchor").attr("data-backup-href");
  1287. }
  1288. }
  1289.  
  1290.  
  1291. /* gfaqsdep */
  1292. // Remove imgur iframes that were automatically opened.
  1293. let divEmbedFrames = $('.embed_frame');
  1294. for (let divEmbedFrame of divEmbedFrames) {
  1295. divEmbedFrame = $(divEmbedFrame)
  1296. let iframe = divEmbedFrame.children().first();
  1297. iframe = $(iframe)
  1298. if (iframe.attr('class').match(/imgur/)) {
  1299. divEmbedFrame.remove();
  1300. }
  1301. }
  1302.  
  1303.  
  1304.  
  1305. var jsPosts = $.makeArray(posts);
  1306.  
  1307. /*For each user post, get anchor tags that refer to media*/
  1308. async.each(jsPosts, function(jsPost, cb) {
  1309. var jqPost = $(jsPost);
  1310.  
  1311. /*if in post preview mode, need to manually parse through the preview message body and
  1312. replace every plain-text image URL in its anchor form
  1313.  
  1314. Bug fix: messageDetails already embeds clickable links.*/
  1315. if (pageType === "postPreview")
  1316. {
  1317. var postHtml = jqPost.html();
  1318. postHtml = postHtml.replace(/https?:\/\/[^<>\s]*?\.(?:png|jpg|gif|jpeg|webm|gifv|mp4)[^<>\s]*/ig, "<a href=\"$&\">$&</a>");
  1319.  
  1320. jqPost.html(postHtml);
  1321. }
  1322.  
  1323. /*get all anchor tags*/
  1324. let jsAnchorTags = $.makeArray(jqPost.find("a"));
  1325.  
  1326. jsPost.jsMediaAnchorTags = filterIn_mediaAnchorTags(jsAnchorTags);
  1327.  
  1328. markSigAnchorTags(jsPost.jsMediaAnchorTags, jqPost);
  1329.  
  1330. if (!allowImgInSig)
  1331. jsPost.jsMediaAnchorTags = removeSigAnchorTags(jsPost.jsMediaAnchorTags);
  1332.  
  1333. // Give each anchor tag the post number
  1334. let postID = jqPost.attr("data-msgnum"); /* gfaqsdep */
  1335. let i =0 ;
  1336. for (let jsAnchorTag of jsPost.jsMediaAnchorTags) {
  1337. jsAnchorTag.postID = postID
  1338. }
  1339.  
  1340. cb();
  1341. });
  1342.  
  1343.  
  1344. function filterIn_mediaAnchorTags(jsAnchorTags) {
  1345. var jsMediaAnchorTags = [];
  1346.  
  1347. for (let jsAnchorTag of jsAnchorTags) {
  1348. var jqAnchorTag = $(jsAnchorTag);
  1349. var anchorURL = jqAnchorTag.attr("href");
  1350.  
  1351. /* Bug fix: Hidden quote link */
  1352. if (anchorURL == null) {
  1353. continue;
  1354. }
  1355.  
  1356. /*Bug fix: Ignore @Username links*/
  1357. if (anchorURL.match(/^\/community\//)) {
  1358. continue;
  1359. }
  1360.  
  1361. /*Bug fix: Ignore 'FightingGames posted...' links*/
  1362. if (anchorURL.match(/^\/boards\//)) {
  1363. continue;
  1364. }
  1365.  
  1366. if (isTampermonkey)
  1367. if (jqAnchorTag.attr("data-embed-type") == "imgur") {
  1368. if (jqAnchorTag[0] !== undefined) {
  1369. if (jqAnchorTag[0].nextSibling !== null && jqAnchorTag[0].nextSibling.nodeType === 3 /*isText?*/)
  1370. jqAnchorTag[0].nextSibling.nodeValue = ""; // remove "&nbsp;" text
  1371. if (jqAnchorTag.prev().is('br'))
  1372. jqAnchorTag.prev().remove(); // remove <br>
  1373. if (jqAnchorTag.next().next().is('br'))
  1374. jqAnchorTag.next().next().remove(); // remove <br> that's after the subsequent embedded anchor.
  1375. }
  1376.  
  1377. jqAnchorTag.remove();
  1378.  
  1379. continue;
  1380. }
  1381. else if (jqAnchorTag.hasClass("embed")) { // e.g. Twitter URL
  1382. continue;
  1383. }
  1384. else if (jqAnchorTag.prev().hasClass("embed")) { // e.g. small button
  1385. continue;
  1386. }
  1387.  
  1388. /*Bug fix: Occurs when opening image on same tab and returning. It will reparse URLs that
  1389. have already been rendered */
  1390. /*
  1391. if (jqAnchorTag.parent().hasClass("widget")) {
  1392. print("Filtered out URL header")
  1393. cb();
  1394. return;
  1395. }
  1396.  
  1397. if (jqAnchorTag.hasClass("embeddedImgAnchor")) {
  1398. print("Filtered out embeddedImgAnchor")
  1399. cb();
  1400. return;
  1401. }
  1402.  
  1403. if (jqAnchorTag.attr('href') == '#') {
  1404. print("Filtered out toggles")
  1405. cb();
  1406. return;
  1407. }
  1408.  
  1409. if (anchorURL.match(/(google\.com\/searchbyimage)|(iqdb\.org\/\?url)/)) {
  1410. print("Filtered out reverse image search links")
  1411. cb();
  1412. return;
  1413. }
  1414. */
  1415.  
  1416. /*In addition, convert URL if needed*/
  1417. anchorURL = standardizeURL(anchorURL);
  1418. jqAnchorTag.attr("href", anchorURL);
  1419. jqAnchorTag.html(anchorURL); /*bug fix: needed as well with href*/
  1420.  
  1421. if (getMediaTyping(anchorURL))
  1422. {
  1423. print("Filtered in " + anchorURL);
  1424. jqAnchorTag.addClass("imgAnchor");
  1425. jsMediaAnchorTags.push(jsAnchorTag);
  1426. }
  1427. }
  1428.  
  1429. return jsMediaAnchorTags;
  1430. }
  1431.  
  1432.  
  1433. function standardizeURL(url) {
  1434. var standardizeURL = ""
  1435.  
  1436. var mediaTyping = getMediaTyping(url);
  1437. if (mediaTyping != undefined && mediaTyping.subtype === "gifv")
  1438. {
  1439. standardizeURL = url.replace(/gifv$/i, "gif");
  1440. }
  1441. else
  1442. {
  1443. standardizeURL = url;
  1444. }
  1445.  
  1446. return standardizeURL;
  1447. }
  1448.  
  1449.  
  1450. function markSigAnchorTags(jsAnchorTags, jqPost) {
  1451. var postHtml;
  1452. var sigStrIdx;
  1453.  
  1454. /*get index of sig separator*/
  1455. if (legacySigDetection)
  1456. {
  1457. postHtml = jqPost.html();
  1458. sigStrIdx = postHtml.indexOf("<br>---<br>");
  1459. }
  1460.  
  1461. /*For each anchor tag, addClass('withinSig') if below signature mark*/
  1462. async.each(jsAnchorTags, function(jsAnchorTag, cb) {
  1463. var jqAnchorTag = $(jsAnchorTag);
  1464. var anchorURL = jqAnchorTag.attr('href');
  1465. var anchorTagStrIdx;
  1466.  
  1467. if (legacySigDetection)
  1468. anchorTagStrIdx = postHtml.indexOf(anchorURL + "</a>", 0);
  1469.  
  1470. /*if anchor is within of sig*/
  1471. if (jqAnchorTag.closest(".sig_text").length || /*gfaqsdep*/
  1472. legacySigDetection && sigStrIdx !== -1 && anchorTagStrIdx > sigStrIdx)
  1473. {
  1474. jqAnchorTag.addClass("withinSig");
  1475. }
  1476.  
  1477. cb();
  1478. });
  1479. }
  1480.  
  1481. function removeSigAnchorTags(jsAnchorTags) {
  1482. var nonSigAnchorTags = [];
  1483.  
  1484. async.eachSeries(jsAnchorTags, function(jsAnchorTag, cb) {
  1485. if (!$(jsAnchorTag).hasClass("withinSig"))
  1486. nonSigAnchorTags.push(jsAnchorTag);
  1487. cb();
  1488. });
  1489.  
  1490. return nonSigAnchorTags;
  1491. }
  1492.  
  1493.  
  1494. /*Merge all media anchor tags from all posts into a single array*/
  1495. //async.eachSeries(jsPosts, function(jsPost, cb) {
  1496. // imgAnchorTags = $.merge(imgAnchorTags, jsPost.jsMediaAnchorTags);
  1497. // cb();
  1498. //});
  1499. imgAnchorTags = []
  1500. for (let jsPost of jsPosts) {
  1501. for (let jsTag of jsPost.jsMediaAnchorTags)
  1502. imgAnchorTags.push(jsTag)
  1503. }
  1504.  
  1505.  
  1506.  
  1507.  
  1508. /*For each valid image URL anchor tag, replace with template div for embedded image template*/
  1509. var embeddedImgContainers = [];
  1510. for (let [imgAnchorIdx, imgAnchor] of imgAnchorTags.entries())
  1511. {
  1512. imgAnchor = $(imgAnchor);
  1513. var imgURL = imgAnchor.html();
  1514. var mediaTyping = getMediaTyping(imgURL);
  1515. var imgWithinSig_class = imgAnchor.hasClass("withinSig") ? "withinSig" : "";
  1516. var imgIsHidable_class = allowImgInSig && imgAnchor.hasClass("withinSig") && !hideSigsWhenHideMode ? "" : "isHidable";
  1517. var imgInitiallyBlacklisted_class = "";
  1518. var imgSpoilersTagged_class = imgAnchor.closest("s").length > 0 ? "spoilersTagged" : "";
  1519. var postID;
  1520. var isBlacklisted;
  1521. var imgContainerStyle_attr;
  1522.  
  1523. print("Creating container for " + imgURL);
  1524.  
  1525. isBlacklisted = isURLinBlacklist(imgURL, blacklist);
  1526. if (isBlacklisted)
  1527. imgInitiallyBlacklisted_class = "initiallyBlacklisted";
  1528.  
  1529. imgContainerStyle_attr = imgContainerStyle;
  1530. if (imgContainerStyle_attr === "sideBySideSig")
  1531. {
  1532. if (imgWithinSig_class === "withinSig")
  1533. imgContainerStyle_attr = "sideBySide";
  1534. else
  1535. imgContainerStyle_attr = "stacked";
  1536. }
  1537.  
  1538. if (pageType === "topic")
  1539. {
  1540. postID = imgAnchor[0].postID;
  1541. }
  1542. else /*if in post preview mode, need to provide missing message ID*/
  1543. {
  1544. imgAnchor.closest("td").addClass("msg_body"); /*gfaqsdep*/
  1545. imgAnchor.closest(".msg_body").attr("name", 0); /*gfaqsdep*/
  1546. postID = 0;
  1547. }
  1548.  
  1549. imgAnchor.replaceWith(
  1550. "<div class='embeddedImgContainer "+imgWithinSig_class+" "+imgIsHidable_class+" "+imgInitiallyBlacklisted_class+
  1551. " "+imgSpoilersTagged_class+"' data-postID='"+postID+"' data-style='"+imgContainerStyle_attr+"' data-idx='"+imgAnchorIdx+"'>" +
  1552. "<a class='embeddedImgAnchor' href='" + imgURL + "' data-backup-href='" + imgURL + "'>" +
  1553. "</a>" +
  1554. "</div>"
  1555. );
  1556.  
  1557. let embeddedImgContainer = $(".embeddedImgContainer[data-idx='"+imgAnchorIdx+"']");
  1558. embeddedImgContainers.push( embeddedImgContainer );
  1559.  
  1560. if (imgContainerStyle_attr == "stacked")
  1561. addDefaultWidgets(embeddedImgContainer);
  1562.  
  1563. print("Finished creating container for " + imgURL);
  1564. }
  1565.  
  1566.  
  1567. function getPredefinedWidgetStr(widgetClassStr, postID /*only for hideButtons*/)
  1568. {
  1569. if (widgetClassStr == "closeMediaButton")
  1570. {
  1571. return "<a class='closeMediaButton widget' target='_blank' href='#' title=''>"+
  1572. "[Close Media]"+
  1573. "</a>";
  1574. }
  1575. else if (widgetClassStr == "showButton")
  1576. {
  1577. return "<a class='imgHideToggle widget' href='#' title='Show image'>show</a>";
  1578. }
  1579. else if (widgetClassStr == "hideButtons")
  1580. {
  1581. return "<a class='imgHideAll widget' href='#' style='margin-right: 5px' title='Hide all images in page'>--</a>" +
  1582. "<a class='imgHidePost widget' href='#' style='margin-right: 5px' data-postID='"+postID+"' title='Hide all images in post'>-</a>" +
  1583. "<a class='imgShowPost widget' href='#' style='margin-right: 5px' data-postID='"+postID+"' title='Show all images in post'>+</a>" +
  1584. "<a class='imgShowAll widget' href='#' title='Show all images in page'>++</a>";
  1585. }
  1586. else if (widgetClassStr == "whitelistButton")
  1587. {
  1588. return "<a class='imgBlacklistToggle widget' href='#' title='Whitelist image'>whitelist</a>";
  1589. }
  1590. }
  1591.  
  1592. function addDefaultWidgets(container)
  1593. {
  1594. var embeddedImgContainer;
  1595. var imgURL = "";
  1596. var postID;
  1597. var isBlacklisted;
  1598. var isSpoilersTagged;
  1599. var isHidable;
  1600.  
  1601. var imgWidgets = {
  1602. URLSpan: "",
  1603. resolutionSpan: "",
  1604. filesizeSpan: "",
  1605. googleReverse: "",
  1606. IQDBreverse: "",
  1607. blacklistToggle: "",
  1608. resizeToggles: "",
  1609. hideToggles: "",
  1610. hideToggle: "",
  1611. };
  1612.  
  1613. if (container.hasClass("imgMenu"))
  1614. embeddedImgContainer = getEmbeddedImgContainer(container);
  1615. else
  1616. embeddedImgContainer = container;
  1617.  
  1618. imgURL = embeddedImgContainer.children(".embeddedImgAnchor").attr("href");
  1619. postID = embeddedImgContainer.attr("data-postID");
  1620. isBlacklisted = embeddedImgContainer.hasClass("blacklisted");
  1621. isSpoilersTagged = embeddedImgContainer.hasClass("spoilersTagged");
  1622. isHidable = embeddedImgContainer.hasClass("isHidable");
  1623. isImgMenu = container.hasClass("imgMenu");
  1624.  
  1625. print("Started addDefaultWidgets() for " + imgURL);
  1626.  
  1627. imgWidgets.URLSpan =
  1628. "<span class='imgURL widget'>" +
  1629. "<a target='_blank' href="+imgURL+">"+imgURL+"</a>" +
  1630. "</span>";
  1631.  
  1632. imgWidgets.resolutionSpan =
  1633. "<span class='imgResolution widget'>" +
  1634. "" +
  1635. "</span>";
  1636.  
  1637. imgWidgets.filesizeSpan =
  1638. "<span class='imgFilesize widget'>" +
  1639. "" +
  1640. "</span>";
  1641.  
  1642. imgWidgets.googleReverse =
  1643. "<a class='widget' target='_blank' title='Reverse image search on general images' href='https://www.google.com/searchbyimage?image_url="+imgURL+"'>" +
  1644. "google" +
  1645. "</a>";
  1646.  
  1647. imgWidgets.IQDBreverse =
  1648. "<a class='widget' target='_blank' title='Reverse image search on weeb images' href='http://iqdb.org/?url="+imgURL+"'>" +
  1649. "iqdb" +
  1650. "</a>";
  1651.  
  1652. if (isBlacklisted)
  1653. {
  1654. alert('here');
  1655. imgWidgets.blacklistToggle =
  1656. getPredefinedWidgetStr("whitelistButton");
  1657. }
  1658. else
  1659. {
  1660. imgWidgets.blacklistToggle =
  1661. "<a class='imgBlacklistToggle widget' href='#' title='Blacklist image'>blacklist</a>";
  1662. }
  1663.  
  1664. imgWidgets.resizeToggles =
  1665. "<a class='imgCloseAll widget' href='#' style='margin-right: 5px' title='Close all images in page'>&lt;&lt;</a>" +
  1666. "<a class='imgClosePost widget' href='#' style='margin-right: 5px' data-postID='"+postID+"' title='Close all images in post'>&lt;</a>" +
  1667. "<a class='imgExpandPost widget' href='#' style='margin-right: 5px' data-postID='"+postID+"' title='Expand all images in post'>&gt;</a>" +
  1668. "<a class='imgExpandAll widget' href='#' title='Expand all images in page'>&gt;&gt;</a>";
  1669.  
  1670. imgWidgets.hideToggles =
  1671. getPredefinedWidgetStr("hideButtons", postID);
  1672.  
  1673. /*
  1674. Bug fix: Added !isImgMenu in condition. Otherwise, when revealing a hidden
  1675. spoilered side-by-side image for the first time, its hideToggle widget will
  1676. mistakenly be set to "Show" instead of "Hide"
  1677. */
  1678. if (!isImgMenu && (isBlacklisted || isSpoilersTagged || (isHideMode && isHidable)))
  1679. {
  1680. imgWidgets.hideToggle =
  1681. getPredefinedWidgetStr("showButton");
  1682. }
  1683. else
  1684. {
  1685. imgWidgets.hideToggle =
  1686. "<a class='imgHideToggle widget' href='#' title='Hide image'>hide</a>";
  1687. }
  1688.  
  1689. print("Built widget strings in addDefaultWidgets() for " + imgURL);
  1690.  
  1691. async.eachOfSeries(imgWidgets, function(widget, widgetName, cb){
  1692.  
  1693. /*Bug fix: If 'hide toggle' widget disabled, then there's no way to reveal
  1694. a spoilered image. So, always add 'hide toggle' for spoilered images.*/
  1695. if (isShow[widgetName] || (widgetName == "hideToggle" && isSpoilersTagged))
  1696. {
  1697. //console.log("Adding widget (\""+widgetName+"\") string (\""+widget+"\") in addDefaultWidgets() for " + imgURL);
  1698. setWidget(widget, container);
  1699. }
  1700. cb();
  1701. });
  1702. }
  1703.  
  1704. function setWidget(widgetStr, container, removeClassStr)
  1705. {
  1706. var widget;
  1707. var widgets;
  1708.  
  1709. /*If adding first widget*/
  1710. if (!removeClassStr && container.children(".widgets").length == 0)
  1711. {
  1712. container.prepend("<div class='widgets'></div>");
  1713. }
  1714. widgets = container.children(".widgets");
  1715.  
  1716. if (container.hasClass("embeddedImgContainer"))
  1717. {
  1718. if (removeClassStr)
  1719. {
  1720. widgets.find("." + removeClassStr).remove();
  1721. }
  1722. else
  1723. {
  1724. widgets.append(widgetStr);
  1725.  
  1726. /*add leading whitespace to new widget*/
  1727. widget = widgets.children(':last');
  1728. widget.css('margin-right', '5px');
  1729. }
  1730. }
  1731. else if (container.hasClass("imgMenu"))
  1732. {
  1733. if (removeClassStr)
  1734. {
  1735. widgets.find("." + removeClassStr).closest(".imgMenuItem").remove();
  1736. }
  1737. else
  1738. {
  1739. widgetStr = "<div class='imgMenuItem'>" +
  1740. widgetStr +
  1741. "</div>";
  1742. widgets.append(widgetStr);
  1743. }
  1744. }
  1745. else
  1746. {
  1747.  
  1748. }
  1749.  
  1750. /*If removed last widget*/
  1751. if (removeClassStr && widgets.children().length == 0)
  1752. {
  1753. widgets.remove();
  1754. }
  1755. }
  1756.  
  1757. function getWidget(classStr, container)
  1758. {
  1759. var widgets = container.children(".widgets");
  1760. return widgets.find("." + classStr);
  1761. }
  1762.  
  1763. function hasWidget(classStr, container)
  1764. {
  1765. return container.find(".widgets").find("."+classStr).length > 0;
  1766. }
  1767.  
  1768. function getEmbeddedImgContainer(obj)
  1769. {
  1770. var embeddedImgContainer = obj.closest(".embeddedImgContainer");
  1771. if (embeddedImgContainer.length == 0)
  1772. {
  1773. var imgAnchor = getImageAnchorOfImgMenu();
  1774. embeddedImgContainer = imgAnchor.closest(".embeddedImgContainer");
  1775. }
  1776.  
  1777. return embeddedImgContainer;
  1778. }
  1779.  
  1780.  
  1781.  
  1782. /*Optimize positioning of media*/
  1783. async.eachSeries(embeddedImgContainers, function(embeddedImgContainer, cb) {
  1784.  
  1785. print("Started optimizing media position for " + getMediaURL(embeddedImgContainer));
  1786.  
  1787. var imgContainerStyle_attr = embeddedImgContainer.attr('data-style');
  1788.  
  1789. /*keep iterating through the next sibling elements until find an element that's
  1790. not whitespace*/
  1791. var curNextEleJS = embeddedImgContainer[0].nextSibling;
  1792. while (curNextEleJS !== null &&
  1793. ($(curNextEleJS).is("br") || is_ignorable(curNextEleJS)))
  1794. {
  1795. curNextEleJS = curNextEleJS.nextSibling;
  1796. }
  1797.  
  1798. /*now do the same thing, but iterating through the previous siblings*/
  1799. var curPrevEleJS = embeddedImgContainer[0].previousSibling;
  1800. while (curPrevEleJS !== null &&
  1801. is_ignorable(curPrevEleJS))
  1802. {
  1803. curPrevEleJS = curPrevEleJS.previousSibling;
  1804. }
  1805.  
  1806. /*at this point, we have two non-whitespace elements surrounding the embeddedImgContainer*/
  1807.  
  1808. /*remove the next br elements if next non-whitespace sibling is a going-to-be image*/
  1809.  
  1810. var curNextEle;
  1811. if (curNextEleJS !== null && $(curNextEleJS).hasClass("embeddedImgContainer"))
  1812. {
  1813. curNextEle = embeddedImgContainer.next();
  1814. while (curNextEle.length !== 0 && curNextEle.is("br"))
  1815. {
  1816. if (curNextEle.next().length === 0)
  1817. {
  1818. curNextEle.remove();
  1819. break;
  1820. }
  1821. else
  1822. {
  1823. curNextEle = curNextEle.next();
  1824. curNextEle.prev().remove();
  1825. }
  1826. }
  1827. }
  1828. /*if sig separator after image, remove <br> in between*/ /*gfaqsdeplegacy - no br anymore*/
  1829. else if (curNextEleJS !== null && curNextEleJS.nodeType === 3 && curNextEleJS.nodeValue === "---")
  1830. {
  1831. curNextEle = embeddedImgContainer.next();
  1832. if (curNextEle.is("br"))
  1833. curNextEle.remove();
  1834. }
  1835. /*if text after image*/ /*gfaqsdeplegacy - no br anymore*/
  1836. else if (curNextEleJS !== null && curNextEleJS.nodeType === 3 && curNextEleJS.nodeValue !== "---")
  1837. {
  1838. curNextEle = embeddedImgContainer.next();
  1839. if (curNextEle.is("br"))
  1840. curNextEle.remove();
  1841. }
  1842.  
  1843. if (imgContainerStyle_attr === "sideBySide")
  1844. {
  1845. /*if the next non-whitespace sibling not going to be an image, create a newline to
  1846. set the cursor below this image*/
  1847. if (curNextEleJS !== null && !$(curNextEleJS).hasClass("embeddedImgContainer"))
  1848. {
  1849. $(curNextEleJS).before(
  1850. "<div style='display: block;'></div>"
  1851. );
  1852. }
  1853.  
  1854. /*do same with previous non-whitespace sibling*/
  1855. if (curPrevEleJS !== null && !$(curPrevEleJS).hasClass("embeddedImgContainer"))
  1856. {
  1857. $(curPrevEleJS).after(
  1858. "<div style='display: block;'></div>"
  1859. );
  1860. }
  1861.  
  1862. /*set container to float*/
  1863. embeddedImgContainer.css("display", "inline-block");
  1864. embeddedImgContainer.css("vertical-align", "top");
  1865. embeddedImgContainer.css("margin-right", "5px");
  1866. embeddedImgContainer.css("margin-bottom", "5px");
  1867. }
  1868. else
  1869. {
  1870. embeddedImgContainer.css("margin-bottom", "10px");
  1871. }
  1872.  
  1873. print("Finished optimizing media position for " + getMediaURL(embeddedImgContainer));
  1874.  
  1875. cb();
  1876. });
  1877.  
  1878.  
  1879. /*for each template div, insert an embedded image*/
  1880. async.each(embeddedImgContainers, function(embeddedImgContainer, cb) {
  1881.  
  1882. if (isHideMode)
  1883. {
  1884. if (embeddedImgContainer.hasClass("isHidable"))
  1885. hideMedia(embeddedImgContainer, false);
  1886. else
  1887. loadMedia(embeddedImgContainer, false);
  1888. }
  1889. else
  1890. {
  1891. var isBlacklisted = embeddedImgContainer.hasClass("initiallyBlacklisted");
  1892. var isSpoilersTagged = embeddedImgContainer.hasClass("spoilersTagged");
  1893.  
  1894. if (isBlacklisted)
  1895. hideMedia(embeddedImgContainer, true);
  1896. else if (isSpoilersTagged)
  1897. hideMedia(embeddedImgContainer, false);
  1898. else
  1899. loadMedia(embeddedImgContainer, false);
  1900. }
  1901.  
  1902. cb();
  1903. });
  1904.  
  1905.  
  1906. function getMediaSize(media, span)
  1907. {
  1908. var url = getEmbeddedImgContainer(media).find(".embeddedImgAnchor").attr('href');
  1909.  
  1910. GM_xmlhttpRequest({
  1911. method: "GET",
  1912. url: url,
  1913. onload: function(response) {
  1914.  
  1915. var contentLengthStr = response.responseHeaders.match(/Content-Length: \d+/)[0];
  1916. var fileSizeBytes = parseInt(contentLengthStr.substring(16));
  1917.  
  1918. var fileSizeDisplayStr;
  1919.  
  1920. if (fileSizeBytes < 1000000) /*if less than 1.0MB*/
  1921. {
  1922. fileSizeDisplayStr = Math.round(fileSizeBytes / 1000) + "KB";
  1923. }
  1924. else
  1925. {
  1926. fileSizeDisplayStr = (fileSizeBytes / 1000000).toFixed(2) + "MB";
  1927. }
  1928.  
  1929. span.html("("+fileSizeDisplayStr+")");
  1930. }
  1931. });
  1932. }
  1933.  
  1934. function getMediaResolution(media, span)
  1935. {
  1936. if (span.length == 0)
  1937. return;
  1938.  
  1939. var mediaTyping = getMediaTyping(media);
  1940.  
  1941. if (mediaTyping.type == "video")
  1942. {
  1943. /*when video's metadata has been loaded, record its natural width and resolution*/
  1944. media[0].onloadedmetadata = function() {
  1945. var video = this;
  1946. span.html("(" + video.videoWidth + "x" + video.videoHeight + ")");
  1947. };
  1948.  
  1949. /*If video already loaded*/
  1950. if (media[0].videoWidth !== 0)
  1951. span.html("(" + media[0].videoWidth + "x" + media[0].videoHeight + ")");
  1952. }
  1953. else if (mediaTyping.type == "image")
  1954. {
  1955. media.load(function() {
  1956. span.html("(" + media[0].naturalWidth + "x" + media[0].naturalHeight + ")");
  1957. });
  1958.  
  1959. /*if image already loaded*/
  1960. if (media[0].naturalWidth !== 0)
  1961. {
  1962. span.html("(" + media[0].naturalWidth + "x" + media[0].naturalHeight + ")");
  1963. }
  1964. }
  1965. }
  1966.  
  1967. function showMedia(mediaContainer, isToWhitelist)
  1968. {
  1969. var mediaAnchor = mediaContainer.children(".embeddedImgAnchor");
  1970. var mediaURL = mediaAnchor.attr("data-backup-href");
  1971. var mediaContainerStyle = mediaContainer.attr("data-style");
  1972. var isSpoilersTagged = mediaContainer.hasClass("spoilersTagged");
  1973.  
  1974. if (!mediaContainer.hasClass("loaded"))
  1975. loadMedia(mediaContainer);
  1976.  
  1977. mediaContainer.removeClass("hidden");
  1978.  
  1979. if (isToWhitelist)
  1980. {
  1981. /*remove url from blacklist*/
  1982. var urlIdx = blacklist.indexOf(mediaURL);
  1983. if (urlIdx > -1)
  1984. {
  1985. blacklist.splice(urlIdx, 1);
  1986. }
  1987.  
  1988. mediaAnchor.removeClass("blacklisted");
  1989.  
  1990. if (mediaContainerStyle === "stacked" && isShow.blacklistToggle)
  1991. {
  1992. var imgBlacklistToggle = getWidget("imgBlacklistToggle", mediaContainer);
  1993. imgBlacklistToggle.html("blacklist");
  1994. imgBlacklistToggle.attr("title", "Blacklist image");
  1995. }
  1996.  
  1997. settings.blacklistStr = blacklist.join("\n");
  1998. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  1999. }
  2000.  
  2001. mediaAnchor.show();
  2002.  
  2003. if (mediaContainerStyle === "sideBySide")
  2004. {
  2005. mediaAnchor.siblings("a.imgMenuItem").remove();
  2006. mediaAnchor.siblings("div.imgMenuItem").remove();
  2007.  
  2008. mediaAnchor.attr("style", "");
  2009. mediaAnchor.parent().css("max-width", "");
  2010. }
  2011. else
  2012. {
  2013. /*Bug fix: Spoilered images must have hideToggle regardless.*/
  2014. if (isShow.hideToggle || isSpoilersTagged)
  2015. {
  2016. var imgHideToggle = getWidget("imgHideToggle", mediaContainer);
  2017.  
  2018. imgHideToggle.html("hide");
  2019. imgHideToggle.attr("title", "Hide image");
  2020. }
  2021. }
  2022. }
  2023.  
  2024.  
  2025.  
  2026. function hideMedia(mediaContainer, isToBlacklist)
  2027. {
  2028. var mediaAnchor = mediaContainer.children(".embeddedImgAnchor");
  2029. var media = mediaAnchor.children(".embeddedImg");
  2030. var mediaURL = mediaAnchor.attr("data-backup-href");
  2031. var postID = mediaContainer.attr("data-postID");
  2032. var mediaContainerStyle = mediaContainer.attr("data-style");
  2033. var isSig = mediaContainer.hasClass("withinSig");
  2034. var isSpoilersTagged = mediaContainer.hasClass("spoilersTagged");
  2035. var mediaThumbnailImgWidth = isSig ? thumbnailImgWidthSig : thumbnailImgWidth;
  2036. var mediaThumbnailImgHeight = isSig ? thumbnailImgHeightSig : thumbnailImgHeight;
  2037. var mediaThumbnailImgWidth_css = mediaThumbnailImgWidth === 0 ? "" : mediaThumbnailImgWidth+"px";
  2038. var mediaThumbnailImgHeight_css = mediaThumbnailImgHeight === 0 ? "" : mediaThumbnailImgHeight+"px";
  2039.  
  2040. print("Started hideMedia() for " + mediaURL);
  2041.  
  2042. /*shrink image*/
  2043. if (media.hasClass("expandedImg"))
  2044. toggleSizesOfImages(media, false);
  2045.  
  2046. mediaContainer.addClass("hidden");
  2047. mediaAnchor.hide();
  2048.  
  2049. print("Hidden mediaAnchor in hideMedia() for " + mediaURL);
  2050.  
  2051. if (isToBlacklist)
  2052. {
  2053. mediaAnchor.addClass("blacklisted");
  2054.  
  2055. if (blacklist.indexOf(mediaURL) === -1)
  2056. blacklist.push(mediaURL);
  2057.  
  2058. var imgBlacklistToggle = getWidget("imgBlacklistToggle", mediaContainer);
  2059. imgBlacklistToggle.html("blacklist");
  2060. imgBlacklistToggle.attr("title", "Blacklist image");
  2061.  
  2062. settings.blacklistStr = blacklist.join("\n");
  2063. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  2064. }
  2065.  
  2066. if (mediaContainerStyle === "sideBySide")
  2067. {
  2068. mediaContainer.css("max-width", mediaThumbnailImgWidth_css);
  2069.  
  2070. if (isShow.URLSpan)
  2071. {
  2072. mediaContainer.append("<a target='_blank' class='imgMenuItem' href="+mediaURL+">"+mediaURL+"</a>");
  2073. widget = mediaContainer.children(':last');
  2074. widget.css('margin-right', '5px');
  2075. widget.css('display', 'inline');
  2076. }
  2077.  
  2078. if (mediaAnchor.hasClass("blacklisted"))
  2079. {
  2080. if (isShow.blacklistToggle)
  2081. {
  2082. mediaContainer.append(
  2083. "<div class='imgMenuItem'>" +
  2084. getPredefinedWidgetStr("whitelistButton") +
  2085. "</div>"
  2086. );
  2087.  
  2088. /*add leading whitespace to new widget*/
  2089. widget = mediaContainer.children(':last');
  2090. widget.css('margin-right', '5px');
  2091. widget.css('display', 'inline');
  2092. }
  2093. }
  2094.  
  2095. if (isShow.hideAllToggle)
  2096. {
  2097. mediaContainer.append(
  2098. "<div class='imgMenuItem'>" +
  2099. getPredefinedWidgetStr("hideButtons", postID) +
  2100. "</div>"
  2101. );
  2102.  
  2103. /*add leading whitespace to new widget*/
  2104. widget = mediaContainer.children(':last');
  2105. widget.css('margin-right', '5px');
  2106. widget.css('display', 'inline');
  2107. }
  2108.  
  2109. /*Bug fix: Hidden images must always have show button.*/
  2110. mediaContainer.append(
  2111. "<div class='imgMenuItem'>" +
  2112. getPredefinedWidgetStr("showButton") +
  2113. "</div>"
  2114. );
  2115.  
  2116. /*add leading whitespace to new widget*/
  2117. widget = mediaContainer.children(':last');
  2118. widget.css('margin-right', '5px');
  2119. widget.css('display', 'inline');
  2120.  
  2121. /*remove leading whitespace to last widget*/
  2122. widget = mediaContainer.children(':last');
  2123. widget.css('margin-right', '');
  2124.  
  2125. hideImgMenu();
  2126. }
  2127. else /* stacked */
  2128. {
  2129. if (isShow.blacklistToggle && isToBlacklist)
  2130. {
  2131. var imgBlacklistToggle = getWidget("imgBlacklistToggle", mediaContainer);
  2132. imgBlacklistToggle.html("whitelist");
  2133. imgBlacklistToggle.attr("title", "Whitelist image");
  2134. }
  2135. }
  2136.  
  2137. if (mediaContainerStyle === "sideBySide")
  2138. {
  2139.  
  2140. }
  2141. else
  2142. {
  2143. /*Bug fix: Spoilered images must have hideToggle regardless.*/
  2144. if (isShow.hideToggle || isSpoilersTagged)
  2145. {
  2146. var imgHideToggle = getWidget("imgHideToggle", mediaContainer);
  2147.  
  2148. imgHideToggle.html("show");
  2149. imgHideToggle.attr("title", "Show image");
  2150. }
  2151. }
  2152. }
  2153.  
  2154.  
  2155.  
  2156.  
  2157. function loadMedia(mediaContainer)
  2158. {
  2159. print("Started loadMedia() for " + getMediaURL(mediaContainer));
  2160.  
  2161. getMediaRemoteInfo(mediaContainer, function(mediaInfo) {
  2162.  
  2163. print("Started callback for getMediaRemoteInfo() for " + getMediaURL(mediaContainer));
  2164.  
  2165. if (mediaInfo == undefined)
  2166. return;
  2167.  
  2168. updateEmbeddedImgContainer(mediaContainer, mediaInfo);
  2169. _loadMedia(mediaContainer);
  2170. });
  2171. }
  2172.  
  2173. function _loadMedia(mediaContainer, mediaInfo)
  2174. {
  2175. var mediaAnchor = mediaContainer.children(".embeddedImgAnchor");
  2176. var mediaURL = mediaAnchor.attr("href");
  2177. var mediaTyping = getMediaTyping(mediaURL);
  2178. var mediaResolutionSpan = getWidget("imgResolution", mediaContainer);
  2179. var mediaFilesizeSpan = getWidget("imgFilesize", mediaContainer);
  2180. var mediaIdx = mediaContainer.attr("data-idx");
  2181. var isSig = mediaContainer.hasClass("withinSig");
  2182. var mediaThumbnailImgWidth = isSig ? thumbnailImgWidthSig : thumbnailImgWidth;
  2183. var mediaThumbnailImgHeight = isSig ? thumbnailImgHeightSig : thumbnailImgHeight;
  2184. var mediaThumbnailImgWidth_css = mediaThumbnailImgWidth === 0 ? "" : mediaThumbnailImgWidth+"px";
  2185. var mediaThumbnailImgHeight_css = mediaThumbnailImgHeight === 0 ? "" : mediaThumbnailImgHeight+"px";
  2186.  
  2187. print("Started _loadMedia() for " + mediaURL);
  2188.  
  2189. var mediaStr = "";
  2190. var media;
  2191.  
  2192. mediaStr = createEmbeddedMediaStr({src: mediaURL});
  2193. mediaAnchor.html(mediaStr);
  2194. media = mediaAnchor.find(".embeddedImg");
  2195. bypassHotlink(media);
  2196.  
  2197. var styles = {
  2198. "max-width": getOptimalImgMaxWidth_css(media, mediaThumbnailImgWidth),
  2199. "max-height": mediaThumbnailImgHeight_css,
  2200. };
  2201. addStyling(media, styles);
  2202. addEvents(media);
  2203. media.attr("display", "");
  2204.  
  2205. if (mediaResolutionSpan.length > 0)
  2206. getMediaResolution(media, mediaResolutionSpan);
  2207.  
  2208. if (mediaFilesizeSpan.length > 0)
  2209. getMediaSize(media, mediaFilesizeSpan);
  2210.  
  2211. if (mediaContainer.hasClass("withinSig"))
  2212. mediaContainer.find(".embeddedImg").addClass("withinSig");
  2213.  
  2214. if (mediaTyping.type == "video" && loopThumbnailVideo)
  2215. {
  2216. media.prop('muted', true); // Must be done before play() according to chrome 66 auto-play policy
  2217. media[0].play();
  2218. media.prop("loop", true);
  2219. }
  2220.  
  2221. mediaContainer.addClass("loaded");
  2222.  
  2223. print("Finished _loadMedia() for " + mediaURL);
  2224. }
  2225.  
  2226. function bypassHotlink(media) {
  2227.  
  2228. media.prop('referrerPolicy', 'no-referrer');
  2229.  
  2230. if (media.is('img')) {
  2231. media.attr('src', media.attr('data-src'));
  2232. }
  2233. else {
  2234. var videoSource = media.find('source');
  2235. videoSource.attr('src', videoSource.attr('data-src'));
  2236. }
  2237. }
  2238.  
  2239. /*
  2240. Get an embeddable media element.
  2241.  
  2242. Param:
  2243. properties [obj] =
  2244. .src = [string]
  2245. .isFloatingImg = (bool)
  2246. */
  2247. function createEmbeddedMediaStr(properties) {
  2248. var mediaStr = "";
  2249. var mediaTyping = getMediaTyping(properties.src);
  2250. var mediaClassStr = "";
  2251.  
  2252. if (properties.isFloatingImg === true)
  2253. mediaClassStr = "floatingImg";
  2254. else
  2255. mediaClassStr = "embeddedImg thumbnailImg";
  2256.  
  2257. if (mediaTyping.type == "image")
  2258. {
  2259. mediaStr = "<img class='"+mediaClassStr+"' alt='' display='none' data-src='"+properties.src+"'></img>";
  2260. }
  2261. else if (mediaTyping.type == "video")
  2262. {
  2263. mediaStr = "<video class='"+mediaClassStr+"' display='none' style=''>"+
  2264. "<source data-src='"+properties.src+"' type='video/"+mediaTyping.subtype+"'>" +
  2265. "</video>";
  2266. }
  2267.  
  2268. return mediaStr;
  2269. }
  2270.  
  2271. function getMediaRemoteInfo(mediaContainer, cb)
  2272. {
  2273. var mediaInfo = {};
  2274.  
  2275. var url = mediaContainer.find(".embeddedImgAnchor").attr("href");
  2276. var mediaTyping = getMediaTyping(url);
  2277.  
  2278. print("Started getMediaRemoteInfo() for " + url);
  2279.  
  2280. /*If gfycat url*/
  2281. if (mediaTyping != undefined && mediaTyping.type == "iframe" && mediaTyping.subtype == "gfycat")
  2282. {
  2283. url = url.match(/\w+$/)[0];
  2284. url = "https://gfycat.com/cajax/get/" + url;
  2285.  
  2286. GM.xmlHttpRequest({
  2287. method: "GET",
  2288. url: url,
  2289. onload: function(response) {
  2290. var rawInfo = JSON.parse(response.responseText);
  2291. if (rawInfo.error)
  2292. {
  2293. errorEvent(mediaContainer);
  2294. cb(undefined)
  2295. }
  2296. else
  2297. {
  2298. mediaInfo.url = rawInfo.gfyItem.webmUrl;
  2299. cb(mediaInfo);
  2300. }
  2301. },
  2302. onerror: function(response) {
  2303. errorEvent(mediaContainer);
  2304. cb(undefined)
  2305. }
  2306. });
  2307. }
  2308. /*If imgur url*/
  2309. else if (mediaTyping != undefined && mediaTyping.type == "iframe" && mediaTyping.subtype == "imgur")
  2310. {
  2311. // [2] captures 2nd (..) group
  2312. var imgId = url.match(/https?:\/\/(.\.)?imgur\.com\/(\w+)$/)[2];
  2313. var requestUrl = `https://api.imgur.com/3/image/${imgId}`;
  2314.  
  2315. GM.xmlHttpRequest({
  2316. method: "GET",
  2317. url: requestUrl,
  2318. headers: {'Authorization': "Client-ID 05793c686154d2a"},
  2319. onload: function(response) {
  2320. var rawInfo = JSON.parse(response.responseText);
  2321. if (rawInfo.error)
  2322. {
  2323. errorEvent(mediaContainer);
  2324. cb(undefined)
  2325. }
  2326. else
  2327. {
  2328. if (rawInfo.data.animated)
  2329. mediaInfo.url = rawInfo.data.link; // gif instead of video
  2330. else
  2331. mediaInfo.url = rawInfo.data.link;
  2332.  
  2333. cb(mediaInfo);
  2334. }
  2335. },
  2336. onerror: function(response) {
  2337. errorEvent(mediaContainer);
  2338. cb(undefined)
  2339. }
  2340. });
  2341. }
  2342. else if (mediaTyping != undefined && mediaTyping.type == "iframe" && mediaTyping.subtype == "imgtc")
  2343. {
  2344. let requestUrl = url;
  2345.  
  2346. GM.xmlHttpRequest({
  2347. method: "GET",
  2348. url: requestUrl,
  2349. headers: {"origin": currentURL},
  2350. onload: function(response) {
  2351. var rawInfo = response.responseText;
  2352. if (rawInfo.error)
  2353. {
  2354. errorEvent(mediaContainer);
  2355. cb(undefined)
  2356. }
  2357. else
  2358. {
  2359. /*
  2360. <html style="background-color: #212121; float: center;">
  2361. <center><video controls autoplay muted loop><source src="u2KmIOe.webm" type="video/webm"></video></center>
  2362. </html>
  2363. */
  2364.  
  2365. let type = (rawInfo.match(/video\/webm/)) ? 'webm' :
  2366. (rawInfo.match(/video\/mp4/)) ? 'mp4' :
  2367. 'gif';
  2368.  
  2369. mediaInfo.url = url.replace(/gifm$/i, type);
  2370. cb(mediaInfo);
  2371. }
  2372. },
  2373. onerror: function(response) {
  2374. errorEvent(mediaContainer);
  2375. cb(undefined)
  2376. }
  2377. });
  2378. }
  2379. else
  2380. {
  2381. cb(mediaInfo);
  2382. }
  2383. }
  2384.  
  2385. function updateEmbeddedImgContainer(mediaContainer, mediaInfo)
  2386. {
  2387. if (mediaInfo.url)
  2388. {
  2389. print("Started updateEmbeddedImgContainer() for " + mediaInfo.url);
  2390.  
  2391. var mediaAnchor = mediaContainer.find(".embeddedImgAnchor");
  2392. mediaAnchor.attr("href", mediaInfo.url);
  2393. mediaAnchor.attr("data-backup-href", mediaInfo.url);
  2394. }
  2395. }
  2396.  
  2397.  
  2398. function addEvents(media) {
  2399. var mediaTyping = getMediaTyping(media);
  2400.  
  2401. if (media.hasClass("embeddedImg") && mediaTyping.type == "image")
  2402. {
  2403. media.error(function() {
  2404. errorEvent(media);
  2405. });
  2406. }
  2407. else if (media.hasClass("embeddedImg") && mediaTyping.type == "video")
  2408. {
  2409. /*Assigning events to videos via jquery doesn't work*/
  2410. media[0].addEventListener('error', function(event) {
  2411. errorEvent(media);
  2412. }, true);
  2413. }
  2414. }
  2415.  
  2416. /*
  2417. Handle case where media cannot be loaded.
  2418.  
  2419. Param:
  2420. media :: embeddedImg or embeddedImgContainer
  2421. */
  2422. function errorEvent(media)
  2423. {
  2424. var embeddedImgContainer = getEmbeddedImgContainer(media);
  2425. var embeddedImgAnchor = embeddedImgContainer.find(".embeddedImgAnchor");
  2426. var mediaResolutionSpan = getWidget("imgResolution", embeddedImgContainer);
  2427. var mediaFilesizeSpan = getWidget("imgFilesize", embeddedImgContainer);
  2428.  
  2429. embeddedImgAnchor.html("Media cannot be loaded");
  2430. mediaResolutionSpan.html("");
  2431. mediaFilesizeSpan.html("");
  2432. }
  2433.  
  2434.  
  2435. function addStyling(media, styles) {
  2436. var mediaTyping = getMediaTyping(media);
  2437.  
  2438. if (media.hasClass("embeddedImg") && mediaTyping.type.match(/video|image|iframe/i))
  2439. {
  2440. media.css(styles);
  2441. }
  2442. else if (media.hasClass("floatingImg"))
  2443. {
  2444. styles["position"] = "absolute";
  2445. styles["z-index"] = 100;
  2446. media.css(styles);
  2447. }
  2448. }
  2449.  
  2450.  
  2451.  
  2452.  
  2453. /*
  2454. Get the natural width of an embedded image.
  2455.  
  2456. @image :: jQuery object with the class "embeddedImg"
  2457. */
  2458. function getNatWidthOfEmbeddedImg(image)
  2459. {
  2460. var mediaTyping = getMediaTyping(image);
  2461.  
  2462. if (mediaTyping.type == "video")
  2463. {
  2464. return image[0].videoWidth;
  2465. }
  2466. else if (mediaTyping.type == "image")
  2467. {
  2468. return image[0].naturalWidth;
  2469. }
  2470. }
  2471.  
  2472.  
  2473.  
  2474. /*
  2475. Get the natural width of an embedded image.
  2476.  
  2477. @image :: jQuery object with the class "embeddedImg"
  2478. */
  2479. function getNatHeightOfEmbeddedImg(image)
  2480. {
  2481. var mediaTyping = getMediaTyping(image);
  2482.  
  2483. if (mediaTyping.type == "video")
  2484. {
  2485. return image[0].videoHeight;
  2486. }
  2487. else if (mediaTyping.type == "image")
  2488. {
  2489. return image[0].naturalHeight;
  2490. }
  2491. }
  2492.  
  2493.  
  2494.  
  2495. var isShowSome;
  2496. async.some(isShow, function(val, cb){
  2497. cb(null, val);
  2498. }, function results(err, results) {
  2499. isShowSome = results;
  2500. });
  2501.  
  2502. if (isShowSome)
  2503. {
  2504. $("body").append(
  2505. "<div class='imgMenuButton' style='display: none'>" +
  2506. "<div class='imgMenuButton_symbol'>" +
  2507. "+" +
  2508. "</div>" +
  2509. "</div>"
  2510. );
  2511. }
  2512.  
  2513. var imgMenuButton = $("body div.imgMenuButton");
  2514.  
  2515.  
  2516. /*
  2517. Show a transparent button in the top-right corner of a thumbnail image.
  2518.  
  2519. @param thumbnail :: embedded thumbnail image
  2520. @param isAppear :: true if the button should appear
  2521. */
  2522. function showImgMenuButton(thumbnail, isAppear)
  2523. {
  2524. if (imgMenuButton.length == 0)
  2525. return false;
  2526.  
  2527. if (isAppear)
  2528. {
  2529. if (imgMenu !== null &&
  2530. getEmbeddedImgContainer(thumbnail).attr("data-idx") === imgMenu.attr("data-idx"))
  2531. {
  2532. imgMenuButton.children().html("-");
  2533. }
  2534. else
  2535. {
  2536. imgMenuButton.children().html("+");
  2537. }
  2538.  
  2539. imgMenuButton.css("display", "table");
  2540. imgMenuButton.css("left", (thumbnail.offset().left + thumbnail.width() - 18) + "px");
  2541. imgMenuButton.css("top", thumbnail.offset().top - 1 + "px");
  2542. imgMenuButton.css("width", imgMenuButton.height());
  2543. }
  2544. else
  2545. {
  2546. imgMenuButton.css("display", "none");
  2547. }
  2548. }
  2549.  
  2550. function isHoverOverImgMenuButton(event)
  2551. {
  2552. if (imgMenuButton.length == 0)
  2553. return false;
  2554.  
  2555. var isCursorInButton =
  2556. imgMenuButton.css("display") === "table" &&
  2557. isHovered(imgMenuButton, event);
  2558.  
  2559. return isCursorInButton;
  2560. }
  2561.  
  2562.  
  2563.  
  2564.  
  2565.  
  2566. var imgMenu = null; // can be undefined instead of null
  2567. function showImgMenu(image)
  2568. {
  2569. if (imgMenu != null) { // check both null and undefined
  2570. imgMenu.remove();
  2571. }
  2572.  
  2573. var idx = getEmbeddedImgContainer(image).attr('data-idx');
  2574.  
  2575. imgMenu = $("<div class='imgMenu' data-idx='"+idx+"' style='display: none; position: absolute; z-index: 90;'>" +
  2576. "</div>").appendTo($("body"));
  2577.  
  2578. addDefaultWidgets(imgMenu);
  2579.  
  2580. /*Handle filesize, resolution, and displaying imgMenu*/
  2581.  
  2582. var imgFilesizeSpan = getWidget("imgFilesize", imgMenu);
  2583. var imgResolutionSpan = getWidget("imgResolution", imgMenu);
  2584.  
  2585. var imgURL = image.parent().attr("data-backup-href");
  2586.  
  2587. if (isShow['filesizeSpan'])
  2588. getMediaSize(image, imgFilesizeSpan);
  2589.  
  2590. if (isShow['resolutionSpan'])
  2591. getMediaResolution(image, imgResolutionSpan);
  2592.  
  2593. var img_rightOffset = image.offset().left + image.width();
  2594. var imgMenu_rightOffset = img_rightOffset + imgMenu.width();
  2595.  
  2596. /*if imgMenu right offset exceeds beyond window's right offset*/
  2597. if (imgMenu_rightOffset > $(window).scrollLeft() + $(window).width())
  2598. imgMenu.css("left", ($(window).scrollLeft() + $(window).width() - imgMenu.width()) + "px");
  2599. else
  2600. imgMenu.css("left", img_rightOffset + "px");
  2601.  
  2602. imgMenu.css("top", image.offset().top + "px");
  2603. imgMenu.css("display", "inline");
  2604.  
  2605.  
  2606.  
  2607. imgMenuButton.children().html("-");
  2608. }
  2609.  
  2610. function hideImgMenu()
  2611. {
  2612. if (imgMenu !== undefined && imgMenu !== null)
  2613. {
  2614. imgMenu.remove();
  2615. imgMenu = null;
  2616. }
  2617. }
  2618.  
  2619. function getImageAnchorOfImgMenu()
  2620. {
  2621. var imgIdx = $("body > .imgMenu").attr("data-idx");
  2622.  
  2623. return $(".embeddedImgContainer[data-idx='"+imgIdx+"']").find(".embeddedImgAnchor");
  2624. }
  2625.  
  2626. function isHovered(jQueryObj, event)
  2627. {
  2628. //return !!$(jQueryObj).filter(function() { return $(this).is(":hover"); }).length;
  2629.  
  2630. return (
  2631. event.pageX > jQueryObj.offset().left &&
  2632. event.pageX < jQueryObj.offset().left + jQueryObj.width() &&
  2633. event.pageY > jQueryObj.offset().top &&
  2634. event.pageY < jQueryObj.offset().top + jQueryObj.height()
  2635. );
  2636. }
  2637.  
  2638. function getHoveredThumbnail(event)
  2639. {
  2640. var hoveredMedia = undefined;
  2641.  
  2642. async.each(embeddedImgContainers, function(embeddedImgContainer, cb) {
  2643. var media = embeddedImgContainer.find(".embeddedImg.thumbnailImg");
  2644.  
  2645. if (media.length > 0 && isHovered(media, event))
  2646. {
  2647. hoveredMedia = media;
  2648. }
  2649.  
  2650. cb();
  2651. });
  2652.  
  2653. return hoveredMedia;
  2654. }
  2655.  
  2656.  
  2657.  
  2658. $(document).on("click", function(event) {
  2659.  
  2660. if (imgMenu)
  2661. {
  2662. var widgets = imgMenu.find(".widget");
  2663. widgets = $.makeArray(widgets);
  2664.  
  2665. var isHoveredSomeWidget = false;
  2666. async.some(widgets, function(widget, cb){
  2667. cb(null, isHovered($(widget), event));
  2668. }, function results(err, result){
  2669. isHoveredSomeWidget = result;
  2670. });
  2671.  
  2672. var isHoveredImgMenu = isHovered(imgMenu, event);
  2673.  
  2674. if (! isHoveredImgMenu || isHoveredSomeWidget) {
  2675. hideImgMenu();
  2676. }
  2677. }
  2678. });
  2679.  
  2680.  
  2681.  
  2682. function handleFloatingImage(event)
  2683. {
  2684. if (!enable_floatingImg)
  2685. return;
  2686.  
  2687. var floatingImgCSS = {
  2688. "left": undefined,
  2689. "top": undefined,
  2690. "max-width": undefined,
  2691. "max-height": undefined,
  2692. };
  2693.  
  2694. /*if user is hovering over thumbnail*/
  2695. if (currentHoveredThumbnail !== null)
  2696. {
  2697. /*calculate floating image size and position*/
  2698. floatingImgCSS["max-width"] = parseInt(getNatWidthOfEmbeddedImg(currentHoveredThumbnail));
  2699.  
  2700. floatingImgCSS["left"] = event.pageX + floatingImgRightOffset;
  2701.  
  2702. /*if right of image exceeds beyond right of window, restrict max width*/
  2703. if (floatingImgCSS["left"] + floatingImgCSS["max-width"] > $(window).scrollLeft() + $(window).width() - floatingImgBorder)
  2704. {
  2705. floatingImgCSS["max-width"] = $(window).scrollLeft() + $(window).width() - floatingImgBorder - floatingImgCSS["left"];
  2706. }
  2707.  
  2708. if (floatingImgCSS["max-width"] < 0)
  2709. floatingImgCSS["max-width"] = 0;
  2710.  
  2711. floatingImgCSS["max-height"] = Math.round(getNatHeightOfEmbeddedImg(currentHoveredThumbnail) * (floatingImgCSS["max-width"] / getNatWidthOfEmbeddedImg(currentHoveredThumbnail)));
  2712.  
  2713. floatingImgCSS["top"] = event.pageY - (floatingImgCSS["max-height"] / 2);
  2714.  
  2715. /*if bottom of image exceeds beyond the window, shift top upwards*/
  2716. if (floatingImgCSS["top"] + floatingImgCSS["max-height"] > $(window).scrollTop() + $(window).height() - floatingImgBorder)
  2717. {
  2718. floatingImgCSS["top"] = $(window).scrollTop() + $(window).height() - floatingImgBorder - floatingImgCSS["max-height"];
  2719. }
  2720.  
  2721. /*if top of image expands beyond top of window, lower top of image*/
  2722. if (floatingImgCSS["top"] < $(window).scrollTop() + floatingImgBorder)
  2723. {
  2724. floatingImgCSS["top"] = $(window).scrollTop() + floatingImgBorder;
  2725. }
  2726.  
  2727. /*if bottom of image exceeds beyond the window, restrict max height*/
  2728. if (floatingImgCSS["top"] + floatingImgCSS["max-height"] > $(window).scrollTop() + $(window).height() - floatingImgBorder)
  2729. {
  2730. floatingImgCSS["max-height"] = $(window).scrollTop() + $(window).height() - floatingImgBorder - floatingImgCSS["top"];
  2731. }
  2732.  
  2733. async.eachOf(floatingImgCSS, function(val, key, cb) {
  2734. floatingImgCSS[key] = floatingImgCSS[key] + "px";
  2735. cb();
  2736. });
  2737.  
  2738. /* If floating image and current hovered thumbnail isn't the same,
  2739. cut off previous reference to indicate a new floating image
  2740. */
  2741. if (curFloatingImg !== null &&
  2742. getMediaURL(curFloatingImg) != getMediaURL(currentHoveredThumbnail))
  2743. {
  2744. showImgMenuButton(null, false);
  2745. curFloatingImg.remove();
  2746. curFloatingImg = null;
  2747. }
  2748.  
  2749. /*if floating image doesn't exist or doesn't have an image yet...*/
  2750. if (curFloatingImg === null)
  2751. {
  2752. var mediaTyping = getMediaTyping(currentHoveredThumbnail);
  2753. var mediaURL = getMediaURL(currentHoveredThumbnail);
  2754. var mediaStr = createEmbeddedMediaStr({src: mediaURL, isFloatingImg: true});
  2755.  
  2756. /*create the floating image element*/
  2757. $("body").append(mediaStr);
  2758. curFloatingImg = $("body").children(".floatingImg");
  2759. bypassHotlink(curFloatingImg);
  2760.  
  2761. /*bug fix: need to be refreshed if curFloatingImg outdated*/
  2762. curFloatingImg.embeddedImgRef = currentHoveredThumbnail;
  2763.  
  2764. /*if webm video*/
  2765. if (mediaTyping.type == "video")
  2766. {
  2767. curFloatingImg[0].play();
  2768. curFloatingImg.prop("loop", true);
  2769. curFloatingImg.prop('muted', false);
  2770. }
  2771.  
  2772. if (getEmbeddedImgContainer(currentHoveredThumbnail).attr('data-style') == "sideBySide")
  2773. showImgMenuButton(currentHoveredThumbnail, true);
  2774. }
  2775.  
  2776. addStyling(curFloatingImg, floatingImgCSS);
  2777. curFloatingImg.attr("display", "");
  2778. }
  2779. /*if user is not hovering over thumbnail and floating image still exists*/
  2780. else if (currentHoveredThumbnail === null && curFloatingImg !== null)
  2781. {
  2782. showImgMenuButton(null, false);
  2783.  
  2784. curFloatingImg.remove();
  2785. curFloatingImg = null;
  2786. }
  2787. }
  2788.  
  2789.  
  2790.  
  2791.  
  2792. var curFloatingImg = null;
  2793.  
  2794. // For URL hover
  2795. var selectedContainer = null;
  2796. var isContinueWaitingForImageFromURLHover = false;
  2797. var savedEvent = null;
  2798.  
  2799.  
  2800. /*if cursor is hovering inside a thumbnail image, display expanded image as floating div
  2801. Also show a transparent button to expand the image menu (side-by-side image view only)
  2802. */
  2803. $(document).on("mousemove", function(event) {
  2804. handleFloatingImage(event);
  2805. });
  2806.  
  2807.  
  2808.  
  2809.  
  2810. /*if mouse hovers inside the image, show floating image*/
  2811. $(document).on("mouseover", ".embeddedImg", function(event){
  2812. $("html").css("cursor", "pointer");
  2813.  
  2814. isContinueWaitingForImageFromURLHover = false;
  2815.  
  2816. if (! $(this).hasClass("expandedImg"))
  2817. {
  2818. currentHoveredThumbnail = $(event.target);
  2819. handleFloatingImage(event);
  2820. }
  2821. });
  2822.  
  2823. /*if mouse hovers outside the image, hide floating image*/
  2824. $(document).on("mouseout", ".embeddedImg", function(event){
  2825. $("html").css("cursor", "default");
  2826.  
  2827. isContinueWaitingForImageFromURLHover = false;
  2828.  
  2829. currentHoveredThumbnail = null;
  2830. handleFloatingImage(event);
  2831. });
  2832.  
  2833.  
  2834.  
  2835. // img ok but width undefined
  2836.  
  2837. function waitUntilCanShowFloatingImage() {
  2838. if (!isContinueWaitingForImageFromURLHover) {
  2839. return;
  2840. }
  2841.  
  2842. if (!selectedContainer.hasClass("loaded")) {
  2843. setTimeout(waitUntilCanShowFloatingImage, 100);
  2844. return;
  2845. }
  2846.  
  2847. var img = selectedContainer.find(".embeddedImg");
  2848. var imgWidth = getNatWidthOfEmbeddedImg(img);
  2849.  
  2850. if (img.length == 0 || imgWidth == 0) {
  2851. setTimeout(waitUntilCanShowFloatingImage, 100);
  2852. }
  2853. else {
  2854. isContinueWaitingForImageFromURLHover = false;
  2855. selectedContainer = null;
  2856. currentHoveredThumbnail = img;
  2857. handleFloatingImage(savedEvent);
  2858. }
  2859. }
  2860.  
  2861.  
  2862.  
  2863. /*if mouse hovers inside the image URL, show floating image*/
  2864. $(document).on("mouseover", ".imgURL", function(event){
  2865. if (!enable_floatingImg_url)
  2866. return false;
  2867.  
  2868. selectedContainer = getEmbeddedImgContainer($(this));
  2869. var img = selectedContainer.find(".embeddedImg");
  2870.  
  2871. if (img.length == 0) {
  2872. showMedia(selectedContainer, false);
  2873. hideMedia(selectedContainer, false);
  2874. }
  2875.  
  2876. isContinueWaitingForImageFromURLHover = true;
  2877. savedEvent = event;
  2878. setTimeout(waitUntilCanShowFloatingImage, 1);
  2879. });
  2880.  
  2881. /*if mouse hovers outside the image URL, restore cursor to default graphic*/
  2882. $(document).on("mouseout", ".imgURL.widget", function(event){
  2883. if (!enable_floatingImg_url)
  2884. return false;
  2885.  
  2886. isContinueWaitingForImageFromURLHover = false;
  2887. currentHoveredThumbnail = null;
  2888. handleFloatingImage(event);
  2889. });
  2890.  
  2891.  
  2892.  
  2893.  
  2894.  
  2895. /*if clicked on the image URL anchor that's surrounding the embedded image, prevent default
  2896. behaviour of anchor (e.g. don't click on hyperlink when expanding image), unless it's a middle click
  2897.  
  2898. FIREFOX: mouseup,mousedown rather than click is only recognized for middle click.
  2899. */
  2900. $("body").on("mouseup mousedown click", ".embeddedImgAnchor", function(event){
  2901.  
  2902. /*left-click*/
  2903. if (event.which === 1)
  2904. {
  2905. event.preventDefault();
  2906. }
  2907. /*middle-click*/
  2908. else if (event.which === 2)
  2909. {
  2910.  
  2911. }
  2912. });
  2913.  
  2914.  
  2915.  
  2916.  
  2917. /* jshint -W107 */
  2918.  
  2919. /*toggle image size on left-click*/
  2920. $("body").on("click", ".embeddedImg", function(event){
  2921. /*left-click*/
  2922. if (event.which === 1)
  2923. {
  2924. if (isHoverOverImgMenuButton(event) && imgMenu === null)
  2925. {
  2926. event.stopImmediatePropagation();
  2927. event.preventDefault();
  2928. showImgMenu($(this));
  2929. return false;
  2930. }
  2931. else if (isHoverOverImgMenuButton(event) && imgMenu !== null)
  2932. {
  2933. /*if referring to same thumbnail*/
  2934. if (imgMenu.attr("data-idx") === getEmbeddedImgContainer($(this)).attr("data-idx"))
  2935. {
  2936. hideImgMenu();
  2937. }
  2938. else
  2939. {
  2940. showImgMenu($(this));
  2941. }
  2942.  
  2943. showImgMenuButton($(this), true);
  2944. return false;
  2945. }
  2946. else if (imgMenu !== null)
  2947. {
  2948. hideImgMenu();
  2949. }
  2950.  
  2951. showImgMenuButton($(this), false);
  2952.  
  2953. toggleEmbeddedImgSize($(this), false);
  2954.  
  2955. var embeddedImgContainer = getEmbeddedImgContainer($(this));
  2956.  
  2957. /*If shrunk image and still hovering over thumbnail*/
  2958. if ($(this).hasClass("thumbnailImg"))
  2959. {
  2960. var hoveredThumbnail = getHoveredThumbnail(event);
  2961. if (hoveredThumbnail)
  2962. {
  2963. if (getEmbeddedImgContainer(hoveredThumbnail).attr('data-style') == "sideBySide")
  2964. {
  2965. showImgMenuButton(hoveredThumbnail, true);
  2966. }
  2967.  
  2968. currentHoveredThumbnail = hoveredThumbnail;
  2969. handleFloatingImage(event);
  2970. }
  2971. }
  2972.  
  2973. if (autoScrollWhenThumbnailClicked)
  2974. $(window).scrollTop( embeddedImgContainer.offset().top );
  2975.  
  2976. return false;
  2977. }
  2978. });
  2979.  
  2980.  
  2981.  
  2982.  
  2983.  
  2984.  
  2985. /*
  2986. @param isAll :: true if your intention is to expand/shrink all images
  2987. */
  2988. function toggleEmbeddedImgSize(embeddedImg, isAll)
  2989. {
  2990. var embeddedImgContainer = embeddedImg.closest(".embeddedImgContainer");
  2991. var embeddedImgAnchor = embeddedImg.closest(".embeddedImgAnchor");
  2992. var isSig = embeddedImgContainer.hasClass("withinSig");
  2993. var mediaTyping = getMediaTyping(embeddedImg);
  2994. var thumbnailImgWidth_this = isSig ? thumbnailImgWidthSig : thumbnailImgWidth;
  2995. var thumbnailImgHeight_this = isSig ? thumbnailImgHeightSig : thumbnailImgHeight;
  2996. var thumbnailImgWidth_this_css = thumbnailImgWidth_this === 0 ? "" : thumbnailImgWidth_this+"px";
  2997. var thumbnailImgHeight_this_css = thumbnailImgHeight_this === 0 ? "" : thumbnailImgHeight_this+"px";
  2998.  
  2999. /*When resizing all media, don't affect sigs if set*/
  3000. if (!includeSigWhenExpanding && embeddedImg.hasClass("withinSig") && isAll)
  3001. {
  3002. return;
  3003. }
  3004. else
  3005. {
  3006. embeddedImg.toggleClass("thumbnailImg expandedImg");
  3007. }
  3008.  
  3009. toggleMediaCloseButton(embeddedImgContainer);
  3010.  
  3011. /*if going to expand image*/
  3012. if (embeddedImg.hasClass("expandedImg"))
  3013. {
  3014. $("#floatingImg").remove();
  3015.  
  3016. embeddedImg.css("max-width", getOptimalImgMaxWidth_css(embeddedImg, expandedImgWidth));
  3017. embeddedImg.css("max-height", expandedImgHeight_css);
  3018.  
  3019. /*if video file, start playing it*/
  3020. if (mediaTyping.type == "video")
  3021. {
  3022. embeddedImg[0].play();
  3023. embeddedImg.prop("controls", true); /*add controls*/
  3024. embeddedImg.prop('muted', false); /*unmute*/
  3025.  
  3026. if (loopExpandedVideo)
  3027. embeddedImg.attr("loop", "");
  3028.  
  3029. /* Prevent URL anchor from doing anything when expanded video is clicked on */
  3030. /* See also: Below case and on.click for .embeddedImgAnchor */
  3031. if (isFirefox)
  3032. embeddedImgAnchor.removeAttr("href");
  3033. }
  3034. }
  3035. else /*if shrinking image back to thumbnail*/
  3036. {
  3037. embeddedImg.css("max-width", getOptimalImgMaxWidth_css(embeddedImg, thumbnailImgWidth_this));
  3038. embeddedImg.css("max-height", thumbnailImgHeight_this_css);
  3039.  
  3040. /*if video file, stop playing it unless specified otherwise*/
  3041. if (mediaTyping.type == "video")
  3042. {
  3043. if (!loopThumbnailVideo)
  3044. embeddedImg[0].pause();
  3045.  
  3046. embeddedImg.prop("controls", false);
  3047. embeddedImg.prop("muted", true);
  3048.  
  3049. /* Restore URL anchor functionality */
  3050. if (isFirefox) {
  3051. var url = embeddedImgAnchor.attr("data-backup-href");
  3052. embeddedImgAnchor.attr("href", url);
  3053. }
  3054. }
  3055. }
  3056.  
  3057. currentHoveredThumbnail = null;
  3058. handleFloatingImage(null);
  3059. }
  3060.  
  3061.  
  3062. /*
  3063. Get the optimal max-width for an image.
  3064.  
  3065. The width will be one of the following:
  3066. - until the right of the browser windows border
  3067. - until the right of the parent container of the image container
  3068. - until the user-defined width limit
  3069.  
  3070. The smallest of the 3 will be chosen.
  3071.  
  3072. @param embeddedImg :: jQuery object of the image
  3073. @param userDefinedWidthLimit :: use 0 if no limit
  3074.  
  3075. The returned width will be the smaller of the three.
  3076. */
  3077. function getOptimalImgMaxWidth_css(embeddedImg, userDefinedWidthLimit)
  3078. {
  3079. var rv;
  3080. var optimalWidth;
  3081. var toContainerWidth;
  3082. var toWindowWidth;
  3083.  
  3084. if (userDefinedWidthLimit === undefined)
  3085. userDefinedWidthLimit = 0;
  3086.  
  3087. toContainerWidth =
  3088. embeddedImg.closest(".msg_body, blockquote").width(); /*gfaqsdep*/
  3089.  
  3090. toWindowWidth =
  3091. $(window).width() - embeddedImg.closest(".msg_body, blockquote").offset().left; /*gfaqsdep*/
  3092.  
  3093. if (toContainerWidth > toWindowWidth)
  3094. optimalWidth = toWindowWidth;
  3095. else
  3096. optimalWidth = toContainerWidth;
  3097.  
  3098. optimalWidth = Number(optimalWidth);
  3099. userDefinedWidthLimit = Number(userDefinedWidthLimit);
  3100.  
  3101. /*if no user-defined limit on the width*/
  3102. if (userDefinedWidthLimit === 0)
  3103. {
  3104. rv = optimalWidth;
  3105. }
  3106. else
  3107. {
  3108. if (optimalWidth > userDefinedWidthLimit)
  3109. rv = userDefinedWidthLimit;
  3110. else
  3111. rv = optimalWidth;
  3112. }
  3113.  
  3114. return rv === 0 ? "" : rv + "px";
  3115. }
  3116.  
  3117.  
  3118.  
  3119.  
  3120.  
  3121.  
  3122.  
  3123.  
  3124.  
  3125.  
  3126. function toggleSizesOfImages(embeddedImages, isExpanding) {
  3127.  
  3128. embeddedImages.each(function(index, element) {
  3129. if ($(this).closest(".embeddedImgContainer").hasClass("hidden")) {
  3130. return false;
  3131. }
  3132.  
  3133. if (isExpanding && $(this).hasClass("expandedImg"))
  3134. {
  3135. /*update its max-width*/
  3136. $(this).css("max-width", getOptimalImgMaxWidth_css($(this)));
  3137. }
  3138. else
  3139. {
  3140. toggleEmbeddedImgSize($(this), true);
  3141. }
  3142. });
  3143. }
  3144.  
  3145. function toggleMediaCloseButton(embeddedMediaContainer)
  3146. {
  3147. if (! hasWidget("closeMediaButton", embeddedMediaContainer))
  3148. {
  3149. var url = embeddedMediaContainer.find('.embeddedImgAnchor').attr('data-backup-href');
  3150. var mediaTyping = getMediaTyping(url);
  3151. if (mediaTyping && mediaTyping.type == "video")
  3152. {
  3153. var closeMediaButtonStr = getPredefinedWidgetStr("closeMediaButton");
  3154. setWidget(closeMediaButtonStr, embeddedMediaContainer);
  3155. }
  3156. }
  3157. else
  3158. {
  3159. /*remove widget*/
  3160. setWidget(null, embeddedMediaContainer, "closeMediaButton");
  3161. }
  3162. }
  3163.  
  3164.  
  3165.  
  3166.  
  3167.  
  3168. $("body").on("click", ".imgCloseAll", function(event) {
  3169. event.preventDefault();
  3170. toggleSizesOfImages($(".embeddedImg.expandedImg"), false);
  3171. nextImageSize = "expanded";
  3172.  
  3173. if (autoScrollWhenThumbnailClicked)
  3174. $(window).scrollTop( getEmbeddedImgContainer($(this)).offset().top );
  3175.  
  3176. if (imgMenu)
  3177. hideImgMenu();
  3178. });
  3179.  
  3180. $("body").on("click", ".imgClosePost", function(event) {
  3181. event.preventDefault();
  3182.  
  3183. var embeddedImgContainer = getEmbeddedImgContainer($(this));
  3184. var postID = embeddedImgContainer.attr("data-postID");
  3185.  
  3186. var postMedia = $(".embeddedImgContainer[data-postID="+postID+"]").find(".embeddedImg.expandedImg");
  3187. toggleSizesOfImages(postMedia, false);
  3188.  
  3189. if (autoScrollWhenThumbnailClicked)
  3190. $(window).scrollTop( embeddedImgContainer.offset().top );
  3191.  
  3192. if (imgMenu)
  3193. hideImgMenu();
  3194. });
  3195.  
  3196. $("body").on("click", ".imgExpandPost", function(event) {
  3197. event.preventDefault();
  3198.  
  3199. var embeddedImgContainer = getEmbeddedImgContainer($(this));
  3200. var postID = embeddedImgContainer.attr("data-postID");
  3201.  
  3202. var postMedia = $(".embeddedImgContainer[data-postID="+postID+"]").find(".embeddedImg");
  3203. toggleSizesOfImages(postMedia, true);
  3204.  
  3205. if (autoScrollWhenThumbnailClicked)
  3206. $(window).scrollTop( embeddedImgContainer.offset().top );
  3207.  
  3208. if (imgMenu)
  3209. hideImgMenu();
  3210. });
  3211.  
  3212. $("body").on("click", ".imgExpandAll", function(event) {
  3213. event.preventDefault();
  3214. toggleSizesOfImages($(".embeddedImg"), true);
  3215. nextImageSize = "thumbnail";
  3216.  
  3217. if (autoScrollWhenThumbnailClicked)
  3218. $(window).scrollTop( getEmbeddedImgContainer($(this)).offset().top );
  3219.  
  3220. if (imgMenu)
  3221. hideImgMenu();
  3222. });
  3223.  
  3224.  
  3225.  
  3226.  
  3227.  
  3228.  
  3229.  
  3230.  
  3231.  
  3232.  
  3233. $("body").on("click", ".imgBlacklistToggle", function(event) {
  3234.  
  3235. event.preventDefault();
  3236.  
  3237. var imgURL;
  3238. var imgAnchor;
  3239.  
  3240. /*Get embeddedImgContainer and anchor*/
  3241. imgAnchor = getEmbeddedImgContainer($(this)).find('.embeddedImgAnchor');
  3242. imgURL = getMediaURL($(this));
  3243.  
  3244. /*if image isn't blacklisted (but going to blacklist)*/
  3245. if (! imgAnchor.hasClass("blacklisted"))
  3246. {
  3247. /*for every embedded image anchor*/
  3248. async.each(embeddedImgContainers, function(curContainer, cb) {
  3249. var curURL = getMediaURL(curContainer);
  3250.  
  3251. /*if has URL that was just blacklisted, hide it*/
  3252. if (isURLmatchBlacklistedURL(imgURL, curURL))
  3253. {
  3254. hideMedia(curContainer, true);
  3255. }
  3256.  
  3257. cb();
  3258. });
  3259. }
  3260. else /*if image is blacklisted (but going to whitelist)*/
  3261. {
  3262. /*for every embedded image anchor*/
  3263. async.each(embeddedImgContainers, function(curContainer, cb) {
  3264. var curURL = getMediaURL(curContainer);
  3265.  
  3266. /*if has URL that was just whitelisted, show it*/
  3267. if (curURL === imgURL)
  3268. {
  3269. showMedia(curContainer, true);
  3270. }
  3271.  
  3272. cb();
  3273. });
  3274. }
  3275.  
  3276. if (imgMenu)
  3277. hideImgMenu();
  3278. });
  3279.  
  3280.  
  3281.  
  3282. $("body").on("click", "a.imgHideToggle", function(event) {
  3283.  
  3284. event.preventDefault();
  3285.  
  3286. var embeddedImgContainer = getEmbeddedImgContainer($(this));
  3287.  
  3288. /*if image is going to be hidden*/
  3289. if ($(this).html() === "hide")
  3290. {
  3291. hideMedia(embeddedImgContainer, false);
  3292. }
  3293. else /*if image is going to be shown*/
  3294. {
  3295. showMedia(embeddedImgContainer, false);
  3296. }
  3297.  
  3298. if (imgMenu)
  3299. hideImgMenu();
  3300. });
  3301.  
  3302.  
  3303.  
  3304. $("body").on("click", "a.imgHideAll", function(event) {
  3305. event.preventDefault();
  3306.  
  3307. toggleImageVisiblity($(".embeddedImgContainer"), "closeAnchor");
  3308.  
  3309. if (autoScrollWhenThumbnailClicked)
  3310. $(window).scrollTop( getEmbeddedImgContainer($(this)).offset().top );
  3311.  
  3312. if (imgMenu)
  3313. hideImgMenu();
  3314. });
  3315. $("body").on("click", "a.imgHidePost", function(event) {
  3316. event.preventDefault();
  3317.  
  3318. var embeddedImgContainer = getEmbeddedImgContainer($(this));
  3319. var postID = embeddedImgContainer.attr("data-postID");
  3320.  
  3321. var postEmbeddedImgContainers = $(".embeddedImgContainer[data-postID="+postID+"]");
  3322. toggleImageVisiblity(postEmbeddedImgContainers, "closeAnchor");
  3323.  
  3324. if (autoScrollWhenThumbnailClicked)
  3325. $(window).scrollTop( embeddedImgContainer.offset().top );
  3326.  
  3327. if (imgMenu)
  3328. hideImgMenu();
  3329. });
  3330. $("body").on("click", "a.imgShowPost", function(event) {
  3331. event.preventDefault();
  3332.  
  3333. var embeddedImgContainer = getEmbeddedImgContainer($(this));
  3334. var postID = embeddedImgContainer.attr("data-postID");
  3335.  
  3336. var postEmbeddedImgContainers = $(".embeddedImgContainer[data-postID="+postID+"]");
  3337. toggleImageVisiblity(postEmbeddedImgContainers, "showAnchor");
  3338.  
  3339. if (autoScrollWhenThumbnailClicked)
  3340. $(window).scrollTop( embeddedImgContainer.offset().top );
  3341.  
  3342. if (imgMenu)
  3343. hideImgMenu();
  3344. });
  3345. $("body").on("click", "a.imgShowAll", function(event) {
  3346. event.preventDefault();
  3347.  
  3348. toggleImageVisiblity($(".embeddedImgContainer"), "showAnchor");
  3349.  
  3350. if (autoScrollWhenThumbnailClicked)
  3351. $(window).scrollTop( getEmbeddedImgContainer($(this)).offset().top );
  3352.  
  3353. if (imgMenu)
  3354. hideImgMenu();
  3355. });
  3356.  
  3357.  
  3358.  
  3359. $("body").on("click", "a.closeMediaButton", function(event) {
  3360. event.preventDefault();
  3361.  
  3362. var embeddedMediaContainer = getEmbeddedImgContainer($(this));
  3363. var embeddedImg = embeddedMediaContainer.find(".embeddedImg");
  3364.  
  3365. toggleEmbeddedImgSize(embeddedImg, false);
  3366.  
  3367. if (autoScrollWhenThumbnailClicked)
  3368. $(window).scrollTop( embeddedMediaContainer.offset().top );
  3369. });
  3370.  
  3371.  
  3372.  
  3373. })(); /*end of greasemonkey script*/