User:Fred Gandt/userResourceManager.js

/********************************************************************************************************************************* * 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 ) + ''; },		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( '' + resource( jsOrCss( r )[ 1 ], r ) + ' ' ); }			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 = 'User Resources Manage this JavaScripts / StyleSheets ' + peaSoup( mngr.optnvlu.js.on ) + ' ' + peaSoup( mngr.optnvlu.js.off ) + ' ' + peaSoup( mngr.optnvlu.css.on ) + ' ' + peaSoup( mngr.optnvlu.css.off ) + ' <button class="' + BASE + '">Close <button class="' + EXT + 'purge" title="Purge session cache">Purge <button id="' +					SAVING + '" class="' + SAVING + '">Save <div id="' + BIN + '" class="' + DROPZONE + '">  Saving... '; 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 ) );