MediaWiki:Edittools.js: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
rmv legacy check |
Make sure both ways of loading are conditional. |
||
(65 intermediate revisions by 10 users not shown) | |||
Line 1: | Line 1: | ||
/*! |
|||
// <source lang="javascript"> |
|||
* EditTools support |
|||
* |
|||
* Add a selector, change into true buttons, enable for all text input fields |
|||
* If enabled in preferences, the script puts the buttons into the WikiEditor Toolbar |
|||
The special characters to insert are defined at [[MediaWiki:Edittools]]. |
|||
* The special characters to insert are defined at [[MediaWiki:Edittools]]. |
|||
*/ |
*/ |
||
// <nowiki> |
|||
/* global jQuery, mediaWiki */ |
|||
/* eslint indent:["error","tab",{"outerIIFEBody":0}] */ |
|||
( function ( $, mw ) { |
|||
'use strict'; |
|||
var oldEdittools, |
|||
// Globals: getElementsByClassName, hookEvent, addEvent (from wiki.js) |
|||
$currentFocused, |
|||
$spec, |
|||
if (typeof (EditTools_set_focus) == 'undefined') |
|||
$sb, |
|||
var EditTools_set_focus = true; |
|||
$toolbar, |
|||
EditTools = window.EditTools = { |
|||
if (typeof (EditTools_set_focus_initially) == 'undefined') |
|||
var EditTools_set_focus_initially = EditTools_set_focus; |
|||
insertTags: function ( start, peri, end ) { |
|||
if (typeof (EditTools_initial_subset) == 'undefined') |
|||
if ( $currentFocused.length ) { |
|||
var EditTools_initial_subset = 0; |
|||
$currentFocused.textSelection( |
|||
'encapsulateSelection', { |
|||
pre: start, |
|||
peri: peri, |
|||
post: end |
|||
} |
|||
); |
|||
} |
|||
}, |
|||
createSelector: function () { |
|||
var EditTools = |
|||
var $sel; |
|||
{ |
|||
// Only care if there is more than one |
|||
createSelector : function () |
|||
if ( $sb.length <= 1 ) { return; } |
|||
{ |
|||
var spec = document.getElementById ('specialchars'); |
|||
if (!spec) return; |
|||
var sb = getElementsByClassName (spec, 'p', 'specialbasic'); |
|||
if (sb.length<=1) return; // Only care if there is more than one |
|||
var sel = document.createElement ('select'); |
|||
sel.style.display = 'inline'; |
|||
// sel.setAttribute ('onchange', 'EditTools.chooseCharSubset (selectedIndex, true);'); |
|||
// Apparently, this doesn't work on IE6. Use an explicit event handling function instead: |
|||
sel.onchange = EditTools.handleOnchange; |
|||
$sel = $( '<select>' ).on( 'change', this.chooseCharSubset ); |
|||
var initial = EditTools_initial_subset; |
|||
if (isNaN (initial) || initial < 0 || initial >= sb.length) initial = 0; |
|||
$sb.each( function ( i ) { |
|||
for (var i=0; i < sb.length; i++) { |
|||
var id = $( this ).attr( 'id' ).replace( /.([0-9A-F][0-9A-F])/g, '%$1' ).replace( /_/g, ' ' ); |
|||
var o = document.createElement ('option'); |
|||
$sel.append( |
|||
// Ugh. We have encoded Unicode characters in the names... |
|||
$( '<option value="' + i + '">' ).text( decodeURIComponent( id ) ) |
|||
var id = sb[i].id.replace (/.([0-9A-F][0-9A-F])/g, '%$1').replace(/_/g, ' '); |
|||
); |
|||
if (i == initial) o.selected = 'selected'; |
|||
} ); |
|||
o.appendChild (document.createTextNode (decodeURIComponent (id))); |
|||
sel.appendChild (o); |
|||
} |
|||
spec.insertBefore (sel, spec.firstChild); |
|||
$spec.prepend( $sel ); |
|||
EditTools.chooseCharSubset |
|||
this.chooseCharSubset(); |
|||
(initial |
|||
// Move old edittools just below |
|||
, (wgAction != 'submit') |
|||
$( '#editpage-copywarn' ).parent().before( $spec.parent() ); |
|||
&& EditTools_set_focus_initially |
|||
}, |
|||
&& (wgCanonicalNamespace != 'Special' || wgCanonicalSpecialPageName != 'Upload') |
|||
); |
|||
}, |
|||
handleOnchange : function (evt) |
|||
{ |
|||
var e = evt || window.event; // W3C, IE |
|||
var node = e.target || e.srcElement; // W3C, IE |
|||
EditTools.chooseCharSubset (node.selectedIndex, true); |
|||
return true; |
|||
}, |
|||
chooseCharSubset: function () { |
|||
var id = $spec.find( 'select' ).val(), |
|||
{ |
|||
$wanted = $sb.eq( id ); |
|||
var sb = getElementsByClassName (document.getElementById ('specialchars'), 'p', 'specialbasic'); |
|||
EditTools.makeButtons (sb[selected]); |
|||
for (var i = 0; i < sb.length ; i++) { |
|||
sb[i].style.display = i == selected ? 'inline' : 'none'; |
|||
} |
|||
if (set_focus && EditTools_set_focus) { |
|||
var txtarea = EditTools.getTextArea (); |
|||
if (txtarea) txtarea.focus (); |
|||
} |
|||
}, |
|||
$sb.hide(); |
|||
fixateWidth : function () |
|||
EditTools.makeButtons( $wanted ); |
|||
{ |
|||
$wanted.css( 'display', 'inline' ); |
|||
var edit_bar = document.getElementById ('specialchars'); |
|||
}, |
|||
if (!edit_bar) return; |
|||
// Try to fixate the width of that bar, otherwise IE6 may make it wider, which will lead to |
|||
// a table re-layout on the upload form resulting in the right column extending beyond the |
|||
// right edge of the window. |
|||
edit_bar.setAttribute ('width', "" + (edit_bar.clientWidth || edit_bar.offsetWidth)); |
|||
edit_bar.style.maxWidth = "" + (edit_bar.clientWidth || edit_bar.offsetWidth) + "px"; |
|||
// If we're inside a table, fixate the containing table cell, too. |
|||
var parent = edit_bar.parentNode; |
|||
while (parent && parent != document.body && parent.nodeName.toLowerCase () != 'td') |
|||
parent = parent.parentNode; |
|||
if (parent && parent != document.body) { |
|||
parent.setAttribute ('width', "" + (parent.clientWidth || parent.offsetWidth)); |
|||
parent.style.maxWidth = "" + (parent.clientWidth || parent.offsetWidth) + "px"; |
|||
} |
|||
}, |
|||
bindOnClick: function ( $button, self ) { |
|||
// Copy event |
|||
{ |
|||
var onclick = self.getAttribute( 'onclick' ), // TODO: outdated? For FF, IE8, Chrome |
|||
var edit_bar = section || document.getElementById ('specialchars'); |
|||
$self = $( self ), |
|||
if (!edit_bar) return; |
|||
start = $self.data( 'mw-charinsert-start' ), |
|||
var links = edit_bar.getElementsByTagName ('a'); |
|||
end = $self.data( 'mw-charinsert-end' ); |
|||
// 'links' is a *live* collection! |
|||
var b = null; |
|||
while (links.length) { |
|||
b = document.createElement ('input'); |
|||
b.type = 'button'; |
|||
b.style.fontSize = '0.9em'; |
|||
b.style.paddingLeft = '1px'; |
|||
b.style.paddingRight = '1px'; |
|||
b.style.marginLeft = '1px'; |
|||
b.onclick = links[0].onclick; |
|||
b.value = links[0].firstChild.data; |
|||
var parent = links[0].parentNode; |
|||
parent.replaceChild (b, links[0]); // This removes links[0] from links! |
|||
b.blur (); // IE6 insists on marking some buttons as having the focus... |
|||
var margin_added = false; |
|||
// Remove text nodes (nodeType == Node.TEXT_NODE, but IE6 doesn't know that...) |
|||
// Insert some spacing where desired. |
|||
while (b.nextSibling && b.nextSibling.nodeType == 3) { |
|||
if (!margin_added && b.nextSibling.data.search(/\S/) >= 0) { |
|||
b.style.marginRight = '4px'; |
|||
margin_added = true; |
|||
} |
|||
parent.removeChild (b.nextSibling); |
|||
} |
|||
} |
|||
}, |
|||
if ( !$.isFunction( onclick ) ) { |
|||
enableForAllFields : function () |
|||
if ( start || end ) { |
|||
{ |
|||
// Create new event |
|||
if (typeof (insertTags) != 'function') return; |
|||
onclick = function ( e ) { |
|||
// insertTags from the site-wide /skins-1.5/common/edit.js just inserts in the first |
|||
e.preventDefault(); |
|||
// textarea in the document. Evidently, that's not good if we have multiple textareas. |
|||
EditTools.insertTags( start, '', end ); |
|||
// My first idea was to simply add a hidden textarea as the first one, and redefine |
|||
}; |
|||
// insertTags such that it copied first the last active textareas contents over to that hidden |
|||
// Shorten button text |
|||
// field, set the cursor or selection there, let the standard insertTags do its thing, and |
|||
if ( start && end ) { $button.text( $self.text().replace( end, '' ) ); } |
|||
// then copy the hidden field's text, cursor position and selection back to the currently |
|||
} else if ( !onclick && $.isFunction( $._data ) ) { |
|||
// active field. Unfortunately, that is just as complex as simply copying the whole code |
|||
// Fallback hack for backward compatibility |
|||
// from wikibits to here and let it work on the right text field in the first place. |
|||
onclick = $._data( self, 'events' ).click; |
|||
var texts = document.getElementsByTagName ('textarea'); |
|||
if ( $.isArray( onclick ) && onclick.length ) { |
|||
onclick = onclick[ 0 ].handler; |
|||
addEvent (texts[i], 'focus', EditTools.registerTextField); |
|||
} |
|||
} |
|||
} |
|||
// While we're at it, also enable it for input fields |
|||
} |
|||
texts = document.getElementsByTagName ('input'); |
|||
$button.on( 'click', onclick ); |
|||
for (var i = 0; i < texts.length; i++) { |
|||
}, |
|||
if (texts[i].type == 'text') addEvent (texts[i], 'focus', EditTools.registerTextField); |
|||
} |
|||
insertTags = EditTools.insertTags; // Redefine the global insertTags |
|||
}, |
|||
makeButtons: function ( $wanted ) { |
|||
last_active_textfield : null, |
|||
var $links = $wanted.find( 'a' ), |
|||
self = this; |
|||
$links.each( function () { |
|||
var $button = $( '<button type="button">' ) |
|||
{ |
|||
.text( $( this ).text() ); |
|||
var e = evt || window.event; |
|||
var node = e.target || e.srcElement; |
|||
if (!node) return; |
|||
EditTools.last_active_textfield = node.id; |
|||
return true; |
|||
}, |
|||
self.bindOnClick( $button, this ); |
|||
getTextArea : function () |
|||
{ |
|||
var txtarea = null; |
|||
if (EditTools.last_active_textfield && EditTools.last_active_textfield != "") |
|||
txtarea = document.getElementById (EditTools.last_active_textfield); |
|||
if (!txtarea) { |
|||
// Fallback option: old behaviour |
|||
if (document.editform) { |
|||
txtarea = document.editform.wpTextbox1; |
|||
} else { |
|||
// Some alternate form? Take the first one we can find |
|||
txtarea = document.getElementsByTagName ('textarea'); |
|||
if (txtarea.length > 0) txtarea = txtarea[0]; else txtarea = null; |
|||
} |
|||
} |
|||
return txtarea; |
|||
}, |
|||
insertTags : function (tagOpen, tagClose, sampleText) |
|||
{ |
|||
var txtarea = EditTools.getTextArea (); |
|||
if (!txtarea) return; |
|||
$( this ).replaceWith( $button ).blur(); |
|||
/* Usability initiative compatibility */ |
|||
} ); |
|||
if ( typeof $j != 'undefined' && typeof $j.fn.textSelection != 'undefined' ) { |
|||
$wanted.contents().not( 'button' ).remove(); |
|||
$j( txtarea ).textSelection( |
|||
}, |
|||
'encapsulateSelection', { 'pre': tagOpen, 'peri': sampleText, 'post': tagClose } |
|||
); |
|||
return; |
|||
} |
|||
makeToolbarButtons: function () { |
|||
var selText, isSample = false; |
|||
EditTools.done = 1; |
|||
var section = [], |
|||
self = this; |
|||
// Add Edittool section |
|||
function checkSelectedText () { |
|||
$toolbar.wikiEditor( 'addToToolbar', { |
|||
if (!selText) { |
|||
sections: { |
|||
selText = sampleText; isSample = true; |
|||
Edittools: { |
|||
} else if (selText.charAt (selText.length - 1) == ' ') { // Exclude ending space char |
|||
type: 'booklet', |
|||
selText = selText.substring (0, selText.length - 1); |
|||
label: 'Edittools', |
|||
tagClose += ' ' |
|||
pages: { |
|||
} |
|||
Edittools1: { |
|||
} |
|||
layout: 'characters', |
|||
label: 'Edittools2' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} ); |
|||
$sb.eq( 0 ).find( 'a' ) |
|||
if (document.selection && document.selection.createRange) { // IE/Opera |
|||
.each( function () { |
|||
// Save window scroll position |
|||
var $button = $( '<span>' ) |
|||
var winScroll = 0; |
|||
.text( $( this ).text() ); |
|||
if (document.documentElement && document.documentElement.scrollTop) |
|||
self.bindOnClick( $button, this ); |
|||
winScroll = document.documentElement.scrollTop; |
|||
section.push( $button ); |
|||
else if (document.body) |
|||
} ); |
|||
winScroll = document.body.scrollTop; |
|||
$( '.page-Edittools1 div' ) |
|||
// Get current selection |
|||
.append( section ) |
|||
txtarea.focus(); |
|||
.addClass( 'com-editbuttons' ); |
|||
var range = document.selection.createRange(); |
|||
selText = range.text; |
|||
// Insert tags |
|||
checkSelectedText (); |
|||
range.text = tagOpen + selText + tagClose; |
|||
// Mark sample text as selected |
|||
if (isSample && range.moveStart) { |
|||
if (window.opera) tagClose = tagClose.replace (/\n/g, ""); |
|||
range.moveStart( 'character', - tagClose.length - selText.length); |
|||
range.moveEnd ('character', - tagClose.length); |
|||
} |
|||
range.select (); |
|||
// Restore window scroll position |
|||
if (document.documentElement && document.documentElement.scrollTop) |
|||
document.documentElement.scrollTop = winScroll; |
|||
else if (document.body) |
|||
document.body.scrollTop = winScroll; |
|||
} else if (txtarea.selectionStart || txtarea.selectionStart == '0') { // Mozilla |
|||
// Save textarea scroll position |
|||
var textScroll = txtarea.scrollTop; |
|||
// Get current selection |
|||
txtarea.focus(); |
|||
var startPos = txtarea.selectionStart; |
|||
var endPos = txtarea.selectionEnd; |
|||
selText = txtarea.value.substring (startPos, endPos); |
|||
// Insert tags |
|||
checkSelectedText (); |
|||
txtarea.value = txtarea.value.substring (0, startPos) |
|||
+ tagOpen + selText + tagClose |
|||
+ txtarea.value.substring (endPos); |
|||
// Set new selection |
|||
if (isSample) { |
|||
txtarea.selectionStart = startPos + tagOpen.length; |
|||
txtarea.selectionEnd = startPos + tagOpen.length + selText.length; |
|||
} else { |
|||
txtarea.selectionStart = startPos + tagOpen.length + selText.length + tagClose.length; |
|||
txtarea.selectionEnd = txtarea.selectionStart; |
|||
} |
|||
// Restore textarea scroll position |
|||
txtarea.scrollTop = textScroll; |
|||
} |
|||
}, // end insertTags |
|||
// Must start after toolbar creation |
|||
setup : function () |
|||
this.createSelector(); |
|||
{ |
|||
// $( '.mw-editTools' ).remove(); // The full remove is not implicit and there is more as only the standard buttons |
|||
EditTools.fixateWidth (); |
|||
}, |
|||
EditTools.createSelector (); |
|||
EditTools.enableForAllFields (); |
|||
} |
|||
enableForAllFields: function () { |
|||
} // end EditTools |
|||
$currentFocused = $toolbar; |
|||
// Apply to dynamically created textboxes as well as normal ones |
|||
$( document ).on( 'focus', 'textarea, input:text, .CodeMirror', function () { |
|||
// CodeMirror hooks into #wpTextbox1 for textSelection changes |
|||
$currentFocused = $( this ); |
|||
if ( $currentFocused.hasClass( 'CodeMirror' ) ) { $currentFocused = $( '#wpTextbox1' ); } |
|||
} ); |
|||
}, |
|||
// As elements from ext.wikiEditor are not immediately ready on load. |
|||
// Do not use addOnloadHook; it runs *before* the onload event fires. At that time, onclick or |
|||
handleToolbarQueue: function () { |
|||
// onfocus handlers may not yet be set up properly. |
|||
// FIXME: the current sync load is a bit hackish (double try) [[phab:T30563]] |
|||
hookEvent ('load', EditTools.setup); |
|||
// Try early as possible to put the event |
|||
$toolbar.on( 'wikiEditor-toolbar-doneInitialSections', function () { |
|||
if ( !EditTools.done ) { EditTools.makeToolbarButtons(); } |
|||
} ); |
|||
// Try again if we missed the event. |
|||
mw.loader.using( 'ext.wikiEditor', function () { |
|||
if ( !EditTools.done ) { EditTools.makeToolbarButtons(); } |
|||
} ); |
|||
}, |
|||
setup: function () { |
|||
// </source> |
|||
mw.loader.load( '//commons.wikimedia.org/?title=MediaWiki:Edittools.css&action=raw&ctype=text/css', 'text/css' ); |
|||
$sb = $spec.find( 'p.specialbasic' ); |
|||
// Decide whether to use the toolbar |
|||
if ( $toolbar && $toolbar[ 0 ] && !( window.oldEdittools || oldEdittools ) && !$( '#wpUploadDescription' ).length ) { |
|||
this.handleToolbarQueue(); |
|||
} else { |
|||
this.createSelector(); |
|||
} |
|||
mw.hook( 'wikipage.content' ).add( this.enableForAllFields ); |
|||
} |
|||
}; |
|||
$( function () { |
|||
$spec = $( '#specialchars' ); |
|||
// Don't do anything if no edittools present. |
|||
if ( !$spec.length ) { return; } |
|||
mw.loader.using( 'user.options', function () { |
|||
// Check user preferences |
|||
oldEdittools = mw.user.options.get( 'gadget-OldEdittools' ); |
|||
if ( ( mw.user.options.get( 'usebetatoolbar' ) || mw.loader.getState( 'ext.wikiEditor' ) !== 'registered' ) && !oldEdittools ) { |
|||
$toolbar = $( '#wpTextbox1' ); |
|||
EditTools.setup(); |
|||
} |
|||
} ); |
|||
} ); |
|||
}( jQuery, mediaWiki ) ); |
|||
// </nowiki> |
Latest revision as of 19:58, 9 March 2019
/*!
* EditTools support
*
* Add a selector, change into true buttons, enable for all text input fields
* If enabled in preferences, the script puts the buttons into the WikiEditor Toolbar
* The special characters to insert are defined at [[MediaWiki:Edittools]].
*/
// <nowiki>
/* global jQuery, mediaWiki */
/* eslint indent:["error","tab",{"outerIIFEBody":0}] */
( function ( $, mw ) {
'use strict';
var oldEdittools,
$currentFocused,
$spec,
$sb,
$toolbar,
EditTools = window.EditTools = {
insertTags: function ( start, peri, end ) {
if ( $currentFocused.length ) {
$currentFocused.textSelection(
'encapsulateSelection', {
pre: start,
peri: peri,
post: end
}
);
}
},
createSelector: function () {
var $sel;
// Only care if there is more than one
if ( $sb.length <= 1 ) { return; }
$sel = $( '<select>' ).on( 'change', this.chooseCharSubset );
$sb.each( function ( i ) {
var id = $( this ).attr( 'id' ).replace( /.([0-9A-F][0-9A-F])/g, '%$1' ).replace( /_/g, ' ' );
$sel.append(
$( '<option value="' + i + '">' ).text( decodeURIComponent( id ) )
);
} );
$spec.prepend( $sel );
this.chooseCharSubset();
// Move old edittools just below
$( '#editpage-copywarn' ).parent().before( $spec.parent() );
},
chooseCharSubset: function () {
var id = $spec.find( 'select' ).val(),
$wanted = $sb.eq( id );
$sb.hide();
EditTools.makeButtons( $wanted );
$wanted.css( 'display', 'inline' );
},
bindOnClick: function ( $button, self ) {
// Copy event
var onclick = self.getAttribute( 'onclick' ), // TODO: outdated? For FF, IE8, Chrome
$self = $( self ),
start = $self.data( 'mw-charinsert-start' ),
end = $self.data( 'mw-charinsert-end' );
if ( !$.isFunction( onclick ) ) {
if ( start || end ) {
// Create new event
onclick = function ( e ) {
e.preventDefault();
EditTools.insertTags( start, '', end );
};
// Shorten button text
if ( start && end ) { $button.text( $self.text().replace( end, '' ) ); }
} else if ( !onclick && $.isFunction( $._data ) ) {
// Fallback hack for backward compatibility
onclick = $._data( self, 'events' ).click;
if ( $.isArray( onclick ) && onclick.length ) {
onclick = onclick[ 0 ].handler;
}
}
}
$button.on( 'click', onclick );
},
makeButtons: function ( $wanted ) {
var $links = $wanted.find( 'a' ),
self = this;
$links.each( function () {
var $button = $( '<button type="button">' )
.text( $( this ).text() );
self.bindOnClick( $button, this );
$( this ).replaceWith( $button ).blur();
} );
$wanted.contents().not( 'button' ).remove();
},
makeToolbarButtons: function () {
EditTools.done = 1;
var section = [],
self = this;
// Add Edittool section
$toolbar.wikiEditor( 'addToToolbar', {
sections: {
Edittools: {
type: 'booklet',
label: 'Edittools',
pages: {
Edittools1: {
layout: 'characters',
label: 'Edittools2'
}
}
}
}
} );
$sb.eq( 0 ).find( 'a' )
.each( function () {
var $button = $( '<span>' )
.text( $( this ).text() );
self.bindOnClick( $button, this );
section.push( $button );
} );
$( '.page-Edittools1 div' )
.append( section )
.addClass( 'com-editbuttons' );
// Must start after toolbar creation
this.createSelector();
// $( '.mw-editTools' ).remove(); // The full remove is not implicit and there is more as only the standard buttons
},
enableForAllFields: function () {
$currentFocused = $toolbar;
// Apply to dynamically created textboxes as well as normal ones
$( document ).on( 'focus', 'textarea, input:text, .CodeMirror', function () {
// CodeMirror hooks into #wpTextbox1 for textSelection changes
$currentFocused = $( this );
if ( $currentFocused.hasClass( 'CodeMirror' ) ) { $currentFocused = $( '#wpTextbox1' ); }
} );
},
// As elements from ext.wikiEditor are not immediately ready on load.
handleToolbarQueue: function () {
// FIXME: the current sync load is a bit hackish (double try) [[phab:T30563]]
// Try early as possible to put the event
$toolbar.on( 'wikiEditor-toolbar-doneInitialSections', function () {
if ( !EditTools.done ) { EditTools.makeToolbarButtons(); }
} );
// Try again if we missed the event.
mw.loader.using( 'ext.wikiEditor', function () {
if ( !EditTools.done ) { EditTools.makeToolbarButtons(); }
} );
},
setup: function () {
mw.loader.load( '//commons.wikimedia.org/?title=MediaWiki:Edittools.css&action=raw&ctype=text/css', 'text/css' );
$sb = $spec.find( 'p.specialbasic' );
// Decide whether to use the toolbar
if ( $toolbar && $toolbar[ 0 ] && !( window.oldEdittools || oldEdittools ) && !$( '#wpUploadDescription' ).length ) {
this.handleToolbarQueue();
} else {
this.createSelector();
}
mw.hook( 'wikipage.content' ).add( this.enableForAllFields );
}
};
$( function () {
$spec = $( '#specialchars' );
// Don't do anything if no edittools present.
if ( !$spec.length ) { return; }
mw.loader.using( 'user.options', function () {
// Check user preferences
oldEdittools = mw.user.options.get( 'gadget-OldEdittools' );
if ( ( mw.user.options.get( 'usebetatoolbar' ) || mw.loader.getState( 'ext.wikiEditor' ) !== 'registered' ) && !oldEdittools ) {
$toolbar = $( '#wpTextbox1' );
EditTools.setup();
}
} );
} );
}( jQuery, mediaWiki ) );
// </nowiki>