MediaWiki:Gadget-DebateTools.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* DebateTools implements various tools for [[Wikiversity:Wikidebate]]
* License: GNU General Public License (http://www.gnu.org/licenses/gpl.html)
* Author: Sophivorus
*/
// <nowiki>
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 = $( '<div class="debatetools-argument"></div>' );
$argument.contents().filter( function () {
return this.tagName !== 'UL';
} ).wrapAll( $wrapper );
},
/**
* Add the "Add argument" button
*/
addButton: function () {
var $list = $( this );
var $link = $( '<a role="button">add argument</a>' ).css( 'color', '#3366cc' ).on( 'click', DebateTools.addArgumentForm );
var $span = $( '<span></span>' ).append( ' [ ', $link, ' ]' ).css( { 'color': '#54595d', 'font-size': 'small', 'user-select': 'none', 'white-space': 'nowrap' } );
var $item = $( '<li></li>' ).html( $span );
$list.append( $item );
},
/**
* Add the "Edit" and "Reply" buttons
*/
addButtons: function () {
var $argument = $( this );
var $button1 = $( '<a role="button">edit</a>' ).css( 'color', '#3366cc' ).on( 'click', DebateTools.addEditForm );
var $button2 = $( '<a role="button">add objection</a>' ).css( 'color', '#3366cc' ).on( 'click', DebateTools.addObjectionForm );
var $span = $( '<span></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* {{' + template + '}} ' + 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 = $( '<li class="debatetools-objection-form-item"></li>' ).html( $form );
if ( $list.length ) {
$list.prepend( $item );
} else {
$list = $( '<ul class="debatetools-objection-form-list"></ul>' ).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 + ' {{Objection}} ' + 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( ':header, .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( ':header, .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( ':header, .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( ':header, .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 );
// </nowiki>