User:Alexis Jazz/AnonLoader.js

/* AnonLoader, allows anons to load scripts specified in window.AnonLoaderScripts (which can be defined in MediaWiki:common.js) through portlet links AnonLoader is public domain, irrevocably released as WTFPL Version 2[www.wtfpl.net/about/] by its author, Alexis Jazz. Installation instructions: AnonLoaderScripts format description: Gadgets: ['GADGET1:gadget[:displayname][:urlLoad]','GADGET2[,GADGET3][,GADGET4]:gadget[:displayname]','GADGET5:gadget[:displayname]'] Only one gadget can have urlLoad. This will intercept links to add &withgadget=gadgetname. Dark mode requires this to prevent white flashes. To load multiple gadgets for one entry, enter them separated by commas. Scripts (that can't be loaded as gadgets): ['SCRIPTNAME1[:0][:displayname]','SCRIPTNAME2[:0][:displayname]','SCRIPTNAME3[:0][:displayname]'] If :gadget is added (HIGHLY RECOMMENDED) it will load ext.gadget.SCRIPTNAME, e.g. https://commons.wikimedia.beta.wmflabs.org/w/load.php?lang=en&modules=ext.gadget.AnonLoader Otherwise it will load MediaWiki:Gadget-SCRIPTNAME.js. Insecure page titles are not supported. If you include displayname that will be used for the link name. Technically anons could modify their localStorage to load scripts/gadgets that aren't specified in AnonLoaderScripts, but they would still have to comply with the requirements of being a gadget or titled MediaWiki:Gadget-SCRIPTNAME. There's no UI for that, but very meta, one could write a gadget for that and load it with AnonLoader. /*globals mw:false,$:false*/ window.AnonL = {}; var AnonL = window.AnonL; AnonL.L = function(name,u1,u2,u3,u4,p,p2,p3,e,ls,lsk,s,s2,i,i2,i3,a,el,eln,dir,g,n,le,c,mobile,pID,aID,dN,cg,sa,gpv) { 'use strict'; gpv = function(k){ return mw.util.getParamValue(k); }; cg = function(k){ return mw.config.get(k); }; if (cg('wgUserName')){ return;//only anons need this as they don't have Special:Preferences } if (Array.from(document.getElementsByTagName('body')[0].classList).indexOf('rtl') != -1){ dir='right'; } else { dir='left'; } a = (window.AnonLoaderScripts||[]); sa = gpv('AnonLoaderShowAll') || window.AnonLoaderShowAll; //ShowAll, no menu that toggles the gadget list, just show all mobile = cg('skin') == 'minerva'; le = '.AnonLMin,.AnonLPlus'; c = 'AnonLNoD'; p = gpv('ALSet'); p2 = gpv('ALRemove'); p3 = Number(mw.storage.get('AnonLCheck')||0); dN = new Date.getTime; ls = JSON.parse((mw.storage.get('AnonLoader')||'{}')); if ( p3+30000 > dN ) { //link was clicked <30s ago if ( p ) { ls[p.split(/:/)[0]] = p;	} if ( p2 ) { delete ls[p2.split(':')[0]]; } } mw.storage.remove('AnonLCheck'); lsk = Object.keys(ls); for(i3=0;i3<lsk.length;i3++){ if ( a.indexOf(ls[lsk[i3]]) == -1 ) { delete ls[lsk[i3]]; //remove entries from localStorage that don't match window.AnonLoaderScripts lsk = Object.keys(ls); } } mw.storage.set('AnonLoader',JSON.stringify(ls)); g=[]; AnonL.fixUrl = function(event,targ,uri){ targ = event.delegateTarget; try{ uri = new mw.Uri(targ.href); if ( uri.host == cg('wgServerName') ) { uri.extend({withgadget:AnonL.gdgt}); targ.href = uri.toString; event.delegateTarget.href = uri.toString; }	} catch (ev) {} }; AnonL.fixU = function(el,skip) { $(el).on('click mousedown focus',AnonL.fixUrl); if ( !skip ) { $(el).addClass('ALwgdgt'); } }; /* test scenarios (in all skins+mobile from the search box and Special:Search): AnonL.rS = function(s,i2) { //restore search api.php action for (i2=0;i2<$(s).length;i2++) { $(s)[i2].attributes.action.value = cg('wgScript'); } }; AnonL.fixSearch = function(els,i,i2,u,uri,t,nl,s,sv,me) { els = $('.suggestions-results a,.skin-minerva .results-list-container a'); s = 'form#searchform,.skin-minerva form.search-box'; if ( !els.length ) { AnonL.rS(s); } else { sv = $('input#searchInput')[0].value; me = '.skin-minerva .search-overlay .search-box input.search'; if ( mobile && sv == '' && $(me)[0] ) { sv = $(me)[0].value; }	}	for (i=0;i<els.length;i++) { u = els[i]; try{ uri = new mw.Uri(u.href); t = uri.query.search; if ( t ) { nl = cg('wgArticlePath').replace('$1',t); if ( i == 0 && sv.toLowerCase == t.toLowerCase ) { //entered search term matches first result, you'd get redirected there anyway, I'll save you the trip for (i2=0;i2<$(s).length;i2++) { $(s)[i2].attributes.action.value = nl; }				} else if ( i == 0 ) { AnonL.rS(s); }				u.href = cg('wgServer')+nl; } else if ( cg('wgArticlePath').replace('$1',sv).toLowerCase == uri.path.toLowerCase ) { //adjust search form target for (i2=0;i2<$(s).length;i2++) { $(s)[i2].attributes.action.value = uri.path; if ( !$(s)[i2].classList.contains('ALwgdgt') ) { //add hidden input to minerva overlay search $(s)[i2].append(AnonL.hEl[0]); $(s)[i2].classList.add('ALwgdgt'); }				}			}			$(u).on('click mousedown focus',AnonL.fixUrl); } catch (ev) {} } }; AnonL.uLoad = function(fl,fEl,hEl,i,sEls,sElUrls,s){ AnonL.observe = function(rec,newEl,i,i2,nn) { AnonL.fixSearch; //AnonL.fixU('.suggestions-results a:not(.ALwgdgt)'); /*		newEl = rec[0].addedNodes; for (i=0;i<newEl.length;i++){ try{ nn = newEl[i].nodeName; if ( nn == 'A' ) { AnonL.fixU(newEl[i]); } else if ( nn != '#text' ) { AnonL.fixU(newEl[i].querySelectorAll('a:not(.ALwgdgt)')); }			} catch (ev) {} }	};	AnonL.observer = new MutationObserver(AnonL.observe); AnonL.observer.observe(document.body,{childList: true,subtree: true,attributes: false,characterData: false}); //MediaWiki magically creates elements in some cases (like basic search turning into something else on click) /*	sEls = 'input:not(.ALwgdgt),.results-list-container:not(.ALwgdgt)'; sElUrls = '.results-list-container a:not(.ALwgdgt)'; AnonL.hrefFix = function { $(sEls).on('click touchend keydown',AnonL.hrefFix); //spread $(sEls).addClass('ALwgdgt'); AnonL.fixU(sElUrls); };	$(sEls).on('click touchend keydown',AnonL.hrefFix); AnonL.fixU('a',1); fEl = 'form:not(.ALwgdgt)'; //adding hidden input to forms so withgadget will persist when they are submitted hEl = {}; $('body,input').on('click touchend',function(event,targ,uri){		for(i=0;i<$(fEl).length;i++){			if ( $(fEl)[i].attributes.action.value.match(/^\/[^\/]/) ) {				hEl[i] = document.createElement('input');				hEl[i].type = 'hidden';				hEl[i].name = 'withgadget';				hEl[i].value = AnonL.gdgt;				AnonL.hEl = hEl;				$(fEl)[i].append(hEl[i]);				$(fEl)[i].classList.add('ALwgdgt');			}		}	}); s = 'input#searchInput:not(.ALwgdgt),.skin-minerva .search-box input.search:not(.ALwgdgt)'; $(s).on('click touchend change',AnonL.fixSearch); $(s).addClass('ALwgdgt'); }; for (i=0;i<lsk.length;i++){ s = ls[lsk[i]].split(':'); if (s[1]=='gadget') { s2 = s[0].split(','); for (i2=0;i2<s2.length;i2++){ g.push('ext.gadget.'+s2[i2]);	//collect gadget }		if ( s[3] == 'urlLoad' ) { //there can be only one gadget loaded this way it seems AnonL.gdgt = s2[0]; AnonL.uLoad; }	} else { mw.loader.load(cg('wgScriptPath')+'/index.php?title=MediaWiki:gadget-'+s[0]+'.js&action=raw&ctype=text/javascript'); } } mw.loader.load(g);//load gadgets el={}; eln={}; if ( mobile ) { pID = 'p-navigation'; aID = ''; } else { pID = 'p-tb'; aID = '#t-specialpages'; } if ( !sa ) { el.menu = mw.util.addPortletLink(		pID,		'',		AnonL.lang['prefs-gadgets'],		'AnonLoaderMenu',		AnonL.lang['prefs-gadgets'],		undefined,		aID	); if ( mobile ) { $('.mw-ui-icon-portletlink-AnonLoaderMenu').addClass('mw-ui-icon-minerva-die'); } } $('#AnonLoaderMenu,#AnonLoaderMenu a').on('click',function(event){	event.stopPropagation;event.preventDefault;	if ( $(le).hasClass(c) ) {		$(le).removeClass(c);	} else {		$(le).addClass(c);	} }); for (i=0;i<a.length;i++){ if ( ls[a[i].split(':')[0]] ) { u1='Remove'; u3='AnonLMin'; } else { u1='Set'; u3='AnonLPlus'; }	n = a[i].split(':'); u4 = ''; if ( n[3] == 'urlLoad' && u1 == 'Set' ) { u4 = '&withgadget='+n[0]; }	eln[i] = (n[2] || n[0]); if ( sa && u1 == 'Set' ) { eln[i] = AnonL.lang['AnonLoader-enable'].replace('$1',eln[i]); } else if ( sa && u1 == 'Remove' ) { eln[i] = AnonL.lang['AnonLoader-disable'].replace('$1',eln[i]); }	el[i] = mw.util.addPortletLink(	pID,	'?AL'+u1+'='+a[i]+u4,	eln[i],	'AnonLoader'+i,	eln[i],	undefined,	aID	); $('body.skin-minerva #'+'AnonLoader'+i+' .mw-ui-icon').addClass(c); if ( mobile ) { document.getElementById('AnonLoader'+i.toString).querySelectorAll('a')[0].classList.add(u3); } else { document.getElementById('AnonLoader'+i.toString).classList.add(u3); } } $(le).addClass(c); $(le).on('click',function{	mw.storage.set('AnonLCheck',new Date.getTime); }); if ( !sa ) { mw.util.addCSS('.AnonLNoD{display:none !important}.AnonLMin:before,.AnonLPlus:before{margin-'+dir+':0.5em;display:inline-block;text-align:center;width:1em;font-size:larger;font-weight:bold}.AnonLMin:before{content:"✔";color:#060}.AnonLPlus:before{content:"✘";color:#600}'); } }; AnonL.loadLang = function(l,ls,d,e) { l = mw.config.get('wgUserLanguage'); ls = JSON.parse((mw.storage.get('AnonLoaderLang2')||'{}')); e = 'Enable $1'; d = 'Disable $1'; ls.en = {'prefs-gadgets':'Gadgets','AnonLoader-enable':e,'AnonLoader-disable':d}; if ( ls[l] ) { AnonL.wait(ls[l]); } else { mw.loader.using(['mediawiki.api'], function{			var api = new mw.Api;			api.get( {action:'query',meta:'allmessages',ammessages:['prefs-gadgets','AnonLoader-enable','AnonLoader-disable'],amlang:l}).done( function ( data,dq ) { dq = data.query.allmessages; ls[l] = {'prefs-gadgets':dq[0]['*'],'AnonLoader-enable':(dq[1]['*']||e),'AnonLoader-disable':(dq[2]['*']||d)}; mw.storage.set('AnonLoaderLang2',JSON.stringify(ls)); AnonL.wait(ls[l]); });		});	} }; AnonL.wait = function(g,i) { AnonL.lang = g;	i=0; var AnonW = setInterval(function{ //wait for the presence of window.AnonLoaderScripts		i++;		if (window.AnonLoaderScripts || i > 50) { //test 10 times a second for 5 seconds			clearInterval(AnonW);			mw.loader.using(['mediawiki.Uri','mediawiki.util'], function{ AnonL.L; });		}	}, 100); }; AnonL.loadLang; //
 * Create MediaWiki:Gadget-AnonLoader.js with the contents of this file.
 * Add this line to MediaWiki:Gadgets-definition: "AnonLoader[ResourceLoader|dependencies=mediawiki.util,mediawiki.storage|targets=desktop,mobile|default|hidden]|AnonLoader.js"
 * Put (for example) window.AnonLoaderScripts = ['CD','Factotum:gadget','dark-mode-toggle,dark-mode-toggle-pagestyles:gadget:Dark mode','dark-mode:gadget:Instant dark mode:urlLoad']; in MediaWiki:Common.js AND MediaWiki:Mobile.js (MobileFrontend doesn't load Common.js)
 * Optional: add window.AnonLoaderShowAll=1 in common.js+mobile.js to show all gadgets without a collapsible menu.
 * Enter search term, click ajax result
 * Enter search term that matches a title, press enter (goes to article)
 * Enter search term that doesn't, press enter (goes to results)
 * Skins: cologneblue, modern, timeless, minerva, monobook, vector, vector-2022
 * dark-mode does not support cologneblue