Hentai Foundry - Image Hover

Fetches a larger version of the image upon hovering over a thumbnail.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Hentai Foundry - Image Hover
// @namespace    https://github.com/Kayla355
// @version      0.3.2
// @description  Fetches a larger version of the image upon hovering over a thumbnail.
// @author       Kayla355
// @match        http://www.hentai-foundry.com/*
// @match        https://www.hentai-foundry.com/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @icon         http://img.hentai-foundry.com/themes/Hentai/favicon.ico
// @require      http://code.jquery.com/jquery-2.1.3.min.js
// @require      http://cdn.jsdelivr.net/jquery.visible/1.1.0/jquery.visible.min.js
// @require      https://cdn.rawgit.com/Kayla355/MonkeyConfig/d152bb448db130169dbd659b28375ae96e4c482d/monkeyconfig.js
// @history      0.2 Fixed an issue with smartPreload not loading in the image correctly. Also fixed an issue with flash files.
// @history      0.2.1 Fixed some issues with loading getting stuck.
// @history      0.2.2 Added more image positions.
// @history      0.2.3 Fixed the images not loading with the new HF theme. Some issues still remain with screen boundries, works when combined with my CSS fixes script.
// @history      0.2.4 Fixed the image boundries and also fixed an issue with the title showing up over the image somtimes.
// @history      0.2.5 Fixed an issue where images were still attempting to cache even after having already been cached.
// @history      0.3.0 Added MonkeyConfig options config, for actually storing options.
// @history      0.3.1 Quick bug fix from some changes to the website.
// @history      0.3.2 Fixed an issue with the "smart-preload" option.
// ==/UserScript==

// Options //
var imagePosition;
var hoverSize;
var smartPreload;
var preloadAll;

cfg = new MonkeyConfig({
    title: 'Hentai Foundry - Image Hover Configuration',
    menuCommand: true,
    params: {
        image_position: {
            type: 'select',
            choices: [ 'top-left', 'top-right', 'bottom-left', 'bottom-right', 'middle-left', 'middle-right'],
            default: 'middle-right'
        },
        hover_size: {
            type: 'number',
            default: 512
        },
        preload_option: {
            type: 'select',
            choices: [ 'Smart Preload', 'Preload All', 'none'],
            default: 'Smart Preload'
        }
    },
    onSave: setOptions
});

function setOptions() {
    imagePosition = cfg.get('image_position');
    hoverSize     = cfg.get('hover_size');
    smartPreload  = false;
    preloadAll    = false;

    switch(cfg.get('preload_option')) {
        case "Smart Preload":
            smartPreload = true;
            preloadAll = false;
            break;
        case "Preload All":
            smartPreload = false;
            preloadAll = true;
            break;
        default:
            smartPreload  = false;
            preloadAll    = false;
    }
}
setOptions();

switch(cfg.get('preload_option')) {
    case "Smart Preload":
        smartPreload = true;
        preloadAll = false;
        break;
    case "Preload All":
        smartPreload = false;
        preloadAll = true;
        break;
    default:
        smartPreload  = false;
        preloadAll    = false;
}

// Styles //
GM_addStyle(".image-hover {"
            +"position: absolute;"
            +"z-index: 9999;"
            +"box-shadow: 5px 5px 10px 0px rgba(50, 50, 50, 0.75);"
            +"pointer-events: none;"
            +"}"

            +".loader {"
            +"position: absolute;"
            +"margin: 8px 0px 0px 8px;"
            +"border-bottom: 6px solid rgba(255, 255, 255, 0.4);"
            +"border-left: 6px solid rgba(255, 255, 255, 0.4);"
            +"border-right: 6px solid rgba(255, 255, 255, 0.4);"
            +"border-top: 6px solid rgba(0, 0, 0, 0.8);"
            +"border-radius: 100%;"
            +"height: 25px;"
            +"width: 25px;"
            +"animation: rot 0.6s infinite linear;"
            +"}"
            +"@keyframes rot {"
            +"from {transform: rotate(0deg);}"
            +"to {transform: rotate(359deg);}"
            +"}"

            +"#pl-background {"
            +"position: absolute;"
            +"background-color: white;"
            +"height: 20px;"
            +"width: 125px;"
            +"border-radius: 25px;"
            +"}"

            +"#pl-fill {"
            +"display: inline-block;"
            +"background-color: red;"
            +"height: 20px;"
            +"border-radius: 25px;"
            +"}"

            +"#pl-background center {"
            +"position: absolute;"
            +"top: 0px;"
            +"left: 0px;"
            +"width: 125px;"
            +"text-align: center;"
            +"font-size: 10px;"
            +"font-weight: 900;"
            +"line-height: 20px;"
            +"}"
            +".thumb:hover {"
            +"position: relative !important;"
            +"padding: 0;"
            +"margin: 0;"
            +"background-size: cover;"
            +"border: 0;"
            +"}");

