Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// User:Bradv/Scripts/Powerbar.js
$(function() {
    'use strict';

    // Private functions
    var pb = {
        styleSheet: mw.util.addCSS(
            'body {margin-right: 11em} ' +
            '#p-personal {margin-right: 11em} ' +
            '#right-navigation {margin-right: 11em} ' +
            '#powerbar {position: fixed; right: 0; top: 0; bottom: 0; width: 10em; border-left: 1px solid #a7d7f9; padding: 0.5em;} ' +
            '#powerbar > div {font-size: 0.75em;} ' +
            '#powerbar-title {color: #444444; margin: 0 0 0.5em; font-size: 1em;} ' +
            '#powerbar-contents ul {list-style: none; margin:0; } ' +
            '#powerbar-contents ul li {line-height: 1.125em;} ' +
            '#powerbar-contents ul li div {color: #444444; margin: 0.5em 0 0.5em 0.7em} ' +
            '#powerbar-contents ul li a {display:block; padding: 0.2em 0 0.2em 1em; text-indent: -1em;} ' +
            '#powerbar-fade {bottom: 0; left: 0; right: 0; height: 4em; position:absolute; background-image: linear-gradient(rgba(246, 246, 246, 0), rgba(246,246,246,1), rgba(246,246,246,1));} ' +
            '#powerbar-logo {position: absolute; right: 0.5em; bottom: 0.5em; font-size: 1.1em; } ' +
            '#powerbar-logo a {color: rgba(0,0,0,0.4) } ' +
            '#powerbar-logo1 {font-weight: 100} ' +
            '#powerbar-logo2 {font-weight: 700} '
        ),
        show: function() {
            pb.styleSheet.disabled=false;
            var a = $("#pb-hide a");
            $("#powerbar").removeAttr("style");
            a.removeAttr("style");
        },
        hide: function() {
            pb.styleSheet.disabled=true;
            var a = $("#pb-hide a");
            a.css('display', 'none');
            $("#powerbar").css("display","none");
            if (window.Powerbar) Powerbar.save('plugin', '');
        },
        init: function() {
            $("#p-powerbar").remove();
            $("#powerbar").remove();
            var div = document.createElement("div");
            div.className = "vectorMenu emptyPortlet";
            div.id = "p-powerbar";
            $("#p-search").before(div);
            var h3 = document.createElement("h3");
            div.append(h3);
            var span = document.createElement("span");
            h3.append(span);
            span.append(document.createTextNode("Powerbar"));
            var a = document.createElement("a");
            a.href = "#";
            $(a).click(function (e) {e.preventDefault();});
            h3.append(a);
            var menu=document.createElement("div");
            menu.className="menu";
            div.append(menu);
            var ul = menu.append(document.createElement("ul"));

            mw.util.addPortletLink('p-powerbar', '', 'Hide', 'pb-hide');
            $("#pb-hide").click(function(e) {
                e.preventDefault();
                pb.hide();
            });

            var bar = document.createElement("div");
            bar.id = "powerbar";
            $("body").append(bar);
            var title = document.createElement("div");
            title.id = "powerbar-title";
            bar.append(title);
            var contents = document.createElement("div");
            contents.id = "powerbar-contents";
            bar.append(contents);
            var fade = document.createElement("div");
            fade.id = "powerbar-fade";
            bar.append(fade);
            var logo = document.createElement("div");
            logo.id = "powerbar-logo";
            fade.append(logo);
            var lnk = document.createElement("a");
            lnk.id = "powerbar-logo-a";
            lnk.href = mw.util.getUrl("User:Bradv/Scripts/Powerbar");
            logo.append(lnk);
            var s1 = document.createElement("span");
            s1.id = "powerbar-logo1";
            s1.append(document.createTextNode("power"));
            lnk.append(s1);
            var s2 = document.createElement("span");
            s2.id = "powerbar-logo2";
            s2.append(document.createTextNode("bar"));
            lnk.append(s2);

            pb.hide();
        },
        plugin: {
            setTitle: function(title) {
                var t = $("#powerbar-title");
                t.empty();
                t.append(document.createTextNode(title));
            },
            contents: {
                clear: function() {
                    $("#powerbar-contents").empty();
                    var ul = document.createElement("ul");
                    $("#powerbar-contents").append(ul);
                },

                addElement: function(element) {
                    var li = document.createElement("li");
                    li.append(element);
                    $("#powerbar-contents ul").append(li);
                },

                addLabel: function(text) {
                    var lbl = document.createElement("div");
                    lbl.append(document.createTextNode(text));
                    this.addElement(lbl);
                },

                addLink: function(text, url, onclick) {
                    var a = document.createElement("a");
                    a.append(document.createTextNode(text));
                    a.href=url;
                    if (onclick) { $(a).click(onclick); }
                    this.addElement(a);
                }
            }
        }
    };
    pb.init();

    //Public functions
    window.Powerbar = {
        save: function(key, value) {
            window.localStorage.setItem("powerbar." + key, value);
        },
        retrieve: function(key, value) {
            return window.localStorage.getItem("powerbar." + key);
        },
        createPlugin: function(params) {
            var pid = "pb-" + params.id;
            mw.util.addPortletLink('p-powerbar', '', params.title, pid, params.tooltip, '', '#pb-hide');
            var link = $("#" + pid);
            link.click(function(e) {
                e.preventDefault();
                clearTimeout(Powerbar.timer);
                Powerbar.save("plugin", pid);
                pb.plugin.setTitle(params.title);
                pb.plugin.contents.clear();
                pb.plugin.contents.addLabel("Loading...");
                pb.show();
                pb.callback = function() {
                    clearTimeout(Powerbar.timer);
                    params.callback(pb.plugin.contents);
                    Powerbar.timer = setTimeout(pb.callback, params.refresh*1000);
                };
                pb.callback();
            });
            if (this.retrieve("plugin") == pid) {
                link.click();
            }
        },
    };

    // AFC Plugin
    Powerbar.createPlugin({
        id: 'afc-old',
        title: 'Articles for Creation - Backlog',
        tooltip: 'Browse oldest Articles for Creation submissions',
        refresh: 10,
        callback: function(contents) {
            var api = new mw.Api();
            api.get( {
                action: 'query',
                format: 'json',
                list: 'categorymembers',
                cmtitle: 'Category:Pending AfC submissions',
                cmprop: 'title',
                cmnamespace: '118|2',
                cmlimit: 50,
                cmsort: 'timestamp',
                cmdir: 'ascending',
            } ).done( function(data) {
                contents.clear();
                data.query.categorymembers.forEach(function (item) {
                    contents.addLink(item.title, mw.util.getUrl(item.title));
                });
            } );
        }
    });

    Powerbar.createPlugin({
        id: 'afc-new',
        title: 'Articles for Creation - New',
        tooltip: 'Browse newest Articles for Creation submissions',
        refresh: 10,
        callback: function(contents) {
            var api = new mw.Api();
            api.get( {
                action: 'query',
                format: 'json',
                list: 'categorymembers',
                cmtitle: 'Category:Pending AfC submissions',
                cmprop: 'title',
                cmnamespace: '118|2',
                cmlimit: 50,
                cmsort: 'timestamp',
                cmdir: 'descending',
            } ).done( function(data) {
                contents.clear();
                data.query.categorymembers.forEach(function (item) {
                    contents.addLink(item.title, mw.util.getUrl(item.title));
                });
            } );
        }
    });

    //NPP Plugin
    Powerbar.createPlugin({
        id: 'npp-old',
        title: 'New Page Patrol - Backlog',
        tooltip: 'Browse oldest unreviewed new pages',
        refresh: 10,
        callback: function(contents) {
            var api = new mw.Api();
            api.get( {
                action: 'pagetriagelist',
                format: 'json',
                showunreviewed: true,
                dir: 'oldestfirst',
                namespace: 1,
                limit: 50,
            } ).done( function(data) {
                contents.clear();
                data.pagetriagelist.pages.forEach(function (item) {
                    contents.addLink(item.title, mw.util.getUrl(item.title)+'?showcurationtoolbar=1');
                });
            } );
        }
    });

    Powerbar.createPlugin({
        id: 'npp-new',
        title: 'New Page Patrol - New',
        tooltip: 'Browse unreviewed new pages',
        refresh: 10,
        callback: function(contents) {
            var api = new mw.Api();
            api.get( {
                action: 'pagetriagelist',
                format: 'json',
                showunreviewed: true,
                dir: 'newestfirst',
                namespace: 1,
                limit: 50,
            } ).done( function(data) {
                contents.clear();
                data.pagetriagelist.pages.forEach(function (item) {
                    contents.addLink(item.title, mw.util.getUrl(item.title)+'?showcurationtoolbar=1');
                });
            } );
        }
    });

});