TOC TouchButton

Add Touchfriendly TOC and next and previous link variant

// ==UserScript==
// https://greasyfork.org/scripts/380720-toc-touchbutton
// @name TOC TouchButton
// @namespace Touchfriendly TOC Variant
// @match https://*.wordpress.com/*
// @version     0.8
// @description Add Touchfriendly TOC and next and previous link variant
// @grant none
// ==/UserScript==

//Known Bug: look for a way to detect readerview/android page reload
//      onreload with active reader view(firefox) -> loads original version with longText link


const SELECTORTOC = 'p > a';
const MEDIAQUERYMAXWIDTH = '480px';

const DEFAULTSHORTTOC = "ToC";
const DEFAULTSHORTPREV = "-1←";
const DEFAULTSHORTNEXT = "→+1";
/**
 * Setting to Pair ["Longtext", "ShortText"]
 * ShortText will be shown as button
 * @param {String} LongText longer String which should be replaced
 * @param {Array} ShortText replacement for longText
 */
const LINKVARIANTS = [
    {
        "shortVersion": DEFAULTSHORTTOC,
        "longVariants": ["Table of Contents", "Index", "ToC"]
    },
    {
        "shortVersion": DEFAULTSHORTPREV,
        "longVariants": ["Previous Chapter", "Prev", "<- Previous Chapter"]
    },
    {
        "shortVersion": DEFAULTSHORTNEXT,
        "longVariants": ["Next Chapter", "Next", "Next Chapter -&gt;"]
    }];
//"<- Previous Chapter" or "&lt;- Previous Chapter" possible
//"Next Chapter ->"    same as before

// ↥ front UI ↥ for adjustment for different sites copy script and adjust SELECTORTOC and LINKVARIANTS and metas(@match, @namespace)
// ↧ script ↧
var nodesArray = [];

function readCssStyle(element, attribute) {
    let style = window.getComputedStyle ? getComputedStyle(element, null) : element.currentStyle;
    return style[attribute]
}

/**
 * toggle Hidden attributes depending on css value (set by mediaquery)
 * 
 * without active javascript attribute toggling ->  the longT Version would get read by Firefox Reader View and maybe other readability plugins
 */
function checkHidden() {
    let shortT = document.querySelectorAll('.shortTOC');
    let longT = document.querySelectorAll('.longTOC');

    //console.log("checkHidden","checkHidden shortT length: "+shortT.length +" # "+ readStyle(shortT[0],"display") );
    //http://john.foliot.ca/aria-hidden/
    for (var i = 0; i < shortT.length; i++) {
        //console.log("checkHidden","checkHidden shortT setAttribute aria-hidden + " + i);
        if (readCssStyle(shortT[i], "display") === "none") {
            //console.log("checkHidden","checkHidden shortT setAttribute aria-hidden display none");
            shortT[i].setAttribute('aria-hidden', 'true');
            shortT[i].setAttribute('hidden', "true");
            shortT[i].setAttribute('role', 'presentation');
        }
        else if (readCssStyle(shortT[i], "display") == "inline-block") {
            shortT[i].setAttribute('aria-hidden', 'false')
            shortT[i].removeAttribute('role');
            shortT[i].removeAttribute('hidden');
        }

    }
    for (let i = 0; i < longT.length; i++) {
        if (readCssStyle(longT[i], "display") == "none") {
            longT[i].setAttribute('aria-hidden', 'true');
            longT[i].setAttribute('hidden', "true");
            longT[i].setAttribute('role', 'presentation');
        }
        else if (readCssStyle(longT[i], "display") == "inline-block") {
            longT[i].setAttribute('aria-hidden', 'false')
            longT[i].removeAttribute('role');
            longT[i].removeAttribute('hidden');
        }
    }
}


