Bước tới nội dung

Thành viên:NgocAnMaster/Gadget-EditNoticesOnMobile.js

Bách khoa toàn thư mở Wikipedia

Chú ý: Sau khi lưu thay đổi trang, bạn phải xóa bộ nhớ đệm của trình duyệt để nhìn thấy các thay đổi. Google Chrome, Firefox, Internet ExplorerSafari: Giữ phím ⇧ Shift và nhấn nút Reload/Tải lại trên thanh công cụ của trình duyệt. Để biết chi tiết và hướng dẫn cho các trình duyệt khác, xem Trợ giúp:Xóa bộ nhớ đệm.

//<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.
If an edit notice contains an element with the "nopopupnotice" class the automatic popup will be suppressed.
At @media screen and (max-width:767px) the button to (re)visit a notice will be hidden in the visual editor. (that toolbar is already crowded) UNLESS you disable automatic popups in which case I hope you like crowds
MediaWiki:Gadgets-definition example entry: EditNoticesOnMobile[ResourceLoader|default|dependencies=mediawiki.storage|targets=mobile|skins=minerva|supportsUrlLoad=true]|EditNoticesOnMobile.js
----
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/concerns:
-Some people voiced concerns over showing the edit notice over the anoneditwarning, before the user has confirmed they are okay with anon editing. I think it's good actually: before they bother to log in or sign up (captcha!), tell the user that the earth is REALLY not flat and they shouldn't bother us with conspiracy theories and old wives' tales. But if this would end up being a sticking point, ctrl+f for "uncomment this to show the popup after the anoneditwarning" and uncomment that stuff.
-General concern that users will be annoyed by too many popups. (please note that the Android app already has edit notices as popups) The same notice for the same page doesn't repeat, pages without a notice have no popup, the gadget can be turned off in preferences. The community should consider adding an element with the "nopopupnotice" class to notices that are informative in nature and not warnings. Also apply the "nomobile" class where appropriate.
*/
/*globals $:false,OO:false,ve:false,mw:false*/
if ( typeof Object.values == 'undefined' ) { //ES5 :-/
	Object.values = function(obj1,int,arr) {
		arr = [];
		for (int=0;int<Object.keys(obj1).length;int++) {
			arr.push(obj1[Object.keys(obj1)[int]]);
		}
		return arr;
	};
}
//only load EditNoticesOnMobile on Minerva skin on the mobile site and if it's not already loaded
if ( mw.config.get('skin') == 'minerva' && window.location.host.match(/(^|\.)m\./) && ( ! window.enom || ! window.enom.msgs ) ) {
window.enom = {};
var enom = window.enom;
enom.msgs = {
	noticetitle:'View edit notice',
	ok:'OK',
	enable:'Enable automatic popups',
	disable:'Disable automatic popups',
	disablenote:'If you disable automatic popups you may miss out on important information about edit restrictions. <b>Those restrictions will still apply to you.</b> Are you sure you want to disable automatic popups?',
	disablenote2:'The community doesn\'t recommend this. Are you absolutely sure? (final warning)'
};
//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
//
//CRC32 checksum generator. Based on https://simplycalc.com/crc32-source.php ("This source code is in the public domain. You may use, share, modify it freely, without any conditions or restrictions."). Compacted a bit.
//enom.CRC32('test'); //should return d87f7e0c (3632233996)
// Why are you reading this?
// >>> is unsigned right shift/zero-fill right shift. n >>> 1 Could also be written as o=n;if(n<0){o=n+4294967296};h=Number(parseInt('0'+Number(o).toString(2).slice(0,-1), 2));
// & is Bitwise AND. If both bits are 1 it returns 1, otherwise 0. (27 & 30) compares 11011 to 11110 and gives 11010 as the third and fifth bit aren't both 1. As (n & 1) always compares to one that really just says what the last bit is. Can be rewritten as Number(Number(n).toString(2).slice(-1)) in that case.
// ^ is XOR. (27 ^ 30) compares 11011 to 11110 and gives 00101 as only the third and fifth bits differ. 101 is binary for 5 so that's what it'd return
// If you learned nothing go to https://www.w3schools.com/js/js_bitwise.asp
enom.genCRC32t = function(t,i,j,n) {/*jshint bitwise:false*/
	for (i = 0; i < 256; i++) { //table will have 256 entries
		n = i;
		for (j = 8; j > 0; j--) {
			if ((n & 1) == 1) {
				n = (n >>> 1) ^ 0xEDB88320; //0xEDB88320 is the reversed polynomial for CRC32, see en.wikipedia.org/wiki/Cyclic_redundancy_check
			} else {
				n = n >>> 1;
			}
		}
		t[i] = n;
	}
	return t;
};
enom.CRC32 = function(s,i,c) {/*jshint bitwise:false*/
	//console.log('enom: create CRC32 checksum');
	if ( ! enom.CRC32t ) { //CRC32 table hasn't been generated yet
		enom.CRC32t = enom.genCRC32t([]); //passes empty array as first argument
	}
	c = 0xFFFFFFFF;
	for (i = 0; i < s.length; i++){ //iterate through input string
		c = (c >>> 8) ^ enom.CRC32t[(s.charCodeAt(i)) ^ (c & 255)]; //add byte. Unsigned right shift, XOR to table entry for charCode of the Nth character in the string to XOR to bitwise AND of c and 255. s.charCodeAt(i) is the same as s.substr(i).charCodeAt(0)
	}
	return ((c > -1) ? (0xFFFFFFFF - c) : c -c-c-1).toString(16);
};
//console.log('enom: EditNoticesOnMobile loaded');
enom.time = new Date().getTime();
enom.evilClasses = ['fmbox','tmbox','tmbox-content','messagebox'];
enom.setItemLS = function(key,val) {
	return mw.storage.set(key,val);
};
enom.testValidJSON = function (string) {
	if(string==null){return false;}
	try{enom.parsedJSON = JSON.parse(enom.LZString.decompressFromUTF16(string.slice(11)));}catch(e){return false;}
	return enom.parsedJSON;
};
enom.getDisabledSetting = function(){
	if ( mw.storage.get('ENOMDisablePopup') ) {
		enom.neverPopup = true;
	} else {
		delete enom.neverPopup;
	}
};
enom.getDisabledSetting();
enom.storeNotice = function(notice){
	enom.cachedNotices = enom.testValidJSON(mw.storage.get('ENOM'));
	if ( ! enom.cachedNotices ) {
		enom.setItemLS('ENOM','lzsdeflU16,'+enom.LZString.compressToUTF16('{}'));
		enom.cachedNotices = enom.testValidJSON(mw.storage.get('ENOM'));
	}
	if ( enom.cachedNotices ) {
		enom.cachedNotices[enom.pageTitle] = {CRC32:enom.testIfEmptyInnerTextCRC32,date:enom.time,text:notice};
		//console.log('enom: generated checksum '+enom.cachedNotices[enom.pageTitle].CRC32+' for notice on '+enom.pageTitle);
		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 = '{}';
		}
		enom.setItemLS('ENOM','lzsdeflU16,'+enom.LZString.compressToUTF16(enom.cachedNoticesNew));
	}
};
enom.getNotice = function(trigger) {
	enom.cachedNotices = enom.testValidJSON(mw.storage.get('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.pageTitle = enom.pageTitle.replace(/_/g,' ');
	enom.newNotice = false;
	if ( enom.cachedNotices ) {
		enom.update = false;
		enom.oldEditnoticeCRC32 = 0;
		if ( enom.cachedNotices[enom.pageTitle] ) {
			enom.oldEditnotice = enom.cachedNotices[enom.pageTitle].text;
			enom.oldEditnoticeCRC32 = enom.cachedNotices[enom.pageTitle].CRC32;
		}
		for (enom.cachedInt=0;enom.cachedInt<Object.keys(enom.cachedNotices).length;enom.cachedInt++){
			enom.checkEntry = enom.cachedNotices[Object.keys(enom.cachedNotices)[enom.cachedInt]];
			if ( typeof window.enomCacheExpiry == 'number' ) { //give the user some control over the expiry period by specifying enomCacheExpiry in their common.js.
				enom.cacheExpiry = window.enomCacheExpiry;
			} else {
				enom.cacheExpiry = 1209600000; //keep checksums for 2 weeks (1209600000 ms)
			}
			enom.cacheExpiryText = 7200000; //keep text for 2 hours (7200000 ms)
			if ( enom.checkEntry.date < enom.time -enom.cacheExpiry || ( enom.checkEntry.date < enom.time -enom.cacheExpiryText && Object.keys(enom.cachedNotices)[enom.cachedInt] == enom.pageTitle) ) {//entry >2 weels 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;
			} else if ( enom.checkEntry.date < enom.time -enom.cacheExpiryText && enom.cachedNotices[Object.keys(enom.cachedNotices)[enom.cachedInt]].text ) {
				//console.log('enom: remove noticetext for '+Object.keys(enom.cachedNotices)[enom.cachedInt]+' from locally cached notices (older than 2 hours)');
				delete enom.cachedNotices[Object.keys(enom.cachedNotices)[enom.cachedInt]].text;
				enom.update = true;
			}
			if ( enom.update ) {
				enom.setItemLS('ENOM','lzsdeflU16,'+enom.LZString.compressToUTF16(JSON.stringify(enom.cachedNotices)));
			}
		}
		if ( enom.cachedNotices[enom.pageTitle] ){ //notice was cached less than 2 hours ago
			//console.log('enom: found cached notice');
			enom.popupNotice(enom.cachedNotices[enom.pageTitle].text,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.namespaces = mw.util.escapeRegExp(Object.values(mw.config.get('wgFormattedNamespaces')).toString()).replace(/^,/,'').replace(/,/g,'|'); // "Talk|User|User talk|" etc
	enom.pageTitleNoNS = enom.pageTitle.replace(new RegExp('^('+enom.namespaces+'):'),'').replace(/\//g,'-');
	enom.editNoticeParams = {
		format:'json',
		action:'parse',
		disablelimitreport:true,
		title:enom.pageTitle,
		contentmodel:'wikitext',
		pst:'1',
		prop:'text',
		formatversion:'2',
		text:'<div id="EditNoticeOnMobile">{{#ifexist:MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+enom.pageTitleNoNS+'|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+enom.pageTitleNoNS+'}}|{{#ifexist:MediaWiki:Editnotice-{{NAMESPACENUMBER}}|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}}}}}}}</div><div id="enom_end"></div>',
	};
	mw.loader.using(['mediawiki.api'], function(){
		//console.log('enom: download notice');
		enom.newNotice = true;
		var api = new mw.Api();
		
		enom.writeLang = function(data) {
			enom.setItemLS('ENOMUILang','lzsdeflU16,'+enom.LZString.compressToUTF16((data || '{"created":'+new Date().getTime()+'}')));
		};
		enom.wUL = mw.config.get('wgUserLanguage');
		if ( enom.wUL != 'en' ) {
			enom.UILang = ( enom.testValidJSON(mw.storage.get('ENOMUILang')) || {created:new Date().getTime()} );
			if ( enom.UILang.created < new Date().getTime() - 604800000 ) { //localStorage object more than a week old
				enom.writeLang();
			}
			if ( enom.UILang[enom.wUL] ) {
				enom.msgs = enom.UILang[enom.wUL];
			} else {
				api = new mw.Api();
				api.get({action:'query',prop:'revisions',titles:'MediaWiki:Gadget-EditNoticesOnMobile.js/i18n.json',rvlimit:1,rvprop:'content',rvslots:'*'}).then( function(data) {
					enom.langObj = {};
					try{enom.langObj = JSON.parse(data.query.pages[Object.keys(data.query.pages)[0]].revisions[0].slots.main['*']);}catch(e){}
					enom.UILang[enom.wUL] = ( enom.langObj[enom.wUL] || enom.msgs );
					enom.writeLang(JSON.stringify(enom.UILang));
					enom.msgs = enom.UILang[enom.wUL];
				},function(){ //API fail
					enom.UILang[enom.wUL] = enom.msgs;
					enom.writeLang(JSON.stringify(enom.UILang));
				});
			}
		}
		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(/\n/g,'').replace(/.*(<div id="EditNoticeOnMobile">.*<\/div>)<div id="enom_end"><\/div>.*/,'$1');
			enom.testIfEmpty = document.createElement('div');
			enom.testIfEmpty.classList.add('enomTempDiv'); //need to attach this to the DOM to work with it.. maybe it's possible another way but this'll work
			enom.testIfEmpty.classList.add('enomTempDivNew');
			enom.testIfEmpty.innerHTML = enom.parsednotices;
			$('body:eq(0)').append(enom.testIfEmpty);
			enom.suppressPopup = typeof $('.enomTempDivNew .nopopupnotice')[0];
			/*
			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-link,.enomTempDiv .editnotice-group').remove();
			$('.enomTempDiv *').removeClass(enom.evilClasses);
			enom.testIfEmptyInnerText = $('.enomTempDivNew')[0].innerText.trim();
			enom.testIfEmptyInnerTextCRC32 = enom.CRC32(enom.testIfEmptyInnerText);
			//console.log('enom: checksum for this notice: '+enom.testIfEmptyInnerTextCRC32);
			if ( enom.testIfEmptyInnerText == '' ) {
				//console.log('enom: notice is (practically) empty, blanking');
				enom.parsednotices = '';
			}
			$('.enomTempDiv').remove();
			if ( enom.testIfEmptyInnerTextCRC32 == enom.oldEditnoticeCRC32 || enom.suppressPopup == 'object' || enom.neverPopup ) {
				//console.log('enom: notice is identical to what we had cached (based on CRC32 checksum) or contains "nopopupnotice" class or you disabled automatic popups, 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);
			}
			//if ( mw.config.get('wgUserName') != null ) { // 4/5 uncomment this to show the popup after the anoneditwarning
				enom.storeNotice(enom.parsednotices);
			//} // 5/5 uncomment this to show the popup after the anoneditwarning
		});
	});
};
enom.cleanupStyling = function(){
	//console.log('enom: cleaning up notice');
	$('#EditNoticeOnMobile *').removeClass(enom.evilClasses);
	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-collapsible-toggle a{color:#3366cc}#EditNoticeOnMobile .mw-collapsible-toggle{font-weight:normal;}');
			$($('#EditNoticeOnMobile .mw-collapsible')).makeCollapsible(); //T111565 FTFY
		});
	}
};
enom.showPopup = function(noticetext,settingbutton){
	mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
		enom.getDisabledSetting(); //in case the setting was changed in another tab since the script was loaded
		enom.dialogActions = [{action: 'reject',label: enom.msgs.ok,flags: 'primary'}];
		if ( settingbutton ) {
			if ( enom.neverPopup ) {
				enom.buttonLabel = enom.msgs.enable;
				enom.buttonFlag = 'progressive';
			} else {
				enom.buttonLabel = enom.msgs.disable;
				enom.buttonFlag = 'destructive';
			}
			enom.dialogActions.push({action: 'accept',label:enom.buttonLabel,flags:enom.buttonFlag});
		}
		if ( window.innerWidth > 300 ) { //on REALLY narrow (<300px) screens, let OOui handle it however it pleases
			mw.util.addCSS('#EditNoticeOnMobile .mbox-image,.enomTempDiv{display:none} .oo-ui-windowManager-modal>.oo-ui-dialog.oo-ui-window-active>.oo-ui-window-frame{width:700px !important;max-width:90%;max-height:90%;border:1px solid #a2a9b1;box-shadow:0 2px 2px 0 rgba(0,0,0,0.25);border-radius:2px;}'); //T313140
		}
		if ( navigator.userAgent.match(/Firefox/) && $('html.client-dark-mode')[0]) {
			enom.pageXOffset = window.pageXOffset;
			enom.pageYOffset = window.pageYOffset;
			window.scrollTo(0,0);
		} else {
				delete enom.pageXOffset;
				delete enom.pageYOffset;
		}
		/* Hack-free method to have a windowed popup. But actual user experience might be worse. For example a 375px (portrait mode) iPhone SE would get 300px with this while the hack results in 90% (338px).
		if ( window.innerWidth <= 500 ) { //T313140
			enom.popupSize = 'small'; //300px
		} else if ( window.innerWidth <= 700 ) {
			enom.popupSize = 'medium'; //500px
		} else {
			enom.popupSize = 'large'; //700px
		}
		*/
		OO.ui.confirm(new OO.ui.HtmlSnippet(noticetext),{/*size:enom.popupSize,*/actions:enom.dialogActions}).done( function ( disable ) {
			//console.log('enom: dismissed popup');
			$('#EditNoticeOnMobile').remove(); //when the popup is dismissed it is just hidden it seems, any old instances must be gone so waitForElement() can detect when to launch cleanupStyling()
			if ( enom.pageXOffset || enom.pageYOffset ) {
				window.scrollTo(enom.pageXOffset,enom.pageYOffset);
			}
			if ( window.innerWidth > 300 ) {
				mw.util.addCSS('.oo-ui-windowManager-modal>.oo-ui-dialog.oo-ui-window-active>.oo-ui-window-frame{width:unset !important;max-width:100%;max-height:100%;}');
			}
			if ( disable && window.localStorage && ! enom.neverPopup ) {
				//console.log('enom: you decided to disable automatic popups :-(');
				OO.ui.confirm(new OO.ui.HtmlSnippet(enom.msgs.disablenote),{size:'large'}).done( function ( disablenow ) {
					if ( disablenow ) {
						if ( mw.config.get('wgDBname') != 'enwiki' ) {
							enom.setItemLS('ENOMDisablePopup','1');
							enom.getDisabledSetting();
							return;
						}
						//console.log('enom: you are really going through with this? :-( :-(');
						OO.ui.confirm(enom.msgs.disablenote2,{size:'large'}).done( function ( disablenow ) {
							if ( disablenow ) {
								//console.log('enom: you really decided to disable automatic popups! :-( :-(');
								enom.setItemLS('ENOMDisablePopup','1');
								enom.getDisabledSetting();
							}
						});
					}
				} );
			} else if ( disable && enom.neverPopup ) {
				//console.log('enom: user decided to re-enable automatic popups :-)');
				mw.storage.remove('ENOMDisablePopup');
				delete enom.neverPopup;
			}
		} );
		enom.waitForElement('#EditNoticeOnMobile',enom.cleanupStyling); //popup doesn't immediately exist..
	});
};
enom.waitForElement = function(element,action,skipInstant,observerInt){
	observerInt = 0;
	if ( $(element)[0] && ! skipInstant ) {
		//console.log('enom: found element '+element+', it\'s already here.');
		action();
		return;
	}
	enom.processObservations = function(){
		observerInt++;
		if ( $(element)[0] ) {
			//console.log('enom: found element '+element);
			action();
			enom.observer.disconnect();
		} else if ( observerInt > 50 ) {
			enom.observer.disconnect(); //generally shouldn't happen but the observer shouldn't keep running until the end of time if the element never shows
		}
	};
	enom.observer = new MutationObserver(enom.processObservations); //looks for new HTML elements in the DOM. like the source editor if we expect it to be loading
	enom.observer.observe(document.body,{childList: true,subtree: true,attributes: false,characterData: false});
};
enom.applyToSourceToolbar = function(){
	enom.waitingForVE(enom.sourceClass,'source',100,100,true);
	enom.addEventToSwitcher();
};
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;
	}
	if ( ! enom.neverPopup ) {
		mw.util.addCSS('@media screen and (max-width:767px){#enomButtonvisual{display:none !important}}'); // hide button in visual toolbar on narrow screens unless you disabled automatic popups
	}
	if ( popup && ! enom.neverPopup ) { //shove popup into user's face only if freshly downloaded
		 /* // 1/5 uncomment this to show the popup after the anoneditwarning. NOTE: does the anoneditwarning appear for every single edit? Or does it stop showing if you publish an edit as anon? This code assumes the anoneditwarning ALWAYS shows for anons for every single edit, so if it doesn't this would cause problems. (probably failure to automatically popup the notice)
		if ( mw.config.get('wgUserName') == null ) {
			//console.log('enom: anon, put event listener on "Edit without logging in" button instead of showing it now');
			enom.addToAnon = function(){
				//console.log('enom: adding event listener to anonymous button');
				$('.anonwarning a.anonymous').on('click',function(){
					enom.showPopup(noticetext);
					enom.storeNotice(enom.parsednotices);
					enom.waitForElement(enom.sourceClass,enom.applyToSourceToolbar);
					enom.waitingForVE(enom.VEClass,'visual',100,1000);
				});
			};
			enom.waitForElement('.anonwarning a.anonymous',enom.addToAnon);
		} else {
		 */ // 2/5 uncomment this to show the popup after the anoneditwarning
			enom.showPopup(noticetext);
		//} // 3/5 uncomment this to show the popup after the anoneditwarning
	}
	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 = enom.msgs.noticetitle;
			enom['showNoticeButton'+type][0].id = 'enomButton'+type;
			//console.log('enom: adjust title of buttons to tell them apart when debugging');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,true);});
			$(saveButtonSelector)[0].parentElement.insertBefore(enom['showNoticeButton'+type][0],$(saveButtonSelector)[0]);
		}
	};
	enom.addEventToSwitcher = function(int) {
		//console.log('enom: attached button, apply function to editor switch');
		int=0;
		var DelaySwitcherAddEvent = setInterval(function(){ // well I apologize but I'm done with this "you should use events for everything!!" garbage. It's not reliable and for a gazillion things events don't even exist which you noticed yourself. Just hammer the bloody DOM. It's the only way. Let's just get this done, reliably. If your device doesn't completely choke on VE and turn into a little bonfire, it can handle a handful of DOM pokes with one hand tied behind its back, mkay?
			//console.log('enom: addEventToSwitcher. Are we there yet?');
			int++;
			if ( int > 100 ) {
				clearInterval(DelaySwitcherAddEvent); //failsafe. no idea why this would happen
				//console.log('enom: wtf just happen');
			}
			if ( $('.oo-ui-tool-name-editModeSource')[0] && ! $('.oo-ui-tool-name-editModeSource.enomHasEvent')[0] ) {
				clearInterval(DelaySwitcherAddEvent);
				$('.oo-ui-tool-name-editModeVisual,.oo-ui-tool-name-editModeSource').on('click', function() {
					enom.waitForElement(enom.sourceClass,enom.applyToSourceToolbar,true);
					enom.waitingForVE(enom.VEClass,'visual',100,1000);
				});
				$('.oo-ui-tool-name-editModeVisual,.oo-ui-tool-name-editModeSource').addClass('enomHasEvent');
			}
		},100);
	};
	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);
			enom.veInit = function () {
				try{enom.veLoading = ve.init.target.loading;}catch(e){return false;}
				return enom.veLoading;
			};
			if ( type == 'visual' && enom.veInit() ) {
				//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 = '.editor-overlay .overlay-header:not(.hidden) .header-action button:eq(0)';
	enom.VEClass = '.editor-overlay .overlay-header .toolbar .oo-ui-toolbar-tools .ve-ui-toolbar-group-save';
	enom.waitForElement(enom.sourceClass,enom.applyToSourceToolbar); //source toolbar button
	enom.waitingForVE(enom.VEClass,'visual',100,1000); // visual toolbar button
};
if ( $('#ca-edit')[0] ) {
	$('#ca-edit,.mw-editsection .edit-page').on('click', function(event) { enom.getNotice(event); } );
}
if ( window.location.href.match(/\#\/editor\//) ) {
	//console.log('enom: found #editor in location.href, loading');
	enom.getNotice();
}
} else {
	//console.log('enom: don\'t load EditNoticesOnMobile on anything other than the mobile site with Minerva and don\'t try to load it twice.');
}
//</nowiki>