SteamGifts: Open in pop-in

Opens various links in pop-in window. This includes Giveaway links, links to Steam store and new messages.

Versión del día 13/04/2016. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name        SteamGifts: Open in pop-in
// @namespace   lainscripts_steamgifts_steam_popin
// @description Opens various links in pop-in window. This includes Giveaway links, links to Steam store and new messages.
// @include     *://www.steamgifts.com/*
// @version     3.39
// @grant       none
// @run-at      document-start
// ==/UserScript==
/* jshint esnext: true */
(()=>{
    'use strict';

    var isSteamGifts = /^(www\.)?steamgifts\.com$/i,
        isKnown = /^\/giveaways\/|\/(about|account|discussions?|giveaway|go\/comment|legal|messages|roles|support|user)(\/|$)/i,
        isAboutLegal = /^\/(about|legal)\//i,
        isDiscussion = /^\/(discussion|go\/comment)\//i,
        isSearch = /^\/giveaways\/search/i,
        isSteamStore = /^store\.steampowered\.com$/i,
        isSteamPackage = /\/(app|sub)\//i,
        firstLevel = /^\/[^/]+/i;

    var markAsOutdatedClasses = ['form__sync-default','table__remove-default'],
        ofClass = (c) => { return this.classList && this.classList.contains(c); };

    var getFirstLevel = function(pathname) {
        return (firstLevel.exec(pathname)||[''])[0];
    };

    var inIframe = function() {
        try {
            return window.self !== window.top;
        } catch (e) {
            return true;
        }
    };

    // attach styles before document displayed
    (function(style) {
        style.id = 'popinStyles';
        style.type = 'text/css';
        document.head.appendChild(style);

        style.sheet.insertRule('@keyframes spinnerFrames {0% {transform:rotate(0deg)} 100% {transform:rotate(-360deg)}}',0);
        style.sheet.insertRule('#popinSpinner svg {'+
                               ['animation: spinnerFrames linear 1s',
                                'animation-iteration-count: infinite',
                                'transform-origin: 50% 50%'].join(';')+'}', 0);
        style.sheet.insertRule('#popinBackground {'+
                               ['box-sizing: border-box',
                                'position: fixed',
                                'top: 0',
                                'left: 0',
                                'padding: 35px 40px',
                                'width: 100%',
                                'height: 100%',
                                'background-color: rgba(0,0,0,0.85)',
                                'z-index: 1000000',
                                'display: none'].join(';')+'}', 0);
        style.sheet.insertRule('#popinFrame {'+
                               ['position: relative',
                                'width: 100%',
                                'height: 100%',
                                'border: #aaa 2px solid',
                                'border-radius: 3px',
                                'background-color: rgba(0,0,0,0.33)',
                                'z-index: 1'].join(';')+'}', 0);
        style.sheet.insertRule('#popinSpinner {'+
                               ['position: fixed',
                                'top: 50%',
                                'left: 50%',
                                'margin-top: -50px',
                                'margin-left: -50px',
                                'width: 100px',
                                'height: 100px',
                                'z-index: 2'].join(';')+'}', 0);

        // modify navigation elements when various pages are loaded in the pop-in
        if (inIframe()) {
            // make page cover the entire window height and reset padding with margin if someone changed them
            style.sheet.insertRule('html, body {'+['height: 100%',
                                                   'padding: 0!important',
                                                   'margin: 0!important'].join(';')+'}', 0);
            // hide some useless for pop-in blocks
            style.sheet.insertRule(['.offer__outer-wrap',
                                    '.footer__outer-wrap'].join(',')+' {display: none}', 0);
            // make content as heigh as the page + fix for the giveaway pages (they have 1 extra block)
            if (getFirstLevel(document.location.pathname) === '/giveaway') {
                style.sheet.insertRule('.page__outer-wrap {min-height: calc(100% - 50px - 208px)}', 0);
            } else {
                style.sheet.insertRule('.page__outer-wrap {min-height: calc(100% - 50px)}', 0);
            }
            // display floating account button in the top-right corner (except for the account page)
            if (getFirstLevel(document.location.pathname) === '/account') {
                style.sheet.insertRule('header {display: none!important}', 0);
            } else {
                style.sheet.insertRule(['.nav__left-container',
                                        '.nav__button-container--notification'].join(',')+' {display: none}', 0);
                style.sheet.insertRule('header > nav {padding: 0!important}', 0);
                style.sheet.insertRule('.sidebar {top: 25px!important}', 0);
                style.sheet.insertRule('header {'+['top: -5px!important',
                                                   'right: 0',
                                                   'padding: 0',
                                                   'position: fixed',
                                                   'width: auto!important',
                                                   'background: none',
                                                   'z-index: 100'].join(';')+'}', 0);
            }
            // hide Browse category in frames on pages with giveaways lists
            if (getFirstLevel(document.location.pathname) === '/giveaways')
                style.sheet.insertRule('.sidebar__heading:first-of-type, .sidebar__navigation:first-of-type {display: none}', 0);
            // hide sidebar and padding at the left side for discussions
            if (getFirstLevel(document.location.pathname) === '/discussion') {
                style.sheet.insertRule('.sidebar {display: none}', 0);
                style.sheet.insertRule('.widget-container>div:not(:first-child) {'+['padding-left: 0!important',
                                                                                    'margin-left: 0!important',
                                                                                    'border-left: none!important'].join(';')+'}', 0);
                style.sheet.insertRule('.page__heading__breadcrumbs a {pointer-events: none}');
            }
        }

    })(document.createElement('style'));

    function createSVGSpinner(out_radius, in_radius, width, number_of_lines, period, direction) {
        var num = number_of_lines, shift = out_radius + width / 2, step = 0;
        var nameSpace = 'http://www.w3.org/2000/svg';

        var svg = document.createElementNS(nameSpace, 'svg');
        svg.setAttribute('version', '1.1');
        svg.setAttribute('x', '0px');
        svg.setAttribute('y', '0px');
        svg.setAttribute('width', (2 * shift) + 'px');
        svg.setAttribute('height', (2 * shift) + 'px');
        svg.setAttribute('style', ['stroke: #fff',
                                   'stroke-width: ' + width + 'px',
                                   'stroke-linecap: round'].join(';'));

        var g = document.createElementNS(nameSpace, 'g');
        svg.appendChild(g);

        var line;
        while (num--) {
            if (direction >= 0) step = num; else step = number_of_lines - num;
            line = document.createElementNS(nameSpace, 'line');
            line.setAttribute('x1', shift + Math.cos(2 * Math.PI / number_of_lines * num) * in_radius);
            line.setAttribute('y1', shift + Math.sin(2 * Math.PI / number_of_lines * num) * in_radius);
            line.setAttribute('x2', shift + Math.cos(2 * Math.PI / number_of_lines * num) * out_radius);
            line.setAttribute('y2', shift + Math.sin(2 * Math.PI / number_of_lines * num) * out_radius);
            line.setAttribute('stroke-opacity', 1 / (num + 1));
            g.appendChild(line);
        }
        return svg;
    }

    function popinObj() {
        /* jshint validthis: true */
        var _self = this;
        _self.childPopin = null;

        var spinner = document.createElement('div');
        spinner.appendChild(createSVGSpinner(45, 25, 10, 10, 1.8, -1));
        spinner.id = 'popinSpinner';

        var popin = document.createElement('div');
        popin.id = 'popinBackground';

        var ifrm = null;
        var openFrame = function (a) {
            var url = a.href;
            var ifr = document.createElement('iframe');
            ifr.id = 'popinFrame';

            ifr.onload = function() {
                spinner.style.zIndex = 0;
                if (isSteamGifts.test(a.hostname))
                    _self.childPopin = this.contentDocument.popin;
            };

            // Hide spinner if page keeps loading after a 5 seconds
            setTimeout(function() {
                if (popin.style.display === 'block')
                    spinner.style.zIndex = 0;
            }, 5000);

            url = url.replace('http:','https:').replace('/store.steampowered.com/','/steamdb.info/');

            ifr.src = url;
            return ifr;
        };

        popin.appendChild(spinner);
        document.body.appendChild(popin);

        _self.show = function (a) {
            document.body.style.overflowY = 'hidden';
            ifrm = openFrame(a);
            popin.appendChild(ifrm);
            popin.style.display = 'block';
        };

        _self.hide = function () {
            var onUpdateParent = false;
            if (ifrm.src.search('/www.steamgifts.com/') > -1) {
                var ifDoc = ifrm.contentDocument;
                // propagate outdated state of parent document and reload it if applicable
                var deepPopin = _self.childPopin;
                while(deepPopin && deepPopin.childPopin && !deepPopin.isOutdated())
                    deepPopin = deepPopin.childPopin;
                if (deepPopin && deepPopin.isOutdated()) {
                    document.popin.setIsOutdated();
                    // only pages with active giveaways should be reloaded
                    if (document.location.pathname === '/' || isSearch.test(document.location.pathname)) {
                        location.reload();
                        onUpdateParent = true;
                    }
                } else
                    (()=>{
                        // update points and notifications in parent window
                        var points = '.nav__points';
                        var notifs = '.nav__button[href^="/giveaways/"],.nav__button[href="/messages"]';
                        ((pin, pout) => {
                            if (!pin || !pout.length) return;
                            var i = pout.length;
                            while (i--)
                                pout[i].textContent = pin.textContent;
                        })(ifDoc.querySelector(points), document.querySelectorAll(points));
                        ((pin, pout) => {
                            if (!pin.length || !pout.length) return;
                            var replaceNavNotification = function(pTo, to, from) {
                                if (to && from)
                                    to.textContent = from.textContent;
                                if (!to && from)
                                    pTo.appendChild(from.cloneNode(true));
                                if (to && !from)
                                    pTo.removeChild(to);
                            };
                            var i = pout.length, j, nnSelector = '.nav__notification';
                            while (i--) {
                                j = pin.length;
                                while (j--)
                                    if (pout[i].href === pin[j].href) {
                                        pout[i].parentNode.className = pin[j].parentNode.className;
                                        replaceNavNotification(pout[j],
                                                               pout[j].querySelector(nnSelector),
                                                               pin[j].querySelector(nnSelector));
                                    }
                            }
                        })(ifDoc.querySelectorAll(notifs), document.querySelectorAll(notifs));
                    })();
            }
            if (!onUpdateParent) {
                document.body.style.overflowY = 'auto';
                popin.removeAttribute('style');
                spinner.removeAttribute('style');
            }
            _self.childPopin = null;
            ifrm.parentNode.removeChild(ifrm);
            ifrm = null;
        };

        popin.onclick = _self.hide;

        // parent document state checker (in some cases it's only logical to refresh parent page when popin closed)
        var isParentOutdated = false;

        _self.isOutdated = function() {
            return isParentOutdated;
        };

        _self.setIsOutdated = function() {
            isParentOutdated = true;
        };
    }

    document.addEventListener ("DOMContentLoaded", function() {
        document.popin = new popinObj();

        document.body.addEventListener('click', function(e) {
            var t = e.target;

            // handle only LMB clicks
            if (e.which !== 1)
                return;

            if (markAsOutdatedClasses.some(ofClass, t) ||
                markAsOutdatedClasses.some(ofClass, t.parentNode))
                document.popin.setIsOutdated();

            // try to drill up to an A element if present
            while (t.parentNode && !t.href)
                t = t.parentNode;

            // if user clicked a link - try to handle it properly
            if (t.href) {
                // handle known links - specific categories on SteamGifts and links to packages on Steam
                if ((isSteamGifts.test(t.hostname) && isKnown.test(t.pathname) && !isSearch.test(t.pathname)) ||
                    (isSteamStore.test(t.hostname) && isSteamPackage.test(t.pathname))) {
                    // do not handle links to pages of the same category as the current one
                    if ((document.location.pathname !== '/giveaways' &&
                         (getFirstLevel(t.pathname) === getFirstLevel(document.location.pathname))) ||
                        (isAboutLegal.test(t.pathname) && isAboutLegal.test(document.location.pathname)) ||
                        (isDiscussion.test(t.pathname) && isDiscussion.test(document.location.pathname)))
                        return;
                    // Workaround for EasySteamGifts: do not handle join button if present
                    if (t.parentNode.classList.contains('esg-join'))
                        return;
                    // if we are still here, then
                    document.popin.show(t);
                    e.preventDefault();
                } else {
                    // handle unknown links when page is loaded in a frame
                    if (inIframe())
                        t.target = '_blank';
                }
            }
        });
    });
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。