//https://stackoverflow.com/questions/23683439/gm-addstyle-equivalent-in-tampermonkey
function addGlobalStyle(css) {
    let head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

//https://stackoverflow.com/questions/1686571/greasemonkey-how-to-apply-a-css-rule-only-for-media-print
//https://stackoverflow.com/questions/8624210/getting-jquery-and-gm-addstyle-to-work-in-a-chrome-userscript-based-off-of-a-wor
//multiline for better readability add at end of line -> \
//https://stackoverflow.com/questions/23608346/how-to-style-a-div-like-the-button-element
addGlobalStyle('\
.shortTOC{  display:none;visiblity:hidden;}\
.longTOC{ display:inline-block;visiblity:visible;} \
@media (max-width: '+ MEDIAQUERYMAXWIDTH + ') { \
    .shortTOC { display: inline-block;visiblity:visible;\
        width: 23%;\
        text-align: center;\
        line-height:3.5em;\
        font-size:6vw;\
        -ms-touch-action: manipulation;\
        touch-action: manipulation;\
        cursor: pointer;\
        -webkit-user-select: none;\
        -moz-user-select: none;\
        -ms-user-select: none;\
        user-select: none;\
        background-image: none;\
        border: 1px solid transparent;\
        border-radius: 4px;\
        border-color: #ccc;\
    }\
     .longTOC { display:none;visiblity:hidden;}\
 }\
 ');

/**
 * results has only Nodes which match filterString
 * @param {Array} Nodes 
 * @param {String} filterString 
 */
function getNodes(Nodes, filterString) {
    //filter both HTML entity character and pure text
    //innerHTML:characters as is (example %nbsp; kept as space) ; textContent: example &nbsp; converted to space character
    let TOCFilter = Nodes.filter(e => (e.innerHTML == filterString || e.textContent == filterString));
    /*
    let TOCNodes = TOCFilter.map((e, i) => {
        //console.log("elementcontent: " +e + " at index: "+i +" nodeValueOuterhtml: " + e.outerHTML + " nodeValueinnerhtml: " + e.innerHTML );        
        return e
    });
    */
    return TOCFilter
}
/**
 * add custom classes to link text
 * @param {array} Nodes linkarray
 * @param {string} shortText 
 */
function setClasses(Nodes, shortText) {
    Nodes.forEach(element => {
        let TOCButton = document.createElement("a")
        TOCButton.href = element;
        TOCButton.setAttribute("class", "shortTOC")
        TOCButton.textContent = shortText
        element.setAttribute("class", "longTOC")
        element.parentNode.insertBefore(TOCButton, element.nextSibling);
    });
}
/**
 * Compare long link text and replace with shortText version
 * @param {Array} nodes link array 
 * @param {String} longText longer String which should be replaced and is used as filter for the linkarray
 * @param {String} shortText replacement for longText
 */
function addTouchfriendlyVariant(nodes, longText, shortText) {
    let TOCNodes = getNodes(nodes, longText)
    setClasses(TOCNodes, shortText);
    //console.log('Toc element: ' + TOCNodes);
}


function main() {
    let getSelectedLinks = function () {
        let links = Array.from(
            document.querySelectorAll(SELECTORTOC)
        );

        return links;
    };
    const links = getSelectedLinks();

    /*
    addTouchfriendlyVariant(links, "Table of Contents", "ToC");
    addTouchfriendlyVariant(links, "Previous Chapter", "-1←");
    addTouchfriendlyVariant(links, "Next Chapter", '→+1');
    */

    LINKVARIANTS.forEach(key => {
        key.longVariants.forEach(variant => {
            //console.log("LINKVARIANTS: " + key.shortVersion + " # " + variant);
            addTouchfriendlyVariant(links, variant, key.shortVersion);
        });
    });


}
main()

//https://www.sitepoint.com/jquery-document-ready-plain-javascript/
var callback = function () {
    // Handler when the DOM is fully loaded
    checkHidden();
};

//https://stackoverflow.com/questions/2720658/how-to-detect-when-a-tab-is-focused-or-not-in-chrome-with-javascript
var focusCheckCallback = function () {
    //console.log("onvisibilityState reaction: " + document.visibilityState);
    if (document.visibilityState == "visible") //hidden -> readerview rebuild?
        checkHidden();
}

if (
    document.readyState === "complete" ||
    (document.readyState !== "loading" && !document.documentElement.doScroll)
) {
    callback();
} else {
    document.addEventListener("DOMContentLoaded", callback);
}

window.addEventListener("resize", function () {
    checkHidden();
});
window.addEventListener("visibilitychange", focusCheckCallback);
//window.addEventListener("blur",focusCheckCallback);