User:NguoiDungKhongDinhDanh/FormattedEditRequest.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.
// Created on request by Enterprisey at [[WP:US/R]] ([[Special:Permalink/1080444570#2021 Q4]])

mw.loader.using(['mediawiki.diff', 'mediawiki.diff.styles', 'oojs-ui-core']).done(function() {
	if (
		!['view', 'edit'].includes(mw.config.get('wgAction')) ||
		mw.config.get('wgNamespaceNumber') < 0 ||
		mw.config.get('wgArticleId') < 1
	) {
		return;
	}
	
	var FER = {};
	FER.pagename = mw.config.get('wgPageName');
	FER.contentmodel = null;
	FER.contentmodels = ['wikitext', 'text', 'sanitized-css', 'json', 'javascript', 'css', 'Scribunto'];
	
	mw.util.addPortletLink('p-cactions', '', 'FER', 'pt-fer', 'Initialize Formatted Edit Request');
	
	$('#pt-fer').click(function(e) {
		e.preventDefault();
		
		if (!$('#FER-main').length) {
			mw.notify('Initializing, please wait...', {
				autoHide: false,
				tag: 'FER-notification'
			});
			main();
		} else {
			$('#mw-content-text, #FER-main').toggle();
		}
		
		function call(data, onsuccess, onerror) {
			$.ajax({
				url: mw.config.get('wgScriptPath') + '/api.php',
				data: data,
				type: 'POST',
				dataType: 'json',
				success: function(response) {
					onsuccess(response);
				},
				error: function(response) {
					onerror(response);
				},
			});
		}
		
		function main() {
			(new mw.Api()).postWithEditToken({
				action: 'query',
				titles: FER.pagename,
				prop: 'revisions',
				rvprop: ['content', 'contentmodel'],
				rvlimit: 1,
				format: 'json',
				formatversion: 2
			}).done(function(response) {
				FER.content = response.query.pages[0].revisions[0].content;
				FER.contentmodel = response.query.pages[0].revisions[0].contentmodel;
			
				if (!FER.contentmodels.includes(FER.contentmodel)) {
					mw.notify('Page content model is not a simple text-based one.', {
						title: 'Unallowed content model',
						type: 'error',
						autoHide: true,
						autoHideSeconds: 5,
						tag: 'FER-notification'
					});
					return;
				}
				
				$('#mw-content-text').hide();
				
				FER.nochange = 
					'<table class="diff" id="FER-nochange">' +
						'<tbody>' +
							'<tr><td colspan="1" class="diff-notice">' +
							'<div class="mw-diff-empty">(No difference)</div>' +
							'</td></tr>' +
						'</tbody>' +
					'</table>';
				FER.textarea = new OO.ui.MultilineTextInputWidget({
					value: FER.content,
					type: 'text',
					id: 'FER-textarea-div',
					inputId: 'FER-textarea'
				});
				FER.submit = new OO.ui.ButtonWidget({
					label: 'Submit',
					flags: [
						'primary',
						'progressive'
					],
					classes: 'FER-buttons',
					id: 'FER-submit'
				});
				FER.copy = new OO.ui.ButtonWidget({
					label: 'Copy',
					classes: 'FER-buttons',
					id: 'FER-copy'
				});
				FER.cancel = new OO.ui.ButtonWidget({
					label: 'Cancel',
					flags: [
						'destructive'
					],
					classes: 'FER-buttons',
					id: 'FER-cancel'
				});
				
				$('.mw-notification-tag-FER-notification').click();
				$('#mw-content-text').after(
					$('<div>').attr('id', 'FER-main').append(
						FER.textarea.$element,
						$('<div>').attr('id', 'FER-buttons').css({
							'display': 'flex',
							'padding': '5px',
							'justify-content': 'space-between'
						}),
						$('<div>').attr('id', 'FER-diff').css({
							'border': '1px solid #A2A9B1',
							'padding': '0 5px'
						}).html(FER.nochange)
					)
				);
				$('#FER-buttons').prepend(
					$('<div>').append(
						FER.submit.$element, FER.copy.$element
					),
					$('<div>').append(
						FER.cancel.$element
					)
				);
				
				$('#FER-textarea-div').css({
					'margin': 0,
					'max-width': '100%'
				});
				$('#FER-textarea').css({
					'min-height': '300px',
					'min-width': '100%',
					'resize': 'vertical',
					'font-size': 'small',
					'font-family': 'monospace, monospace'
				});
				$('#FER-buttons > .FER-buttons').css({
					'padding-left': '5px'
				});
				$('#FER-copy').css({
					'display': 'none'
				});
				
				$('#FER-submit').click(function() {
					$('#FER-diff').empty();
					$('#FER-copy').fadeOut('fast');
					
					// Submit once, please.
					$(this).removeClass('oo-ui-widget-enabled').addClass('oo-ui-widget-disabled');
					$(this).children().removeClass('oo-ui-widget-enabled').addClass('oo-ui-widget-disabled');
					
					// Notify user that FER is running
					$('#FER-copy').after(
						$('<img>').attr({
							src: 'https://upload.wikimedia.org/wikipedia/commons/5/51/Ajax-loader4.gif',
							width: 30,
							height: 30,
							alt: 'Loading...',
							id: 'FER-submitting',
						})
					);
					
					// Hidden counter.
					$('#FER-diff').append(
						$('<span>')
							.text('10')
							.attr('id', 'FER-counter')
							.css('display', 'none')
					);
					
					// Timeout after 10 seconds or so.
					FER.interval = window.setInterval(function() {
						$('#FER-counter').text($('#FER-counter').text() - 1);
						if ($('#FER-counter').text() == 0) {
							window.clearInterval(FER.interval);
							$('#FER-diff').css('border', '');
							$('#FER-counter').show().html(
								$('<div>').html(
									$('<strong>')
										.text('Cannot get diff for some reason. Please make your request manually.')
										.attr('class', 'error')
										.css('color', '#DD3333')
								).css({
									'display': 'block',
									'text-align': 'center',
									'font-size': 'larger'
								})
							);
							$('.mw-notification-tag-FER-notification').click();
							$('[id^=FER-]:not(#FER-diff, #FER-counter)').fadeOut('fast', 'swing', function() {
								$(this).remove();
							});
							window.setTimeout(function() {
								location.reload();
							}, 2000);
						}
					}, 1000);
					
					/* (new mw.Api()).get({ // Somehow mw.Api() doesn't work when countering long URLs.
						action: 'compare',
						// fromslots: 'main',
						fromtitle: FER.pagename,
						toslots: 'main',
						'totext-main': $('#FER-textarea').val(),
						format: 'json',
						formatversion: 2
					}).done(function(response) { */
					
					call({
						action: 'compare',
						fromtitle: FER.pagename,
						toslots: 'main',
						'totext-main': $('#FER-textarea').val(),
						format: 'json',
						formatversion: 2
					}, function(response) {
						console.log(response);
						window.clearInterval(FER.interval);
						
						FER.diff =
						(response.compare.body == '' ? FER.nochange :
							'<table class="diff diff-editfont-monospace" id="FER-diff-inner" ' +
							'style="margin: auto; font-size: small; overflow-wrap: break-word;">' +
								'<colgroup>'+
									'<col class="diff-marker">'+
									'<col class="diff-content">'+
									'<col class="diff-marker">'+
									'<col class="diff-content">'+
								'</colgroup>'+
								'<tbody>' + response.compare.body + '</tbody>'+
							'</table>'
						);
						
						// Revert things back to normal.
						$('#FER-diff').empty(); // Clean up old diff or #FER-nochange.
						$('#FER-diff').html(FER.diff); // Adding new one.
						$('#FER-submit').removeClass('oo-ui-widget-disabled').addClass('oo-ui-widget-enabled');
						$('#FER-submit').children().removeClass('oo-ui-widget-disabled').addClass('oo-ui-widget-enabled');
						$('#FER-submitting').remove();
						if (!($('#FER-nochange')[0] || !$('#FER-diff-inner')[0])) $('#FER-copy').fadeIn('fast'); // Show copy button.
						
						// Remove diff markers
						$('#FER-diff .diff-marker').remove();
						$('td[colspan="2"]').removeAttr('colspan');
					});
				});
						
				// Wikify and copy to clipboard.
				$('#FER-copy').click(function() {
					$('#FER-copy').fadeOut('fast');
					
					// No change, no copying.
					if ($('#FER-nochange')[0] || !$('#FER-diff-inner')[0]) {
						mw.notify('No changes were made, no diff to copy.', {
							title: 'No changes made',
							type: 'error',
							autoHide: true,
							autoHideSeconds: 5,
							tag: 'FER-notification'
						});
						return;
					}
					
					// Cloning diff.
					$('#FER-diff-inner').clone().attr('id', 'FER-clone').appendTo('#FER-diff').hide();
					
					// Replace all td > div s with nowiki s, excluding ins es and del es.
					// For attribution: //stackoverflow.com/questions/3665820#3666167
					$('#FER-clone').find('td > div:only-child').each(function() {
						var re1 = new RegExp('<'  + $(this).prop('tagName'), 'i');
						var re2 = new RegExp('</' + $(this).prop('tagName'), 'i');
						
						var newhtml = $(this).prop('outerHTML')
							.replace(re1, '<'  + 'nowiki')
							.replace(re2, '</' + 'nowiki');
						
						$(this).replaceWith(newhtml);
					});
					/* $('#FER-clone').find('ins, del').each(function() {
						var re1 = new RegExp('<('   + $(this).prop('tagName') + ')((\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>', 'i');
						var re2 = new RegExp('</('  + $(this).prop('tagName') +                                     ')>', 'i');
						
						var newhtml = $(this).prop('outerHTML')
							.replace(re1, '</nowiki><'  + '$1' + '$2><nowiki>')
							.replace(re2, '</nowiki></' + '$1' +   '><nowiki>');
						
						$(this).replaceWith(newhtml);
					});
					$('#FER-clone').find('nowiki').each(function() {
						var re = new RegExp('<nowiki></nowiki>', 'gi');
						var newhtml = $(this).prop('outerHTML')
							.replace(re, '');
						
						$(this).replaceWith(newhtml);
					}); */
					$('#FER-clone').find('colgroup').remove(); // Colgroup doesn't work in wikitext.
					
					// Wikify and advert.
					FER.clone = '<!-- Generated using w:en:User:NguoiDungKhongDinhDanh/FormattedEditRequest -->\n';
					$('#FER-clone').find('a[name^="movedpara"]').remove();
					$('#FER-clone').find('td').each(function() {
						if ($(this).html().trim() === '<br>') {
							$(this).empty();
						}
					});
					FER.clone += $('#FER-clone').show().removeAttr('id').prop('outerHTML')
						// Line break between tbody and first tr.
						.replace(/(<tbody(?:(?:\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>)(<tr(?:(?:\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>)/, '$1\n$2')
						
						// Table tag.
						.replace(/<table((\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>/, '{|$1')
						.replace(/(?<=^\{\|.+)\s*display:\s*none;/m, '')
						.replace(/<\/table>/, '|}')
						
						// Tbody tag.
						.replace(/<tbody((\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>/, '')
						.replace(/<\/tbody>/, '')
						
						// Tr tags.
						.replace(/<tr((\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>/g, '|-$2')
						.replace(/<\/tr>/g, '')
						
						// Td tags.
						.replace(/<td((\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>/g, '|$2 | ')
						.replace(/<\/td>/g, '')
						
						// Ins[ert]es and del[et]es. Somehow the functions above doesn't work.
						.replace(/<(ins|del)((\s*[^\t\n\f \/>"\'=]+="[^"]+?")*?)>/g, '</nowiki><$1$2><nowiki>')
						.replace(/<\/(ins|del)>/g, '</nowiki></$1><nowiki>')
						
						// Blank nowikis.
						.replace(/<nowiki><\/nowiki>/g, '')
						
						// Blank lines, leading spaces.
						.replace(/\n{2,}/gm, '\n')
						.replace(/^\s*/gm, '');
					
					// Copy diff to clipboard.
					// navigator.clipboard.writeText(FER.clone);
					
					// For old browsers: [[Special:Diff/1080535780]].
					// For attribution: [[User:Qwerfjkl/scripts/copy.js]]
					var $ta = $('<textarea>').text(FER.clone).css('position', 'fixed').appendTo(document.body);
					$ta[0].select();
					// No trying and catching. If this doesn't work, I quit.
					document.execCommand('copy');
					$ta.remove();
					
					// Cleanup.
					$('#FER-diff > table:nth-child(2)').remove();
					mw.notify('Diff copied to clipboard.', {
						title: 'Copied!',
						type: 'success',
						autoHide: true,
						autoHideSeconds: 5,
						tag: 'FER-notification'
					});
				});
				
				$('#FER-cancel').click(function() {
					$('#mw-content-text, #FER-main').toggle();
					$('#FER-main').remove();
				});
			});
		}
	});
});