// Variables //
var hovering         = false;
var mouse            = {X: 0, Y: 0};
var imageExt         = [".jpg", ".jpeg", ".png", ".gif"];
loaded           = {};
var plProgress       = {current: 0, total: 0, percent: "0%"};
var loadingStatus    = "inactive";
var oldTitle         = "";
var done;
// Timers
var hoverTimer;
var hoverTimerStart;
var scrollTimer;

// Code //

// Event Listeners //

// Start preloading images
if(preloadAll || smartPreload) {
    if(preloadAll) {
        smartPreload = false;
    }
    loadImages();
}

// Listen for Events on thumbnails
$(".thumb").on({
    mousemove: function(e) {
        // Get mouse location
        if(e.pageY && e.pageX) {
            mouse.Y = e.pageY + 2;
            mouse.X = e.pageX + 5;
        }

        // Run function to keep image inside of window.
        if(hovering) {
            keepInside();
        }
        //console.log("X:", e.pageX, ", Y:", e.pageY);
    },
    mouseenter: function(e) {
        // Create links, id, etc.
        var link = e.target.parentNode.href.match(/(https?:\/\/www.hentai-foundry.com\/pictures\/user)(\/.*\/?)/)[2];
        var id   = link.match(/(?:\/.*\/)(.*)(?:\/)/)[1];
        var cat  = link.slice(1, 2).toLowerCase();
        if(cat.match(/-/)) {
            cat = "_";
        } else if(cat.match(/[^a-z]/)) {
            cat = "0";
        }
        var src = "http://pictures.hentai-foundry.com/" + cat + "/" + link.slice(0, -1);
        var obj = {id: id, src: src, target: e.target, from: "hover"};

        // Title issue fix
        oldTitle = this.title;
        this.title = "";

        // Create content div
        $('<div class="image-hover">'
          +'<div id="hoverLoader" class="loader"></div>'
          +'<div id="'+ id +'" style="display:none"></div>'
          +'</div>').appendTo("body");

        // Check if user has recently hovered over an object, thereby triggering the hover mode.
        if(!hovering) {
            clearTimeout(hoverTimerStart);
            hoverTimerStart = setTimeout(function() {
                hovering = true;
                hoverFunc(obj);
            }, 500);
        } else {
            hoverFunc(obj);
        }
        // Clear timer to exit "hovermode"
        clearTimeout(hoverTimer);
    },
    mouseleave: function(e) {
        // Clear timeouts and remove divs.
        clearTimeout(hoverTimerStart);
        $("div.image-hover").remove();
        hoverTimer = setTimeout(function() { hovering = false; }, 500);
        // Title issue fix
        this.title = oldTitle;
        oldTitle = "";
    }
});

// If smartPreload is enabled, Listen for when the document is scrolled
if(smartPreload) {
    $(document).on('scroll', function() {
        console.log("Scrolled, loading is", loadingStatus);
        if(preloadAll || smartPreload) {
            clearTimeout(scrollTimer);
            scrollTimer = setTimeout(function() {
                if(loadingStatus === "active") {
                    $(document).on("stoppedLoading", function() {
                        console.log("stopped loading, starting again");
                        loadImages();
                    });
                } else {
                    loadImages();
                }
            }, 1000);
        }
    });
}

// Listen for update to the pre-load progress.


// Re-usable Functions //

// Function that is run when hovering over an image.
function hoverFunc(obj) {
    var target = obj.target;
    var id     = obj.id;

    $('#'+id).on("imageLoaded", function() {
        $('#hoverLoader').remove();
      // Set size of image
        loaded[id].image.style.maxHeight = hoverSize +"px";
        loaded[id].image.style.maxWidth  = hoverSize +"px";

        $('.image-hover div#'+ obj.id).css("background-color", "#a3a3ab").append(loaded[id].image).show();
        $(obj.target).trigger("mousemove");
    });

    $(target).trigger("mousemove");
    if(plProgress.current != plProgress.total) {
        $('#hoverLoader').remove();
        $('.image-hover').append('<div id="pl-background"><div id="pl-fill"></div><center></center></div>');
    } else if (validateImage(id)) {
        if(loaded[id].status === "done") {
            loaded[id].from = "hover";
            createImages(loaded[id]);
        }
    } else {
        imageExt.eachImage(obj);
    }

    $(document).trigger("plStatusChange");
}

