User:Alexis Jazz/AnonLoader.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:Alexis Jazz/AnonLoader. |
/* <nowiki>
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:
* 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.
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):
* 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
*/
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();
//</nowiki>