Greasy Fork is available in English.

Facebookenlarge

Enlarges pictures when you roll over them

// ==UserScript==
// @name           Facebookenlarge
// @namespace      http://userscripts.org/users/23652
// @description    Enlarges pictures when you roll over them
// @include        http://*.facebook.com/*
// @include        https://*.facebook.com/*
// @include        http://facebook.com/*
// @include        https://facebook.com/*
// @copyright      JoeSimmons
// @version        1.0.7
// @license        GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @require        https://greasyfork.org/scripts/1885-joesimmons-library/code/JoeSimmons'%20Library.js?version=4838
// @require        https://greasyfork.org/scripts/2817-jsl-ajax-plugin/code/JSL%20-%20AJAX%20plugin.js?version=7911
// @grant          GM_xmlHttpRequest
// ==/UserScript==

/* CHANGELOG ////////////////////////////////////////

1.0.7 (11/12/2014)
    - fixed problem with some images not showing up at all
        it was because the script couldn't differentiate between good responses and empty responses
        some working images had an HTTP status of 0, and so did empty responses
        I've now switched to a GET request and just check if the response text is empty or not
        It not only preloads the image, but it can 100% check if the modified (large) image is working

1.0.6 (7/1/2014)
    - fixed feature to not show enlarged thumbnail in fullscreen view or on album image hover
    - fixed another small error with displaying some images

1.0.58 (6/26/2014)
    - fixed an error with displaying some images (removed "v/t1.0-1/" out of url)
    - made it not show a popup when the popup url is the same as the hovered url
          this prevents a popup when hovering over a photo in full screen viewing mode
    - added a small system to check if the enlarged url exists - if not, the user will
          be shown the thumbnail in the popup (no enlargement because it's not possible)
          instead of a small, empty, black box

1.0.57 (4/6/2014)
    - fixed a problem with banners (on shared pages boxes) unable to be enlarged

1.0.56 (3/28/2014)
    - fixed a problem with hovering over the "Photos" thumbnail on a page
    - fixed a problem with some thumbnails on the "About" page

1.0.55 (3/22/2014)
    - fixed an issue where hovering over someone's cover photo wouldn't show it

1.0.54 (3/20/2014)
    - allowed enlarging of the banner picture above "Like Page" button
    - adapted to the newest JSL

1.0.53 (9/13/2013)
    - added more image compatibility

1.0.52 (9/12/2013)
    - updated the 'ispic' RegExp

1.0.51
    - fixed the 'ispic' RegExp. some pics weren't getting matched

1.0.50
    - fixed a bug wherein the preview wouldn't always display in the correct corner
    - fixed a bug wherein some pictures would display incorrectly
    - updated HQ pic getting methods so that it shows the biggest picture possible (without ajax)
    - added a method so that the preview would never overlap the mouse cursor
    - now middle & right clicking don't hide the preview, only left-clicking

1.0.49
    - fixed a regexp bug that would cause some pictures to not show
    - added an anonymous function wrapper
    - updated GM_addStyle check/function

*/ //////////////////////////////////////////////////




// By: Ian Williams and edited for Facebook by JoeSimmons

