User:Enterprisey/superjump.js

// vim: ts=4 sw=4 et ai // ( function {    // sample value: {"a": {"b": "WP:RFA", "c": {"d": "WP:ANI"}}}    var gMenuJson = null;

// sample value: ["a", "b"] var gPressedKeys = [];

var gGeneralKeyListener = null;

// GUI components var gBackBtn; //var gBreadcrumb;

var SUPERJUMP_PANEL_ID = "superjump-container-panel"; var SUPERJUMP_TABLE_ID = "superjump-keys-table"; var MENU_JSON_LOCALSTORAGE_KEY = "enwp_superjump_menu_json"; var MENU_JSON_LOCALSTORAGE_SEPARATOR = "|";

function initAccessKeyListeners { for( var key in gMenuJson ) { initAccessKeyListener( key, function { handleKey( key, gMenuJson, /* accessKey */ true ); } ); }   }

function navigateToPath( keys ) { var menu = gMenuJson; for( var i = 0; i < keys.length - 1; i++ ) { menu = menu[ keys[i] ]; }       gPressedKeys = keys.slice( 0, keys.length - 1 ); var lastKey = keys[ keys.length - 1 ]; handleKey( lastKey, menu ); }

function handleKey( key, menu, accessKey ) { if( key == "Escape" ) { closeInterface; gPressedKeys = []; return; } else if( key == "Backspace" ) { if( gPressedKeys.length > 1 ) { navigateToPath( gPressedKeys.slice( 0, gPressedKeys.length - 1 ) ); }           return; }

var submenu = menu[ key ];

if( typeof submenu === typeof "" ) { window.location.href = mw.util.getUrl( submenu ); } else if( submenu ) { gPressedKeys.push( key ); displayOrUpdateInterface( makePanel( submenu ) ); if( gGeneralKeyListener ) { document.removeEventListener( "keydown", gGeneralKeyListener ); gGeneralKeyListener = null; }           gGeneralKeyListener = function ( event ) { handleKey( event.key, submenu ); };           document.addEventListener( "keydown", gGeneralKeyListener ); }   }