// Function for creating and loading the images before showing.
function loadImages() {
    var thumbs = $('.thumb');
    var from   = "preload";
    loadingStatus = "active";
    done = 0;

    if(smartPreload) {
        from = "smartload";
        console.log("Filtering!");
        thumbs = $('.thumb').filter(function(e) {
            var id = parseInt(this.style["background-image"].replace(/.*pid=([0-9]+).*/, '$1'));
            if(!id || typeof id !== "number") return false;
            if($(this).visible( true )) {
                if(validateImage(id)) {
                    console.info("["+id+"]", "Image already loaded:", loaded[id].image.src);
                }
                //console.log("Visible:",$(this).visible( true ));
                return true;
            } else {
                //console.log("Visible:", $(this).visible( true ), "Loaded:", loaded[id]);
                return false;
            }
        });
    }
    if(thumbs.length === 0) {
        console.log("Finished loading images");
        loadingStatus = "inactive";
        $(document).trigger("loadingReady");
    }

    thumbs.each(function(i) {
        var e = {target: this};
        var link = e.target.parentNode.href.match(/(https?:\/\/www.hentai-foundry.com\/pictures\/user)(\/.*\/?)/)[2];
        var id   = link.match(/(?:\/.*\/)(.*)(?:\/)/)[1];
        var cat  = link.slice(1, 2).toLowerCase();

        if(cat.match(/-/)) {
            cat = "_";
        } else if(cat.match(/[^a-z]/)) {
            cat = "0";
        }
        var imgSrc = "http://pictures.hentai-foundry.com/" + cat + "/" + link.slice(0, -1);

        loaded[id] = {};

        var fail = 0;

        imageExt.forEach(function(ext) {
            imageExists(imgSrc + ext, function(exists) {
                if(exists) {

                    loaded[id].id      = id;
                    loaded[id].src     = imgSrc;
                    loaded[id].ext     = ext;
                    loaded[id].target  = e.target;
                    loaded[id].from    = from;

                    if(loaded[id].ext && from === "preload") {
                        plProgress.realtotal++;
                        //return;
                    }

                    createImages(loaded[id], thumbs.length);
                } else {
                fail++;
                if(fail === imageExt.length) {
                    done++;
                    console.log("Loading Progress: ", done +" / "+ thumbs.length);
                    loaded[id].ext = "failed";
                    console.error("Could not determine file type:", imgSrc);
                }
            }
            });
        });
    });
}


// Create the image and load it before attaching it to the div.
function createImages(obj, total) {
    var image = new Image();

    if(obj.from === "preload") {
        plProgress.total = total;
        if(plProgress.realtotal > plProgress.total && obj.from === "preload") {
            plProgress.total = plProgress.realtotal;
        }
    }


    if(obj.status === "done") {
        if(loaded[obj.id].image) {
            if($('#'+obj.id+' img').length === 0) {
                $('#'+obj.id).trigger("imageLoaded");
            }
            return;
        }
    }

    if(obj.from != "preload" && obj.status !== "done") {
        image.onload = function () {
            obj.image = image;
            if($('#'+obj.id+' img').length === 0) {
                obj.status = "done";

                if(obj.from === "smartload") {
                    loaded[obj.id].status = obj.status;
                    loaded[obj.id].image = image;
                } else {
                    loaded[obj.id] = obj;
                }

                console.info("["+obj.id+"]", "Image loaded:", obj.image.src);
                $('#'+obj.id).trigger("imageLoaded");
            }
            done++;
            console.log("Loading Progress: ", done +" / "+ total);
            if(done === total) {
                console.log("Finished loading images");
                loadingStatus = "inactive";
                $(document).trigger("loadingReady");
            } else if((total - done) <= Math.round(total/3)) {
                console.log("Accepting new Images");
                loadingStatus = "ready";
                $(document).trigger("loadingReady");
            }
        };
    } else if(obj.from === "preload") {
        image.onload = function () {
            plProgress = {current: plProgress.current+1, total: plProgress.total, percent: Math.round((plProgress.current / plProgress.total) * 100)};
            $(document).trigger("plStatusChange");
        };
    }

    image.onerror = function () {
        obj.status = "failed";
        console.error("Cannot load image");
    };

    obj.status = "loading";
    image.src = obj.src + obj.ext;
}

// Prototype for checking each of the image extensions listed in 'imageExt'.
Array.prototype.eachImage = function(obj) {
    var fail = 0;
    this.forEach(function(ext) {
        imageExists(obj.src + ext, function(exists) {
            if(exists) {
                obj.ext = ext;
                createImages(obj);
            } else {
                fail++;
                if(fail === imageExt.length) {
                    done++;
                    console.log("Loading Progress: ", done +" / "+ thumbs.length);
                    loaded[id].ext = "failed";
                    console.error("Could not determine file type:", imgSrc);
                }
            }
        });
    });
};

// Checks if the given image url exists
function imageExists(url, callback) {
    GM_xmlhttpRequest({
        url: url,
        method: "HEAD",
        onload: function(response) {
            callback(response.status < 400);
        }
    });
}

