User:Fred Gandt/userResourceManager.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Fred Gandt/userResourceManager and an accompanying .css page at User:Fred Gandt/userResourceManager.css. |
/*********************************************************************************************************************************
* 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 ) );