Jump to content

MediaWiki:Gadget-EditNoticesOnMobile.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Xaosflux (talk | contribs) at 16:42, 12 July 2022 (load in, sourced from User:Alexis Jazz/EditNoticesOnMobile.js). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)
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.
//<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 EditNoticeOnMobile 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');
if ( ! window.location.host.match(/(^|\.)m\./) && mw.config.get('skin') == 'minerva' ) { //desktop Minerva! The edit notice is here but it's playing hide-and-seek. child's play
	//console.log('enom: unhide notices on desktop Minerva');
	mw.util.addCSS('.content.fmbox,.content.tmbox {display:unset !important} .mw-editnotice * {display:unset !important}');
	$('.editnotice-page .fmbox,.editnotice-page .tmbox,.editnotice-page .tmbox-content').removeClass(['fmbox','tmbox','tmbox-content']);
} else if ( mw.config.get('skin') == 'minerva' ) { //mobile Minerva, a bit more complicated. need to get the notice and create a popup
	//console.log('enom: you are on the mobile domain I think');
	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}}-'+mw.config.get('wgPageName').replace(/\//g,'-')+'|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+mw.config.get('wgPageName').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);
				$('.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(['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);
				enom.cleanupStyling();
			}, 200);
		});
	};
	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>