mediawiki-extensions-WikiEd.../modules/jquery.wikiEditor.dialogs.config.js
Ed Sanders 79151572ae Remove unnecessary local context variables
Change-Id: Ia8e9d9b750f3d9cc1ab9859afe0cb5c6084058df
2024-11-15 12:47:27 +00:00

958 lines
35 KiB
JavaScript

/**
* Configuration of Dialog module for wikiEditor
*
* @private
*/
const toolbarModule = require( './jquery.wikiEditor.toolbar.js' ),
InsertLinkTitleInputField = require( './insertlink/TitleInputField.js' ),
LinkTextField = require( './insertlink/LinkTextField.js' ),
LinkTypeField = require( './insertlink/LinkTypeField.js' ),
insertLinkTitleInputField = new InsertLinkTitleInputField(),
insertLinkLinkTextField = new LinkTextField(),
insertLinkLinkTypeField = new LinkTypeField(),
configData = require( './data.json' );
function triggerButtonClick( $element ) {
// The dialog action should always be a DOMElement.
const dialogAction = $element.data( 'dialogaction' );
const $button = dialogAction ? $( dialogAction ) : $element.find( 'button' ).first();
// Since we're reading from data attribute, make sure we got an element before clicking.
// Note when closing a dialog this can be false leading to TypeError: $button.trigger is not a function
// (T261529)
if ( $button ) {
$button.trigger( 'click' );
}
}
module.exports = {
/**
* @param {jQuery} $textarea
* @memberof module:ext.wikiEditor
*/
replaceIcons: function ( $textarea ) {
$textarea
.wikiEditor( 'addToToolbar', {
section: 'main',
group: 'insert',
tools: {
link: {
label: mw.msg( 'wikieditor-toolbar-tool-link' ),
type: 'button',
oouiIcon: 'link',
action: {
type: 'dialog',
module: 'insert-link'
},
hotkey: 75 // K
},
file: {
label: mw.msg( 'wikieditor-toolbar-tool-file' ),
type: 'button',
oouiIcon: 'image',
action: {
type: 'dialog',
module: 'insert-file'
}
}
}
} )
.wikiEditor( 'addToToolbar', {
section: 'advanced',
group: 'insert',
tools: {
table: {
label: mw.msg( 'wikieditor-toolbar-tool-table' ),
type: 'button',
oouiIcon: 'table',
action: {
type: 'dialog',
module: 'insert-table'
}
}
}
} )
.wikiEditor( 'addToToolbar', {
section: 'advanced',
groups: {
search: {
tools: {
replace: {
label: mw.msg( 'wikieditor-toolbar-tool-replace' ),
type: 'button',
oouiIcon: 'articleSearch',
action: {
type: 'dialog',
module: 'search-and-replace'
}
}
}
}
}
} );
},
/**
* @return {Object}
* @memberof module:ext.wikiEditor
*/
getDefaultConfig: function () {
return { dialogs: {
'insert-link': {
title: mw.message( 'wikieditor-toolbar-tool-link-title' ).parse(),
id: 'wikieditor-toolbar-link-dialog',
html: $( '<fieldset>' ).append(
insertLinkTitleInputField.$element,
insertLinkLinkTextField.$element,
insertLinkLinkTypeField.$element
),
init: function () {
/**
* Convenience function for enabling/disabling the main insert button. This is a workaround for
* the fact that the button isn't yet in the DOM when init() is run, so we have to query for it
* in the event handlers.
*
* @param {boolean} enable Whether to enable or disable the button
*/
const setButtonState = function ( enable ) {
$( '.wikieditor-toolbar-tool-link-insert' ).button( 'option', 'disabled', !enable );
};
// Automatically copy the value of the internal link page title field to the link text field unless the
// user has changed the link text field - this is a convenience thing since most link texts are going to
// be the same as the page title.
insertLinkTitleInputField.connect( this, {
change: function ( val ) {
insertLinkLinkTypeField.setIsExternal( insertLinkTitleInputField.isExternal() );
insertLinkLinkTextField.setValueIfUntouched( val );
setButtonState( val !== '' );
},
invalid: function () {
setButtonState( false );
}
} );
// Tell the title input field when the internal/external radio changes.
insertLinkLinkTypeField.connect( this, {
change: function ( isExternal ) {
const urlMode = isExternal ?
LinkTypeField.static.LINK_MODE_EXTERNAL :
LinkTypeField.static.LINK_MODE_INTERNAL;
insertLinkTitleInputField.setUrlMode( urlMode );
}
} );
},
dialog: {
width: 500,
dialogClass: 'wikiEditor-toolbar-dialog',
buttons: {
'wikieditor-toolbar-tool-link-insert': {
class: 'wikieditor-toolbar-tool-link-insert',
text: mw.msg( 'wikieditor-toolbar-tool-link-insert' ),
click: function () {
function escapeInternalText( s ) {
return s.replace( /(\]{2,})/g, '<nowiki>$1</nowiki>' );
}
function escapeExternalTarget( s ) {
return s.replace( / /g, '%20' )
.replace( /\[/g, '%5B' )
.replace( /\]/g, '%5D' );
}
function escapeExternalText( s ) {
return s.replace( /(\]+)/g, '<nowiki>$1</nowiki>' );
}
// Make sure that this button isn't disabled.
if ( $( '.wikieditor-toolbar-tool-link-insert' ).button( 'option', 'disabled' ) ) {
return;
}
let target = insertLinkTitleInputField.getField().getValue();
let text = insertLinkLinkTextField.getField().getValue();
if ( text.trim() === '' ) {
// [[Foo| ]] creates an invisible link
// Instead, generate [[Foo|]]
text = '';
}
let insertText = '';
if ( insertLinkLinkTypeField.isInternal() ) {
if ( target === text || !text.length ) {
insertText = '[[' + target + ']]';
} else {
insertText = '[[' + target + '|' + escapeInternalText( text ) + ']]';
}
} else {
target = target.trim();
// Prepend http:// if there is no protocol
if ( !/^[a-z]+:\/\/./.test( target ) ) {
target = 'http://' + target;
}
// Detect if this is really an internal link in disguise
const match = target.match( $( this ).data( 'articlePathRegex' ) );
if ( match && !$( this ).data( 'ignoreLooksInternal' ) ) {
const buttons = {};
const linkDialog = this;
buttons[ mw.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal-int' ) ] =
function () {
let titleValue = match[ 1 ];
try {
titleValue = decodeURI( titleValue );
} catch ( ex ) {
// Ignore invalid URLs; use plain titleValue instead.
}
insertLinkTitleInputField.getField().setValue( titleValue );
insertLinkTitleInputField.setUrlMode( LinkTypeField.static.LINK_MODE_INTERNAL );
$( this ).dialog( 'close' );
// Select the first match (i.e. the value set above) so that the
// message under the title field will be updated correctly.
insertLinkTitleInputField.getField().selectFirstMatch();
};
buttons[ mw.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal-ext' ) ] =
function () {
$( linkDialog ).data( 'ignoreLooksInternal', true );
$( linkDialog ).closest( '.ui-dialog' ).find( 'button' ).first().trigger( 'click' );
$( linkDialog ).data( 'ignoreLooksInternal', false );
$( this ).dialog( 'close' );
};
$.wikiEditor.modules.dialogs.quickDialog(
mw.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal', match[ 1 ] ),
{ buttons: buttons }
);
return;
}
const escTarget = escapeExternalTarget( target );
const escText = escapeExternalText( text );
if ( escTarget === escText ) {
insertText = escTarget;
} else if ( text === '' ) {
insertText = '[' + escTarget + ']';
} else {
insertText = '[' + escTarget + ' ' + escText + ']';
}
}
const whitespace = $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace' );
// Preserve whitespace in selection when replacing
if ( whitespace ) {
insertText = whitespace[ 0 ] + insertText + whitespace[ 1 ];
}
$( this ).dialog( 'close' );
toolbarModule.fn.doAction( $( this ).data( 'context' ), {
type: 'replace',
options: {
pre: insertText
}
}, $( this ) );
// Blank form
insertLinkTitleInputField.reset();
insertLinkLinkTextField.getField().setValue( '' );
insertLinkLinkTypeField.getField().selectItem( null );
}
},
'wikieditor-toolbar-tool-link-cancel': function () {
$( this ).dialog( 'close' );
}
},
open: function () {
// Obtain the server name without the protocol. wgServer may be protocol-relative
const serverName = mw.config.get( 'wgServer' ).replace( /^(https?:)?\/\//, '' );
// Cache the articlepath regex
$( this ).data( 'articlePathRegex', new RegExp(
'^https?://' + mw.util.escapeRegExp( serverName + mw.config.get( 'wgArticlePath' ) )
.replace( /\\\$1/g, '(.*)' ) + '$'
) );
// Pre-fill the text fields based on the current selection
const context = $( this ).data( 'context' );
const selection = context.$textarea.textSelection( 'getSelection' );
insertLinkTitleInputField.getField().focus();
// Trigger the change event, so the link status indicator is up to date.
// It may be triggered again for the selection, below.
insertLinkTitleInputField.getField().emit( 'change', insertLinkTitleInputField.getField().getValue() );
$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ '', '' ] );
if ( selection !== '' ) {
let matches, target, text, isExternal;
if ( ( matches = selection.match( /^(\s*)\[\[([^\]|]+)(\|([^\]|]*))?\]\](\s*)$/ ) ) ) {
// [[foo|bar]] or [[foo]]
target = matches[ 2 ];
text = ( matches[ 4 ] ? matches[ 4 ] : matches[ 2 ] );
isExternal = false;
// Preserve whitespace when replacing
$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ matches[ 1 ], matches[ 5 ] ] );
} else if ( ( matches = selection.match( /^(\s*)\[([^\] ]+)( ([^\]]+))?\](\s*)$/ ) ) ) {
// [http://www.example.com foo] or [http://www.example.com]
target = matches[ 2 ];
text = ( matches[ 4 ] || '' );
isExternal = true;
// Preserve whitespace when replacing
$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ matches[ 1 ], matches[ 5 ] ] );
} else {
// Trim any leading and trailing whitespace from the selection,
// but preserve it when replacing
target = text = selection.trim();
if ( target.length < selection.length ) {
$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [
selection.slice( 0, selection.indexOf( target.charAt( 0 ) ) ),
selection.slice( selection.lastIndexOf( target.charAt( target.length - 1 ) ) + 1 ) ]
);
}
}
// Change the values of the title and text fields to the parts extracted from the selection.
if ( typeof target !== 'undefined' ) {
insertLinkTitleInputField.getField().setValue( target );
}
if ( typeof text !== 'undefined' ) {
insertLinkLinkTextField.getField().setValue( text );
}
// Don't overwrite values from user's selection.
insertLinkLinkTextField.setTouched( true );
if ( typeof isExternal !== 'undefined' ) {
const urlMode = isExternal ?
LinkTypeField.static.LINK_MODE_EXTERNAL :
LinkTypeField.static.LINK_MODE_INTERNAL;
insertLinkTitleInputField.setUrlMode( urlMode );
}
}
if ( !$( this ).data( 'dialogkeypressset' ) ) {
$( this ).data( 'dialogkeypressset', true );
// Execute the action associated with the first button
// when the user presses Enter
const $dialog = $( this ).closest( '.ui-dialog' );
$dialog.on( 'keypress', ( e ) => {
if ( ( e.keyCode || e.which ) === 13 ) {
triggerButtonClick( $dialog );
e.preventDefault();
}
} );
// Make tabbing to a button and pressing
// Enter do what people expect
$dialog.find( 'button' ).on( 'focus', ( e ) => {
$dialog.data( 'dialogaction', e.delegateTarget );
} );
}
}
}
},
'insert-file': {
title: mw.message( 'wikieditor-toolbar-tool-file-title' ).parse(),
id: 'wikieditor-toolbar-file-dialog',
htmlTemplate: 'dialogInsertFile.html',
init: function () {
const magicWordsI18N = configData.magicWords;
$( this ).find( '[data-i18n-magic]' )
.text( function () {
return magicWordsI18N[ $( this ).attr( 'data-i18n-magic' ) ][ 0 ];
} )
.removeAttr( 'data-i18n-magic' );
const defaultMsg = mw.msg( 'wikieditor-toolbar-file-default' );
$( this ).find( '#wikieditor-toolbar-file-size' )
.attr( 'placeholder', defaultMsg )
// The message may be long in some languages
.attr( 'size', defaultMsg.length );
$( this ).find( '[rel]' )
.text( function () {
// eslint-disable-next-line mediawiki/msg-doc
return mw.msg( $( this ).attr( 'rel' ) );
} )
.removeAttr( 'rel' );
const altHelpText = mw.msg( 'wikieditor-toolbar-file-alt-help' );
const altHelpLabel = mw.msg( 'wikieditor-toolbar-file-alt-help-label' );
// Expandable help message for 'alt text' field
const $altHelp = $( this ).find( '.wikieditor-toolbar-file-alt-help' )
.text( altHelpLabel )
.on( 'click', () => {
$altHelp.text( ( i, text ) => text === altHelpLabel ? altHelpText : altHelpLabel );
} );
// Preload modules of file upload dialog.
mw.loader.load( [
'mediawiki.ForeignStructuredUpload.BookletLayout',
'mediawiki.Upload.Dialog',
'oojs-ui-windows'
] );
},
dialog: {
resizable: false,
dialogClass: 'wikiEditor-toolbar-dialog',
width: 590,
buttons: {
'wikieditor-toolbar-tool-file-insert': function () {
const hasPxRgx = /.+px$/,
magicWordsI18N = configData.magicWords;
let fileName = $( '#wikieditor-toolbar-file-target' ).val();
const caption = $( '#wikieditor-toolbar-file-caption' ).val();
const fileAlt = $( '#wikieditor-toolbar-file-alt' ).val();
const fileFloat = $( '#wikieditor-toolbar-file-float' ).val();
const fileFormat = $( '#wikieditor-toolbar-file-format' ).val();
let fileSize = $( '#wikieditor-toolbar-file-size' ).val();
const whitespace = $( '#wikieditor-toolbar-file-dialog' ).data( 'whitespace' );
// Append px to end to size if not already contains it
if ( fileSize !== '' && !hasPxRgx.test( fileSize ) ) {
fileSize += 'px';
}
if ( fileName !== '' ) {
let fileTitle = mw.Title.newFromText( fileName );
// Append file namespace prefix to filename if not already contains it
if ( fileTitle && fileTitle.getNamespaceId() !== 6 ) {
fileTitle = mw.Title.makeTitle( 6, fileName );
}
if ( fileTitle ) {
fileName = fileTitle.toText();
}
}
let options = [ fileSize, fileFormat, fileFloat ];
// Filter empty values
options = options.filter( ( val ) => val.length && val !== 'default' );
if ( fileAlt.length ) {
options.push( magicWordsI18N.img_alt[ 0 ].replace( '$1', fileAlt ) );
}
if ( caption.length ) {
options.push( caption );
}
const fileUse = options.length === 0 ? fileName : ( fileName + '|' + options.join( '|' ) );
$( this ).dialog( 'close' );
toolbarModule.fn.doAction(
$( this ).data( 'context' ),
{
type: 'replace',
options: {
pre: whitespace[ 0 ] + '[[',
peri: fileUse,
post: ']]' + whitespace[ 1 ],
ownline: true
}
},
$( this )
);
// Restore form state
$( [ '#wikieditor-toolbar-file-target',
'#wikieditor-toolbar-file-caption',
'#wikieditor-toolbar-file-alt',
'#wikieditor-toolbar-file-size' ].join( ',' )
).val( '' );
$( '#wikieditor-toolbar-file-float' ).val( 'default' );
$( '#wikieditor-toolbar-file-format' ).val( magicWordsI18N.img_thumbnail[ 0 ] );
},
'wikieditor-toolbar-tool-file-cancel': function () {
$( this ).dialog( 'close' );
},
'wikieditor-toolbar-tool-file-upload': function () {
$( this ).dialog( 'close' );
mw.loader.using( [
'mediawiki.ForeignStructuredUpload.BookletLayout',
'mediawiki.Upload.Dialog',
'oojs-ui-windows'
] ).then( () => {
const windowManager = new OO.ui.WindowManager(),
uploadDialog = new mw.Upload.Dialog( {
bookletClass: mw.ForeignStructuredUpload.BookletLayout
} );
windowManager.$element.appendTo( document.body );
windowManager.addWindows( [ uploadDialog ] );
windowManager.openWindow( uploadDialog );
uploadDialog.uploadBooklet.on( 'fileSaved', ( imageInfo ) => {
uploadDialog.close();
windowManager.$element.remove();
const context = $( this ).data( 'context' );
$.wikiEditor.modules.dialogs.api.openDialog( context, 'insert-file' );
$( '#wikieditor-toolbar-file-target' ).val( imageInfo.canonicaltitle );
} );
} );
}
},
open: function () {
const magicWordsI18N = configData.magicWords,
fileData = {
pre: '',
post: '',
fileName: '',
caption: '',
fileAlt: '',
fileSize: '',
fileFloat: 'default',
fileFormat: magicWordsI18N.img_thumbnail[ 0 ]
};
const parseFileSyntax = function ( wikitext ) {
const escapedPipe = '\u0001';
if ( wikitext.indexOf( escapedPipe ) !== -1 ) {
return false;
}
const match = /^(\s*)\[\[(.*)\]\](\s*)$/.exec( wikitext );
if ( !match ) {
return false;
}
// Escape pipes inside links and templates,
// then split the parameters at the remaining pipes
const params = match[ 2 ].replace( /\[\[[^[\]]*\]\]|\{\{[^{}]\}\}/g, ( link ) => link.replace( /\|/g, escapedPipe ) ).split( '|' );
const file = mw.Title.newFromText( params[ 0 ] );
if ( !file || file.getNamespaceId() !== 6 ) {
return false;
}
const result = {
pre: match[ 1 ],
post: match[ 3 ],
fileName: file.getMainText()
};
for ( let i = 1; i < params.length; i++ ) {
const paramOrig = params[ i ];
const param = paramOrig.toLowerCase();
if ( magicWordsI18N.img_right.indexOf( param ) !== -1 ) {
result.fileFloat = magicWordsI18N.img_right[ 0 ];
} else if ( magicWordsI18N.img_left.indexOf( param ) !== -1 ) {
result.fileFloat = magicWordsI18N.img_left[ 0 ];
} else if ( magicWordsI18N.img_none.indexOf( param ) !== -1 ) {
result.fileFloat = magicWordsI18N.img_none[ 0 ];
} else if ( magicWordsI18N.img_center.indexOf( param ) !== -1 ) {
result.fileFloat = magicWordsI18N.img_center[ 0 ];
} else if ( magicWordsI18N.img_thumbnail.indexOf( param ) !== -1 ) {
result.fileFormat = magicWordsI18N.img_thumbnail[ 0 ];
} else if ( magicWordsI18N.img_framed.indexOf( param ) !== -1 ) {
result.fileFormat = magicWordsI18N.img_framed[ 0 ];
} else if ( magicWordsI18N.img_frameless.indexOf( param ) !== -1 ) {
result.fileFormat = magicWordsI18N.img_frameless[ 0 ];
} else if ( magicWordsI18N.img_alt.indexOf( param.split( '=', 2 )[ 0 ] + '=$1' ) !== -1 ) {
result.fileAlt = paramOrig.split( '=', 2 )[ 1 ];
} else if ( /.+px$/.test( param ) ) {
result.fileSize = param.replace( /px$/, '' );
} else if ( param === '' ) {
continue;
} else if ( i === params.length - 1 ) { // Last param -> caption
result.caption = paramOrig.replace( new RegExp( mw.util.escapeRegExp( escapedPipe ), 'g' ), '|' );
} else { // Unknown param
return false;
}
}
if ( !result.fileFormat ) {
result.fileFormat = 'default';
}
return result;
};
// Retrieve the current selection
const context = $( this ).data( 'context' );
const selection = context.$textarea.textSelection( 'getSelection' );
// Pre-fill the text fields based on the current selection
if ( selection !== '' ) {
Object.assign( fileData, parseFileSyntax( selection ) );
}
// Initialize the form fields
$( '#wikieditor-toolbar-file-dialog' )
.data( 'whitespace', [ fileData.pre, fileData.post ] );
$( '#wikieditor-toolbar-file-target' ).val( fileData.fileName );
$( '#wikieditor-toolbar-file-caption' ).val( fileData.caption );
$( '#wikieditor-toolbar-file-alt' ).val( fileData.fileAlt );
$( '#wikieditor-toolbar-file-float' ).val( fileData.fileFloat );
$( '#wikieditor-toolbar-file-format' ).val( fileData.fileFormat );
$( '#wikieditor-toolbar-file-size' ).val( fileData.fileSize );
// Set focus
$( '#wikieditor-toolbar-file-target' ).trigger( 'focus' );
if ( !( $( this ).data( 'dialogkeypressset' ) ) ) {
$( this ).data( 'dialogkeypressset', true );
// Execute the action associated with the first button
// when the user presses Enter
const $dialog = $( this ).closest( '.ui-dialog' );
$dialog.on( 'keypress', ( e ) => {
if ( e.which === 13 ) {
triggerButtonClick( $dialog );
e.preventDefault();
}
} );
// Make tabbing to a button and pressing
// Enter do what people expect
$dialog.find( 'button' ).on( 'focus', ( e ) => {
$dialog.data( 'dialogaction', e.delegateTarget );
} );
}
}
}
},
'insert-table': {
title: mw.message( 'wikieditor-toolbar-tool-table-title' ).parse(),
id: 'wikieditor-toolbar-table-dialog',
htmlTemplate: 'dialogInsertTable.html',
init: function () {
$( this ).find( '[rel]' ).each( function () {
// eslint-disable-next-line mediawiki/msg-doc
$( this ).text( mw.msg( $( this ).attr( 'rel' ) ) );
} );
$( '#wikieditor-toolbar-table-dimensions-rows' ).val( 3 );
$( '#wikieditor-toolbar-table-dimensions-columns' ).val( 3 );
$( '#wikieditor-toolbar-table-wikitable' ).on( 'click', () => {
// eslint-disable-next-line no-jquery/no-class-state
$( '.wikieditor-toolbar-table-preview' ).toggleClass( 'wikitable' );
} );
// Hack for sortable preview: dynamically adding
// sortable class doesn't work, so we use a clone
$( '#wikieditor-toolbar-table-preview' )
.clone()
.attr( 'id', 'wikieditor-toolbar-table-preview2' )
.addClass( 'sortable' )
.insertAfter( $( '#wikieditor-toolbar-table-preview' ) )
.hide();
mw.loader.using( 'jquery.tablesorter', () => {
$( '#wikieditor-toolbar-table-preview2' ).tablesorter();
} );
$( '#wikieditor-toolbar-table-sortable' ).on( 'click', () => {
// Swap the currently shown one clone with the other one
$( '#wikieditor-toolbar-table-preview' )
.hide()
.attr( 'id', 'wikieditor-toolbar-table-preview3' );
$( '#wikieditor-toolbar-table-preview2' )
.attr( 'id', 'wikieditor-toolbar-table-preview' )
.show();
$( '#wikieditor-toolbar-table-preview3' ).attr( 'id', 'wikieditor-toolbar-table-preview2' );
} );
$( '#wikieditor-toolbar-table-dimensions-header' ).on( 'click', () => {
// Instead of show/hiding, switch the HTML around
// We do this because the sortable tables script styles the first row,
// visible or not
const headerHTML = $( '.wikieditor-toolbar-table-preview-header' ).html(),
hiddenHTML = $( '.wikieditor-toolbar-table-preview-hidden' ).html();
$( '.wikieditor-toolbar-table-preview-header' ).html( hiddenHTML );
$( '.wikieditor-toolbar-table-preview-hidden' ).html( headerHTML );
const $sortable = $( '#wikieditor-toolbar-table-preview, #wikieditor-toolbar-table-preview2' )
.filter( '.sortable' );
mw.loader.using( 'jquery.tablesorter', () => {
$sortable.tablesorter();
} );
} );
},
dialog: {
resizable: false,
dialogClass: 'wikiEditor-toolbar-dialog',
width: 590,
buttons: {
'wikieditor-toolbar-tool-table-insert': function () {
const rowsVal = $( '#wikieditor-toolbar-table-dimensions-rows' ).val(),
colsVal = $( '#wikieditor-toolbar-table-dimensions-columns' ).val(),
rows = parseInt( rowsVal, 10 ),
cols = parseInt( colsVal, 10 ),
header = $( '#wikieditor-toolbar-table-dimensions-header' ).prop( 'checked' ) ? 1 : 0;
if ( isNaN( rows ) || isNaN( cols ) || String( rows ) !== rowsVal || String( cols ) !== colsVal || rowsVal < 0 || colsVal < 0 ) {
// eslint-disable-next-line no-alert
alert( mw.msg( 'wikieditor-toolbar-tool-table-invalidnumber' ) );
return;
}
if ( rows + header === 0 || cols === 0 ) {
// eslint-disable-next-line no-alert
alert( mw.msg( 'wikieditor-toolbar-tool-table-zero' ) );
return;
}
if ( ( rows * cols ) > 1000 ) {
// eslint-disable-next-line no-alert
alert( mw.msg( 'wikieditor-toolbar-tool-table-toomany', mw.language.convertNumber( 1000 ) ) );
return;
}
const captionText = mw.msg( 'wikieditor-toolbar-tool-table-example-caption' );
const headerText = mw.msg( 'wikieditor-toolbar-tool-table-example-header' );
const normalText = mw.msg( 'wikieditor-toolbar-tool-table-example' );
let table = '';
table += '|+ ' + captionText + '\n';
for ( let r = 0; r < rows + header; r++ ) {
table += '|-\n';
for ( let c = 0; c < cols; c++ ) {
const isHeader = ( header && r === 0 );
let delim = isHeader ? '!' : '|';
if ( c > 0 ) {
delim += delim;
}
table += delim + ' ' + ( isHeader ? headerText : normalText ) + ' ';
}
// Replace trailing space by newline
// table[table.length - 1] is read-only
table = table.slice( 0, table.length - 1 ) + '\n';
}
const classes = [];
if ( $( '#wikieditor-toolbar-table-wikitable' ).is( ':checked' ) ) {
classes.push( 'wikitable' );
}
if ( $( '#wikieditor-toolbar-table-sortable' ).is( ':checked' ) ) {
classes.push( 'sortable' );
}
const classStr = classes.length > 0 ? ' class="' + classes.join( ' ' ) + '"' : '';
$( this ).dialog( 'close' );
toolbarModule.fn.doAction(
$( this ).data( 'context' ),
{
type: 'replace',
options: {
pre: '{|' + classStr + '\n',
peri: table,
post: '|}',
ownline: true
}
},
$( this )
);
// Restore form state
$( '#wikieditor-toolbar-table-dimensions-rows' ).val( 3 );
$( '#wikieditor-toolbar-table-dimensions-columns' ).val( 3 );
// Simulate clicks instead of setting values, so the according
// actions are performed
if ( !$( '#wikieditor-toolbar-table-dimensions-header' ).is( ':checked' ) ) {
$( '#wikieditor-toolbar-table-dimensions-header' ).trigger( 'click' );
}
if ( !$( '#wikieditor-toolbar-table-wikitable' ).is( ':checked' ) ) {
$( '#wikieditor-toolbar-table-wikitable' ).trigger( 'click' );
}
if ( $( '#wikieditor-toolbar-table-sortable' ).is( ':checked' ) ) {
$( '#wikieditor-toolbar-table-sortable' ).trigger( 'click' );
}
},
'wikieditor-toolbar-tool-table-cancel': function () {
$( this ).dialog( 'close' );
}
},
open: function () {
$( '#wikieditor-toolbar-table-dimensions-rows' ).trigger( 'focus' );
if ( !( $( this ).data( 'dialogkeypressset' ) ) ) {
$( this ).data( 'dialogkeypressset', true );
// Execute the action associated with the first button
// when the user presses Enter
const $dialog = $( this ).closest( '.ui-dialog' );
$dialog.on( 'keypress', ( e ) => {
if ( ( e.keyCode || e.which ) === 13 ) {
triggerButtonClick( $dialog );
e.preventDefault();
}
} );
// Make tabbing to a button and pressing
// Enter do what people expect
$dialog.find( 'button' ).on( 'focus', ( e ) => {
$dialog.data( 'dialogaction', e.delegateTarget );
} );
}
}
}
},
'search-and-replace': {
title: mw.message( 'wikieditor-toolbar-tool-replace-title' ).parse(),
id: 'wikieditor-toolbar-replace-dialog',
htmlTemplate: 'dialogReplace.html',
init: function () {
$( this ).find( '[rel]' ).each( function () {
// eslint-disable-next-line mediawiki/msg-doc
$( this ).text( mw.msg( $( this ).attr( 'rel' ) ) );
} );
// TODO: Find a cleaner way to share this function
$( this ).data( 'replaceCallback', ( mode ) => {
$( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide();
// Search string cannot be empty
let searchStr = $( '#wikieditor-toolbar-replace-search' ).val();
if ( searchStr === '' ) {
$( '#wikieditor-toolbar-replace-emptysearch' ).show();
return;
}
// Replace string can be empty
const replaceStr = $( '#wikieditor-toolbar-replace-replace' ).val();
// Prepare the regular expression flags
let flags = 'm';
const matchCase = $( '#wikieditor-toolbar-replace-case' ).is( ':checked' );
if ( !matchCase ) {
flags += 'i';
}
const isRegex = $( '#wikieditor-toolbar-replace-regex' ).is( ':checked' );
if ( !isRegex ) {
searchStr = mw.util.escapeRegExp( searchStr );
}
const matchWord = $( '#wikieditor-toolbar-replace-word' ).is( ':checked' );
if ( matchWord ) {
searchStr = '\\b(?:' + searchStr + ')\\b';
}
if ( mode === 'replaceAll' ) {
flags += 'g';
}
let regex;
try {
regex = new RegExp( searchStr, flags );
} catch ( e ) {
$( '#wikieditor-toolbar-replace-invalidregex' )
.text( mw.msg( 'wikieditor-toolbar-tool-replace-invalidregex',
e.message ) )
.show();
return;
}
const $textarea = $( this ).data( 'context' ).$textarea;
let text = $textarea.textSelection( 'getContents' );
let match = false;
let offset, textRemainder;
if ( mode !== 'replaceAll' ) {
offset = $( this ).data( mode === 'replace' ? 'matchIndex' : 'offset' );
textRemainder = text.slice( offset );
match = textRemainder.match( regex );
}
if ( !match ) {
// Search hit BOTTOM, continuing at TOP
// TODO: Add a "Wrap around" option.
offset = 0;
textRemainder = text;
match = textRemainder.match( regex );
}
if ( !match ) {
$( '#wikieditor-toolbar-replace-nomatch' ).show();
} else if ( mode === 'replaceAll' ) {
$textarea.textSelection( 'setContents', text.replace( regex, replaceStr ) );
$( '#wikieditor-toolbar-replace-success' )
.text( mw.msg( 'wikieditor-toolbar-tool-replace-success', mw.language.convertNumber( match.length ) ) )
.show();
$( this ).data( 'offset', 0 );
} else {
if ( mode === 'replace' ) {
let actualReplacement;
if ( isRegex ) {
// If backreferences (like $1) are used, the actual actual replacement string will be different
actualReplacement = match[ 0 ].replace( regex, replaceStr );
} else {
actualReplacement = replaceStr;
}
if ( match ) {
// Do the replacement
$textarea.textSelection( 'encapsulateSelection', {
peri: actualReplacement,
replace: true,
selectionStart: offset + match.index,
selectionEnd: offset + match.index + match[ 0 ].length,
selectPeri: true
} );
// Reload the text after replacement
text = $textarea.textSelection( 'getContents' );
}
// Find the next instance
offset = offset + match.index + actualReplacement.length;
textRemainder = text.slice( offset );
match = textRemainder.match( regex );
if ( !match ) {
// If no new string was found, try searching from the beginning.
// TODO: Add a "Wrap around" option.
offset = 0;
textRemainder = text;
match = textRemainder.match( regex );
}
if ( !match ) {
// Give up
match = { index: 0, 0: { length: 0 } };
}
}
const start = offset + match.index;
const end = start + match[ 0 ].length;
$( this ).data( 'matchIndex', start );
$textarea.textSelection( 'setSelection', {
start: start,
end: end } );
$textarea.textSelection( 'scrollToCaretPosition' );
$( this ).data( 'offset', end );
$textarea[ 0 ].focus();
}
} );
},
dialog: {
width: 500,
dialogClass: 'wikiEditor-toolbar-dialog',
modal: false,
buttons: {
'wikieditor-toolbar-tool-replace-button-findnext': function ( e ) {
$( this ).closest( '.ui-dialog' ).data( 'dialogaction', e.target );
$( this ).data( 'replaceCallback' ).call( this, 'find' );
},
'wikieditor-toolbar-tool-replace-button-replace': function ( e ) {
$( this ).closest( '.ui-dialog' ).data( 'dialogaction', e.target );
$( this ).data( 'replaceCallback' ).call( this, 'replace' );
},
'wikieditor-toolbar-tool-replace-button-replaceall': function ( e ) {
$( this ).closest( '.ui-dialog' ).data( 'dialogaction', e.target );
$( this ).data( 'replaceCallback' ).call( this, 'replaceAll' );
},
'wikieditor-toolbar-tool-replace-close': function () {
$( this ).dialog( 'close' );
}
},
open: function () {
$( this ).data( { offset: 0, matchIndex: 0 } );
const $dialog = $( this ).closest( '.ui-dialog' );
$( '#wikieditor-toolbar-replace-search' ).trigger( 'focus' );
$( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide();
if ( !( $( this ).data( 'onetimeonlystuff' ) ) ) {
$( this ).data( 'onetimeonlystuff', true );
// Execute the action associated with the first button
// when the user presses Enter
$dialog.on( 'keypress', ( e ) => {
if ( ( e.keyCode || e.which ) === 13 ) {
triggerButtonClick( $dialog );
e.preventDefault();
}
} );
// Make tabbing to a button and pressing
// Enter do what people expect
$dialog.find( 'button' ).on( 'focus', ( e ) => {
$dialog.data( 'dialogaction', e.delegateTarget );
} );
}
const $textarea = $( this ).data( 'context' ).$textarea;
$textarea
.on( 'keypress.srdialog', ( e ) => {
if ( e.which === 13 ) {
// Enter
triggerButtonClick( $textarea );
e.preventDefault();
} else if ( e.which === 27 ) {
// Escape
$( this ).dialog( 'close' );
}
} );
},
close: function () {
$( this ).data( 'context' ).$textarea
.off( 'keypress.srdialog' );
$( this ).closest( '.ui-dialog' ).data( 'dialogaction', false );
}
}
}
} };
}
};