User:Mikevoir/betterDiff.js

$(function {   // Load dependencies and cache    importScriptPage('User:Mikevoir/lib.js', 'community');    importArticles({ type: 'script', articles: [ 'u:dev:MediaWiki:Modal.js' ] });	var lib;	var api = new mw.Api;	var config = mw.config.get(['wgDiffNewId', 'wgAction', 'wgCanonicalSpecialPageName']);	var tokens = {		patrol: ,		rollback: 	};	var can = {		block: mw.config.get('wgUserGroups').some(function(group){return ['sysop', 'wiki-representative', 'soap'].includes(group);}),		patrol: mw.config.get('wgUserGroups').some(function(group){return ['sysop', 'content-moderator'].includes(group);}),		rollback: mw.config.get('wgUserGroups').some(function(group){return ['sysop', 'content-moderator', 'rollback'].includes(group);})	};	// Main class	var betterDiff = {		init: function {			// Get tokens			if (can.patrol) {				api.get({ meta: 'tokens', type: 'patrol' }).then(function(data){ if (data.query.tokens.patroltoken && data.query.tokens.patroltoken.length>2) { tokens.patrol = data.query.tokens.patroltoken; }				});			}			// Check we're in Special:RecentChanges			if (config.wgCanonicalSpecialPageName == 'Recentchanges') {				lib.waitFor('.mw-changeslist div', function{ betterDiff.newDiffLink; betterDiff.quickDiff; lib.waitFor('.mw-rcfilters-ui-filterWrapperWidget-top', betterDiff.userPatrol); // start observing betterDiff.RecentChangesReload(betterDiff.newDiffLink); });			}			// Check we're in Special:Contributions			else if (config.wgCanonicalSpecialPageName == 'Contributions') {				lib.waitFor('ul.mw-contributions-list li', function{ betterDiff.newDiffLink; // start observing betterDiff.RecentChangesReload(betterDiff.newDiffLink, 'ul.mw-contributions-list'); });			}			// Check we're in a history page			else if (config.wgAction == 'history') {				lib.waitFor('.mw-history-histlinks', betterDiff.newDiffLink);			}			// Check we're in a diff page			else if (config.wgDiffNewId) {				lib.waitFor('#mw-diff-ntitle1', betterDiff.newDiff);			}		},		// Each mutation array represents a reload (e.g., through filter change)		RecentChangesReload: function(callback, query) {			var observer = new MutationObserver(function (mutations, me) { if (					mutations[0] &&					mutations[0].target &&					mutations[0].target.classList.contains('mw-rcfilters-ui-changesListWrapperWidget')				) { callback; }			});			observer.observe(document.querySelector(query ? query : '.mw-changeslist'), { childList: true, subtree: true });		},		// Properly build diff links with page creation edits		newDiffLink: function {			var newDiffLink = {				searchRevid: function (row) {					var O = {						row: row,						revid: []					};					// Special:Contributions					if ( O.row.nodeName == 'LI' && O.row.getAttribute('data-mw-revid')) { 						O.revid.push(O.row.getAttribute('data-mw-revid')); // Page creation revision					// Special:RecentChanges					} else {						// sole edit						if (O.row.getAttribute('data-mw-revid')) { 							O.revid.push(O.row.getAttribute('data-mw-revid'));						// Top edit						} else if (O.row.classList.contains('mw-rcfilters-ui-highlights-enhanced-toplevel')) {							O.revid.push(O.row.nextElementSibling.getAttribute('data-mw-revid')); // Last revision							O.revid.push(O.row.parentElement.lastElementChild.getAttribute('data-mw-revid')); // Page creation revision						}					}					if (O.revid.length>0) { return newDiffLink.buildLink(O); } else { return; }				},				buildLink: function(options) { var target = newDiffLink.getTarget(options.row); var page = newDiffLink.getTitle(options.row); if (target) { var link = document.createElement('a'); var from = options.revid[options.revid.length-1]; var to = options.revid[0]; var href = '/wiki/' + page + '?diff=' + to; if (from == to) { link.classList.add('mw-changeslist-diff'); } else { href = href + '&oldid=' + from; options.label = options.revid.length + ' changes'; link.classList.add('mw-changeslist-groupdiff'); }						link.href = href; link.title = page; if (target.nodeType == 3) { var split = /^([^\d\w]*)([\d\w\s]*)([^\d\w]*)$/.exec(target.textContent); link.innerHTML = split[2]; if (split[1].length>0) {target.parentNode.insertBefore(document.createTextNode(split[1]), target);} target.parentNode.insertBefore(link, target); if (split[3].length>0) {target.parentNode.insertBefore(document.createTextNode(split[3]), target);} target.remove; return; } else { link.innerHTML = options.label; target.replaceChildren(link); return; }					} else { return; } },				getTitle: function(row) { var title; var queries = [ '.mw-changeslist-title', // Normal Special:RecentChanges '.mw-changeslist-date', // Nested Special:RecentChanges '.mw-enhanced-rc-time > a' // Special:Contributions ];					queries.forEach(function(query) {						if (!title && row.querySelector(query) && row.querySelector(query).getAttribute('title')) {							title = row.querySelector(query).getAttribute('title');						}					}); return title || ''; },				getTarget: function(row) { var target; var queries = [ '.mw-changeslist-links:not(.mw-history-histlinks, .mw-usertoollinks) > span:first-child', // Special:Contributions && Normal Special:RecentChanges '.mw-changeslist-diff-cur + .mw-changeslist-separator', // Nested Special:RecentChanges '.mw-changeslist-links.mw-history-histlinks:not(.mw-usertoollinks) > span:last-child', // ?action=history ];					queries.forEach(function(query) {						var test = row.querySelector(query);						if (!target && test && test.classList.length==0) {							target = test.firstChild;						} else if (!target && test && test.classList.length>0 && test.classList.length>0) {							target = test.previousSibling;						}					}); return target; }			};			// Check we're in Special:RecentChanges if (config.wgCanonicalSpecialPageName == 'Recentchanges') { if (					document.querySelector('.mw-changeslist-src-mw-new') &&					( !document.querySelector('.mw-changeslist-src-mw-new .mw-changeslist-groupdiff') || !document.querySelector('.mw-changeslist-src-mw-new .mw-changeslist-diff') )				){					document.querySelectorAll('.mw-changeslist-src-mw-new').forEach(function(node){if (node) {newDiffLink.searchRevid(node);}}); }			}			// Check we're in Special:Contributions else if (config.wgCanonicalSpecialPageName == 'Contributions') { if (document.querySelector('ul.mw-contributions-list abbr.newpage')) { document.querySelectorAll('ul.mw-contributions-list abbr.newpage').forEach(function(node){						var row = node.parentElement;						if (row) {newDiffLink.searchRevid(row);}					}); }			}			// Check we're in a history page else if (config.wgAction == 'history') { if (!document.querySelector('ul.mw-contributions-list:last-of-type li:last-of-type .mw-history-histlinks > span:nth-child(2) > a')) { var node = document.querySelector('ul.mw-contributions-list:last-of-type li:last-of-type'); if (node) {newDiffLink.searchRevid(node);} }			}		},		// Properly display diff for page creation edits newDiff: function { if (document.querySelector('#mw-diff-ntitle1 > strong > a')) { var table = document.querySelector('.diff'); var revid = document.querySelector('#mw-diff-ntitle1 > strong > a').href.replace(/^.+\?oldid=/g, ''); var api_opt = { action: 'compare', torev: revid, fromslots: 'main', 'fromtext-main': '', prop: 'diff|ids', formatversion: 2 };				if ( revid && document.querySelector('#mw-diff-otitle1 > strong > a') && !document.querySelector('#differences-prevlink') ) { //Add edit link to page creation diff table.querySelector('#mw-diff-otitle4').innerHTML = ' strong > a').title +					'?diff=' +					document.querySelector('#mw-diff-otitle1 > strong > a').href.replace(/^.+\?oldid=/g, '') +					'&oldid=0" title="' +					document.querySelector('#mw-diff-otitle1 > strong > a').title +					'" id="differences-prevlink">← Older edit'; } else if (revid && !document.querySelector('#differences-prevlink')) { table.querySelector('.diff-ntitle').colSpan = 4; table.querySelector('.diff-notice').parentNode.remove; api.get(api_opt).then(function(data) {						table.innerHTML = '  ' + table.innerHTML;						table.querySelector('tbody').innerHTML = table.querySelector('tbody').innerHTML + data.compare.body;					}); }			}		},		// Mass patrol recent edits from specific user userPatrol: function { if (!document.querySelector('#userPatrol') && can.patrol) { var wrapper = $(					' '+						'User to mass patrol: '+						' '+						' '+						' '+						' Patrol User '+					' '				); $('.mw-rcfilters-ui-filterWrapperWidget-top').after(wrapper); document.querySelector('#submitUserPatrol').addEventListener('click', function(event) {					var user = document.querySelector('#userPatrol').value; // Username without the "User:" prefix					if (user.length>0) {						api.get({ action: 'query', list: 'recentchanges', rcshow: '!patrolled', rcprop: 'ids', rcuser: user, format: 'json', formatversion: '2', rclimit: 'max' }).then(function(data){ if (data.query.recentchanges.length>0) { document.querySelector('#userPatrolDetails').innerHTML = 'Patrolling '+data.query.recentchanges.length+' edits...'; var patrolID = function(page) { api.post({										action: 'patrol',										format: 'json',										revid: page.revid,										token: tokens.patrol									}); };								data.query.recentchanges.forEach(patrolID); document.querySelector('#userPatrolDetails').innerHTML = 'Patrolled '+data.query.recentchanges.length+' edits!'; } else { document.querySelector('#userPatrolDetails').innerHTML = 'User has no edits to patrol!'; }						});					} else { document.querySelector('#userPatrolDetails').innerHTML = 'No user specified.'; }				}); } else { console.log('User does not have patrolling rights.'); } },		// Diff pages but without moving away from the page, allowing patrolling still quickDiff: function { // Add the extra liks var href = ''; var addQuick = function(diff) { if (diff && diff.getAttribute('href')) { var href = diff.getAttribute('href'); var newid = /diff=(\d+)/.exec(href)[1]; var oldid = /oldid=\d+/.test(href) ? /oldid=(\d+)/.exec(href)[1] : '0'; var link = document.createElement('a'); link.setAttribute('newid', newid); link.setAttribute('oldid', oldid); link.setAttribute('data-target-page', diff.closest('table').querySelector('a.mw-changeslist-title').getAttribute('title')); link.innerHTML = 'view'; link.style.cursor = 'pointer'; link.classList.add('quickDiff'); if (diff.parentElement.nodeName == 'SPAN') { var span = document.createElement('span'); span.appendChild(link); diff.parentElement.after(span); } else { diff.after(' | ', link); }				}			};			var addLinks = function { if (!document.querySelector('.quickDiff')) { document.querySelectorAll('.mw-changeslist-groupdiff').forEach(addQuick); document.querySelectorAll('.mw-changeslist-diff').forEach(addQuick); }			};			addLinks; betterDiff.RecentChangesReload(addLinks); mw.hook('dev.modal').add(function(Modal) {				var popup = 					new Modal.Modal({ title: 'Quick Diff', id: 'quickDiff-popup', size: 'full', content: '', buttons: [ {								text:'Copy Diff Link', title:'Copy Diff Link', id:'quickDiff-CopyDiffLink', primary: true, event: 'CopyDiffLink' }						],						events: { CopyDiffLink: function { navigator.clipboard.writeText(href); }						}					});				popup.create;				document.addEventListener('click', function(event) { if (event.target && event.target.classList.contains('quickDiff')) { if (tokens.rollback=='' && document.querySelector('.mw-rollback-link a')) { tokens.rollback = document.querySelector('.mw-rollback-link a').href.replace(/^.+token=/, ''); }						var generateHeader = function(data) { var header = ''; if (data.fromtimestamp) { var todate = new Date(data.totimestamp); var fromdate = new Date(data.fromtimestamp); href = mw.config.get('wgServer')+'/wiki/'+betterDiff.urlEncode(data.totitle)+'?diff='+data.torevid+'&oldid='+data.fromrevid; header += // Old revid ''+ ' '+										' '+											''+ 'Revision as of '+ todate.getHours+':'+todate.getMinutes+ ', '+todate.getDate+' '+ (new Intl.DateTimeFormat('en-US', {month: 'long'}).format(todate))+' '+ todate.getFullYear+ ' '+ ' ('+												'edit'+											') '+ ' '+									' '+									' '+										' '+data.fromuser+'  '+ ' ('+											'wall</a> | '+											'contribs</a>'+										') '+ ' '+									' '+										' '+											(data.fromparsedcomment ? ('('+data.fromparsedcomment+')') : '')+ ' '+									' '+								' '+								// New revid '<td class="diff-ntitle diff-side-added" colspan="2">'+ ' '+										' '+											''+ 'Revision as of '+ todate.getHours+':'+todate.getMinutes+ ', '+todate.getDate+' '+ (new Intl.DateTimeFormat('en-US', {month: 'long'}).format(todate))+' '+ todate.getFullYear+ '</a> '+ ' ('+												'edit</a>'+											') '+ ' ('+												'undo</a>'+											') '+ ' '+									' '+									' '+										' '+data.touser+' </a> '+ ' ('+											'wall</a> | '+											'contribs</a>'+										') '+ (tokens.rollback.length>2 ? ( ' '+												''+													'rollback'+ '</a>'+ ' '										) : )+									' '+									' '+										' '+											(data.toparsedcomment ? ('('+data.toparsedcomment+')') : )+ ' '+									' '+									' '+								' ';							} else { var date = new Date(data.totimestamp); href = mw.config.get('wgServer')+'/wiki/'+betterDiff.urlEncode(data.totitle)+'?diff='+data.torevid; header += '<td class="diff-ntitle" colspan="4">'+ ' '+										' '+											''+ 'Revision as of '+ date.getHours+':'+date.getMinutes+ ', '+date.getDate+' '+ (new Intl.DateTimeFormat('en-US', {month: 'long'}).format(date))+' '+ date.getFullYear+ '</a>'+ ' ('+												'edit</a>'+											') '+ ' '+									' '+									' '+										' '+data.touser+' </a> '+ ' ('+											'wall</a> | '+											'contribs</a>'+										') '+ (tokens.rollback.length>2 ? ( ' '+												''+													'rollback'+ '</a>'+ ' '										) : )+									' '+									' '+										' '+											(data.toparsedcomment ? ('('+data.toparsedcomment+')') : )+ ' '+									' '+									' '+								' ';							}							return header; };						var api_opt = { action: 'compare', torev: event.target.getAttribute('newid'), prop: 'diff|ids|timestamp|user|comment|title', formatversion: 2 };						if (event.target.getAttribute('oldid') !== '0') { api_opt.fromrev = event.target.getAttribute('oldid'); } else { api_opt.fromslots = 'main'; api_opt['fromtext-main'] = ''; }						api.get(api_opt).then(function(data) {							popup.setTitle('Changes: '+data.compare.totitle);							popup.setContent( ' '							);							if (can.patrol && tokens.patrol.length>2) {								// Add patrol button if any revision to patrol								api.get({ action: 'query', list: 'recentchanges', rcshow: '!patrolled', rcprop: 'ids', format: 'json', rctitle: data.compare.totitle, formatversion: '2', rclimit: 'max' }).then(function(check) { var num = 0; while (check.query.recentchanges[num]) { if (											(check.query.recentchanges[num] && data.compare.torevid && data.compare.fromrevid && check.query.recentchanges[num].revid <= data.compare.torevid && check.query.recentchanges[num].revid >= data.compare.fromrevid) || 											(check.query.recentchanges[num] && data.compare.torevid && !data.compare.fromrevid && check.query.recentchanges[num].revid == data.compare.torevid)										) { document.querySelector('#mw-diff-ntitle4').innerHTML = ' ['+													''+														'Mass Patrol'+ '</a>'+ '] ';											num = -1; } else { num++; } }								}).catch(console.log);							}							popup.show;						}); } else if (event.target && event.target.nodeName == 'A' && event.target.closest('.patrollink') && event.target.getAttribute('torevid')) { var torevid = event.target.getAttribute('torevid'); var fromrevid = event.target.getAttribute('fromrevid'); if (fromrevid) { api.get({								action: 'query',								list: 'recentchanges',								rcshow: '!patrolled',								rcprop: 'ids',								format: 'json',								rctitle: event.target.getAttribute('title'),								formatversion: '2',								rclimit: 'max'							}).then(function(data) {								var num = 0;								var revids = [];								while (data.query.recentchanges[num] && data.query.recentchanges[num].revid >= fromrevid && data.query.recentchanges[num].revid <= torevid) {									revids.push(data.query.recentchanges[num].revid);									num++;								}								revids.forEach(betterDiff.patrolRevision);								document.querySelector('.patrollink a').outerHTML = 'Edits patrolled: '+revids.length;							}).catch(console.log); } else { betterDiff.patrolRevision(torevid); document.querySelector('.patrollink a').outerHTML = 'Edits patrolled: 1'; }					}				});			});		},		urlEncode: function(url) { url = url || ''; url = url .replace(' ', '_') .replace(/\?/, '%3F') .replace(/\&/, '%26'); return url; },		patrolRevision: function(id) { if (can.patrol && id && tokens.patrol) { api.post({					action: 'patrol',					format: 'json',					revid: id,					token: tokens.patrol				}).catch(console.log); }		}	};	// Start when API and LIB are loaded mw.loader.using('mediawiki.diff.styles'); mw.loader.using('mediawiki.api').then(function{		mw.hook('userjs._LIB').add(function(_LIB){ lib = _LIB; betterDiff.init; });	}); });