ImageFAQs

Converts plain-text image and webm URLs into their embedded form

当前为 2015-04-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ImageFAQs
  3. // @namespace FightingGames@gfaqs
  4. // @include http://www.gamefaqs.com
  5. // @include http://www.gamefaqs.com*
  6. // @description Converts plain-text image and webm URLs into their embedded form
  7. // @version 1.05
  8. // @grant GM_addStyle
  9. // @grant GM_getResourceText
  10. // @grant GM_log
  11. // @grant GM_xmlhttpRequest
  12. // @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
  13. // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js
  14. // @resource jqueryuibaseCSS https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery.ui.base.css
  15. // @resource jqueryuithemeCSS https://code.jquery.com/ui/1.11.4/themes/cupertino/jquery-ui.css
  16. // ==/UserScript==
  17.  
  18. /*
  19. The MIT License (MIT)
  20.  
  21. This greasemonkey script for GameFAQs converts image and webm
  22. links into their embedded form. It can be considered as a spiritual successor
  23. to text-to-image for GameFAQs. Many of its features are inspired from appchan x
  24. by zixaphir at http://zixaphir.github.io/appchan-x/.
  25. Copyright (c) 2015 FightingGames@gamefaqs <adrenalinebionicarm@gmail.com>
  26.  
  27. Permission is hereby granted, free of charge, to any person obtaining a copy
  28. of this software and associated documentation files (the "Software"), to deal
  29. in the Software without restriction, including without limitation the rights
  30. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  31. copies of the Software, and to permit persons to whom the Software is
  32. furnished to do so, subject to the following conditions:
  33.  
  34. The above copyright notice and this permission notice shall be included in
  35. all copies or substantial portions of the Software.
  36.  
  37. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  38. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  39. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  40. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  41. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  42. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  43. THE SOFTWARE.
  44.  
  45. */
  46.  
  47.  
  48.  
  49. GM_addStyle (GM_getResourceText("jqueryuibaseCSS"));
  50. GM_addStyle (GM_getResourceText("jqueryuithemeCSS"));
  51. this.$ = this.jQuery = jQuery.noConflict(true);
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59. /*this top portion of the script handles the settings box and local storage*/
  60.  
  61. var thumbnailImgWidth = 150;
  62. var thumbnailImgHeight = 150;
  63. var settingsJSON = localStorage.getItem("imagefaqs");
  64. var settings; /*key-value array of JSON*/
  65. var sessionSpoilersHotkey;
  66.  
  67. /*if imageFAQs was freshly installed*/
  68. if (settingsJSON == null)
  69. {
  70. settings = {
  71. enable_imagefaqs: true,
  72. enable_sigs: false,
  73. thumbnailWidth: thumbnailImgWidth,
  74. thumbnailHeight: thumbnailImgHeight,
  75. imgContainerBackgroundColor: "#DFE6F7",
  76. isSpoilersMode: false,
  77. spoilersHotkey: {115: 115}, /*F4*/
  78. blacklistStr: ""
  79. };
  80. localStorage.setItem("imagefaqs", JSON.stringify(settings)); /*save default settings*/
  81. }
  82. else
  83. {
  84. settings = jQuery.parseJSON(settingsJSON);
  85. }
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92. function keyCodeToString(keyCode)
  93. {
  94. keyCode = parseInt(keyCode);
  95. if (keyCode == 16)
  96. {
  97. return "shift";
  98. }
  99. else if (keyCode == 17)
  100. {
  101. return "ctrl";
  102. }
  103. else if (keyCode == 18)
  104. {
  105. return "alt";
  106. }
  107. else if (keyCode >= 112 && keyCode <= 123) /*F keys*/
  108. {
  109. return "F" + (keyCode - 111);
  110. }
  111. else
  112. {
  113. return String.fromCharCode(keyCode);
  114. }
  115. }
  116.  
  117.  
  118. function keyCodeKVArrayToString(array)
  119. {
  120. var string = "";
  121. for (var key in array)
  122. {
  123. string += keyCodeToString(key) + " ";
  124. }
  125. return string;
  126. }
  127.  
  128.  
  129. function cloneKVArray(KVArray)
  130. {
  131. var newKVArray = {};
  132. for (var key in KVArray)
  133. {
  134. newKVArray[key] = KVArray[key];
  135. }
  136. return newKVArray;
  137. }
  138.  
  139.  
  140.  
  141. sessionSpoilersHotkey = cloneKVArray(settings.spoilersHotkey);
  142.  
  143.  
  144.  
  145.  
  146. /*event where user opens up settings menu*/
  147. $("body").on("click", "#imagefaqs_settings_but", function(event) {
  148.  
  149. var popupBox = $("#imagefaqs_settings_popup");
  150. event.preventDefault();
  151. if (popupBox.length == 0)
  152. {
  153. $("body").append(
  154. "<div id='imagefaqs_settings_popup' title='ImageFAQs Settings. Version "+ GM_info.script.version+"'>" +
  155. "Update history: <a target='_blank' href='http://fightinggames.bitbucket.org/imagefaqs/'>http://fightinggames.bitbucket.org/imagefaqs/</a>." +
  156. "<br/>" +
  157. "Feedback and bug reporting: <a target='_blank' href='http://www.gamefaqs.com/boards/565885-blood-money'>http://www.gamefaqs.com/boards/565885-blood-money</a>." +
  158. "<br/><br/>" +
  159. "<label><input id='enableImagefaqs_checkbox' type='checkbox'>Enable ImageFAQs</label>" +
  160. "<br/>" +
  161. "<label><input id='enable_sigs_checkbox' type='checkbox'>Enable embedding in signatures</label>" +
  162. "<br/><br/>" +
  163. "<label><input id='thumbnailImgWidth_text' type='text'>Thumbnail max-width (150 default)</label>" +
  164. "<br/>" +
  165. "<label><input id='thumbnailImgHeight_text' type='text'>Thumbnail max-height (150 default)</label>" +
  166. "<br/><br/>" +
  167. "<label><input id='imgContainerBackgroundColor_text' type='text'>Image container background color (#DFE6F7 default)</label>" +
  168. "<br/><br/>" +
  169. "<label>Newline separated list of blacklisted image URLs</label>" +
  170. "<br/>" +
  171. "<textarea id='blacklist_text'></textarea>" +
  172. "<br/><br/>" +
  173. "<span>Tip: You can blacklist an entire domain (e.g. pbsrc.com)</span>" +
  174. "<br/><br/>" +
  175. "<label><input id='spoilersHotkey_text' type='text'>Toggle spoilers hotkey (F4 default)</label>" +
  176. "<br/><br/>" +
  177. "<span>Tip: At any time, you can toggle spoilers mode. This can be done before or after<br/> entering a topic.</span>" +
  178. "<br/><br/>" +
  179. "<button id='saveImagefaqsSettingsBut' type='button'>Save</button> " +
  180. "<button id='cancelImagefaqsSettingsBut' type='button'>Cancel</button> " +
  181. "<br/><br/>" +
  182. "<span id='responseSpan'>&nbsp;</span>" + /*for reporting success or failure*/
  183. "</div>"
  184. );
  185. popupBox = $("#imagefaqs_settings_popup");
  186. }
  187.  
  188. popupBox.find("#enableImagefaqs_checkbox").prop("checked", settings.enable_imagefaqs);
  189. popupBox.find("#enable_sigs_checkbox").prop("checked", settings.enable_sigs);
  190. popupBox.find("#thumbnailImgWidth_text").val(settings.thumbnailWidth);
  191. popupBox.find("#thumbnailImgHeight_text").val(settings.thumbnailHeight);
  192. popupBox.find("#imgContainerBackgroundColor_text").val(settings.imgContainerBackgroundColor);
  193. popupBox.find("#spoilersHotkey_text").val( keyCodeKVArrayToString(settings.spoilersHotkey) );
  194. popupBox.find("#blacklist_text").val( settings.blacklistStr );
  195. $("#imagefaqs_settings_popup").children("#responseSpan").html("&nbsp;");
  196.  
  197. $("#imagefaqs_settings_popup").dialog({
  198. width:'auto'
  199. });
  200. });
  201.  
  202.  
  203.  
  204.  
  205.  
  206. $("body").on("click", "#saveImagefaqsSettingsBut", function(event) {
  207.  
  208. event.preventDefault();
  209. var settingsPopup = $("#imagefaqs_settings_popup");
  210. /*if valid thumbnail dimensions*/
  211. if (
  212. !isNaN($("#thumbnailImgWidth_text").val()) && $("#thumbnailImgWidth_text").val() >= 1 &&
  213. !isNaN($("#thumbnailImgHeight_text").val()) && $("#thumbnailImgHeight_text").val() >= 1)
  214. {
  215. settings.thumbnailWidth = $("#thumbnailImgWidth_text").val();
  216. settings.thumbnailHeight = $("#thumbnailImgHeight_text").val();
  217. }
  218. else
  219. {
  220. reportResponseMsgInImagefaqsSettingsPopupBox("Error: Invalid thumbnail dimensions. Use natural numbers only.", settingsPopup);
  221. return;
  222. }
  223. /*if valid image container background color*/
  224. if (settingsPopup.find("#imgContainerBackgroundColor_text").val().search(/^#[a-zA-Z0-9]{6}$/i) >= 0)
  225. {
  226. settings.imgContainerBackgroundColor = settingsPopup.find("#imgContainerBackgroundColor_text").val();
  227. }
  228. else
  229. {
  230. reportResponseMsgInImagefaqsSettingsPopupBox("Error: Invalid image container background color.", settingsPopup);
  231. return;
  232. }
  233. settings.enable_imagefaqs = settingsPopup.find("#enableImagefaqs_checkbox").prop("checked");
  234. settings.enable_sigs = settingsPopup.find("#enable_sigs_checkbox").prop("checked");
  235. settings.spoilersHotkey = cloneKVArray(sessionSpoilersHotkey);
  236. settings.blacklistStr = settingsPopup.find("#blacklist_text").val();
  237. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  238. reportResponseMsgInImagefaqsSettingsPopupBox("Settings saved.", settingsPopup);
  239. });
  240.  
  241.  
  242.  
  243. /*
  244. @param msg :: message string to show to the user
  245. @param box :: $("#imagefaqs_settings_popup")
  246. */
  247. function reportResponseMsgInImagefaqsSettingsPopupBox(msg, box)
  248. {
  249. var msgBox = box.children("#responseSpan");
  250. msgBox.html(msg);
  251. msgBox.effect("highlight");
  252. }
  253.  
  254.  
  255.  
  256. $("body").on("click", "#cancelImagefaqsSettingsBut", function(event) {
  257.  
  258. event.preventDefault();
  259. $("#imagefaqs_settings_popup").dialog("close");
  260. });
  261.  
  262.  
  263.  
  264.  
  265.  
  266.  
  267. /*begin main scripting*/
  268. (function(){
  269.  
  270. var enable_imagefaqs = settings.enable_imagefaqs;
  271. var isSpoilersMode = settings.isSpoilersMode;
  272. var thumbnailImgWidth = settings.thumbnailWidth;
  273. var thumbnailImgHeight = settings.thumbnailHeight;
  274. var blacklist = settings.blacklistStr.split("\n"); /*array of URLs to block*/
  275. var currentURL;
  276. var pageType; /* "topic" or "postPreview" or "other" */
  277. var allowImgInSig = settings.enable_sigs;
  278. var isFloatingImgExist = false;
  279. var floatingImgWidth;
  280. var floatingImgRightOffset = 50;
  281. var floatingImgBorder = 10;
  282. var currentHoveredThumbnail = null;
  283. var imgAnchorTags = [];
  284.  
  285.  
  286.  
  287.  
  288.  
  289. var heldDownKeys = {};
  290. $("body").on("keydown", "#imagefaqs_settings_popup #spoilersHotkey_text", function(event){
  291. heldDownKeys[event.which] = event.which;
  292. sessionSpoilersHotkey = cloneKVArray(heldDownKeys);
  293. $(this).val(
  294. keyCodeKVArrayToString(sessionSpoilersHotkey)
  295. );
  296. return false;
  297. });
  298.  
  299.  
  300. $("body").on("keyup", "#imagefaqs_settings_popup #spoilersHotkey_text", function(event){
  301. delete heldDownKeys[event.which];
  302. return false;
  303. });
  304.  
  305. $("body").on("keypress", "#imagefaqs_settings_popup #spoilersHotkey_text", function(event){
  306. event.preventDefault();
  307. });
  308.  
  309.  
  310.  
  311.  
  312.  
  313.  
  314.  
  315. $("body").keydown(function(event){
  316.  
  317. var desiredAction;
  318. heldDownKeys[event.which] = event.which;
  319.  
  320. /*are every spoilers hotkey pressed?*/
  321. for (var key in settings.spoilersHotkey)
  322. {
  323. if (settings.spoilersHotkey[key] != heldDownKeys[key])
  324. {
  325. return;
  326. }
  327. }
  328. desiredAction = "toggleSpoilersMode";
  329. if (desiredAction == "toggleSpoilersMode")
  330. {
  331. event.preventDefault();
  332. isSpoilersMode = !isSpoilersMode;
  333.  
  334. if (isSpoilersMode)
  335. {
  336. if (pageType != "other")
  337. {
  338. /*for every embedded image*/
  339. $(".embeddedImg").each(function(index, element) {
  340. /*if image is in thumbnail mode, add spoilers class*/
  341. if ($(this).hasClass("thumbnailImg"))
  342. {
  343. $(this).addClass("thumbnailImg_spoilersExt");
  344. $(this).parent().addClass("embeddedImgAnchor_spoilersExt");
  345. }
  346. });
  347. }
  348. showNotification("ImageFAQs: Spoilers mode is now on.");
  349. }
  350. else
  351. {
  352. if (pageType != "other")
  353. {
  354. /*for every embedded image*/
  355. $(".embeddedImg").each(function(index, element) {
  356. /*if image is in thumbnail mode, remove spoilers class*/
  357. if ($(this).hasClass("thumbnailImg"))
  358. {
  359. $(this).removeClass("thumbnailImg_spoilersExt");
  360. $(this).parent().removeClass("embeddedImgAnchor_spoilersExt");
  361. }
  362. });
  363. }
  364. showNotification("ImageFAQs: Spoilers mode is now off.");
  365. }
  366. settings.isSpoilersMode = isSpoilersMode;
  367. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  368. }
  369. });
  370.  
  371.  
  372.  
  373.  
  374. $("body").keyup(function(event){
  375. delete heldDownKeys[event.which];
  376. });
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.  
  384. /*
  385. Display a notification on the bottom-right of the browser. Notification will
  386. disappear in a few seconds.
  387.  
  388. @param msg :: string message to display
  389. */
  390. function showNotification(msg)
  391. {
  392. var notificationBox = $("#imagefaqs_notificationBox");
  393. notificationBox.remove();
  394. $("body").append(
  395. "<div id='imagefaqs_notificationBox'>" +
  396. "<span style='color: black'>" +
  397. msg +
  398. "</span>" +
  399. "</div>"
  400. );
  401. notificationBox = $("#imagefaqs_notificationBox");
  402.  
  403. notificationBox.css({
  404. "background-color": "white",
  405. "position": "absolute",
  406. "top": ($(window).scrollTop() + $(window).height() - notificationBox.height())+"px",
  407. "left": ($(window).scrollLeft() + $(window).width() - notificationBox.find("span").width())+"px"
  408. });
  409.  
  410. notificationBox.effect("highlight").delay(1000).fadeOut(500);
  411. }
  412.  
  413.  
  414.  
  415.  
  416. if (enable_imagefaqs == false)
  417. {
  418. return;
  419. }
  420.  
  421.  
  422.  
  423. if (blacklist.length == 1 && blacklist[0] == "")
  424. {
  425. blacklist = [];
  426. }
  427. else
  428. {
  429. /*trim white space*/
  430. for (var i = 0; i < blacklist.length; i++)
  431. {
  432. blacklist[i].trim();
  433. }
  434. }
  435.  
  436.  
  437.  
  438.  
  439.  
  440.  
  441.  
  442. function isURLmatchBlacklistedURL(url, blacklistedURL)
  443. {
  444. blacklistedURL = blacklistedURL.trim();
  445. /*escape everything*/
  446. var blacklistedURLregex =
  447. new RegExp( blacklistedURL.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") );
  448.  
  449. if (blacklistedURLregex.exec(url) != null)
  450. {
  451. return true;
  452. }
  453. else
  454. {
  455. return false;
  456. }
  457. }
  458.  
  459.  
  460. /*
  461. Is the url string in the blacklist?
  462.  
  463. @url :: string of the url to check
  464. @blacklist :: array of blacklisted urls
  465.  
  466. Returns true or false
  467. */
  468. function isURLinBlacklist(url, blacklist)
  469. {
  470. for (var i = 0; i < blacklist.length; i++)
  471. {
  472. if (isURLmatchBlacklistedURL(url, blacklist[i]))
  473. {
  474. return true;
  475. }
  476. }
  477. return false;
  478. }
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487. currentURL = window.location.href;
  488. pageType = currentURL.search(/.*gamefaqs\.com\/boards\/.*\/.*/i);
  489. if (currentURL.search(/.*gamefaqs\.com\/boards\/.*\/.*/i) >= 0)
  490. {
  491. pageType = "topic";
  492. }
  493. else if (currentURL.search(/.*gamefaqs\.com\/boards\/post/i) >= 0)
  494. {
  495. pageType = "postPreview";
  496. }
  497. else if (currentURL.search(/.*gamefaqs\.com\/user/i) >= 0)
  498. {
  499. /*add settings link*/
  500. $(".systems").append("<li><a id='imagefaqs_settings_but' href='#'>ImageFAQs Options</a> - Change thumbnail size, enable signature embedding, and more.</li>");
  501. }
  502. else
  503. {
  504. pageType = "other";
  505. }
  506.  
  507.  
  508.  
  509.  
  510. if (pageType == "other")
  511. {
  512. return;
  513. }
  514.  
  515.  
  516.  
  517.  
  518.  
  519.  
  520.  
  521. GM_addStyle ("\
  522. .embeddedImgContainer {\
  523. background: "+settings.imgContainerBackgroundColor+";\
  524. }\
  525. .embeddedImgAnchor {\
  526. display: inline-block;\
  527. }\
  528. .embeddedImgAnchor_placeholderHeight {\
  529. height: "+thumbnailImgHeight+"px;\
  530. }\
  531. .embeddedImgAnchor_spoilersExt {\
  532. background: black;\
  533. }\
  534. .thumbnailImg {\
  535. display: inline-block; /*prevents parent anchor from stretching horizontally*/\
  536. vertical-align: bottom;\ /*eliminates small gap between bottom of image and parent div*/\
  537. }\
  538. .thumbnailImg_spoilersExt {\
  539. opacity: 0.0;\
  540. }\
  541. .expandedImg {\
  542. display: inline-block;\
  543. vertical-align: bottom;\
  544. }\
  545. ");
  546.  
  547. $("body").css("position", "relative");
  548.  
  549. if (pageType == "topic")
  550. {
  551. /*get all user posts*/
  552. var posts = $(".msg_body");
  553. }
  554. else if (pageType == "postPreview")
  555. {
  556. var posts = $("div.body td:eq(1)");
  557. }
  558.  
  559. /*for each user post*/
  560. posts.each(function(index, element) {
  561. var postHtml;
  562. var unanchoredImgURLMatches;
  563. var sigStrIdx;
  564. var anchorTags;
  565. var idxAfterAnchorTagInPostHtml = 0;
  566. postHtml = $(this).html();
  567. /*if in post preview mode, need to manually parse through the preview message body and
  568. replace every plain-text image URL in its anchor form*/
  569. if (pageType == "postPreview")
  570. {
  571. postHtml = postHtml.replace(/https?:\/\/[a-zA-Z0-9\[\]\-\.\_\~\:\/\?\#\[\]\%\@\!\$\&\'\(\)\*\+\,\;\=]*?\.(?:png|jpg|gif|jpeg|webm|gifv)[a-zA-Z0-9\[\]\-\.\_\~\:\/\?\#\[\]\%\@\!\$\&\'\(\)\*\+\,\;\=]*/ig, "<a href='$&'>$&</a>");
  572. $(this).html(postHtml);
  573. }
  574. /*get index of sig separator*/
  575. sigStrIdx = postHtml.indexOf("<br>---<br>");
  576. /*get all anchor tags*/
  577. anchorTags = $(this).find("a");
  578.  
  579. /*filter in anchor tags that refer to images*/
  580. anchorTags.each(function(index, element) {
  581. var anchorURL = $(this).attr("href");
  582. var anchorTagStrIdx = postHtml.indexOf(anchorURL + "</a>", idxAfterAnchorTagInPostHtml);
  583. idxAfterAnchorTagInPostHtml = anchorTagStrIdx + anchorURL.length + 4;
  584. if (anchorURL.search(/https?:\/\/[a-zA-Z0-9\[\]\-\.\_\~\:\/\?\#\[\]\%\@\!\$\&\'\(\)\*\+\,\;\=]*?\.(?:png|jpg|gif|jpeg|webm|gifv)/i) >= 0)
  585. {
  586. /*if sig exists, anchor is part of sig, and sig shouldn't embedded images*/
  587. if (sigStrIdx != -1 && anchorTagStrIdx > sigStrIdx && !allowImgInSig)
  588. {
  589. return;
  590. }
  591. imgAnchorTags.push($(this));
  592. }
  593. });
  594. });
  595.  
  596.  
  597.  
  598. /*for each valid image URL anchor tag, replace with template div for embedded image*/
  599. for (var imgAnchor of imgAnchorTags)
  600. {
  601. var imgURL = imgAnchor.html();
  602. var imgSize;
  603. var imgInfoEleStr;
  604. var postID;
  605. var isBlacklisted = isURLinBlacklist(imgURL, blacklist);
  606. if (pageType == "topic")
  607. {
  608. postID = imgAnchor.parent().attr("name");
  609. }
  610. else /*if in post preview mode, need to provide missing message ID*/
  611. {
  612. imgAnchor.parent().attr("name", 0);
  613. postID = 0;
  614. }
  615. imgInfoEleStr =
  616. "<span style='margin-right: 5px'>" +
  617. "<a href="+imgURL+">"+imgURL+"</a>" +
  618. "</span>";
  619. imgGoogleReverse =
  620. "<a target='_blank' title='Reverse image search on general images' style='margin-right: 5px' href='https://www.google.com/searchbyimage?image_url="+imgURL+"'>" +
  621. "google" +
  622. "</a>";
  623. imgIQDBreverse =
  624. "<a target='_blank' title='Reverse image search on weeb images' style='margin-right: 5px' href='http://iqdb.org/?url="+imgURL+"'>" +
  625. "iqdb" +
  626. "</a>";
  627. if (isBlacklisted)
  628. {
  629. imgBlacklistToggle =
  630. "<a class='imgBlacklistToggle' href='#' title='Whitelist image' style='margin-right: 5px'>whitelist</a>";
  631. }
  632. else
  633. {
  634. imgBlacklistToggle =
  635. "<a class='imgBlacklistToggle' href='#' title='Blacklist image' style='margin-right: 5px'>blacklist</a>";
  636. }
  637. imgCloseAll =
  638. "<a class='imgCloseAll' href='#' title='Close all images in page' style='margin-right: 5px'>&lt;&lt;</a>";
  639. imgClosePost =
  640. "<a class='imgClosePost' href='#' data-postID='"+postID+"' title='Close all images in post' style='margin-right: 5px'>&lt;</a>";
  641. imgExpandPost =
  642. "<a class='imgExpandPost' href='#' data-postID='"+postID+"' title='Expand all images in post' style='margin-right: 5px'>&gt;</a>";
  643. imgExpandAll =
  644. "<a class='imgExpandAll' href='#' title='Expand all images in page' style='margin-right: 5px'>&gt;&gt;</a>";
  645. imgAnchor.replaceWith(
  646. "<div class='embeddedImgContainer'>" +
  647. imgInfoEleStr +
  648. imgGoogleReverse +
  649. imgIQDBreverse +
  650. imgBlacklistToggle +
  651. imgCloseAll +
  652. imgClosePost +
  653. imgExpandPost +
  654. imgExpandAll +
  655. "<br/>" +
  656. "<a class='embeddedImgAnchor embeddedImgAnchor_placeholderHeight' href='" + imgURL + "' data-backup-href='" + imgURL + "'>" +
  657. "</a>" +
  658. "</div>"
  659. );
  660. if (isBlacklisted)
  661. {
  662. var embeddedImgAnchor = $(".embeddedImgAnchor[href='"+imgURL+"']");
  663. embeddedImgAnchor.hide();
  664. embeddedImgAnchor.addClass("blacklisted");
  665. }
  666. }
  667.  
  668.  
  669.  
  670. function getFileSize(url, span)
  671. {
  672. GM_xmlhttpRequest({
  673. method: "GET",
  674. url: url,
  675. onload: function(response) {
  676. var contentLengthStr = response.responseHeaders.match(/Content-Length: \d+/)[0];
  677. var fileSizeBytes = parseInt(contentLengthStr.substring(16));
  678. var fileSizeDisplayStr;
  679. if (fileSizeBytes < 1000000) /*if less than 1.0MB*/
  680. {
  681. fileSizeDisplayStr = Math.round(fileSizeBytes / 1000) + "KB";
  682. }
  683. else
  684. {
  685. fileSizeDisplayStr = (fileSizeBytes / 1000000).toFixed(2) + "MB";
  686. }
  687.  
  688. span.append(" ("+fileSizeDisplayStr+")");
  689. }
  690. });
  691. }
  692.  
  693.  
  694.  
  695. /*for each template div, insert an embedded image*/
  696. $(".embeddedImgContainer").each(function(index, element) {
  697. var curEmbeddedImgContainer = $(this);
  698. var curEmbeddedImgAnchor = $(this).find(".embeddedImgAnchor");
  699. var imgInfoSpan = curEmbeddedImgContainer.find("span");
  700. var imgURL = curEmbeddedImgAnchor.attr("href");
  701. var imgType; /*e.g. "jpg"*/
  702. var image = new Image();
  703. var embeddedImgSpoilersClass;
  704. if (isSpoilersMode)
  705. {
  706. embeddedImgSpoilersClass = "thumbnailImg_spoilersExt";
  707. curEmbeddedImgAnchor.addClass("embeddedImgAnchor_spoilersExt");
  708. }
  709. else
  710. {
  711. embeddedImgSpoilersClass = "";
  712. }
  713. var postID = curEmbeddedImgContainer.parent().attr("name");
  714. imageType = imgURL.split(".").pop();
  715. /*if not a webm video (i.e. image)*/
  716. if (imageType.search(/(webm|gifv)/i) == -1)
  717. {
  718. /*on event that image cannot be loaded, post the error*/
  719. image.onerror = function() {
  720. curEmbeddedImgAnchor.html("Image cannot be loaded.");
  721. curEmbeddedImgAnchor.removeClass("embeddedImgAnchor_placeholderHeight");
  722. };
  723. /*on event that image loaded successfully in memory*/
  724. $(image).load(function() {
  725. /*write down image's natural dimensions*/
  726. imgInfoSpan.append(
  727. " (" + image.naturalWidth + "x" + image.naturalHeight + ")"
  728. );
  729. curEmbeddedImgAnchor.removeClass("embeddedImgAnchor_placeholderHeight");
  730. getFileSize(imgURL, imgInfoSpan);
  731. });
  732. image.src = imgURL;
  733. $(image).addClass("embeddedImg thumbnailImg "+embeddedImgSpoilersClass+"");
  734. $(image).attr("data-postID", postID);
  735. $(image).attr("alt", "");
  736. $(image).css("max-width", thumbnailImgWidth + "px");
  737. $(image).css("max-height", thumbnailImgHeight + "px");
  738. curEmbeddedImgAnchor.html(image);
  739. }
  740. else /*if image is actually a webm file*/
  741. {
  742. if (imageType == "gifv")
  743. {
  744. imgURL = imgURL.replace("gifv", "webm");
  745. }
  746. htmlVideoSrcTag =
  747. "<video id='embeddedImg_"+index+"' data-postID='"+postID+"' " +
  748. "class='embeddedImg webmExt thumbnailImg "+embeddedImgSpoilersClass+"' " +
  749. "style='max-width: "+thumbnailImgWidth+"px; max-height: "+thumbnailImgHeight+"px;'>" +
  750. "<source src='" + imgURL + "' type='video/webm'>" +
  751. "</video>";
  752. curEmbeddedImgAnchor.html(
  753. htmlVideoSrcTag
  754. );
  755. curEmbeddedImgAnchor.removeClass("embeddedImgAnchor_placeholderHeight");
  756. /*when video's metadata has been loaded, record its natural width and resolution*/
  757. $("#embeddedImg_"+index+"")[0].onloadedmetadata = function() {
  758. var video = this;
  759. imgInfoSpan.append(
  760. " (" + video.videoWidth + "x" + video.videoHeight + ")"
  761. );
  762. getFileSize(imgURL, imgInfoSpan);
  763. };
  764. /*when video's metadata has been loaded, record its natural width and resolution*/
  765. $("#embeddedImg_"+index+"")[0].addEventListener('error', function(event) {
  766. curEmbeddedImgAnchor.html("Video cannot be loaded.");
  767. }, true);
  768. }
  769. });
  770.  
  771.  
  772. /*
  773. Get the natural width of an embedded image.
  774.  
  775. @image :: jQuery object with the class "embeddedImg"
  776. */
  777. function getNatWidthOfEmbeddedImg(image)
  778. {
  779. if (image.hasClass("webmExt"))
  780. {
  781. return image[0].videoWidth;
  782. }
  783. else
  784. {
  785. return image[0].naturalWidth;
  786. }
  787. }
  788.  
  789.  
  790.  
  791. /*
  792. Get the natural width of an embedded image.
  793.  
  794. @image :: jQuery object with the class "embeddedImg"
  795. */
  796. function getNatHeightOfEmbeddedImg(image)
  797. {
  798. if (image.hasClass("webmExt"))
  799. {
  800. return image[0].videoHeight;
  801. }
  802. else
  803. {
  804. return image[0].naturalHeight;
  805. }
  806. }
  807.  
  808.  
  809.  
  810. /*if cursor is hovering inside a thumbnail image, display expanded image as floating div*/
  811. $(document).on("mousemove", function(event) {
  812. var floatingImgLeft;
  813. var floatingImgTop;
  814. var floatingImgWidth;
  815. var floatingImgHeight;
  816. var floatingImgStyleStr;
  817. /*if user is hovering over thumbnail, prepare floating image size and position*/
  818. if (currentHoveredThumbnail != null)
  819. {
  820. floatingImgWidth = parseInt(getNatWidthOfEmbeddedImg(currentHoveredThumbnail));
  821.  
  822. floatingImgLeft = event.pageX + floatingImgRightOffset;
  823. /*if right of image exceeds beyond right of window, restrict max width*/
  824. if (floatingImgLeft + floatingImgWidth > $(window).scrollLeft() + $(window).width() - floatingImgBorder)
  825. {
  826. floatingImgWidth = $(window).scrollLeft() + $(window).width() - floatingImgBorder - floatingImgLeft;
  827. }
  828. floatingImgHeight = Math.round(getNatHeightOfEmbeddedImg(currentHoveredThumbnail) * (floatingImgWidth / getNatWidthOfEmbeddedImg(currentHoveredThumbnail)));
  829. floatingImgTop = event.pageY - (floatingImgHeight / 2);
  830. /*if bottom of image exceeds beyond the window, shift top upwards*/
  831. if (floatingImgTop + floatingImgHeight > $(window).scrollTop() + $(window).height() - floatingImgBorder)
  832. {
  833. floatingImgTop = $(window).scrollTop() + $(window).height() - floatingImgBorder - floatingImgHeight;
  834. }
  835. /*if top of image expands beyond top of window, lower top of image*/
  836. if (floatingImgTop < $(window).scrollTop() + floatingImgBorder)
  837. {
  838. floatingImgTop = $(window).scrollTop() + floatingImgBorder;
  839. }
  840. /*if bottom of image exceeds beyond the window, restrict max height*/
  841. if (floatingImgTop + floatingImgHeight > $(window).scrollTop() + $(window).height() - floatingImgBorder)
  842. {
  843. floatingImgHeight = $(window).scrollTop() + $(window).height() - floatingImgBorder - floatingImgTop;
  844. }
  845. /*if floating image doesn't exist, create it*/
  846. if (!isFloatingImgExist)
  847. {
  848. floatingImgStyleStr =
  849. "position: absolute;" +
  850. "left: " + floatingImgLeft + "px;" +
  851. "top: " + floatingImgTop + "px;" +
  852. "max-width: " + floatingImgWidth + "px;" +
  853. "max-height: " + floatingImgHeight + "px;" +
  854. "z-index: 100;";
  855. /*if webm video*/
  856. if (currentHoveredThumbnail.hasClass("webmExt"))
  857. {
  858. floatingImgSrc =
  859. "<video id='floatingImg' style='" + floatingImgStyleStr + "'>" +
  860. "<source src='" + currentHoveredThumbnail.children("source").attr("src") + "' type='video/webm'>" +
  861. "</video>";
  862. }
  863. else
  864. {
  865. floatingImgSrc =
  866. "<img id='floatingImg' style='" + floatingImgStyleStr + "' src='" + currentHoveredThumbnail.attr("src") + "'>";
  867. }
  868. isFloatingImgExist = true;
  869. /*create the floating image*/
  870. $("body").append(
  871. floatingImgSrc
  872. );
  873. /*if webm video*/
  874. if (currentHoveredThumbnail.hasClass("webmExt"))
  875. {
  876. var video = $("#floatingImg");
  877.  
  878. video.attr("loop", "");
  879. video[0].play();
  880. }
  881. }
  882. else /*if floating image already exists, update its size and position*/
  883. {
  884. $("#floatingImg").css({
  885. "left": floatingImgLeft + "px",
  886. "top": floatingImgTop + "px",
  887. "max-width": floatingImgWidth + "px",
  888. "max-height": floatingImgHeight + "px",
  889. });
  890. }
  891. }
  892. /*if user is not hovering over thumbnail and floating image still exists*/
  893. else if (currentHoveredThumbnail == null && isFloatingImgExist)
  894. {
  895. $("#floatingImg").remove();
  896. isFloatingImgExist = false;
  897. }
  898. });
  899.  
  900.  
  901.  
  902. /*if mouse hovers inside the image, change cursor to pointer*/
  903. $(document).on("mouseover", ".embeddedImg", function(event){
  904. $("html").css("cursor", "pointer");
  905. if (! $(this).hasClass("expandedImg"))
  906. {
  907. currentHoveredThumbnail = $(event.target);
  908. }
  909. });
  910.  
  911. /*if mouse hovers outside the image, restore cursor to default graphic*/
  912. $(document).on("mouseout", ".embeddedImg", function(){
  913. $("html").css("cursor", "default");
  914. currentHoveredThumbnail = null;
  915. });
  916.  
  917.  
  918.  
  919. /*if clicked on the image URL anchor that's surrounding the embedded image, prevent default
  920. behaviour of anchor, unless it's a middle click*/
  921. $("body").on("click", ".embeddedImgAnchor", function(event){
  922. /*left-click*/
  923. if (event.which == 1)
  924. {
  925. }
  926. else
  927. {
  928. $(this).attr("href", $(this).attr("data-backup-href"));
  929. }
  930. });
  931.  
  932. $("body").on("mousedown", ".embeddedImgAnchor", function(event){
  933. /*left-click*/
  934. if (event.which == 1)
  935. {
  936. event.preventDefault();
  937. }
  938. else
  939. {
  940. $(this).attr("href", $(this).attr("data-backup-href"));
  941. }
  942. });
  943.  
  944.  
  945. /*toggle image size on left-click*/
  946. $("body").on("click", ".embeddedImg", function(event){
  947. /*left-click*/
  948. if (event.which == 1)
  949. {
  950. var mouseRelativeY = event.pageY - $(this).offset().top;
  951. /*if expanded webm file, don't shrink to thumbnail if clicked on its controls*/
  952. if ($(this).hasClass("webmExt") && $(this).hasClass("expandedImg") &&
  953. (mouseRelativeY > ($(this).height() - 28)))
  954. {
  955. $(this).parent().attr("href", "javascript:void(0);");
  956. }
  957. else
  958. {
  959. toggleEmbeddedImgSize($(this));
  960. $(window).scrollTop( $(this).parent().parent().offset().top );
  961. return false;
  962. }
  963. }
  964. });
  965.  
  966.  
  967.  
  968. function toggleEmbeddedImgSize(embeddedImg)
  969. {
  970. if (isSpoilersMode)
  971. {
  972. /*if going to be expanded*/
  973. if (embeddedImg.hasClass("thumbnailImg"))
  974. {
  975. embeddedImg.removeClass("thumbnailImg_spoilersExt");
  976. embeddedImg.parent().removeClass("embeddedImgAnchor_spoilersExt");
  977. }
  978. else /*if going to be in thumbnail mode*/
  979. {
  980. embeddedImg.addClass("thumbnailImg_spoilersExt");
  981. embeddedImg.parent().addClass("embeddedImgAnchor_spoilersExt");
  982. }
  983. }
  984. embeddedImg.toggleClass("thumbnailImg expandedImg");
  985. /*if expanded*/
  986. if (embeddedImg.hasClass("expandedImg"))
  987. {
  988. currentHoveredThumbnail = null;
  989. $("#floatingImg").remove();
  990. isFloatingImgExist = false;
  991.  
  992. embeddedImg.css("max-width", getOptimalExpandedImgMaxWidth(embeddedImg));
  993. embeddedImg.css("max-height", "");
  994. /*if webm file, start playing it*/
  995. if (embeddedImg.hasClass("webmExt"))
  996. {
  997. embeddedImg[0].play();
  998. embeddedImg.attr("controls", "");
  999. }
  1000. }
  1001. else /*if shrinking image back to thumbnail*/
  1002. {
  1003. embeddedImg.css("max-width", thumbnailImgWidth + "px");
  1004. embeddedImg.css("max-height", thumbnailImgHeight + "px");
  1005. /*if webm file, stop playing it*/
  1006. if (embeddedImg.hasClass("webmExt"))
  1007. {
  1008. embeddedImg[0].pause();
  1009. embeddedImg.removeAttr("controls");
  1010. }
  1011. }
  1012. }
  1013.  
  1014.  
  1015.  
  1016. /*
  1017. Get the optimal max-width for an expanded image.
  1018.  
  1019. The width will either be the length between the left of the thumbnail and the
  1020. right of the image container, or the length between the left of the thumbnail
  1021. and right of the window size.
  1022.  
  1023. @param thumbnailImg :: jQuery object of the image to be expanded. It has the
  1024. class "thumbnailImg"
  1025.  
  1026. The returned width will be the smaller of the two.
  1027. */
  1028. function getOptimalExpandedImgMaxWidth(thumbnailImg)
  1029. {
  1030. var toContainerWidth;
  1031. var toWindowWidth;
  1032. toContainerWidth =
  1033. thumbnailImg.parent().parent().width();
  1034. toWindowWidth =
  1035. $(window).width() - thumbnailImg.offset().left;
  1036. if (toContainerWidth > toWindowWidth)
  1037. {
  1038. return toWindowWidth;
  1039. }
  1040. else
  1041. {
  1042. return toContainerWidth;
  1043. }
  1044. }
  1045.  
  1046.  
  1047.  
  1048.  
  1049.  
  1050.  
  1051.  
  1052.  
  1053.  
  1054.  
  1055.  
  1056.  
  1057. $("body").on("click", ".imgCloseAll", function(event) {
  1058. event.preventDefault();
  1059. /*for every expanded embedded image*/
  1060. $(".embeddedImg.expandedImg").each(function(index, element) {
  1061. toggleEmbeddedImgSize($(this));
  1062. });
  1063. $(window).scrollTop( $(this).parent().offset().top );
  1064. });
  1065.  
  1066.  
  1067. $("body").on("click", ".imgClosePost", function(event) {
  1068. var postID = $(this).attr("data-postID");
  1069. event.preventDefault();
  1070. /*for every expanded embedded image with same post ID*/
  1071. $(".embeddedImg[data-postID="+postID+"].expandedImg").each(function(index, element) {
  1072. toggleEmbeddedImgSize($(this));
  1073. });
  1074. $(window).scrollTop( $(this).parent().offset().top );
  1075. });
  1076.  
  1077.  
  1078. $("body").on("click", ".imgExpandPost", function(event) {
  1079. var postID = $(this).attr("data-postID");
  1080.  
  1081. event.preventDefault();
  1082. /*for every embedded image with same post ID*/
  1083. $(".embeddedImg[data-postID='"+postID+"']").each(function(index, element) {
  1084. /*if image already expanded*/
  1085. if ($(this).hasClass("expandedImg"))
  1086. {
  1087. /*update its max-width*/
  1088. $(this).css("max-width", getOptimalExpandedImgMaxWidth($(this)))
  1089. }
  1090. else
  1091. {
  1092. toggleEmbeddedImgSize($(this));
  1093. }
  1094. });
  1095. $(window).scrollTop( $(this).parent().offset().top );
  1096. });
  1097.  
  1098.  
  1099.  
  1100. $("body").on("click", ".imgExpandAll", function(event) {
  1101. event.preventDefault();
  1102. /*for every embedded image*/
  1103. $(".embeddedImg").each(function(index, element) {
  1104. /*if image already expanded*/
  1105. if ($(this).hasClass("expandedImg"))
  1106. {
  1107. /*update its max-width*/
  1108. $(this).css("max-width", getOptimalExpandedImgMaxWidth($(this)))
  1109. }
  1110. else
  1111. {
  1112. toggleEmbeddedImgSize($(this));
  1113. }
  1114. });
  1115. $(window).scrollTop( $(this).parent().offset().top );
  1116. });
  1117.  
  1118.  
  1119.  
  1120.  
  1121.  
  1122.  
  1123.  
  1124.  
  1125.  
  1126.  
  1127. $("body").on("click", ".imgBlacklistToggle", function(event) {
  1128. event.preventDefault();
  1129. var url = $(this).siblings(".embeddedImgAnchor").attr("href");
  1130. url.trim();
  1131. /*if image isn't blacklisted (but going to blacklist)*/
  1132. if (! $(this).siblings(".embeddedImgAnchor").hasClass("blacklisted"))
  1133. {
  1134. blacklist.push(url);
  1135. /*for every embedded image anchor*/
  1136. $(".embeddedImgAnchor").each(function(index, element) {
  1137. /*if has URL that was just blacklisted, hide it*/
  1138. if (isURLmatchBlacklistedURL(url, $(this).attr("href")))
  1139. {
  1140. $(this).siblings(".imgBlacklistToggle").html("whitelist");
  1141. $(this).siblings(".imgBlacklistToggle").attr("title", "Whitelist image");
  1142. $(this).addClass("blacklisted");
  1143. $(this).hide();
  1144. }
  1145. });
  1146. }
  1147. else /*if image is blacklisted (but going to whitelist)*/
  1148. {
  1149. /*remove url from blacklist*/
  1150. var urlIdx = blacklist.indexOf(url);
  1151. if (urlIdx > -1)
  1152. {
  1153. blacklist.splice(urlIdx, 1);
  1154. }
  1155. /*for every embedded image anchor*/
  1156. $(".embeddedImgAnchor").each(function(index, element) {
  1157. /*if has URL that was just whitelisted, show it*/
  1158. if ($(this).attr("href") == url)
  1159. {
  1160. $(this).siblings(".imgBlacklistToggle").html("blacklist");
  1161. $(this).siblings(".imgBlacklistToggle").attr("title", "Blacklist image");
  1162. $(this).removeClass("blacklisted");
  1163. $(this).show();
  1164. }
  1165. });
  1166. }
  1167. settings.blacklistStr = blacklist.join("\n");
  1168. localStorage.setItem("imagefaqs", JSON.stringify(settings));
  1169. });
  1170.  
  1171.  
  1172.  
  1173. })(); /*end of greasemonkey script*/