MediaWiki:Gadget-DebateTools.js

/** * DebateTools implements various tools for Wikidebate * License: GNU General Public License (http://www.gnu.org/licenses/gpl.html) * Author: Sophivorus */ // var DebateTools = {

/**	 * Will hold the wikitext of the current page */	pageWikitext: '',

/**	 * Initialization script */	init: function { DebateTools.getPageWikitext.done( function {

var $arguments = DebateTools.getArguments; $arguments.each( DebateTools.wrapArgument ); $arguments.each( DebateTools.addButtons ); var $argumentLists = DebateTools.getArgumentLists; $argumentLists.each( DebateTools.addButton ); } );	},

/**	 * Get all the arguments in the page */	getArguments: function { return $( '#mw-content-text' ).find( '.argument' ).closest( 'li' ); },

/**	 * Get all the lists of arguments in the page */	getArgumentLists: function { return $( '#mw-content-text' ).find( '.argument' ).first.closest( 'ul' ).siblings( 'ul' ).addBack.filter( function {			return $( this ).find( '.argument' ).length > 0; // Ignore lists in ==See also==, etc.		} ); },

/**	 * Wrap the contents of the argument but exclude any objections */	wrapArgument: function { var $argument = $( this ); var $wrapper = $( ' ' ); $argument.contents.filter( function {			return this.tagName !== 'UL';		} ).wrapAll( $wrapper ); },

/**	 * Add the "Add argument" button */	addButton: function { var $list = $( this ); var $link = $( ' add argument' ).css( 'color', '#3366cc' ).on( 'click', DebateTools.addArgumentForm ); var $span = $( ' ' ).append( ' [ ', $link, ' ]' ).css( { 'color': '#54595d', 'font-size': 'small', 'user-select': 'none', 'white-space': 'nowrap' } ); var $item = $( '' ).html( $span ); $list.append( $item ); },

/**	 * Add the "Edit" and "Reply" buttons */	addButtons: function { var $argument = $( this ); var $button1 = $( ' edit' ).css( 'color', '#3366cc' ).on( 'click', DebateTools.addEditForm ); var $button2 = $( ' add objection' ).css( 'color', '#3366cc' ).on( 'click', DebateTools.addObjectionForm ); var $span = $( ' ' ).append( ' [ ', $button1, ' | ', $button2, ' ]' ).css( { 'color': '#54595d', 'font-size': 'small', 'user-select': 'none', 'white-space': 'nowrap' } ); $argument.children( '.debatetools-argument' ).append( $span ); },

/**	 * Add the form to publish a new argument */	addArgumentForm: function { var $button = $( this );

var stance = DebateTools.getRelevantStance( $button ); var stanceInput; if ( stance ) { stanceInput = new OO.ui.HiddenInputWidget( { name: 'stance', value: stance } ); } else { stanceInput = new OO.ui.RadioSelectInputWidget( { name: 'stance', options: [	           { data: 'for', label: 'Argument for' },	            { data: 'against', label: 'Argument against' }        	] } ); }		var stanceLayout = new OO.ui.HorizontalLayout( { items: [ stanceInput ] } );

// Make the rest of the form var placeholder = 'Type your argument. Please be concise. You can use wikitext if necessary.'; var wikitextInput = new OO.ui.MultilineTextInputWidget( { name: 'wikitext', placeholder: placeholder, autosize: true } ); var wikitextLayout = new OO.ui.HorizontalLayout( { items: [ wikitextInput ] } ); var publishButton = new OO.ui.ButtonInputWidget( { label: 'Publish', flags: [ 'primary', 'progressive' ] } ); var cancelButton = new OO.ui.ButtonInputWidget( { label: 'Cancel', flags: 'destructive', framed: false } ); var formLayout = new OO.ui.FormLayout( { items: [ stanceLayout, wikitextLayout, publishButton, cancelButton ], classes: [ 'debatetools-argument-form' ] } );

// CSS tweaks formLayout.$element.css( 'overflow', 'hidden' ); stanceInput.$element.css( { 'vertical-align': 'top' } ); wikitextInput.$element.css( { 'font-family': 'monospace', 'max-width': '100%', 'vertical-align': 'top' } );

// Add the form to the DOM var $item = $button.closest( 'li' ); var $form = formLayout.$element; $item.html( $form ); $form.find( 'textarea[name="wikitext"]' ).focus;

// Handle the submit publishButton.on( 'click', DebateTools.onSubmitArgument, [ $form, publishButton, cancelButton ] );

// Handle the cancel cancelButton.on( 'click', function ( $item ) {			var $list = $item.closest( 'ul' );			$item.remove;			DebateTools.addButton.call( $list );		}, [ $item ] );

return false; },

/**	 * Handle the submission of a new argument */	onSubmitArgument: function ( $form, publishButton, cancelButton ) {

// If no text was entered, just focus the input field to hint the user what to do		var $input = $form.find( 'textarea[name="wikitext"]' ); var input = $input.val.replace( /\n/g, ' ' ).trim; if ( !input ) { $input.focus; return; }

// Disable the buttons to prevent further clicks and to signal the user that something's happening publishButton.setDisabled( true ); cancelButton.setDisabled( true );

// Build the wikitext of the argument var stance = $form.find( 'input[name="stance"]' ).val; var template = stance === 'for' ? 'Argument for' : 'Argument against'; var argumentWikitext = '\n* ' + input;

// Add the argument in the right place var $last = $form.closest( 'ul' ).find( '.debatetools-argument' ).last; var lastWikitext = DebateTools.getArgumentWikitext( $last ); if ( !lastWikitext ) { // @todo Handle error }		DebateTools.pageWikitext = DebateTools.pageWikitext.replace( lastWikitext, lastWikitext + argumentWikitext );

// Make the edit summary var section = DebateTools.getRelevantSection( $form ); var summary = ( section ? '/* ' + section + ' */ ' : '' ) + 'Add argument ' + stance + ' #DebateTools';

// Save the changes var params = { action: 'edit', title: mw.config.get( 'wgPageName' ), text: DebateTools.pageWikitext, summary: summary };		new mw.Api.postWithEditToken( params ).done( function {			DebateTools.onSubmitArgumentSuccess( argumentWikitext, $form );		} ).fail( console.log ); // @todo Handle errors },

/**	 * Handle a successful new objection */	onSubmitArgumentSuccess: function ( argumentWikitext, $form ) { var params = { action: 'parse', title: mw.config.get( 'wgPageName' ), text: argumentWikitext, formatversion: 2, prop: 'text' };		new mw.Api.get( params ).done( function ( data ) {			var $argument = $( data.parse.text ).find( 'li' );			var $item = $form.closest( 'li' );			var $list = $item.closest( 'ul' );			$item.replaceWith( $argument );			DebateTools.wrapArgument.call( $argument );			DebateTools.addButtons.call( $argument );			DebateTools.addButton.call( $list );		} ); },

/**	 * Add the form to publish a new objection */	addObjectionForm: function {

// Remove any previous form $( '.debatetools-objection-form-item' ).remove; $( '.debatetools-objection-form-list' ).remove;

// Make a new form var placeholder = 'Type your objection. Please be concise. You can use wikitext if necessary.'; var wikitextInput = new OO.ui.MultilineTextInputWidget( { name: 'wikitext', placeholder: placeholder, autosize: true } ); var wikitextLayout = new OO.ui.HorizontalLayout( { items: [ wikitextInput ] } ); var publishButton = new OO.ui.ButtonInputWidget( { label: 'Publish', flags: [ 'primary', 'progressive' ] } ); var cancelButton = new OO.ui.ButtonInputWidget( { label: 'Cancel', flags: 'destructive', framed: false } ); var formLayout = new OO.ui.FormLayout( { items: [ wikitextLayout, publishButton, cancelButton ], classes: [ 'debatetools-objection-form' ] } );

// CSS tweaks formLayout.$element.css( 'overflow', 'hidden' ); wikitextInput.$element.css( { 'font-family': 'monospace', 'max-width': '100%', 'vertical-align': 'top' } );

// Add the form to the DOM var $button = $( this ); var $argument = $button.closest( '.debatetools-argument' ); var $list = $argument.next( 'ul' ); var $form = formLayout.$element; var $item = $( '' ).html( $form ); if ( $list.length ) { $list.prepend( $item ); } else { $list = $( '' ).html( $item ); $argument.after( $list ); }		$item.find( 'textarea[name="wikitext"]' ).focus;

// Handle the submit publishButton.on( 'click', DebateTools.onSubmitObjection, [ $form, publishButton, cancelButton ] );

// Handle the cancel cancelButton.on( 'click', function ( $item, $list ) {			$item.remove;			$list.filter( '.debatetools-objection-form-list' ).remove;		}, [ $item, $list ] );

return false; },

/**	 * Handle the submission of a new objection */	onSubmitObjection: function ( $form, publishButton, cancelButton ) {

// If no text was entered, just focus the input field to hint the user what to do		var $input = $form.find( 'textarea[name="wikitext"]' ); var input = $input.val.replace( /\n/g, ' ' ).trim; if ( !input ) { $input.focus; return; }

// Disable the buttons to prevent further clicks and to signal the user that something's happening publishButton.setDisabled( true ); cancelButton.setDisabled( true );

// Add the objection in the proper place var $argument = $form.closest( 'ul' ).prev( '.debatetools-argument' ); var argumentWikitext = DebateTools.getArgumentWikitext( $argument ); if ( !argumentWikitext ) { // @todo Handle error }		var argumentDepth = $argument.parents( 'li' ).length; var objectionDepth = argumentDepth + 1; var objectionAsterisks = '*'.repeat( objectionDepth ); var objectionWikitext = '\n' + objectionAsterisks + ' ' + input; DebateTools.pageWikitext = DebateTools.pageWikitext.replace( argumentWikitext, argumentWikitext + objectionWikitext );

// Make the edit summary var section = DebateTools.getRelevantSection( $form ); var summary = ( section ? '/* ' + section + ' */ ' : '' ) + 'Add objection #DebateTools';

// Save the changes var params = { action: 'edit', title: mw.config.get( 'wgPageName' ), text: DebateTools.pageWikitext, summary: summary };		new mw.Api.postWithEditToken( params ).done( function {			DebateTools.onSubmitObjectionSuccess( objectionWikitext, $form );		} ).fail( console.log ); // @todo Handle errors },

/**	 * Handle a successful new objection */	onSubmitObjectionSuccess: function ( objectionWikitext, $form ) { var params = { action: 'parse', title: mw.config.get( 'wgPageName' ), text: objectionWikitext, formatversion: 2, prop: 'text' };		new mw.Api.get( params ).done( function ( data ) {			var $objection = $( data.parse.text ).find( 'li' ).last;			var $item = $form.closest( 'li' );			var $list = $item.closest( 'ul' );			$item.replaceWith( $objection );			DebateTools.wrapArgument.call( $objection );			DebateTools.addButtons.call( $objection );			$list.removeAttr( 'class' ); // Remove any traces of the objection form		} ); },

/**	 * Add the edit form on demand */	addEditForm: function {

// Get the wikitext of the argument to edit var $button = $( this ); var $argument = $button.closest( '.debatetools-argument' ); var argumentWikitext = DebateTools.getArgumentWikitext( $argument ); if ( !argumentWikitext ) { // @todo Handle error }

// Extract the editable wikitext and the leading asterisks var matches = argumentWikitext.match( /^\n(\**) *\{\{[^}]+\}\} */ ); var prefix = matches[0]; var editableWikitext = argumentWikitext.replace( prefix, '' ); var asterisks = matches[1];

// Make the form var wikitextInput = new OO.ui.MultilineTextInputWidget( { name: 'wikitext', value: editableWikitext, autosize: true } ); var wikitextLayout = new OO.ui.HorizontalLayout( { items: [ wikitextInput ] } ); var publishButton = new OO.ui.ButtonInputWidget( { label: 'Publish', flags: [ 'primary', 'progressive' ] } ); var cancelButton = new OO.ui.ButtonInputWidget( { label: 'Cancel', flags: 'destructive', framed: false } ); var formLayout = new OO.ui.FormLayout( { items: [ wikitextLayout, publishButton, cancelButton ], classes: [ 'debatetools-edit-form' ] } );

// CSS tweaks formLayout.$element.css( 'overflow', 'hidden' ); wikitextInput.$element.css( { 'font-family': 'monospace', 'max-width': '100%', 'vertical-align': 'top' } );

// Make the form and add it to the DOM var $form = formLayout.$element; var $item = $argument.closest( 'li' ); $argument.detach; $item.prepend( $form ); $form.find( 'textarea[name="wikitext"]' ).focus;

// Handle the submit publishButton.on( 'click', DebateTools.onEditArgument, [ $form, $argument, publishButton, cancelButton, editableWikitext, prefix, asterisks ] );

// Handle the cancel cancelButton.on( 'click', function {			$form.replaceWith( $argument );		} );

return false; },

/**	 * Handle an argument edit */	onEditArgument: function ( $form, $argument, publishButton, cancelButton, editableWikitext, prefix, asterisks ) {

// If nothing changed, just close the form var input = $form.find( 'textarea[name="wikitext"]' ).val.replace( /\n/g, ' ' ).trim; if ( input === editableWikitext ) { $form.replaceWith( $argument ); return; }

// Disable the buttons to prevent further clicks and to signal the user that something's happening publishButton.setDisabled( true ); cancelButton.setDisabled( true );

// Make the edit summary var section = DebateTools.getRelevantSection( $form ); var action = input ? 'Edit' : 'Delete'; var type = asterisks.length === 1 ? 'argument' : 'objection'; var stance = DebateTools.getRelevantStance( $form ); var summary = ( section ? '/* ' + section + ' */ ' :  ) + action + ' ' + type + ( stance ? ' ' + stance :  ) + ' #DebateTools';

var oldWikitext = prefix + editableWikitext; var newWikitext = input ? prefix + input : ''; DebateTools.pageWikitext = DebateTools.pageWikitext.replace( oldWikitext, newWikitext ); var params = { action: 'edit', title: mw.config.get( 'wgPageName' ), text: DebateTools.pageWikitext, summary: summary };		new mw.Api.postWithEditToken( params ).done( function {			DebateTools.onEditArgumentSuccess( newWikitext, $form );		} ).fail( console.log ); // @todo Handle errors },

/**	 * Handle a successful objection edit */	onEditArgumentSuccess: function ( newWikitext, $form ) { // If the argument was deleted, remove it but not the objections if ( !newWikitext ) { var $item = $form.closest( 'li' ); var $objections = $item.find( 'ul' ); if ( $objections.length ) { $form.remove; } else { $item.remove; }			return; }

var params = { action: 'parse', title: mw.config.get( 'wgPageName' ), text: newWikitext, formatversion: 2, prop: 'text' };		new mw.Api.get( params ).done( function ( data ) {			var argument = $( data.parse.text ).find( 'li' ).last.contents;			var $argument = $( argument );			$form.replaceWith( $argument );			var $item = $argument.closest( 'li' );			DebateTools.wrapArgument.call( $item );			DebateTools.addButtons.call( $item );		} ); },

/**	 * Get the wikitext of the current page */	getPageWikitext: function { var params = { page: mw.config.get( 'wgPageName' ), action: 'parse', prop: 'wikitext', formatversion: 2, };		return new mw.Api.get( params ).done( function ( data ) {			DebateTools.pageWikitext = data.parse.wikitext;		} ); },

/**	 * Helper method to get the relevant wikitext that corresponds to the given argument */	getArgumentWikitext: function ( $argument ) { // The longest text node has the most chances of being unique var text = DebateTools.getLongestText( $argument );

// Some may happen if the argument is just a link if ( !text ) { return; }

// Match all lines that contain the text text = text.replace( /[.*+?^${}|[\]\\]/g, '\\$&' ); // Escape special characters var regexp = new RegExp( '\n.*' + text + '.*', 'g' ); var matches = DebateTools.pageWikitext.match( regexp );

// This may happen if the argument comes from a template if ( !matches ) { return; }

// This may happen if the longest text is very short and repeats somewhere else if ( matches.length > 1 ) { return; }

// We got our relevant wikitext line return matches[0]; },

/**	 * Helper method to get the text of the longest text node */	getLongestText: function ( $argument ) { var text = ''; var $textNodes = $argument.contents.filter( function {			return this.nodeType === Node.TEXT_NODE;		} ); $textNodes.each( function {			var nodeText = $( this ).text.trim;			if ( nodeText.length > text.length ) {				text = nodeText;			}		} ); return text; },

getRelevantStance: function ( $element ) { if ( $element.attr( 'id' ) === 'mw-content-text' ) { return; }		var text; if ( $element.is( ':header, .mw-heading' ) ) { text = $element.find( '.mw-headline' ).text; if ( text === 'Pro' ) { return 'for'; }			if ( text === 'Con' ) { return 'against'; }		}		var $previous = $element.prevAll( ':header, .mw-heading' ).first; if ( $previous.length ) { text = $previous.find( '.mw-headline' ).text; if ( text === 'Pro' || text === 'Arguments for' ) { return 'for'; }			if ( text === 'Con' || text === 'Arguments against' ) { return 'against'; }		}		var $parent = $element.parent; return DebateTools.getRelevantStance( $parent ); },

getRelevantSection: function ( $element ) { if ( $element.attr( 'id' ) === 'mw-content-text' ) { return; }		var text; if ( $element.is( ':header, .mw-heading' ) ) { text = $element.find( '.mw-headline' ).text; if ( text !== 'Pro' && text !== 'Con' || text === 'Arguments for' || text === 'Arguments against' ) { return text; }		}		var $previous = $element.prevAll( ':header, .mw-heading' ); for ( var i = 0; i < $previous.length; i++ ) { text = $previous.eq( i ).find( '.mw-headline' ).text; if ( text !== 'Pro' && text !== 'Con' || text === 'Arguments for' || text === 'Arguments against' ) { return text; }		}		var $parent = $element.parent; return DebateTools.getRelevantSection( $parent ); } };

mw.loader.using( [	'mediawiki.api',	'oojs-ui-core',	'oojs-ui-widgets' ], DebateTools.init ); //