MediaWiki:Timeless.js: Difference between revisions

From Inkipedia, the Splatoon wiki
m (More troubleshooting)
Tags: Blanking Manual revert Reverted
m (Undo revision 299802 by GuyPerfect (talk))
Tag: Undo
 
Line 1: Line 1:
// All JavaScript here will be loaded for users of the Timeless skin


/*
$(document).ready(function() {
// On desktop, display text logo once user scrolls down
function toggleTextLogo() {
if ($(window).width() > 1099) {
if ($(window).scrollTop() > 130) {
if ($('#p-logo-text').css('opacity') === '0') {
$('#p-logo-text').css('visibility', 'visible');
$('#p-logo-text').stop().animate({ opacity: 1 }, 250);
}
} else {
if ($('#p-logo-text').css('opacity') === '1') {
$('#p-logo-text').css('visibility', 'hidden');
$('#p-logo-text').stop().animate({ opacity: 0 }, 250);
}
}
} else {
if ($('#p-logo-text').css('opacity') === '0') {
$('#p-logo-text').css('visibility', 'visible');
$('#p-logo-text').stop().animate({ opacity: 1 }, 250);
}
}
}
toggleTextLogo();
$(window).scroll(toggleTextLogo);
$(window).resize(toggleTextLogo);
// Open Social links in new tab/window
$( '#n-Discord' ).find( 'a' ).attr( 'target' , '_blank' );
$( '#n-Twitter' ).find( 'a' ).attr( 'target' , '_blank' );
$( '#n-Instagram' ).find( 'a' ).attr( 'target' , '_blank' );
$( '#n-Facebook' ).find( 'a' ).attr( 'target' , '_blank' );
});
*/
// Ensure the document has finished loading
new Promise(function(resolve) {
    if (document.readyState == "complete")
        return resolve();
    window.addEventListener("load", resolve);
}).then(function() {
// Auto-increment key for dynamic element IDs
var nextId = 0;
// Initial focus order
document.getElementById("p-banner"  ).tabIndex = 1;
document.getElementById("searchInput").tabIndex = 2;
////////////////////////////// Utility functions //////////////////////////////
// Test whether an element could receive focus
var isVisible = function(element) {
    for (; element instanceof Element; element = element.parentNode) {
        var style = getComputedStyle(element);
        if (
            style.display == "none" ||
            style.visibility == "hidden" ||
            parseFloat(style.top) <= -1000
        ) return false;
    }
    return true;
};
// Retrieve all focusable children of an element
var getFocusableChildren = function(element) {
    return Array.from(element.querySelectorAll(
        "*:is(a[href],area,button,details,input," +
        "textarea,select,[tabindex='0']):not([disabled])"
    )).filter(function(e) { return isVisible(e); });
};
// Determine which responsive layout is in effect
var getLayoutSize = function() {
    return (
        window.innerWidth <=  850 ? "small"  :
        window.innerWidth <  1100 ? "medium" :
        "large"
    );
};
//////////////////////////////// Search button ////////////////////////////////
var searchInput    = document.getElementById("searchButton");
var searchButton  = document.createElement("button");
searchButton.id    = searchInput.id;
searchButton.title = searchInput.title;
searchButton.append(document.createElement("div"));
searchInput.after(searchButton);
searchInput.remove();
var badButton = document.getElementById("mw-searchButton");
if (badButton)
badButton.remove();
/////////////////////////////// Drop-down menus ///////////////////////////////
// Configure drop-down elements
var dropDowns = [
    { root: "#personal"      , menu: "#personal-inner",
        small: true , medium: true , large: true  },
    { root: "#site-navigation", menu: "#site-navigation .sidebar-inner",
        small: true , medium: true , large: false },
    { root: "#site-tools"    , menu: "#site-tools .sidebar-inner",
        small: true , medium: true , large: false },
    { root: "#page-tools"    , menu: "#page-tools .sidebar-inner",
        small: false, medium: true , large: false },
    { root: "#ca-more"        , menu: "#page-tools .sidebar-inner",
        small: true , medium: false, large: false },
    { root: "#ca-languages"  , menu: "#other-languages div",
        small: true , medium: false, large: false },
];
for (var x = 0; x < dropDowns.length; x++) {
    (function(props) {
       
        // Top-level control
        var root = document.querySelector(props.root);
        if (!root)
        return; // Languages may not be available
        // Interactive element
        var ctrl = root.querySelector("*:is(h2,span)");
        // Internal menu
        var menu = document.querySelector(props.menu);
        if (menu)
            menu.id = menu.id || ("id-timeless-" + nextId++);
        // Watch the viewport size
        window.addEventListener("resize", function() {
            var layout = getLayoutSize();
            // Control is a drop-down
            if (props[layout]) {
                ctrl.tabIndex = 0;
                ctrl.setAttribute("role"        , "button");
                if (menu) {
                    ctrl.setAttribute("aria-controls", menu.id);
                    ctrl.setAttribute("aria-haspopup", "menu");
                }
                ctrl.setAttribute("aria-expanded", Array.from(ctrl.classList)
                    .indexOf("dropdown-active") != -1);
            }
            // Control is not a drop-down
            else {
                ctrl.removeAttribute("tabindex");
                ctrl.removeAttribute("role");
                ctrl.removeAttribute("aria-controls");
                ctrl.removeAttribute("aria-expanded");
                ctrl.removeAttribute("aria-haspopup");
            }
            // Menu is not available
            if (!menu)
                return;
            // Menu is inside a drop-down
            if (
                props.menu.startsWith("#page-tools") && layout != "large" ||
                props[layout]
            ) menu.setAttribute("role", "menu");
            // Menu is not inside a drop-down
            else menu.removeAttribute("role");
        });
        // Watch the class attribute of the control
        new MutationObserver(function() {
            if (!props.large && getLayoutSize() == "large")
                return;
            ctrl.setAttribute("aria-expanded",
                Array.from(root.classList).indexOf("dropdown-active") != -1);
        }).observe(root, { attributeFilter: [ "class" ] });
        // Respond to pointer presses on the control
        ctrl.addEventListener("pointerdown", function() { ctrl.focus(); });
        // Respond to key presses on the control
        ctrl.addEventListener("keydown", function(e) {
            // Filter by key
            switch (e.key) {
                case " ": case "Enter": case "ArrowDown": case "ArrowUp":
                    break;
                default: return;
            }
            // Handle the event
            e.preventDefault();
            e.stopPropagation();
            // Open the menu
            if (ctrl.getAttribute("aria-expanded") != "true")
                ctrl.click();
            // The menu is not available
            if (!menu)
                return;
            // Focus the appropriate menu item
            var items = getFocusableChildren(menu);
            if (items.length == 0)
                return;
            items[e.key == "ArrowUp" ? items.length - 1 : 0].focus();
        });
        // Respond to key presses on menu items
        if (menu && props.root != "#ca-more")
        menu.addEventListener("keydown", function(e){
            if (!props.large && getLayoutSize() == "large")
                return;
            var items = getFocusableChildren(menu);
            var index = items.indexOf(document.activeElement);
            var focus;
            switch (index == -1 ? null : e.key) {
                case "ArrowDown":
                    focus = items[(index + 1) % items.length];
                    break;
                case "ArrowUp":
                    focus = items[(index + items.length - 1) % items.length];
                    break;
                case "End":
                    focus = items[items.length - 1];
                    break;
                case " ":
                case "Enter":
                    focus = document.activeElement;
                    focus.click();
                    break;
                case "Home":
                    focus = items[0];
                    break;
                case "Escape":
                case "Tab":
                    focus =
                        props.root == "#page-tools" &&
                        getLayoutSize() == "small" ?
                        document.querySelector("#ca-more span") : ctrl
                    ;
                    focus.click();
                    break;
                case null:
                    focus = items[0] || ctrl;
                    break;
                default: return;
            }
            // Handle the event
            e.preventDefault();
            e.stopPropagation();
            // Focus the appropriate element
            focus.focus();
        });
    })(dropDowns[x]);
    // Simulate a window resize
    window.dispatchEvent(new Event("resize"));
}
///////////////////////////////// Focus order /////////////////////////////////
// Specify the desired element order by unique selector
// If the specified element isn't focusable, takes all its focusable children
var ORDERS = {
    small: [
        "#site-navigation h2",
        "#p-banner",
        "#pt-notifications-alert a",
        "#pt-notifications-notice a",
        "#site-tools h2",
        "#personal h2",
        "#searchInput",
        "#searchButton",
        "#content > .mw-indicators",
        "#p-namespaces",
        "#p-views",
        "#p-more",
        "#bodyContent",
        "#content-bottom-stuff",
        "#footer-list",
        "#footer-copyrightico a",
        "#footer-poweredbyico a",
        "#footer-strategywikiico a"
    ],
    medium: [
        "#p-banner",
        "#searchInput",
        "#searchButton",
        "#pt-notifications-alert a",
        "#pt-notifications-notice a",
        "#personal h2",
        "#site-navigation h2",
        "#site-tools h2",
        "#page-tools h2",
        "#content > .mw-indicators",
        "#p-namespaces",
        "#p-views",
        "#bodyContent",
        "#content-bottom-stuff",
        "#footer-list",
        "#footer-strategywikiico a",
        "#footer-poweredbyico a",
        "#footer-copyrightico a"
    ],
    large: [
        "#p-banner",
        "#searchInput",
        "#searchButton",
        "#pt-notifications-alert a",
        "#pt-notifications-notice a",
        "#personal h2",
        "#p-logo a",
        "#site-navigation",
        "#site-tools",
        "#mw-related-navigation",
        "#content > .mw-indicators",
        "#p-namespaces",
        "#p-views",
        "#bodyContent",
        "#content-bottom-stuff",
        "#footer-list",
        "#footer-strategywikiico a",
        "#footer-poweredbyico a",
        "#footer-copyrightico a"
    ]
}
// Watch key presses on any element that doesn't trap one
document.body.addEventListener("keydown", function(e) {
    // Only monitoring Tab key
    if (e.key != "Tab")
        return;
    // Construct a list of all focusable elements
    var elements = ORDERS[getLayoutSize()].slice();
    for (var y = 0; y < elements.length; y++) {
        var selector = elements[y];
        var element  = elements[y] = document.querySelector(selector);
        // The element is not visible
        if (!element || !isVisible(element)) {
            elements.splice(y--, 1);
            continue;
        }
        // The element is focusable
        if (element.parentNode.querySelector(
            selector + ":is(a[href],area,button,details,input," +
            "textarea,select,[tabindex='0']):not([disabled])"
        )) continue;
        // The element is not focusable
        var children = getFocusableChildren(element);
        elements.splice(y, 1);
        for (var x = 0; x < children.length; x++)
            elements.splice(y++, 0, children[x]);
        y--;
    }
    // Let the user agent decide what to do
    var index = elements.indexOf(document.activeElement);
    if (index == -1)
        return;
    // Handle the event
    e.preventDefault();
    e.stopPropagation();
    elements[
        (index + (e.shiftKey ? elements.length - 1 : 1)) % elements.length
    ].focus();
});
});

