/* <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>