(function () { // anonymous function wrapper

    'use strict';

    var delay = 400,
        coords = {
            'x' : 0,
            'y' : 0
        },
        size = /([\d_]{5,})([_\/])[qstna]([_\.])?/i,
        ispic = /https?:\/\/((fbcdn-)?(profile|s?(photos|content)-\w|s(photos|content))((-\w+)+)?(\.ak|\.xx)?\.(fbcdn|akamaihd))\.net\/(.*\/)+.*([_\/][qstna]([\._])?)?.*(jpe?g|[tg]iff?|bmp|png)([?&][a-z]+=[a-zA-Z0-9]+)*/i,
        rFbexternal = /&(cfs|upscale)(=[^&]+)?/g,
        XbyX = /(v\/t[^\/]+\/)?(\w(\d+\.)+\d+\/)?\w\d{2,4}x\d{2,4}\//,
        c = /\w(\d+\.)+\d+\//,
        app = /www\/app_full_proxy\.php/,
        show_d, // timeout holder
        docFrag = document.createDocumentFragment();

    // Debug by JoeSimmons
    function debug() {
        var d = document.getElementById('debugT'),
            body = document.body,
            strings = [], i, arg;

        for(i = 0; i < arguments.length; i += 1) {
            arg = arguments[i];
            if ( (typeof arg === 'string' ? arg.trim() !== '' : arg ) ) {
                strings.push(arg);
            }
        }
        
        if (!d && body) {
            d = document.createElement('pre');
            d.id = 'debugT';
            d.setAttribute('style', 'position: fixed; width: 97%; height: 20%; margin: 0 1% 6px 1%; padding: 5px; color: #000000; background: #E6F4FF; border: 3px double #0099FF; border-top: 0; z-index: 999999; scroll-y: auto; overflow: auto; white-space: pre-wrap;');
            d.textContent = '[Debug Window 2.0 - Drag box to read fully - Copyright Joe Simmons \'CC BY-ND 3.0\']\n\n\n\n' + strings.join('\n\n');
            d.addEventListener('dblclick', function (e) {e.target.style.display = 'none';}, false);
            body.insertBefore(d, document.body.firstChild);
            } else {
                d.textContent += '\n\n--------------------------------------------------\n\n' + strings.join('\n\n');
            }

        if (d.style.display === 'none') d.style.display = 'block';
    }

    function primeThumbs() {
        JSL('//a[@data-hovercard]/img/..').removeAttribute('data-hovercard');
    }

    // record mouse movement for positioning the preview images
    function handleMove(event) {
        coords.x = event.pageX - pageXOffset;
        coords.y = event.pageY - pageYOffset;
        event.stopPropagation();
    }

    function show(src) {
        var pop = JSL('#picPop'),
            x = coords.x,
            y = coords.y,
            isTop = y < (window.innerHeight / 2), // is mouse at top?
            isLeft = x < ( (window.innerWidth - 15 /* 15 is the scrollbar width, approx. */) / 2), // is mouse at left?
            maxWidth = (isLeft ? (window.innerWidth - x) : x) - 25, // keep the image at least 25px away from the cursor
            vert, hori;

        // make sure the preview doesn't show beyond the browser dimensions or overlap the mouse cursor
        // this alone only limits the max-width, but coupled with the 4-corner system, it works
        pop.css('max-width', maxWidth + 'px');

        // set the preview's source url
        pop.attribute('src', src);

        // determine where the preview should display according to hovered image's position
        // ideally, as far away from the hovered image as possible
        vert = isTop ? 'bottom' : 'top';     // should the preview be on the top or bottom?
        hori = isLeft ? 'right' : 'left';    // should the preview be on the left or right?

        // reset the position of the hover box
        pop.css('top', 'auto').css('right', 'auto').css('bottom', 'auto').css('left', 'auto');

        // set the corner it will appear in
        pop.css(vert, '0').css(hori, '0');

        // show the preview
        pop.show('block');
    }

    function handleMouseover(event) {
        var t = event.target,
            tag = t.tagName.toLowerCase(),
            style = t.getAttribute('style'),
            _class = t.className,
            src = style && style.match(ispic) ? t.getAttribute('style') : unescape(t.src),
            imgExist = JSL('./img | ./i | ./div/img | ./span/img', t),
            odd = JSL('./../img | ./../i | ./../../div[@class="detail"]/i[@class="photo" and contains(@style, "background-image")]', t),
            ohoe = JSL('./ancestor::a[( contains(@href, "oh=") and contains(@href, "oe=") ) or ( contains(@href, "oh%3D") and contains(@href, "oe%3D") )]', t),
            snowlift = JSL('#photos_snowlift'),
            goThroughWithShow, hqImg;

        // checks if enlarged thumbnail's url exists before showing it
        function checkSource(res) {
            if (res.responseText === '') {
                // if the enlarged thumbnail url has a problem, show the original thumbnail instead
                hidePicPop();
                show_d = window.setTimeout(show, 200, this);
            }
        }

        // sometimes the hovered element can be a parent element of the actual thumbnail element
        if (imgExist.exists) {
            t = imgExist[0];
            src = unescape(t.src);
            tag = t.tagName.toLowerCase();

            if ( src.indexOf('fbexternal') !== -1 && src.match(ispic) ) {
                src = src.match(ispic)[0].replace(rFbexternal, '');
            }
        }

        if (ohoe.exists) {
            src = decodeURIComponent( ohoe.attribute('href') ).split('&src=')[1].split('&smallsrc=')[0];
        }

        // support for elements that when hovered over, aren't the image itself
        // or it's an element where it displays the image by css' background-image
        if ( tag === 'div' && (['coverBorder', 'mat'].indexOf(_class) !== -1) ) {
            if (odd.exists) {
                t = odd[0];
                tag = t.tagName.toLowerCase();
                style = t.getAttribute('style');

                if (tag === 'i' && typeof style === 'string' && style.indexOf('background-image') !== -1) {
                    src = style.match(ispic)[0];
                } else {
                    src = unescape(t.src);
                }
            }
        }

        if ( ['img', 'i'].indexOf(tag) !== -1 && src.match(ispic) ) {
            if ( tag === 'img' && src.match(app) ) {
                src = src.match(ispic)[0];
            }

            hqImg = ohoe.exists ? src.replace(XbyX, '') : hq(t, tag, src);
            goThroughWithShow = true;

            // debug( src.match(ispic) != null ? 'src is recognized\norig url: ' + src + '\nhq url:   ' + hqImg : 'src is NOT recognized' );
        } else if ( tag === 'div' && t.className == 'UIMediaItem_PhotoFrame' && t.parentNode.parentNode.parentNode.getAttribute('style').match(ispic) ) {
            hqImg = hq(t, tag);
            goThroughWithShow = true;
        }

        // make sure we don't show enlarged thumbnails for album or fullscreen view images
        if (t.className.indexOf('spotlight') !== -1 || JSL(t).parent('#imagestage').exists) {
            goThroughWithShow = false;
        }

        // if we chose to show the image, let's go through that process
        if (goThroughWithShow === true) {
            // show the image if it's indeed a facebook thumbnail
            // and the enlarged url is not the same as the thumbnail url
            show_d = window.setTimeout(show, delay, hqImg);

            // let's send a quick request to see if this is a valid image
            // if not, we just show the original thumbnail
            // ps: this not only preloads the image, but it's a sure-fire way
            //     of knowing if it's a false positive or not (e.g., status==0 but image displays fine)
            JSL.ajax(hqImg, {
                context : src,
                method : 'GET',
                onload : checkSource,
                onerror : checkSource
            });
        }

        event.stopPropagation();
    }

    function hidePicPop(event) {
        var picPop = JSL('#picPop');

        // don't hide the enlarged picture if a middle or right click happens
        if (typeof event !== 'undefined' && typeof event.which === 'number' && event.which > 1) { return; }

        window.clearTimeout(show_d);

        picPop.hide();
        picPop.removeAttribute('src');

        if (typeof event !== 'undefined') {
            event.stopPropagation();
        }
    }

    function hq(e, tag, src) {
        var r = '', style = e.getAttribute('style');

        switch (tag) {
            case 'div': {
                r = e.parentNode.parentNode.parentNode.getAttribute('style').match(ispic)[0];
                break;
            }

            case 'img': case 'i': {
                if ( typeof style === 'string' && style.match(ispic) ) {
                    r = style.match(ispic)[0];
                } else if (typeof src === 'string') {
                    r = src;
                } else {
                    r = e.src;
                }

                break;
            }
        }

        return r.replace(XbyX, '').replace(c, '').replace(size, '$1$2n$3');
    }

    function info(i) {
        var info = JSL('#infoBox');

        i = (i + '').replace(/[\n\r]/g, '\n<br />\n');

        info.show('block').prop('innerHTML', i);
    }

    // Make sure the page is not in a frame
    if (window.self !== window.top) { return; }

    JSL.addStyle('' +
        '#picPop { ' +
            'z-index: 99999; ' +
            'position: fixed; ' +
            'background: #000000; '+
            'overflow: hidden; ' +
            'border: 2px solid #000000; ' +
            'outline: 2px solid #FFFFFF; ' +
            'max-height: 98%; ' +
        '}\n\n' +
        '.HovercardOverlay, ._5uek, ._7m4, ._8xh, ._3aml, #fbProfileCover .coverBorder { ' +
            'display: none !important; ' +
        '}' +
    '');

    // add the info box
    docFrag.appendChild( JSL.create('span', {id: 'infoBox', style: 'border: 1px solid #666666; border-radius: 6px; position: fixed; top: 4px; left: 45%; font-size: 10pt; font-family: sans-serif, arial; background: #EEEEEE; color: #000000; padding: 10px; z-index: 999999; overflow: auto; display: none;'}) );

    // add the hovering bigger image holder
    docFrag.appendChild( JSL.create('img', {id: 'picPop', style: 'display: none;', 'class':'hover_img_thumb'}) );

    document.body.appendChild(docFrag);

    // keep tabs on where the mouse is so we never problems with the positioning of the preview
    window.addEventListener('mousemove', handleMove, false);

    // hover over a thumbnail
    window.addEventListener('mouseover', handleMouseover, false);

    // hide the preview when moving the mouse off a thumbnail
    window.addEventListener('mouseout', hidePicPop, false);

    // hide the preview when left-clicking
    window.addEventListener('click', hidePicPop, false);

    primeThumbs();
    JSL.setInterval(primeThumbs, 1000);

}());