Latest revision as of 17:43, 23 September 2022

// All JavaScript here will be loaded for users of the Timeless skin

/*
$(document).ready(function() {
	// On desktop, display text logo once user scrolls down
	function toggleTextLogo() {
		if ($(window).width() > 1099) {
			if ($(window).scrollTop() > 130) {
				if ($('#p-logo-text').css('opacity') === '0') {
					$('#p-logo-text').css('visibility', 'visible');
					$('#p-logo-text').stop().animate({ opacity: 1 }, 250);
				}
			} else {
				if ($('#p-logo-text').css('opacity') === '1') {
					$('#p-logo-text').css('visibility', 'hidden');
					$('#p-logo-text').stop().animate({ opacity: 0 }, 250);
				}
			}
		} else {
			if ($('#p-logo-text').css('opacity') === '0') {
				$('#p-logo-text').css('visibility', 'visible');
				$('#p-logo-text').stop().animate({ opacity: 1 }, 250);
			}
		}
	}

	toggleTextLogo();
	$(window).scroll(toggleTextLogo);
	$(window).resize(toggleTextLogo);
	
	// Open Social links in new tab/window
	$( '#n-Discord' ).find( 'a' ).attr( 'target' , '_blank' );
	$( '#n-Twitter' ).find( 'a' ).attr( 'target' , '_blank' );
	$( '#n-Instagram' ).find( 'a' ).attr( 'target' , '_blank' );
	$( '#n-Facebook' ).find( 'a' ).attr( 'target' , '_blank' );
});
*/



