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