A11y

Test for assessibility problems

目前為 2018-06-08 提交的版本,檢視 最新版本

// ==UserScript==
// @name         A11y
// @namespace    assessibility.colivre.org
// @version      0.1
// @description  Test for assessibility problems
// @author       Aurélio A. Heckert
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Test if element has acessibility problems
    // If there is a problem, call `notify(el, <ERROR|WARN>, '<description string>')`
    function testA11yEl(el) {

        if (el.tagName == 'IMG' && empty(el.alt) && empty(el.title))
            notify(el, ERROR, 'Image without alt text.', 'https://www.w3.org/WAI/tutorials/images/');

        if (el.tagName.match(/^H[0-9]$/) && empty(el.innerText))
            notify(el, ERROR, `Heading ${el.tagName} without text.`);

        if (el.tagName.match(/^H[0-9]$/) &&
            el.nextSibling.tagName &&    // The next tag are united to this.
            el.nextSibling.tagName.match(/^H[0-9]$/))
            notify(el, WARN, `Skipping heading ${el.tagName}.`, 'https://www.w3.org/WAI/tutorials/page-structure/headings#heading-ranks');

        if (el.tagName.match(/^H[0-9]$/) &&
            el.nextSibling.constructor == Text && el.nextSibling.textContent.match(/^\s*$/) && // has no text between, only space.
            el.nextElementSibling.tagName.match(/^H[0-9]$/))
            notify(el, WARN, `Skipping heading ${el.tagName}.`, 'https://www.w3.org/WAI/tutorials/page-structure/headings#heading-ranks');

    }

    var elements = [];
    var a11yBox = null, a11yList = null;
    const ERROR = 'ERROR';
    const WARN  = 'WARN';
    const BOXID = 'a11y-userscript-box';

    function empty(val) {
        if (typeof(val) == 'string') val = val.replace(/\s/g, '');
        return typeof(val) == 'undefined' || val == null || val.length == 0
    }

    function mk(tag, attrs) {
        tag = document.createElement(tag);
        for (var attName in attrs) {
            var attVal = attrs[attName];
            if (attName == 'children') attVal.forEach(([t,a])=> {
                mk(t, Object.assign(a, {parent: tag}));
            });
            else if (attName == 'parent') attVal.appendChild(tag);
            else if (attName == 'text') tag.innerText = attVal;
            else if (attName == 'html') tag.innerHTML = attVal;
            else if (attName.match(/^on/)) tag[attName] = attVal;
            else tag.setAttribute(attName, attVal);
        }
        return tag;
    }

    function buildA11yBox() {
        a11yBox = mk('div', {
            parent: document.body,
            id: BOXID,
            class: BOXID+'-toggle-min',
            children: [
                ['i',{ id: BOXID+'-toggle-min-bt', onclick: toggleMin }]
            ]
        });
        a11yList = mk('ul', {parent: a11yBox});
        mk('style', {
            parent: document.documentElement.firstElementChild,
            html: `
                #${BOXID} {position: fixed; bottom: 0; right: 0; background: rgba(255,255,255,0.7); z-index: 99999; opacity: 1;
                    box-shadow: 0 0 30px rgba(0,0,0,0.6); padding: 15px; border-radius: 15px 0 0 0; color: #000}
                #${BOXID}-toggle-min-bt::before {content: "\\00d7"; position: absolute; top: 2px; left: 2px;
                    display: block; text-align: center; border: 1px solid #CCC; border-radius: 30px;
                    font-size: 24px; line-height: 24px; width: 24px; background: #EEE; cursor: pointer}
                #${BOXID} ul {margin: 0; padding: 0; border: 1px solid rgba(0,0,0,0.2);
                    border-top:none; max-height: 90vh; overflow: auto}
                #${BOXID} li {margin: 0; padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1); list-style: none; cursor: pointer}
                .${BOXID}-toggle-min {opacity: 40%}
                .${BOXID}-toggle-min ul {display: none}
                .${BOXID}-ERROR {background: rgba(255,200,200,0.8)}
                .${BOXID}-WARN  {background: rgba(255,250,200,0.8)}
                #${BOXID} li span { font-size: 70%; padding-right: 1em }
                #${BOXID} li p { display: inline; margin: 0 }
                #${BOXID} li a { display: inline-block; margin: 0 0 0 3px; padding: 1px 2px; text-decoration: none;
                    border: 1px solid rgba(0,0,0,0.2); border-radius: 3px; color: #000 }
            `
        });
    }

    function toggleMin() {
        a11yBox.className = (a11yBox.className.length > 0)? '' : BOXID+'-toggle-min';
    }

    function notify(el, type, message, helpURL) {
        if (!a11yBox) buildA11yBox();
        if (elements.filter((reg)=> reg.el==el && reg.message==message).length > 0) return;
        elements.push({ el, message });
        console.log(elements);
        var li = mk('li', {
            parent: a11yList,
            class: BOXID+'-'+type,
            onclick: ()=> {
                console.log(message, el);
                el.style.outline = '2px dashed red';
            },
            children: [
                ['span', {text: type}],
                ['p', {text: message}],
            ]
        });
        if (helpURL) mk('a', {
            href: helpURL,
            target: '_blank',
            text: 'help',
            parent: li
        });
    }

    function walk(el) {
        if (el.id == BOXID) return;
        testA11yEl(el);
        for (var e,i=0; e=el.children[i]; i++) ((e)=> setTimeout(()=> walk(e), 1))(e);
    }

    function testPage() {
        console.log('testing page...');
        walk(document.body);
    }

    testPage();
    setTimeout(testPage, 5000);
})();