var hasOwnProperty = Object.prototype.hasOwnProperty;
// Validate the existing image object
function validateImage(id) {
    if(loaded[id] == null) return false;

    if(loaded[id].image) return true;

    if(loaded[id].length > 0) return true;
    if(loaded[id].length === 0) return false;

    if(typeof loaded[id] !== "object") return false;

    for(var key in loaded[id]) {
        if (hasOwnProperty.call(loaded[id], key)) return true;
    }

    return false;
}

// Function for keeping the image inside the window borders.
function keepInside() {
    var image = {};
    try {
        image  = {
            naturalHeight: $('.image-hover img')[0].height,
            naturalWidth:  $('.image-hover img')[0].width
        };
    } catch(e) {
        image  = {
            naturalHeight: 0,
            naturalWidth:  0
        };
    }

    var screen = {
        height: window.pageYOffset + $(window).height() - 2,
        width:  window.pageXOffset + $(window).width()  - 0,
        naturalHeight: $(window).height(),
        naturalWidth:  $(window).width(),
        margin: {
            height: parseFloat(($(document).height() - $("body").height()) / 2),
            width: parseFloat(($(document).width() - $("body").width()) / 2)
        }
    };

    // Get image height, relative to mouse position.
    try {
        if(imagePosition === "bottom-left" || imagePosition === "bottom-right") {
            image.height = (mouse.Y - 2) + image.naturalHeight;
        } else if(imagePosition === "middle-left" || imagePosition === "middle-right") {
            image.height = {
                top: (mouse.Y - 2) - (image.naturalHeight / 2),     // For checking if colliding with top
                bottom: (mouse.Y - 2) + (image.naturalHeight / 2)   // For checking if colliding with bottom
            };
        } else {
            image.height = (mouse.Y - 2) - image.naturalHeight;
        }
    } catch(e) {
        image.height = 0;
    }

    // Get image width, relative to mouse position.
    try {
        if(imagePosition === "top-right" || imagePosition === "bottom-right" || imagePosition === "middle-right") {
            image.width = (mouse.X + 2) + image.naturalWidth;
        } else {
            image.width = (mouse.X + 2) - image.naturalWidth;
        }
    } catch(e) {
        image.width = 0;
    }

    // Check if image height is outside of screen
      // If on bottom
    if(imagePosition === "bottom-left" || imagePosition === "bottom-right") {
        if(screen.height <= image.height) {
            mouse.Y = mouse.Y - (image.height - screen.height);
        }
      // ELSE IF in middle
    } else if(imagePosition === "middle-left" || imagePosition === "middle-right") {
        if(screen.height <= image.height.bottom) {
            mouse.Y = mouse.Y - (image.height.bottom - screen.height);
        } else if(image.height.top + screen.naturalHeight - 1 <= screen.height) {
            mouse.Y = mouse.Y - image.height.top + (screen.height - screen.naturalHeight) + 1;
        }
      // ELSE on top
    } else {
        if(image.height + screen.naturalHeight - 1 <= screen.height) {
            mouse.Y = mouse.Y - image.height + (screen.height - screen.naturalHeight) + 1;
        }
    }

    // Check if image width is outside of screen
      // IF on right side
    if(imagePosition === "top-right" || imagePosition === "bottom-right" || imagePosition === "middle-right") {
        if(screen.width <= image.width) {
            mouse.X = mouse.X - (image.width - screen.width);
        }
      // ELSE on left side
    } else {
        if (image.width <= 3){
            mouse.X = mouse.X + ~image.width + 5;
        }
    }

    // Offset depending image position relative to mouse set in options
    switch(imagePosition) {
        case "top-left":
            image.Y = mouse.Y - (image.naturalHeight);
            image.X = mouse.X - (image.naturalWidth);
            break;
        case "top-right":
            image.Y = mouse.Y - (image.naturalHeight);
            image.X = mouse.X;
            break;
        case "bottom-left":
            image.Y = mouse.Y;
            image.X = mouse.X - (image.naturalWidth);
            break;
        case "bottom-right":
            image.Y = mouse.Y;
            image.X = mouse.X;
            break;
        case "middle-left":
            image.Y = mouse.Y - (image.naturalHeight / 2);
            image.X = mouse.X - (image.naturalWidth);
            break;
        case "middle-right":
            image.Y = mouse.Y - (image.naturalHeight / 2);
            image.X = mouse.X;
            break;
        default:
            image.Y = mouse.Y;
            image.X = mouse.X;
            break;

    }

    // Margin offsets
    image.Y = image.Y - screen.margin.height;
    image.X = image.X - screen.margin.width;

    // Set image position
    $("div.image-hover").css({
        "top": image.Y,
        "left": image.X
    });
}