Better GamePress FGO

Better servant pages.

As of 2019-06-07. See the latest version.

// ==UserScript==
// @name         Better GamePress FGO
// @version      0.6
// @description  Better servant pages.
// @author       Rukako
// @namespace    rukako
// @match        https://grandorder.gamepress.gg/*
// @grant        none
// @require      https://cdnjs.cloudflare.com/ajax/libs/crel/4.0.1/crel.min.js
// ==/UserScript==

(function() {
    'use strict';
    const $ = document.querySelector.bind(document);
    const _$$ = document.querySelectorAll.bind(document);
    const $$ = x => Array.from(_$$(x));
    const log = console.log.bind(console);
    const assert = console.assert.bind(console);

    // Common functions.

    // correctly set style attributes one at a time.
    crel.attrMap.style = (el, style) => {
        if (typeof style === 'string') {
            el.setAttribute('style', style);
        } else {
            Object.entries(style).forEach(([k,v]) => { el.style[k] = v; });
        }
    }

    // Run specific functions depending on URL.
    if (/^\/servant\//.test(window.location.pathname)) {
        removeDiscussion();
        servantPageImprovements();
        betterSkillUpgradeDisplay();
    }
    if ($('#gamepress-top-content')) {
        backToTop();
    }
    removeFloatingHeader();

    // Specific feature implementations.

    function removeFloatingHeader() {
        $('.gamepress-top-menu .menu-container').style = 'position: inherit';
    }

    function removeDiscussion() {
        const disc = $('.view-discourse-block');
        assert(disc, 'discussion node not present');
        disc.parentNode.removeChild(disc);
    }

    function servantPageImprovements() {
        const toClassName = c => 'section-'+c.toLowerCase().replace(/[^A-Za-z0-9]/g, '-');

        // TABLE OF CONTENTS LINKS
        ['status', 'analysis', 'profile'].map(s => $('#'+s)).forEach(sec => {
            const tabs = sec.querySelector('.servant-tabs');
            assert(tabs, '.servant-tabs not found in', sec);
            tabs.innerHTML += '<br><br><hr style="display:block;width: 100%;">';

            Array.from(sec.querySelectorAll('.main-title'))
                .map(title => [title, title.nextElementSibling])
                .filter(([a,b]) => b && !b.classList.contains('servant-tabs'))
                .forEach(([title, div]) => {
                const text = title.textContent;
                const id = title.id || toClassName(text);
                title.id = id;

                //tabs.innerHTML += `<li><a href="#${id}"><!--<i class="fa fa-user" aria-hidden="true"></i> -->${text}</a></li>`;

                const li = crel('li', crel('a', {
                    href: '#'+id,
                    style: {
                        padding: '5px 6px',
                        overflow: 'hidden',
                        whiteSpace: 'nowrap',
                        textOverflow: 'ellipsis'
                    }
                }, text));
                tabs.appendChild(li);
            });
        });

        // Skill analysis button at skills.
        const levelUpSkillHref = toClassName('Level Up Skill Recommendation');
        const skillMatsHref = toClassName('Skill Enhancement Materials');
        const recHTML = `<ul class="servant-tabs" style="margin-bottom: 0 !important;">
            <li><a href="#${levelUpSkillHref}" style="padding: 0;"><i class="fa fa-pie-chart" aria-hidden="true"></i> Skill Analysis</a></li>
            <li><a href="#${skillMatsHref}" style="padding: 0;"><i class="fa fa-diamond" aria-hidden="true"></i> Materials</a></li>
        </ul>`;
        $('#skills').insertAdjacentHTML('afterbegin', recHTML);

        // SKILL LINKS AND RECOMMENDATION ICONS
        const skillIcons = [null, null, null];
        $$('.view-level-up-skill-recommendation tbody > tr').forEach((tr, i) => {
            const a = tr.querySelector('a[href^="/servant-skill/"]');
            if (!a) return; // No link found. Empty skill row.
            a.href = '#servant-skill-'+(i+1);

            const iconsEl = tr.querySelector('.views-field-description__value');
            assert(iconsEl, 'skill icons cell not found', tr);
            const icons = Array.from(iconsEl.children);
            icons.forEach(icon => {icon.style.color = window.getComputedStyle(icon).color; });
            assert(i < 3, 'too many skills in analysis', i);
            skillIcons[i] = icons.map(x => x.cloneNode(true));
        });

        $$('#skills > div > .field__item').forEach((el, i) => {
            el.id = 'servant-skill-'+(i+1);
            // TIER ICONS IN SKILL LIST
            if (!skillIcons[i]) return; // no skill icons for this skill.
            const title = el.querySelector('a[href^="/servant-skill/"]');
            const iconsSpan = crel('a',
                {href: '#'+levelUpSkillHref,
                 title: 'Level up priority',
                 style: {marginLeft: '6px', fontSize: '16px'}},
                skillIcons[i]);
            title.insertAdjacentElement('afterend', iconsSpan);
        });

        // TIER LINK
        let rarity = $('.taxonomy-term.vocabulary-stars').getAttribute('about').replace('/', '');
        rarity = parseInt(rarity);
        if (rarity <= 3) {
            rarity = '1-3';
        }
        const tierListLink = crel('a', {href: `/${rarity}-star-tier-list`});

        const ratingBar = $('#overall-servant-rating');
        const ref = ratingBar.nextElementSibling;
        tierListLink.appendChild(ratingBar);
        ref.parentNode.insertBefore(tierListLink, ref);

        // Up buttons in section header to go back to TOC.
        let counterTOC = 0;
        let currentTOC = null;
        $$('.servant-new-layout h2.main-title').forEach((el, i) => {
            if (el.textContent.trim().toLowerCase() === 'table of contents') {
                el.id = el.id || ('table-of-contents-'+counterTOC);
                currentTOC = '#'+el.id;
                counterTOC++;
                return;
            }
            if (el.id == 'overall-servant-rating') return;
            if (!currentTOC) return;
            const text = el.textContent;
            const textSpan = crel('span', text);
            const upLink = crel('a', {href: currentTOC});
            upLink.innerHTML = '<i class="fa fa-chevron-up" style="color: white; font-size: 130%;"></i>';

            el.style.display = 'flex';
            el.style.justifyContent = 'space-between';
            el.style.alignItems = 'center';

            el.textContent = '';
            el.appendChild(textSpan);
            el.appendChild(upLink);
        });

        // Fix tier number alignment
        const tierDiv = $('#overall-servant-rating .overall-servant-rating-value');
        if (tierDiv) {
            tierDiv.style.lineHeight = tierDiv.clientHeight + 'px';
        }
    }

    function backToTop() {
        // BACK TO TOP
        const toTopStyles = `
#to-top {
position: fixed;
bottom: 10px;
float: right;
right: 30px;
z-index: 2;
}

#to-top a:not(:hover) {
background-color: #f8f8f8;
}

#to-top a {
width: 45px;
height: 45px;
font-size: 20pt;
padding: 0;
}
`;
        document.head.appendChild(crel('style', toTopStyles));

        $('.page').insertAdjacentHTML('beforeend', '<div id="to-top"><a class="featured-button" href="#"><i class="fa fa-chevron-up"></i></a></div>');
    }

    function betterSkillUpgradeDisplay() {
        const skillStyles = `
.servant-new-layout .servant-skill-upgrade {
margin-top: 1px;
}

.servant-new-layout .field--name-field-servant-skills>.field__item {
border: #d1d8f0 solid 3px;
/*border-left: #585858 solid 5px;*/
}

.servant-new-layout .servant-skill-single {
/*border-left-style: none;*/
}
`;
        document.head.appendChild(crel('style', skillStyles));
    }
})();