User:Stussll/Common.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
mw.loader.load( '/w/index.php?title=User:GhostInTheMachine/TalkHelper2.js&action=raw&ctype=text/javascript' ); // Backlink: [[User:GhostInTheMachine/TalkHelper2.js]];

mw.loader.load( 'https://meta.wikimedia.org/w/index.php?title=User:ESanders_(WMF)/commentlinks.js&action=raw&ctype=text/javascript' );

//<nowiki>
/*
EditNoticesOnMobile is public domain (for the embedded lz-string library licensing see below), irrevocably released as WTFPL Version 2[http://www.wtfpl.net/about/] by its author, Alexis Jazz. The community requested edit notices on the mobile site for years (see community wishlist 2021 and 2022 and T201595), but the WMF hasn't implemented them yet nor given a clear roadmap for their implementation. This workaround was created to get them anyway until a native implementation becomes available.
----
lz-string by Pieroxy[https://pieroxy.net/blog/pages/lz-string/index.html], originally licensed WTFPL, Version 2. Functions that were unneeded for EditNoticesOnMobile were stripped.
The license currently offered by Pieroxy is MIT:
----
MIT License
Copyright (c) 2013 pieroxy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
----
The MIT license is included here to be safe, the lz-string 1.4.4 archive that lz-string was copied from for EditNoticesOnMobile contains a copy of the WTFPL Version 2 license.
----
Known issues:
-should preferably use event listeners instead of any kind of polling if possible (not a massive deal, more of a coders' purist issue)
*/
/*globals $:false,OO:false,ve:false,mw:false*/
window.enom = {};
var enom = window.enom;
//start lz-string by Pieroxy
enom.LZString=function(){/*jshint bitwise:false,asi:true,boss:true,expr:true*/function o(o,r){if(!t[o]){t[o]={};for(var n=0;n<o.length;n++)t[o][o.charAt(n)]=n}return t[o][r]}var r=String.fromCharCode,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",t={},i={compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(o){return null==o?"":""==o?null:i._decompress(o.length,16384,function(r){return o.charCodeAt(r)-32})},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<o.length;i+=1)if(u=o.charAt(i),Object.prototype.hasOwnProperty.call(s,u)||(s[u]=f++,p[u]=!0),c=a+u,Object.prototype.hasOwnProperty.call(s,c))a=c;else{if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();
//end lz-string by Pieroxy
//console.log('enom: EditNoticesOnMobile loaded');
enom.time = new Date().getTime();
enom.testValidJSON = function (string) {
	if(string==null){return false;}
	try{enom.parsedJSON = JSON.parse(string);}catch(e){return false;}
	return enom.parsedJSON;
};
enom.storeNotice = function(notice){
	if ( window.localStorage ) {
		enom.cachedNotices = enom.testValidJSON(enom.LZString.decompressFromUTF16(window.localStorage.ENOM));
	}
	if ( window.localStorage && ! enom.cachedNotices ) {
		window.localStorage.setItem('ENOM',enom.LZString.compressToUTF16('{}'));
	}
	if ( window.localStorage && enom.cachedNotices ) { //not sure how all browsers behave if localStorage is unavailable, so to be safe, test validity
		enom.cachedNotices[mw.config.get('wgPageName')] = {text:notice,date:enom.time};
		enom.cachedNoticesNew = JSON.stringify(enom.cachedNotices);
		if ( enom.cachedNoticesNew.length > 1000000 ) { //it's quite unlikely a user would accumulate >1MB of edit notices, but if it somehow happens we purge all and start with a clean slate
			enom.cachedNoticesNew = '{}';
		}
		window.localStorage.setItem('ENOM',enom.LZString.compressToUTF16(enom.cachedNoticesNew));
	}
};
enom.getNotice = function(trigger) {
	enom.cachedNotices = enom.testValidJSON(enom.LZString.decompressFromUTF16(window.localStorage.ENOM));
	if ( trigger && trigger.target && trigger.target.href ) {
		//console.log('enom: extract page title from URL');
		enom.pageTitle = mw.util.getParamValue("title", trigger.target.href);
	} else {
		//console.log('enom: use title of current page');
		enom.pageTitle = mw.config.get('wgPageName');
	}
	enom.newNotice = false;
	if ( window.localStorage && enom.cachedNotices ) {
		enom.update = false;
		if ( enom.cachedNotices[enom.pageTitle] ) {
			enom.oldEditnotice = enom.cachedNotices[enom.pageTitle].text;
		}
		for (enom.cachedInt=0;enom.cachedInt<Object.keys(enom.cachedNotices).length;enom.cachedInt++){
			enom.checkEntry = enom.cachedNotices[Object.keys(enom.cachedNotices)[enom.cachedInt]];
			if ( enom.checkEntry.date < enom.time -172800000 || ( enom.checkEntry.date < enom.time -7200000 && Object.keys(enom.cachedNotices)[enom.cachedInt] == enom.pageTitle) ) {//entry >48 hours (172800000 ms) old or the current page and >2 hours (7200000 ms) old
				//console.log('enom: removed '+Object.keys(enom.cachedNotices)[enom.cachedInt]+' from locally cached notices');
				delete enom.cachedNotices[Object.keys(enom.cachedNotices)[enom.cachedInt]];
				enom.update = true;
			}
			if ( enom.update ) {
				window.localStorage.setItem('ENOM',enom.LZString.compressToUTF16(JSON.stringify(enom.cachedNotices)));
			}
		}
		if ( enom.cachedNotices[mw.config.get('wgPageName')] ){ //notice was cached less than 2 hours ago
			enom.noticeText = enom.cachedNotices[mw.config.get('wgPageName')].text;
			//console.log('enom: found cached notice');
			enom.popupNotice(enom.noticeText,false);
			return;
		}
	}
	/* provides edit notices in a structured form but also a lot of unneeded big stuff
	enom.editNoticeParams = {
		format:'json',
		action:'visualeditor',
		paction:'metadata',
		page:enom.pageTitle,
		formatversion:'2',
	};
	*/
	enom.editNoticeParams = {
		format:'json',
		action:'parse',
		disableimages:true,
		disablelimitreport:true,
		title:enom.pageTitle,
		pst:'1',
		prop:'text',
		formatversion:'2',
		text:'<div id="EditNoticeOnMobile">{{#ifexist:MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+enom.pageTitle.replace(/\//g,'-')+'|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+enom.pageTitle.replace(/\//g,'-')+'}}|{{#ifexist:MediaWiki:Editnotice-{{NAMESPACENUMBER}}|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}}}}}}}</div>',
	};
	mw.loader.using(['mediawiki.api'], function(){
		//console.log('enom: download notice');
		enom.newNotice = true;
		var api = new mw.Api();
		api.post( enom.editNoticeParams ).done( function ( data ) {
			/* structured editnotices from VE API. see commented out params above
			enom.parsednotices = '<div id="EditNoticeOnMobile">';
			for(enom.noticeint=0;enom.noticeint<Object.keys(data.visualeditor.notices).length;enom.noticeint++){
				if ( Object.keys(data.visualeditor.notices)[enom.noticeint].match(/editnotice/) ) { //there's also semiprotectedwarning, presumably some other protection warnings. is there an overview of all possible messages?
					enom.parsednotices = enom.parsednotices + data.visualeditor.notices[Object.keys(data.visualeditor.notices)[enom.noticeint]];
				}
			}
			enom.parsednotices = enom.parsednotices+'</div>';
			*/
			enom.parsednotices = data.parse.text.replace(/id="mf-section-0"/g,''); //just causes display:none through skins.minerva.talk.styles. I'd fix it in the DOM but being an ID it just has to bloody go ASAP. Why is it even here?
			enom.testIfEmpty = document.createElement('div');
			enom.testIfEmpty.classList = 'enomTempDiv enomTempDivNew'; //need to attach this to the DOM to work with it.. maybe it's possible another way but this'll work
			enom.testIfEmpty.innerHTML = enom.parsednotices;
			enom.oldEditnoticeDiv = document.createElement('div');
			enom.oldEditnoticeDiv.classList = 'enomTempDiv enomTempDivOld';
			enom.oldEditnoticeDiv.innerHTML = enom.oldEditnotice;
			$('body:eq(0)').append(enom.testIfEmpty).append(enom.oldEditnoticeDiv);
			/*
			We are going to test if the notice contains any text content after removing elements that don't contain vital information about editing some particular page.
			* Nomobile elements wouldn't be displayed anyway. Edit notice creators should use "nomobile" as they see fit. Please consider that edit notices on mobile are presented as a popup, not a passive box above the text area.
			* The editnotice-link just links to the page containing the notice. It should be displayed on mobile (so nomobile shouldn't be added to it) but we ignore it when checking if the notice has any text content.
			* ..
			*/
			$('.enomTempDiv .nomobile,.enomTempDiv .editnotice-area .editnotice-link').remove();
			enom.testIfEmptyInnerText = $('.enomTempDivNew')[0].innerText.trim();
			enom.oldEditnoticeDivInnerText = $('.enomTempDivOld')[0].innerText.trim();
			if ( enom.testIfEmptyInnerText == '' ) {
				//console.log('enom: notice is (practically) empty, blanking');
				enom.parsednotices = '';
			}
			$('.enomTempDiv').remove();
			if ( enom.testIfEmptyInnerText == enom.oldEditnoticeDivInnerText ) {
				//console.log('enom: notice is identitcal to what we had cached, adding button but no popup.');
				enom.popupNotice(enom.parsednotices,false);
			} else {
				//console.log('enom: notice is different from what (if anything) was cached, adding button and creating popup');
				enom.popupNotice(enom.parsednotices,true);
			}
			enom.storeNotice(enom.parsednotices);
		});
	});
};
enom.cleanupStyling = function(){
	$('#EditNoticeOnMobile .mf-section-0').addClass('stopHidingMe');
	$('#EditNoticeOnMobile *').removeClass(['fmbox','tmbox','tmbox-content']);
	for (enom.noticeElementsInt=0;enom.noticeElementsInt<$('#EditNoticeOnMobile *').length;enom.noticeElementsInt++){
		$('#EditNoticeOnMobile *')[enom.noticeElementsInt].style.background = ''; //remove background colors that many edit notices have. Not appropriate in a popup
	}
	if ( $('#EditNoticeOnMobile .mw-collapsible')[0] ) {
		mw.loader.using('jquery.makeCollapsible').then( function () { //WP:HD includes a collapsible "Help desk templates" block
			mw.util.addCSS('#EditNoticeOnMobile .mw-parser-output .mw-collapsible-toggle a{color:#3366cc}#EditNoticeOnMobile .mw-parser-output .mw-collapsible-toggle{font-weight:normal;}');
			$($('#EditNoticeOnMobile .mw-collapsible')).makeCollapsible(); //T111565 FTFY
		});
	}
};
enom.showPopup = function(noticetext){
	mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
		OO.ui.alert(new OO.ui.HtmlSnippet(noticetext),{size:'larger'});
		var DelayClassFix = setInterval(function(){ //popup doesn't immediately exist..
			clearInterval(DelayClassFix);
			if ( $('#EditNoticeOnMobile')[0] ){ 
				enom.cleanupStyling();
			} else { //no popup yet, wait some more
				var DelayClassFix = setInterval(function(){
					clearInterval(DelayClassFix);
					enom.cleanupStyling();
				}, 500);
			}
		}, 250);
	});
};
enom.popupNotice = function(noticetext,popup){
	if ( noticetext == '' || noticetext.match(/<div[^>]*EditNoticeOnMobile[^>]*><\/div>/) ) { //empty notice, don't show anything
		//console.log('enom: notice is empty (no notice for this page)');
		return;
	}
	mw.util.addCSS('.stopHidingMe{display:unset !important}#EditNoticeOnMobile .mbox-image,.enomTempDiv{display:none}#EditNoticeOnMobile .tmbox{background:unset;border:unset;margin:unset}@media screen and (max-width:767px){#enomButtonvisual{display:none !important}}');
	if ( popup ) { //shove popup into user's face only if freshly downloaded
		enom.showPopup(noticetext);
	}
	enom.attachButton = function(saveButtonSelector,type) {
		if ( $(saveButtonSelector)[0] && ! $('#enomButton'+type)[0] ) {
			enom['showNoticeButton'+type] = $('.overlay-header:not(.hidden) .header-action button:eq(0)').clone();
			enom['showNoticeButton'+type][0].classList.remove('mw-ui-icon-mf-next-invert','continue');
			enom['showNoticeButton'+type][0].classList.add('mw-ui-icon-mf-alert');
			enom['showNoticeButton'+type][0].disabled = false;
			enom['showNoticeButton'+type][0].style = '';
			enom['showNoticeButton'+type][0].title = 'Editnotice';
			enom['showNoticeButton'+type][0].id = 'enomButton'+type;
			if ( type == 'source' ) {
				enom['showNoticeButton'+type][0].title = 'Editnotice (source)';
			} else {
				enom['showNoticeButton'+type][0].title = 'Editnotice (visual)';
			}
			enom['showNoticeButton'+type].on('click',function(){enom.showPopup(noticetext);});
			$(saveButtonSelector)[0].parentElement.insertBefore(enom['showNoticeButton'+type][0],$(saveButtonSelector)[0]);
			$('.oo-ui-tool-name-editModeVisual,.oo-ui-tool-name-editModeSource').on('click', function() {
				enom.waitingForVE(enom.sourceClass,'source',100,1000);
				enom.waitingForVE(enom.VEClass,'visual',100,1000);
			});
		}
	};
	enom.int=0;
	enom.waitingForVE = function(saveButtonSelector,type,delay,sourceDelay) {
		var DelayWaitForVE = setInterval(function(){ //wait for ve.init.target to come into existence. there's probably another event that could be used for this part, but this is an improvement over the previous lengthy DOM polling
			clearInterval(DelayWaitForVE);
			if ( typeof ve != 'undefined' && typeof ve.init != 'undefined' && typeof ve.init.target != 'undefined' && ve.init.target != null && ve.init.target.loading != null && type == 'visual' ) {
				//console.log('enom: found VE in loading state, attach button on surfaceReady');
				ve.init.target.on( 'surfaceReady', function(){
					//console.log('enom: surfaceReady, attaching button');
					enom.attachButton(saveButtonSelector,type);
				});
			} else if ( enom.int < 30 && type == 'visual' ) {
				//console.log('enom: looking for VE in loading state but not (yet?) found, try again in 100ms');
				enom.int++;
				enom.waitingForVE(saveButtonSelector,type,100,1000);
			} else { // source mode or VE finished loading before enom.waitingForVE was called
				//console.log('enom: attach button ('+type+')');
				var DelayWaitForSource = setInterval(function(){
					clearInterval(DelayWaitForSource);
					enom.attachButton(saveButtonSelector,type);
				},sourceDelay);
			}
		},delay);
	};
	enom.sourceClass = '.overlay-header:not(.hidden) .header-action button:eq(0)';
	enom.VEClass = '.overlay-header .toolbar .oo-ui-toolbar-tools .ve-ui-toolbar-group-save';
	enom.waitingForVE(enom.sourceClass,'source',100,500); // source toolbar (500ms delay), might fail on slow devices/big pages??
	enom.waitingForVE(enom.sourceClass,'source',100,750); // middle ground
	enom.waitingForVE(enom.sourceClass,'source',100,1500); // source toolbar (1500ms delay, unlikely to fail)
	enom.waitingForVE(enom.VEClass,'visual',100,1000); // visual toolbar
};
if ( $('#ca-edit')[0] ) {
	$('#ca-edit,.mw-editsection .edit-page').on('click', function(event) { enom.getNotice(event); } );
}
if ( window.location.href.match(/\#\/editor\//) ) {
	enom.getNotice();
}
//</nowiki>