Difference between revisions of "MediaWiki:Gadget-MarkupFormatting.js"

From Yugipedia
Jump to: navigation, search
(Add button to the edit toolbar, instead of having only the keyboard shortcut.)
(make sure there's no whitespace before list markup)
 
(26 intermediate revisions by 2 users not shown)
Line 3: Line 3:
 
  * Original author is Deltaneos.
 
  * Original author is Deltaneos.
 
  * Adapted to gadget by Becasita.
 
  * Adapted to gadget by Becasita.
  * @author Deltaneos, Becasita
+
* Further improvements by Dinoguy1000.
  * @contact [[User:Becasita]]
+
  * @author Deltaneos, Becasita, Dinoguy1000
 +
  * @contact [[User:Becasita]] <https://yugipedia.com/wiki/User:Becasita>
 +
* @contact [[User:Dinoguy1000]] <https://yugipedia.com/wiki/User:Dinoguy1000>
 
  */
 
  */
 
( function _gadgetMarkupFormatting( window, $, mw, console ) {
 
( function _gadgetMarkupFormatting( window, $, mw, console ) {
 
"use strict";
 
"use strict";
  
var LAST_LOG = '23:10, 28 June 2019 (UTC)';
+
var LAST_LOG = '13:11, 17 February 2024 (UTC)';
  
 
/**
 
/**
Line 17: Line 19:
 
// Get the selected text or all text
 
// Get the selected text or all text
 
var selected_text = (window.getSelection().toString()) ? window.getSelection().toString() : text;
 
var selected_text = (window.getSelection().toString()) ? window.getSelection().toString() : text;
var cleaned_text = '';
+
var cleaned_text, pos, max_pos = -1, j;
var pos           = 0;
 
var max_pos       = 0;
 
var diff          = 0;
 
var j             = 0;
 
var space        = '';
 
  
// Reduce the spacing around the "=" in each parameter to a single space on either side.
+
// Normalize whitespace and put each line in an array
// Put each line in an array
+
var lines = selected_text
var lines         = selected_text.replace(/\t/, ' ').replace(/^\s*\|\s*([^\s=]+)\s*/gm, '| $1 ').split('\n');
+
// paranoia
 +
.replace(/\r\n/g, '\n')
 +
// collapse multiple newlines
 +
// "[^\S\n]" selects all whitespace but newlines <https://stackoverflow.com/a/3469155>
 +
.replace(/(?:\n[^\S\n]*){3,}/g, '\n\n')
 +
// replace all non-newline whitespace characters (except the ideographic space) with U+0020 spaces
 +
.replace(/[^\S\n \u3000]/g, ' ')
 +
// collapse multiple spaces
 +
.replace(/  +/g, ' ')
 +
// remove spaces from the ends of lines
 +
.replace(/ +$/gm, '')
 +
// normalize whitespace within template parameter lines
 +
.replace(/^ ?\| ?([^=\n]+?) ?= ?([^\}])/gm, '| $1 = $2')
 +
// split by line
 +
.split('\n');
  
 
// Loop through each line to find the furthest out "=".
 
// Loop through each line to find the furthest out "=".
 
for (var i = 0; i < lines.length; i++)
 
for (var i = 0; i < lines.length; i++)
 
{
 
{
pos = (lines[i].indexOf('|') === 0) ? lines[i].indexOf('=') : -1;
+
pos = lines[i].startsWith('|') ? lines[i].indexOf(' = ') : -1;
if (pos > max_pos) max_pos = pos;
+
if (pos > max_pos) { max_pos = pos; }
 
}
 
}
  
Line 38: Line 49:
 
for (i = 0; i < lines.length; i++)
 
for (i = 0; i < lines.length; i++)
 
{
 
{
// Get the number of spaces to add
+
// Only touch lines that start with a pipe and have an equals sign (but not {{=}})
pos    = (lines[i].indexOf('|') === 0) ? lines[i].indexOf('=') : -1;
+
if (lines[i].startsWith('|') && lines[i].indexOf(' = ') > 0)
diff    = max_pos - pos;
 
space  = '';
 
// Add the spaces
 
if (String.prototype.repeat) // ES6; not supported in IE
 
 
{
 
{
space = ' '.repeat(diff);
+
// Get the number of spaces to add
 +
pos  = lines[i].indexOf(' = ');
 +
lines[i] = lines[i].substring(0, pos) + ' '.repeat(max_pos - pos) + lines[i].substring(pos);
 
}
 
}
else
 
{
 
space = Array(diff + 1).join(' ');
 
}
 
// Append the reformatted line into a new string for the reformatted text
 
cleaned_text += (lines[i].indexOf('|') === 0) ? lines[i].replace('=', space+'=') : lines[i];
 
if (i != lines.length - 1) cleaned_text += '\n'; // add a line break, unless this is the last line
 
 
}
 
}
  
// Get the text inside each set of gallery tags
+
cleaned_text = lines.join('\n');
var galleries = cleaned_text.match(/<gallery[^>]*>(\n(.*))*?<\/gallery>/g);
 
var gallery_lines;
 
var cleaned_gallery_text;
 
galleries = galleries ? galleries : [];
 
  
for (i = 0; i < galleries.length; i++)
+
// Check for gallery tags
 +
if (cleaned_text.indexOf('<gallery') > -1)
 
{
 
{
// Reset some values lingering from previous iterations
+
// Get the text inside each set of gallery tags
cleaned_gallery_text = '';
+
var galleries = cleaned_text.match(/<gallery[^>]*>(\n[^<][^\n]*)+\n<\/gallery>/g);
pos = max_pos = 0;
+
var gallery_lines;
  
// Reduce the spacing around the "|" in each line to a single space.
+
for (i = 0; i < galleries.length; i++)
// Put each line in an array.
+
{
gallery_lines = galleries[i].replace(/\t/, ' ').replace(/^([^\|]*[^ ])\s*\|\s*/gm, '$1 \| ').split('\n');
+
// Reset max_pos from previous iterations
 +
max_pos = -1;
  
// Loop through each line to find the furthest out "|"
+
// Reduce the spacing around the "|" in each line to a single space.
for (j = 0; j < gallery_lines.length; j++)
+
// Put each line in an array.
{
+
gallery_lines = galleries[i].replace(/^([^\|]+?) ?\| ?/gm, '$1 \| ').split('\n');
pos = gallery_lines[j].indexOf('|');
 
if (pos > max_pos) max_pos = pos;
 
}
 
  
// Loop through each line again
+
// Loop through each line to find the furthest out "|"
for (j = 0; j < gallery_lines.length; j++)
+
for (j = 0; j < gallery_lines.length; j++)
{
 
// Get the amount of space to add
 
pos    = gallery_lines[j].indexOf('|');
 
diff    = max_pos - pos;
 
space  = '';
 
if (String.prototype.repeat) // ES6; not supported in IE
 
 
{
 
{
space = ' '.repeat(diff);
+
pos = gallery_lines[j].indexOf('|');
 +
if (pos > max_pos) { max_pos = pos; }
 
}
 
}
else
+
 
 +
// Check if this gallery needs aligned (some galleries may not have anything but filenames)
 +
if (max_pos > -1)
 
{
 
{
space = Array(diff + 1).join(' ');
+
// Loop through each line again
 +
for (j = 0; j < gallery_lines.length; j++)
 +
{
 +
if (gallery_lines[j].indexOf('|') > 0) {
 +
// Get the amount of space to add
 +
pos  = gallery_lines[j].indexOf('|');
 +
gallery_lines[j] = gallery_lines[j].substring(0, pos) + ' '.repeat(max_pos - pos) + gallery_lines[j].substring(pos);
 +
}
 +
}
 
}
 
}
  
// Append the reformatted line into a new string for the reformatted text
+
cleaned_text = cleaned_text.replace(galleries[i], gallery_lines.join('\n'));
cleaned_gallery_text += gallery_lines[j].indexOf('|') ? gallery_lines[j].replace('|', space+'|') : gallery_lines[j];
 
if (j != gallery_lines.length - 1) cleaned_gallery_text += '\n'; // add a line break, unless this is the last line
 
 
}
 
}
 +
}
  
cleaned_text = cleaned_text.replace(galleries[i], cleaned_gallery_text);
+
// Final changes
}
+
cleaned_text = cleaned_text
 +
// add space between list markup and list item content
 +
.replace(/^ ?([*#:;]+) ?/gm, '$1 ')
 +
// fix [[wikipedia:MOS:LISTGAP]] issues
 +
.replace(/^([*#:;][^\n]+)\n+(?=[*#:;])/gm, '$1\n')
 +
// add space around header content
 +
.replace(/^(=+) ?(.+?) ?\1$/gm, '$1 $2 $1')
 +
// ensure each header is preceded by an empty line
 +
.replace(/\n+((=+) .* \2)/gm, '\n\n$1')
 +
// ensure each header is followed by content on the next line
 +
.replace(/^((=+) .* \2)\n+/gm, '$1\n');
  
 
// Replace the old text with the new text
 
// Replace the old text with the new text
Line 108: Line 119:
  
 
function doAlign( $textBox ) {
 
function doAlign( $textBox ) {
$textBox.text( function( index, text ) {
+
$textBox.val( function( index, text ) {
 
return align( text );
 
return align( text );
 
} );
 
} );
Line 139: Line 150:
 
} );
 
} );
  
$( '#wikiEditor-ui-toolbar' )
+
if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
.find( '.tabs' )
+
// Enhanced edit toolbar:
.append( $button )
+
$( '#wikiEditor-ui-toolbar' )
;
+
.find( '.tabs' )
 +
.append( $button )
 +
;
 +
} else {
 +
// Classic toolbar:
 +
$( '#toolbar' ).append(
 +
$( '<div>' )
 +
.addClass( 'mw-toolbar-editbutton' )
 +
.addClass( 'mw-toolbar-editbutton--custom' )
 +
.append( $button )
 +
);
 +
}
  
 
/**
 
/**
Line 166: Line 188:
 
}
 
}
  
mw.hook( 'wikipage.editform' ).add( init );
+
mw.hook( 'wikipage.editform' ).add( function() {
 +
if( mw.config.get('wgNamespaceNumber') !== 10) {
 +
mw.loader.using( 'mediawiki.toolbar' ).then( init );
 +
}
 +
} );
  
 
console.log( '[Gadget] MarkupFormatting last updated at', LAST_LOG );
 
console.log( '[Gadget] MarkupFormatting last updated at', LAST_LOG );
  
 
} )( window, window.jQuery, window.mediaWiki, window.console );
 
} )( window, window.jQuery, window.mediaWiki, window.console );

Latest revision as of 13:11, 17 February 2024

/**
 * Prettifies template transclusions and gallery tags.
 * Original author is Deltaneos.
 * Adapted to gadget by Becasita.
 * Further improvements by Dinoguy1000.
 * @author Deltaneos, Becasita, Dinoguy1000
 * @contact [[User:Becasita]] <https://yugipedia.com/wiki/User:Becasita>
 * @contact [[User:Dinoguy1000]] <https://yugipedia.com/wiki/User:Dinoguy1000>
 */
( function _gadgetMarkupFormatting( window, $, mw, console ) {
	"use strict";

	var LAST_LOG = '13:11, 17 February 2024 (UTC)';

	/**
	 * By Deltaneos
	 */
	function align( text ) {
		// Get the selected text or all text
		var selected_text = (window.getSelection().toString()) ? window.getSelection().toString() : text;
		var cleaned_text, pos, max_pos = -1, j;

		// Normalize whitespace and put each line in an array
		var lines = selected_text
						// paranoia
						.replace(/\r\n/g, '\n')
						// collapse multiple newlines
						// "[^\S\n]" selects all whitespace but newlines <https://stackoverflow.com/a/3469155>
						.replace(/(?:\n[^\S\n]*){3,}/g, '\n\n')
						// replace all non-newline whitespace characters (except the ideographic space) with U+0020 spaces
						.replace(/[^\S\n \u3000]/g, ' ')
						// collapse multiple spaces
						.replace(/  +/g, ' ')
						// remove spaces from the ends of lines
						.replace(/ +$/gm, '')
						// normalize whitespace within template parameter lines
						.replace(/^ ?\| ?([^=\n]+?) ?= ?([^\}])/gm, '| $1 = $2')
						// split by line
						.split('\n');

		// Loop through each line to find the furthest out "=".
		for (var i = 0; i < lines.length; i++)
		{
			pos = lines[i].startsWith('|') ? lines[i].indexOf(' = ') : -1;
			if (pos > max_pos) { max_pos = pos; }
		}

		// Loop through each line again
		for (i = 0; i < lines.length; i++)
		{
			// Only touch lines that start with a pipe and have an equals sign (but not {{=}})
			if (lines[i].startsWith('|') && lines[i].indexOf(' = ') > 0)
			{
				// Get the number of spaces to add
				pos  = lines[i].indexOf(' = ');
				lines[i] = lines[i].substring(0, pos) + ' '.repeat(max_pos - pos) + lines[i].substring(pos);
			}
		}

		cleaned_text = lines.join('\n');

		// Check for gallery tags
		if (cleaned_text.indexOf('<gallery') > -1)
		{
			// Get the text inside each set of gallery tags
			var galleries = cleaned_text.match(/<gallery[^>]*>(\n[^<][^\n]*)+\n<\/gallery>/g);
			var gallery_lines;

			for (i = 0; i < galleries.length; i++)
			{
				// Reset max_pos from previous iterations
				max_pos = -1;

				// Reduce the spacing around the "|" in each line to a single space.
				// Put each line in an array.
				gallery_lines = galleries[i].replace(/^([^\|]+?) ?\| ?/gm, '$1 \| ').split('\n');

				// Loop through each line to find the furthest out "|"
				for (j = 0; j < gallery_lines.length; j++)
				{
					pos = gallery_lines[j].indexOf('|');
					if (pos > max_pos) { max_pos = pos; }
				}

				// Check if this gallery needs aligned (some galleries may not have anything but filenames)
				if (max_pos > -1)
				{
					// Loop through each line again
					for (j = 0; j < gallery_lines.length; j++)
					{
						if (gallery_lines[j].indexOf('|') > 0) {
							// Get the amount of space to add
							pos  = gallery_lines[j].indexOf('|');
							gallery_lines[j] = gallery_lines[j].substring(0, pos) + ' '.repeat(max_pos - pos) + gallery_lines[j].substring(pos);
						}
					}
				}

				cleaned_text = cleaned_text.replace(galleries[i], gallery_lines.join('\n'));
			}
		}

		// Final changes
		cleaned_text = cleaned_text
								// add space between list markup and list item content
								.replace(/^ ?([*#:;]+) ?/gm, '$1 ')
								// fix [[wikipedia:MOS:LISTGAP]] issues
								.replace(/^([*#:;][^\n]+)\n+(?=[*#:;])/gm, '$1\n')
								// add space around header content
								.replace(/^(=+) ?(.+?) ?\1$/gm, '$1 $2 $1')
								// ensure each header is preceded by an empty line
								.replace(/\n+((=+) .* \2)/gm, '\n\n$1')
								// ensure each header is followed by content on the next line
								.replace(/^((=+) .* \2)\n+/gm, '$1\n');

		// Replace the old text with the new text
		return text.replace(selected_text, cleaned_text);
	}

	function doAlign( $textBox ) {
		$textBox.val( function( index, text ) {
			return align( text );
		} );
	}

	function init() {
		var $editBox = $( '#wpTextbox1' );

		if ( !$editBox.length ) {
			window.MARKUP_FORMATTING_ONKEYDOWN_SET = function() {
				// Nothing, just to prevent errors.
			};

			return;
		}

		var $button = $( '<span>', {
			id: 'ca-alignText',
			'class': 'tab',
			html: $( '<a>', {
				href: '#',
				title: 'Align text.',
				text: 'Align text',
			} ),
			click: function( e ) {
				e.preventDefault();

				doAlign( $editBox );
			},
		} );

		if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
			// Enhanced edit toolbar:
			$( '#wikiEditor-ui-toolbar' )
				.find( '.tabs' )
					.append( $button )
			;
		} else {
			// Classic toolbar:
			$( '#toolbar' ).append(
				$( '<div>' )
					.addClass( 'mw-toolbar-editbutton' )
					.addClass( 'mw-toolbar-editbutton--custom' )
					.append( $button )
			);
		}

		/**
		 * To add a keyboard shortcut, add the following to Special:MyPage/common.js
		 * ```
		 *	mw.loader.using( 'ext.gadget.MarkupFormatting' ).then( function() {
		 *		window.MARKUP_FORMATTING_ONKEYDOWN_SET( function( e ) {
		 *			return // predicate.
		 *		} );
		 *	} );
		 * ```
		 * @param {function} keysPredicate Callback that receives
		 * an onKeydown event and returns a boolean indicating
		 * when the keydown listeners should be triggered.
		 */
		window.MARKUP_FORMATTING_ONKEYDOWN_SET = function( keysPredicate ) {
			$editBox.keydown( function( e ) {
				if ( keysPredicate( e ) ) {
					doAlign( $editBox );
				}
			} );
		};
	}

	mw.hook( 'wikipage.editform' ).add( function() {
		if( mw.config.get('wgNamespaceNumber') !== 10) {
			mw.loader.using( 'mediawiki.toolbar' ).then( init );
		}
	} );

	console.log( '[Gadget] MarkupFormatting last updated at', LAST_LOG );

} )( window, window.jQuery, window.mediaWiki, window.console );