// Ensure the document has finished loading
new Promise(function(resolve) {
    if (document.readyState == "complete")
        return resolve();
    window.addEventListener("load", resolve);
}).then(function() {

// Auto-increment key for dynamic element IDs
var nextId = 0;

// Initial focus order
document.getElementById("p-banner"   ).tabIndex = 1;
document.getElementById("searchInput").tabIndex = 2;



////////////////////////////// Utility functions //////////////////////////////

// Test whether an element could receive focus
var isVisible = function(element) {
    for (; element instanceof Element; element = element.parentNode) {
        var style = getComputedStyle(element);
        if (
            style.display == "none" ||
            style.visibility == "hidden" ||
            parseFloat(style.top) <= -1000
        ) return false;
    }
    return true;
};

// Retrieve all focusable children of an element
var getFocusableChildren = function(element) {
    return Array.from(element.querySelectorAll(
        "*:is(a[href],area,button,details,input," +
        "textarea,select,[tabindex='0']):not([disabled])"
    )).filter(function(e) { return isVisible(e); });
};

// Determine which responsive layout is in effect
var getLayoutSize = function() {
    return (
        window.innerWidth <=  850 ? "small"  :
        window.innerWidth <  1100 ? "medium" :
        "large"
    );
};



//////////////////////////////// Search button ////////////////////////////////

var searchInput    = document.getElementById("searchButton");
var searchButton   = document.createElement("button");
searchButton.id    = searchInput.id;
searchButton.title = searchInput.title;
searchButton.append(document.createElement("div"));
searchInput.after(searchButton);
searchInput.remove();

var badButton = document.getElementById("mw-searchButton");
if (badButton)
	badButton.remove();



/////////////////////////////// Drop-down menus ///////////////////////////////

// Configure drop-down elements
var dropDowns = [
    { root: "#personal"       , menu: "#personal-inner",
        small: true , medium: true , large: true  },
    { root: "#site-navigation", menu: "#site-navigation .sidebar-inner",
        small: true , medium: true , large: false },
    { root: "#site-tools"     , menu: "#site-tools .sidebar-inner",
        small: true , medium: true , large: false },
    { root: "#page-tools"     , menu: "#page-tools .sidebar-inner",
        small: false, medium: true , large: false },
    { root: "#ca-more"        , menu: "#page-tools .sidebar-inner",
        small: true , medium: false, large: false },
    { root: "#ca-languages"   , menu: "#other-languages div",
        small: true , medium: false, large: false },
];
for (var x = 0; x < dropDowns.length; x++) {
    (function(props) {
        
        // Top-level control
        var root = document.querySelector(props.root);
        if (!root)
        	return; // Languages may not be available

        // Interactive element
        var ctrl = root.querySelector("*:is(h2,span)");

        // Internal menu
        var menu = document.querySelector(props.menu);
        if (menu)
            menu.id = menu.id || ("id-timeless-" + nextId++);

        // Watch the viewport size
        window.addEventListener("resize", function() {
            var layout = getLayoutSize();

            // Control is a drop-down
            if (props[layout]) {
                ctrl.tabIndex = 0;
                ctrl.setAttribute("role"         , "button");
                if (menu) {
                    ctrl.setAttribute("aria-controls", menu.id);
                    ctrl.setAttribute("aria-haspopup", "menu");
                }
                ctrl.setAttribute("aria-expanded", Array.from(ctrl.classList)
                    .indexOf("dropdown-active") != -1);
            }

            // Control is not a drop-down
            else {
                ctrl.removeAttribute("tabindex");
                ctrl.removeAttribute("role");
                ctrl.removeAttribute("aria-controls");
                ctrl.removeAttribute("aria-expanded");
                ctrl.removeAttribute("aria-haspopup");
            }

            // Menu is not available
            if (!menu)
                return;

            // Menu is inside a drop-down
            if (
                props.menu.startsWith("#page-tools") && layout != "large" ||
                props[layout]
            ) menu.setAttribute("role", "menu");

            // Menu is not inside a drop-down
            else menu.removeAttribute("role");
        });

        // Watch the class attribute of the control
        new MutationObserver(function() {
            if (!props.large && getLayoutSize() == "large")
                return;
            ctrl.setAttribute("aria-expanded",
                Array.from(root.classList).indexOf("dropdown-active") != -1);
        }).observe(root, { attributeFilter: [ "class" ] });

        // Respond to pointer presses on the control
        ctrl.addEventListener("pointerdown", function() { ctrl.focus(); });

        // Respond to key presses on the control
        ctrl.addEventListener("keydown", function(e) {

            // Filter by key
            switch (e.key) {
                case " ": case "Enter": case "ArrowDown": case "ArrowUp":
                    break;
                default: return;
            }

            // Handle the event
            e.preventDefault();
            e.stopPropagation();

            // Open the menu
            if (ctrl.getAttribute("aria-expanded") != "true")
                ctrl.click();

            // The menu is not available
            if (!menu)
                return;

            // Focus the appropriate menu item
            var items = getFocusableChildren(menu);
            if (items.length == 0)
                return;
            items[e.key == "ArrowUp" ? items.length - 1 : 0].focus();
        });

        // Respond to key presses on menu items
        if (menu && props.root != "#ca-more")
        menu.addEventListener("keydown", function(e){
            if (!props.large && getLayoutSize() == "large")
                return;

            var items = getFocusableChildren(menu);
            var index = items.indexOf(document.activeElement);
            var focus;

            switch (index == -1 ? null : e.key) {
                case "ArrowDown":
                    focus = items[(index + 1) % items.length];
                    break;
                case "ArrowUp":
                    focus = items[(index + items.length - 1) % items.length];
                    break;
                case "End":
                    focus = items[items.length - 1];
                    break;
                case " ":
                case "Enter":
                    focus = document.activeElement;
                    focus.click();
                    break;
                case "Home":
                    focus = items[0];
                    break;
                case "Escape":
                case "Tab":
                    focus =
                        props.root == "#page-tools" &&
                        getLayoutSize() == "small" ?
                        document.querySelector("#ca-more span") : ctrl
                    ;
                    focus.click();
                    break;
                case null:
                    focus = items[0] || ctrl;
                    break;
                default: return;
            }

            // Handle the event
            e.preventDefault();
            e.stopPropagation();

            // Focus the appropriate element
            focus.focus();
        });

    })(dropDowns[x]);

    // Simulate a window resize
    window.dispatchEvent(new Event("resize"));
}



///////////////////////////////// Focus order /////////////////////////////////

// Specify the desired element order by unique selector
// If the specified element isn't focusable, takes all its focusable children
var ORDERS = {
    small: [
        "#site-navigation h2",
        "#p-banner",
        "#pt-notifications-alert a",
        "#pt-notifications-notice a",
        "#site-tools h2",
        "#personal h2",
        "#searchInput",
        "#searchButton",
        "#content > .mw-indicators",
        "#p-namespaces",
        "#p-views",
        "#p-more",
        "#bodyContent",
        "#content-bottom-stuff",
        "#footer-list",
        "#footer-copyrightico a",
        "#footer-poweredbyico a",
        "#footer-strategywikiico a"
    ],
    medium: [
        "#p-banner",
        "#searchInput",
        "#searchButton",
        "#pt-notifications-alert a",
        "#pt-notifications-notice a",
        "#personal h2",
        "#site-navigation h2",
        "#site-tools h2",
        "#page-tools h2",
        "#content > .mw-indicators",
        "#p-namespaces",
        "#p-views",
        "#bodyContent",
        "#content-bottom-stuff",
        "#footer-list",
        "#footer-strategywikiico a",
        "#footer-poweredbyico a",
        "#footer-copyrightico a"
    ],
    large: [
        "#p-banner",
        "#searchInput",
        "#searchButton",
        "#pt-notifications-alert a",
        "#pt-notifications-notice a",
        "#personal h2",
        "#p-logo a",
        "#site-navigation",
        "#site-tools",
        "#mw-related-navigation",
        "#content > .mw-indicators",
        "#p-namespaces",
        "#p-views",
        "#bodyContent",
        "#content-bottom-stuff",
        "#footer-list",
        "#footer-strategywikiico a",
        "#footer-poweredbyico a",
        "#footer-copyrightico a"
    ]
}

// Watch key presses on any element that doesn't trap one
document.body.addEventListener("keydown", function(e) {

    // Only monitoring Tab key
    if (e.key != "Tab")
        return;

    // Construct a list of all focusable elements
    var elements = ORDERS[getLayoutSize()].slice();
    for (var y = 0; y < elements.length; y++) {
        var selector = elements[y];
        var element  = elements[y] = document.querySelector(selector);

        // The element is not visible
        if (!element || !isVisible(element)) {
            elements.splice(y--, 1);
            continue;
        }

        // The element is focusable
        if (element.parentNode.querySelector(
            selector + ":is(a[href],area,button,details,input," +
            "textarea,select,[tabindex='0']):not([disabled])"
        )) continue;

        // The element is not focusable
        var children = getFocusableChildren(element);
        elements.splice(y, 1);
        for (var x = 0; x < children.length; x++)
            elements.splice(y++, 0, children[x]);
        y--;
    }

    // Let the user agent decide what to do
    var index = elements.indexOf(document.activeElement);
    if (index == -1)
        return;

    // Handle the event
    e.preventDefault();
    e.stopPropagation();
    elements[
        (index + (e.shiftKey ? elements.length - 1 : 1)) % elements.length
    ].focus();
});


});