function displayOrUpdateInterface( panel ) { panel.id = SUPERJUMP_TABLE_ID;

var containerPanel = document.getElementById( SUPERJUMP_PANEL_ID ); if( !containerPanel ) { containerPanel = createInterface; document.body.appendChild( containerPanel ); }       containerPanel.replaceChild( panel, document.getElementById( SUPERJUMP_TABLE_ID ) ); gBackBtn.disabled = gPressedKeys.length < 2; //gBreadcrumb.textContent = "Pressed: " + gPressedKeys.map( function ( x ) { return "[" + x + "]"; } ).join( " > " ); }

function createInterface { containerPanel = document.createElement( "div" ); containerPanel.id = SUPERJUMP_PANEL_ID;

var topBar = document.createElement( "div" ); gBackBtn = document.createElement( "button" ); gBackBtn.textContent = "Back [Backspace]"; gBackBtn.addEventListener( "click", function {            navigateToPath( gPressedKeys.slice( 0, gPressedKeys.length - 1 ) );        } ); topBar.appendChild( gBackBtn ); var closeBtn = document.createElement( "button" ); closeBtn.textContent = "Close [Esc]"; closeBtn.addEventListener( "click", closeInterface ); topBar.appendChild( closeBtn ); containerPanel.appendChild( topBar );

var startingPanel = document.createElement( "div" ); startingPanel.id = SUPERJUMP_TABLE_ID; containerPanel.appendChild( startingPanel );

//var bottomBar = document.createElement( "div" ); //gBreadcrumb = document.createElement( "span" ); //bottomBar.appendChild( gBreadcrumb ); //var editConfigBtn = document.createElement( "button" ); //editConfigBtn.textContent = "Edit configuration"; //editConfigBtn.addEventListener( "click", function {        //    window.location.href = mw.util.getUrl( getMenuJsonPageTitle, { action: "edit" } );        //} ); //bottomBar.appendChild( editConfigBtn ); //containerPanel.appendChild( bottomBar );

return containerPanel; }

function closeInterface { var panel = document.getElementById( SUPERJUMP_PANEL_ID ); panel.parentNode.removeChild( panel ); if( gGeneralKeyListener ) { document.removeEventListener( "keydown", gGeneralKeyListener ); }   }

function makePanel( menu ) { var table = document.createElement( "table" );

var keys = Object.keys( menu ); var numItems = keys.length; var tds = new Array( numItems ); for( var i = 0; i < numItems; i++ ) { var td = document.createElement( "td" ); var keySpan = document.createElement( "span" ); keySpan.className = "key-span"; keySpan.textContent = "[" + keys[i] + "]"; td.appendChild( keySpan ); var actionSpan = document.createElement( "span" ); var menuItem = menu[ keys[i] ]; if( typeof menuItem === typeof "" ) { actionSpan.textContent = menuItem; } else { var numChoices = Object.keys( menuItem ).length; actionSpan.textContent = numChoices + " choice" + ( numChoices !== 1 ? "s" : "" ); }

td.appendChild( actionSpan ); tds[i] = td; }

var numRows = Math.max( 2, Math.ceil( Math.sqrt( numItems ) ) ); var numCols = numRows; for( var i = 0; i < numRows; i++ ) { var tr = document.createElement( "tr" ); for( var j = 0; j < numCols; j++ ) { var cell = tds[ i * numCols + j ]; if( cell ) { tr.appendChild( cell ); }           }            table.appendChild( tr ); }

return table; }

function initAccessKeyListener( key, listener ) { var hasOurAccessKey = document.querySelectorAll( "a[accesskey=" + key + "]" ); if( hasOurAccessKey.length ) { for( var i = 0; i < hasOurAccessKey.length; i++ ) { hasOurAccessKey[i].setAttribute( "accesskey", "" ); $( hasOurAccessKey[i] ).updateTooltipAccessKeys; }       }

$( "body" ).append( $( "" )           .attr( { href: "#", accesskey: key } )            .css( { position: "fixed", opacity: "0" } )            .text( "superjump-link-" + key )            .addClass( "superjump-accesskey-sink" )            .click( listener ) ); }

function setMenuJson { var reqParams = { format: "json", action: "query", prop: "revisions", titles: getMenuJsonPageTitle, rvprop: "content|ids", rvslots: "main" };

var storedRevidAndMenuJson = getStoredRevidAndMenuJson; var storedRevid, storedMenuJson; if( storedRevidAndMenuJson ) { storedRevid = storedRevidAndMenuJson[0]; storedMenuJson = storedRevidAndMenuJson[1];

reqParams.rvstartid = parseInt( storedRevid ) + 1; reqParams.rvdir = "newer"; }

return $.getJSON(           mw.util.wikiScript( "api" ),            reqParams        ).then( function ( data ) {            if( !data || !data.query || !data.query.pages ) {                mw.notify( "Error loading menu JSON page!" );               return;            }

var pageId = Object.keys( data.query.pages )[0]; var pageObj = data.query.pages[pageId]; if( pageObj.missing ) { mw.notify( "Error! You'll need to create " + getMenuJsonPageTitle + " to use superjump." ); return; }

var revs = pageObj.revisions; if( !revs ) { gMenuJson = JSON.parse(storedMenuJson); return; }

var mostRecentRev = revs[ revs.length - 1 ]; var apiRevid = mostRecentRev.revid; var apiJson = mostRecentRev.slots.main["*"];

if( isLocalStorageAvailable ) { var storageValue = apiRevid + MENU_JSON_LOCALSTORAGE_SEPARATOR + apiJson; window.localStorage.setItem( MENU_JSON_LOCALSTORAGE_KEY, storageValue ); }

gMenuJson = JSON.parse(apiJson); } );   }

function getMenuJsonPageTitle { return "User:" + mw.config.get( "wgUserName" ) + "/superjump-config.json"; }

function getStoredRevidAndMenuJson { var localStorageAvailable = isLocalStorageAvailable; if( localStorageAvailable ) { var storedRevidAndMenuJson = window.localStorage.getItem( MENU_JSON_LOCALSTORAGE_KEY ); if( storedRevidAndMenuJson && storedRevidAndMenuJson.indexOf( MENU_JSON_LOCALSTORAGE_SEPARATOR ) >= 0 ) { return storedRevidAndMenuJson.split( MENU_JSON_LOCALSTORAGE_SEPARATOR ); }       }

return null; }

function isLocalStorageAvailable { try { var storage = window.localStorage, x = '__storage_test__'; storage.setItem( x, x ); storage.removeItem( x ); return true; } catch( e ) { return false; }   }

$.when( $.ready, mw.loader.using( "mediawiki.util" ) ) .then( setMenuJson ) .then( function {            mw.util.addCSS( "#" + SUPERJUMP_PANEL_ID + "{ position: fixed; "+ "top: 50%; left: 50%; margin-right: -50%; transform: translate(-50%, -50%); " + "background-color:white; padding: 1em; border:thin solid gray;" + "box-shadow:0px 7px 7px 0px rgba(0,0,0,0.3)}"+ "#" + SUPERJUMP_PANEL_ID + " > div * { margin-left: 0.75em; }"+ "#" + SUPERJUMP_TABLE_ID + " td{padding: 0.5em;}"+ "#" + SUPERJUMP_TABLE_ID + " .key-span{font-family:monospace; cursor:default; padding-right: 0.1em; font-size: 200%}"+ "#" + SUPERJUMP_TABLE_ID + " span{vertical-align:middle;}");           initAccessKeyListeners;        } ); } ); //