ArtOfCode-/qpixel

View on GitHub
app/assets/javascripts/keyboard_tools.js

Summary

Maintainability
F
3 days
Test Coverage
$(() => {
    const userLink = $('.header--item.is-complex.is-visible-on-mobile[href^="/users/"]').attr('href');
    const keyboardToolsAreEnabled = !!window.localStorage.getItem("keyboard__enable");

    $(".js-keyboard_tools-status").text(keyboardToolsAreEnabled ? "activated" : "inactive");
    $(".js-keyboard_tools-toggle").click(() => {
        if (window.localStorage.getItem("keyboard__enable")) {
            window.localStorage.removeItem("keyboard__enable");
        }
        else {
            window.localStorage.setItem("keyboard__enable", true);
        }
        window.location.reload();
    })

    if (!keyboardToolsAreEnabled) return;

    window._CodidactKeyboard = {
        state: 'home',
        selectedItem: null,
        user_id: !!userLink ? parseInt(userLink.split("/").pop(), 10) : null,
        is_mod: !!$('.header--item[href="/mod/flags"]').length,
        categories: function () {
            const category_elements = $("a.category-header--tab");
            const return_obj = {};
            category_elements.each(function () {
                return_obj[this.innerText] = this.getAttribute('href');
            });
            return return_obj;
        },
        dialog: function (msg) {
            _CodidactKeyboard.dialogClose();
            const d = document.createElement("div")
            d.classList.add("__keyboard_help");
            d.innerText = msg;
            document.body.appendChild(d);
        },
        dialogClose: function () {
            $(".__keyboard_help").remove();
            _CodidactKeyboard.state = 'home';
        },
        updateSelected: function () {
            $(".__keyboard_selected").removeClass('__keyboard_selected');
            if (_CodidactKeyboard.selectedItem) {
                _CodidactKeyboard.selectedItem.classList.add('__keyboard_selected');
                _CodidactKeyboard.selectedItem.scrollIntoView({ behavior: 'smooth' });
                _CodidactKeyboard.selectedItem.focus();

                _CodidactKeyboard.selectedItemData = {
                    type: _CodidactKeyboard.selectedItem.getAttribute("data-ckb-item-type"),
                    post_id: _CodidactKeyboard.selectedItem.getAttribute("data-ckb-post-id")
                };
            }
        }
    }

    // Use html, so that all prior attempts to access keyup event have priority
    $("html").on("keyup", function (e) {
        if (e.target !== document.body) return;
        if (e.key === "Escape") {
            _CodidactKeyboard.dialogClose();
        }
        else if (_CodidactKeyboard.state === 'home') {
            homeMenu(e);
        }
        else if (_CodidactKeyboard.state === 'goto') {
            gotoMenu(e);
        }
        else if (_CodidactKeyboard.state === 'goto/category') {
            categoryMenu(e);
        }
        else if (_CodidactKeyboard.state === 'goto/category-tags') {
            categoryTagsMenu(e);
        }
        else if (_CodidactKeyboard.state === 'tools') {
            toolsMenu(e);
        }
        else if (_CodidactKeyboard.state === 'tools/vote') {
            voteMenu(e);
        }
    });

    function homeMenu(e) {
        if (e.key === "?") {
            _CodidactKeyboard.dialog('Codidact Keyboard Shortcuts\n' +
                '===========================\n' +
                '?  Open this help\n' +
                'n  New post\n' +
                '   (in current category)\n' +
                's  Search for something\n' +
                'g  Go to a page...\n\n' +
                'a  Go to answer field\n\n' +
                'Selection shortcuts:\n\n' +
                'j  Move one item down\n' +
                'k  Move one item up\n' +
                't  Use a tool (on selection)\n\n' +
                '(Selection shortcuts will select\n' +
                'first post, if none selected)'

            );
        }
        else if (e.key === 'n') {
            const new_post_link = $('a.category-header--nav-item.is-button').attr('href');
            if (new_post_link) {
                window.location.href = new_post_link;
            }
        }
        else if (e.key === 'g') {
            _CodidactKeyboard.dialog('Go to ...\n' +
                '=========\n' +
                'm  Main page\n' +
                'u  User list\n' +
                'h  Help\n' +
                'p  Your profile page\n' +
                'c  Category ...\n' +
                't  Tags of category ...' +
                (_CodidactKeyboard.is_mod ? '\nf  Flags (mod only)' : '')
            );
            _CodidactKeyboard.state = 'goto';
        }
        else if (e.key === 'k') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            else {
                _CodidactKeyboard.selectedItem = $(_CodidactKeyboard.selectedItem).nextAll('[data-ckb-list-item]')[0] || _CodidactKeyboard.selectedItem;
            }
            _CodidactKeyboard.updateSelected();
        }
        else if (e.key === 'j') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            else {
                _CodidactKeyboard.selectedItem = $(_CodidactKeyboard.selectedItem).prevAll('[data-ckb-list-item]')[0] || _CodidactKeyboard.selectedItem;
            }
            _CodidactKeyboard.updateSelected();
        }
        else if (e.key === 't') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            _CodidactKeyboard.updateSelected();

            if (_CodidactKeyboard.selectedItemData.type === "post") {
                _CodidactKeyboard.dialog('Use tool ...\n' +
                    '============\n' +
                    'f  Flag\n' +
                    'e  Edit\n' +
                    'c  Comment\n' +
                    'l  Get permalink\n' +
                    'h  View history\n' +
                    'v  Vote ...' +
                    (_CodidactKeyboard.is_mod ? '\nt  Use tools' : '')
                );
                _CodidactKeyboard.state = 'tools';
            }
        }
        else if (e.key === 'a') {
            const cl = $('#answer_body_markdown');
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }
        else if (e.key === 'Enter') {
            if (_CodidactKeyboard.selectedItemData.type === "link") {
                window.location.href = $(_CodidactKeyboard.selectedItem).find("[data-ckb-item-link]").attr("href");
            }
        }
    }

    function gotoMenu(e) {
        if (e.key === 'm') {
            window.location.href = '/';
        }
        else if (e.key === 'u') {
            window.location.href = '/users';
        }
        else if (e.key === 'h') {
            window.location.href = '/help';
        }
        else if (e.key === 'p') {
            window.location.href = '/users/' + _CodidactKeyboard.user_id;
        }
        else if (e.key === 'f') {
            window.location.href = '/mod/flags';
        }
        else if (e.key === 'f') {
            window.location.href = '/mod/flags';
        }
        else if (e.key === "t") {
            const data = Object.entries(_CodidactKeyboard.categories());
            let string_response = "";
            for (let i = 0; i < data.length; i++) {
                const entry = data[i];
                string_response += (i + 1) + "  " + entry[0] + "\n"
            }
            _CodidactKeyboard.dialog('Go to tags of category ...\n' +
                '==================\n' +
                string_response.trim()
            );
            _CodidactKeyboard.state = 'goto/category-tags';

            // FIXME what's happened here? tlink isn't defined
            window.location.href = tlink;
        }
        else if (e.key === 'c') {
            const data = Object.entries(_CodidactKeyboard.categories());
            let string_response = "";
            for (let i = 0; i < data.length; i++) {
                const entry = data[i];
                string_response += (i + 1) + "  " + entry[0] + "\n"
            }
            _CodidactKeyboard.dialog('Go to category ...\n' +
                '==================\n' +
                "c  Category List\n" +
                string_response.trim()
            );
            _CodidactKeyboard.state = 'goto/category';
        }
    
    }

    function categoryMenu(e) {
        if (e.key === "c") {
            window.location.href = "/categories";
        }
        else {
            const number = parseInt(e.key);
            if (!isNaN(number)) {
                data = _CodidactKeyboard.categories();
                const data = Object.entries(data);
            
                const category = data[number - 1];
                window.location.href = category[1];
            }
        }
    }

    function categoryTagsMenu(e) {
        const number = parseInt(e.key);
        if (!isNaN(number)) {
            const data = Object.entries(_CodidactKeyboard.categories());
        
            const category = data[number - 1];
            window.location.href = category[1] + "/tags";
        }
    }

    function toolsMenu(e) {
        if (e.key === 'e') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-pencil-alt').parent().attr("href");
        }
        else if (e.key === 'h') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-history').parent().attr("href");
        }
        else if (e.key === 'l') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-link').parent().attr("href");
        }
        else if (e.key === 'c') {
            const cl = $(_CodidactKeyboard.selectedItem).find('.js-add-comment');
            cl.nextAll("form").css("display", "block");
            cl.nextAll("form")[0].scrollIntoView({ behavior: "smooth" });
            cl.nextAll("form").find(".js-comment-content").focus();
            _CodidactKeyboard.dialogClose();
        }
        else if (e.key === 'f') {
            const cl = $(_CodidactKeyboard.selectedItem).find('.post--action-dialog.js-flag-box');
            cl.addClass("is-active");
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.find(".js-flag-comment").focus();
            _CodidactKeyboard.dialogClose();
        }
        else if (e.key === 'v') {
            _CodidactKeyboard.dialog('Vote ...\n' +
                '========\n' +
                'u  Up\n' +
                'd  Down\n' +
                'c  Close'
            );
            _CodidactKeyboard.state = 'tools/vote';
        }
        else if (e.key === 't') {
            let cl = $(_CodidactKeyboard.selectedItem).find('a.tools--item i.fa.fa-wrench').parent();
            cl = $(cl.attr("data-modal"));
            cl.toggleClass("is-active");
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }
    
    }

    function voteMenu(e) {
        if (e.key === 'u') {
            const cl = $(_CodidactKeyboard.selectedItem).find('.vote-button[data-vote-type="1"]');
            cl.click();
            _CodidactKeyboard.dialogClose();
        }
        else if (e.key === 'd') {
            const cl = $(_CodidactKeyboard.selectedItem).find('.vote-button[data-vote-type="-1"]');
            cl.click();
            _CodidactKeyboard.dialogClose();
        }
        else if (e.key === 'c') {
            const cl = $(_CodidactKeyboard.selectedItem).find('.post--action-dialog.js-close-box');
            cl.addClass("is-active");
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }
    }
});