MediaWiki:Edittools.js: Difference between revisions

From Wikimedia Commons, the free media repository
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

/*
*
EditTools support: add a selector, change into true buttons, enable for all text input fields
* 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 (selected, set_focus)
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";
}
},


makeButtons : function (section)
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');
for (var i = 0; i < texts.length; i++) {
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;


registerTextField : function (evt)
$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>