/*********************************************************************************************************************************
* Currently still in development, this is designed to provide control over user JavaScripts and StyleSheets.
* If you encounter any problems using this script, please tell User:Fred_Gandt on either my talk page or this script's talk page.
*
*********************************************************************************************************************************/
( function( DOM_d ) {
"use strict";
var BASE = "fg-js-and-css-manager",
EXT = BASE + "-",
DROPEE_BOTTOM = EXT + "dropee-bottom",
JAVASCRIPTS = EXT + "javascripts",
STYLESHEETS = EXT + "stylesheets",
DROPEE_TOP = EXT + "dropee-top",
MANAGABLE = EXT + "managable",
DROPZONE = EXT + "dropzone",
CHANGED = EXT + "changed",
SAVING = EXT + "saving",
DRAGEE = EXT + "dragee",
FALSE = EXT + "false",
TRUE = EXT + "true",
THIS = EXT + "this",
FILE = EXT + "file",
BIN = EXT + "bin",
FILE_IMG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAARTAAAEUwECr+6lAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcG" +
"Uub3Jnm+48GgAAAkZQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwcHAAAAGBgYAAAAAAAAAAAAAAAACQkJCQkJDQ0NDAwMDAwMHh4eHR0dHR0dHBwcIC" +
"AgIiIiJCQkJiYmIyMjIyMjLCwsLy8vIiIiIiIiJCQkJSUlJycnKCgoKioqLCwsJCQkJSUlJycnKCgoKioqKysrLS0tLi4uKioqLS0tKCgoKysrKCgoKSkpKysrMDAwMDAwOjo6PDw8Pz8/PT09QEBAQUFBQ0NDHx" +
"8fICAgIyMjJCQkJiYmJycnKSkpKioqKysrLCwsLS0tLi4uLy8vMDAwMTExMjIyMzMzNDQ0NTU1NjY2Nzc3ODg4OTk5Ojo6Ozs7PDw8PT09Pj4+Pz8/QEBAQUFBQkJCQ0NDRERERUVFRkZGR0dHSEhISUlJSkpKS0" +
"tLTExMTU1NTk5OT09PUFBQUVFRUlJSU1NTVFRUVVVVVlZWV1dXWFhYWVlZWlpaW1tbXFxcXV1dXl5eX19fYGBgYWFhYmJiY2NjZGRkZWVlZmZmZ2dnaGhoampqa2trbGxsbW1tbm5ub29vcHBwcnJyc3NzdHR0dX" +
"V1eHh4eXl5enp6e3t7gICAgYGBgoKChISEhYWFh4eHiIiIkZGRk5OTlpaWmZmZmpqanJycnZ2dnp6en5+foKCgpKSkq6ursLCwtLS0tra2t7e3uLi4u7u7vLy8vb29vr6+w8PDxsbGyMjIy8vLzs7O0tLS1dXVB+" +
"nTgQAAAEp0Uk5TAAECAwQGBwkKCw0REhUWFxgfJikqLi8wMjY3PD5AVFdZWmlqhZWgoqKio6SkpKSkpKSlpaWlpaWlpbu7xcXGxsbT4Ovx8vP09fd9otd6AAACsElEQVQ4y21Ve0/TUBT/nduuXTfKHOAggko0xp" +
"CgURL+8wv4af0QJibGF0LiI0QQiYrbGFvLHr299x7/2PqmyWmT29Pfq6e9BAAgIkLhYGYuLNjzc6shCstkwrHkSqNw2/tAYd30jvuz/IoFAOT5m47t05I2KXVteSx1hRpNtOOWozBKQfiufXgZZZiJNHLudd2TAI" +
"krEsHzJysulajddif4p7oA0bwAI9c2dSBNiZrQ0U7oiJWQhmuWks54Ngz9PT68SrwvEFc6FlyLApIzo651HEXRVNnOHTWMTQERZLfquh93yWACBkws5chqPOSDMOYC4mxzJmNub4d6odKASI2X/MEcM0V0f04Ajv" +
"oAAUwED2AQ1lcjzXkzft27BIGTEgIMgCEs4nyOUy38OoGI0ozmZVtFM47b4l5kGTCBCYUq5Di6M/W9HdPfvOhyTgGISvG4ZxPgDzBgUAUur3E1okRhUWXxFQIBI286gyxppImAmd+zoPOPlBFv3+bRqq71bVFjeL" +
"3zssrUDF3/EheGDMAAmQyy3HgFajaUcDQUKOJRqrI8jxpkGSWacinyZ3FdmljfnCMYAXinV6dGoG7Zde5OTT7HtHFOFXsNfb1s4/xxb1369b9XHJVdE3jLkSdg8AXs+J0w9NtWFWqiJQ9XRi8eUQQDJnVTjmzUzC" +
"QKqsOTaZxMFq+OUayKGeytDN8/Wxse75vw6Gm0Jr/tMj4NEkiRWCHx6J37YPugueF9vbu9sR7qvc/rhyEq1ODTF1CDweut5nM1ADass0vuZ9TpPKLRPvEuO7svl0dvakQ4ffXx5nmk2Y/Owen4/pe+DI5wPBkS03" +
"fK5pEAQCzv7M8zwuIbzdXb08BkiMNUaSXHKeeolfwgrPyvPr0aI1VGjZpXs0rbQtKq42mcNVa3j8o+8h9pp2sDbmYYIwAAAABJRU5ErkJggg==",
rsrcfll = [],
drgee;
var cE = function( e ) {
return DOM_d.createElement( e );
},
jsOrCss = function( s ) {
return ( /^User\:(.+)\.(js|css)$/ ).exec( s );
},
eByWN = function( w, p, n, i, nl ) {
nl = ( w === "tag" ? p.getElementsByTagName( n ) : p.getElementsByClassName( n ) ); return i !== undefined ? nl[ i ] : nl;
};
var mngr = {
optnnm: { local: EXT + mw.config.get( "wgUserName" ).replace( / /g, "-" ), global: "userjs-" + BASE },
optnvlu: { js: { on: [], off: [] }, css: { on: [], off: [] } },
css: "User:Fred_Gandt/userResourceManager.css",
ui: cE( "li" )
},
DROP_BOT = eByWN( "class", mngr.ui, DROPEE_BOTTOM ),
DROP_TOP = eByWN( "class", mngr.ui, DROPEE_TOP ),
DRAG_IMG = cE( "img" ),
DOM_h = eByWN( "tag", DOM_d, "head", 0 ),
WG_pagename = jsOrCss( mw.config.get( "wgPageName" ) ),
sssnstrg = JSON.parse( sessionStorage[ mngr.optnnm.local ] || "{}" ),
strngyvlu = JSON.stringify( mngr.optnvlu );
var notIn = function( h, n ) {
return !~h.indexOf( n );
},
isJS = function( jrc ) {
return jrc[ 2 ] === "js";
},
nl2a = function( nl ) {
return [].slice.call( nl );
},
changed = function() {
mngr.ui.classList.add( CHANGED );
},
eById = function( id ) {
return DOM_d.getElementById( id );
},
highlightThis = function( ths ) {
ths.setAttribute( "class", THIS );
},
getSection = function( jrc, ooo ) {
return eByWN( "class", eById( jrc ), ooo, 0 );
},
present = function( s ) {
return underSpace( s, true ).replace( "/", " - " );
},
resourcesOn = function() {
return mngr.optnvlu.js.on.concat( mngr.optnvlu.css.on );
},
underSpace = function( s, b ) {
return b ? s.replace( /_/g, " " ) : s.replace( / /g, "_" );
},
storeSession = function() {
sessionStorage[ mngr.optnnm.local ] = JSON.stringify( { vlu: mngr.optnvlu, incld: sssnstrg.incld } );
},
resource = function( d, u ) {
return present( d ) + '<a href="/wiki/' + u + '" target="_blank" title="Visit this resource page" draggable="false"></a>';
},
notUnmanagable = function( jrc ) {
return jrc[ 1 ] !== "Fred_Gandt/userResourceManager" &&
!( /^.+\/(common|cologneblue|modern|monobook|vector)$/ ).test( jrc[ 1 ] ); // Is this related to a managed resource?
},
clean = function() {
mngr.ui.removeAttribute( "class" );
eById( BIN ).innerHTML = "";
},
setCSS = function( txt, nre ) {
nre = cE( "style" );
nre.textContent = txt;
DOM_h.appendChild( nre );
},
peaSoup = function( a ) {
var v, r, o = [];
for ( v in a ) {
r = a[ v ];
o.push( '<p id="' + r + '" draggable="true">' + resource( jsOrCss( r )[ 1 ], r ) + '</p>' );
}
return o.join( "" );
},
clearDropeeClasses = function( trg, ps, p ) {
ps = nl2a( DROP_TOP ).concat( nl2a( DROP_BOT ) );
for ( p in ps ) {
if ( trg != ps[ p ] ) {
ps[ p ].removeAttribute( "class" );
}
}
},
idArray = function( p ) {
var e, o = [], a = nl2a( eByWN( "tag", p, "p" ) );
for ( e in a ) {
o.push( a[ e ].id );
}
return o;
},
manageThis = function( dst, jrc, ooo, p ) {
p = cE( "p" );
p.setAttribute( "id", jrc[ 0 ] );
p.setAttribute( "draggable", "true" );
p.innerHTML = resource( jrc[ 1 ], jrc[ 0 ] );
eByWN( "class", dst, EXT + !!ooo, 0 ).appendChild( p );
return p;
},
apiQuery = function( dt, fnc, mthd ) {
dt.format = "json";
$.ajax( {
type: mthd || "GET",
url: "/w/api.php",
dataType: dt.format,
data: dt,
success: function( data ) { fnc( data ); },
error: function( data) { console.error( data ); } // TODO: Inform the user
} );
},
fetchResources = function( ra, tmp, tbi ) {
// TODO Version checking with confirmation before "upgrade"?
apiQuery( { action: "query", prop: "revisions", rvprop: "content", titles: ra.join( "|" ) }, function( data ) {
var pgs = data.query.pages, pg, cpg, rsrc, rsrcs = {};
for ( pg in pgs ) {
cpg = pgs[ pg ];
rsrcs[ underSpace( cpg.title ) ] = cpg.revisions[ 0 ][ "*" ]; //.replace( /[\t\r\n]+/g, "" ); // TODO: Develop effective minification
}
if ( !tmp ) {
for ( rsrc in rsrcs ) {
sssnstrg.incld[ underSpace( rsrc ) ] = rsrcs[ rsrc ];
}
storeSession();
if ( !tbi ) {
applyResources();
return;
}
}
applyResources( rsrcs );
} );
},
applyResources = function( rsrcs ) {
var execute = function( r, c ) {
if ( notIn( rsrcfll, r ) ) {
rsrcfll.push( r );
if ( isJS( jsOrCss( r ) ) ) {
try {
$.globalEval( c );
} catch ( err ) {
console.error( r + "\n" + err ); // TODO: Inform the user
}
} else {
setCSS( c ); // TODO: Prioritize setting CSS
}
}
}, rsrc, crsrc;
setCSS( sssnstrg.incld[ mngr.css ] );
if ( rsrcs ) {
for ( rsrc in rsrcs ) {
if ( rsrc !== mngr.css ) {
execute( rsrc, rsrcs[ rsrc ] );
}
}
} else {
rsrcs = resourcesOn();
for ( rsrc in rsrcs ) {
crsrc = rsrcs[ rsrc ];
execute( crsrc, sssnstrg.incld[ crsrc ] );
}
}
},
dropZone = function( dz ) {
dz.addEventListener( "dragover", function( evt, trg ) {
evt.preventDefault();
trg = evt.target;
evt.dataTransfer.dropEffect = "move";
drgee.setAttribute( "class", DRAGEE );
if ( trg.nodeName.toLowerCase() === "p" && trg !== drgee ) {
if ( evt.offsetY < trg.offsetHeight / 2 ) {
trg.setAttribute( "class", DROPEE_TOP );
} else {
trg.setAttribute( "class", DROPEE_BOTTOM );
}
clearDropeeClasses( trg );
}
}, false );
dz.addEventListener( "drop", function( evt, trg, trgp ) {
evt.preventDefault();
trg = evt.target;
trgp = trg.parentElement;
if ( trgp.classList.contains( DROPZONE ) ) {
if ( evt.offsetY < trg.offsetHeight / 2 ) {
trgp.insertBefore( drgee, trg );
} else {
trgp.insertBefore( drgee, trg.nextElementSibling );
}
changed();
} else if ( trg.classList.contains( DROPZONE ) ) {
trg.appendChild( drgee );
changed();
}
}, false );
},
save = function() {
var js_on = idArray( getSection( JAVASCRIPTS, TRUE ) ),
css_on = idArray( getSection( STYLESHEETS, TRUE ) ),
bnnd = idArray( eById( BIN ) ),
on = js_on.concat( css_on ),
tbi = [], n, rsrc, incldd, tkn;
mngr.optnvlu = { js: { on: js_on, off: idArray( getSection( JAVASCRIPTS, FALSE ) ) }, css: { on: css_on, off: idArray( getSection( STYLESHEETS, FALSE ) ) } };
for ( n in on ) {
rsrc = on[ n ];
if ( !sssnstrg.incld[ rsrc ] ) {
tbi.push( rsrc );
}
}
for ( incldd in sssnstrg.incld ) {
if ( incldd !== mngr.css && ( notIn( on, incldd ) || !notIn( bnnd, incldd ) ) ) {
delete sssnstrg.incld[ incldd ];
}
}
if ( tbi.length ) {
fetchResources( tbi, false, true );
} else {
storeSession();
}
apiQuery( { action: "options", token: mw.user.tokens.values.csrfToken, optionname: mngr.optnnm.global, optionvalue: JSON.stringify( mngr.optnvlu ) }, function( data ) {
if ( data.options && data.options === "success" ) {
clean();
}
}, "POST" );
},
setListeners = function() {
var dzs = nl2a( eByWN( "class", mngr.ui, DROPZONE ) ), dz, jrc, ths;
for ( dz in dzs ) {
dropZone( dzs[ dz ] );
}
mngr.ui.addEventListener( "click", function( evt ) {
var trg = evt.target, id = trg.id;
if ( trg.nodeName.toLowerCase() === "button" ) {
if ( id === MANAGABLE ) {
jrc = WG_pagename;
ths = eById( jrc[ 0 ] );
if ( !ths ) {
changed();
}
if ( isJS( jrc ) ) {
ths = ths || manageThis( eById( JAVASCRIPTS ), jrc );
} else {
ths = ths || manageThis( eById( STYLESHEETS ), jrc );
mngr.ui.classList.add( STYLESHEETS );
}
highlightThis( ths );
mngr.ui.classList.add( BASE );
} else if ( trg.classList.contains( EXT + "purge" ) && confirm( "This action will clear the session cache of resources.\n" +
"This will NOT affect your resource configuration;\nIt will ONLY initialize refreshing the cache.\nDo you wish to continue?" ) ) {
delete sessionStorage[ mngr.optnnm.local ];
} else {
mngr.ui.classList.toggle( trg.getAttribute( "class" ).replace( / |webfonts-changed/gi, "" ) );
if ( id === SAVING ) {
save();
}
}
} else if ( id === BIN ) {
mngr.ui.classList.toggle( BIN );
} else if ( trg.parentElement.classList.contains( FALSE ) ) {
if ( notIn( rsrcfll, id ) && confirm( 'Include ' + present( jsOrCss( id )[ 1 ] ).replace( " - ", "'s \"" ) + '" temporarily?' ) ) {
fetchResources( [ id ], true );
}
}
}, false );
mngr.ui.addEventListener( "change", function( evt ) {
var trg = evt.target, trgp = trg.parentElement;
if ( trg.getAttribute( "type" ) === "text" ) {
jrc = jsOrCss( trg.value.trim() ); // TODO: Accept variations of text - with or without "User:" and/or underscores etc.
// TODO Interwiki resources?
if ( !!jrc && notUnmanagable( jrc ) && ( ( trgp.id === JAVASCRIPTS && isJS( jrc ) ) || ( trgp.id === STYLESHEETS && !isJs( jrc ) ) ) ) {
ths = eById( jrc[ 0 ] );
if ( !ths ) {
ths = manageThis( trgp, jrc );
changed();
}
highlightThis( ths );
trg.value = "";
}
}
}, false );
mngr.ui.addEventListener( "dragstart", function( evt ) {
evt.dataTransfer.effectAllowed = "move";
evt.dataTransfer.setDragImage( DRAG_IMG, 24, 24 );
drgee = evt.target;
}, false );
mngr.ui.addEventListener( "dragend", function( evt ) {
evt.target.removeAttribute( "class" );
clearDropeeClasses();
if ( !eById( BIN ).childNodes.length ) {
mngr.ui.classList.remove( BIN );
}
}, false );
},
createUI = function( mngbl, mngd, rsrc ) {
$( DOM_d ).ready( function() {
DRAG_IMG.setAttribute( "class", FILE );
DRAG_IMG.setAttribute( "src", FILE_IMG );
mngr.ui.setAttribute( "id", BASE );
mngr.ui.innerHTML = '<button class="' + BASE + '">User Resources</button><button id="' + MANAGABLE + '">Manage this</button><div class="' +
BASE + '"><button class="' + STYLESHEETS + '">JavaScripts / StyleSheets</button><div id="' + JAVASCRIPTS +
'"><input type="text" placeholder="Add a new script"><div class="' + DROPZONE + ' ' + TRUE + '">' + peaSoup( mngr.optnvlu.js.on ) +
'</div><div class="' + DROPZONE + ' ' + FALSE + '">' + peaSoup( mngr.optnvlu.js.off ) + '</div></div><div id="' + STYLESHEETS +
'"><input type="text" placeholder="Add a new stylesheet"><div class="' + DROPZONE + ' ' + TRUE + '">' + peaSoup( mngr.optnvlu.css.on ) +
'</div><div class="' + DROPZONE + ' ' + FALSE + '">' + peaSoup( mngr.optnvlu.css.off ) + '</div></div><div class="' + EXT +
'actions"><button class="' + BASE + '">Close</button><button class="' + EXT + 'purge" title="Purge session cache">Purge</button><button id="' +
SAVING + '" class="' + SAVING + '">Save</button><div id="' + BIN + '" class="' + DROPZONE + '"></div></div><i>Saving...</i></div>';
eByWN( "tag", eById( "p-personal" ), "ul", 0 ).appendChild( mngr.ui );
setListeners();
if ( !!WG_pagename && notUnmanagable( WG_pagename ) ) {
mngr.ui.classList.add( MANAGABLE );
if ( mngbl = eById( WG_pagename[ 0 ] ) ) {
highlightThis( mngbl );
}
}
} );
},
init = function() {
sssnstrg.incld = {};
fetchResources( resourcesOn().concat( [ mngr.css ] ) );
createUI();
};
if ( sssnstrg.incld ) {
mngr.optnvlu = sssnstrg.vlu;
applyResources();
createUI();
} else {
mngr.optnvlu = JSON.parse( mw.user.options.values[ mngr.optnnm.global ] || strngyvlu );
init();
}
} ( document ) );