mediawiki-extensions-Templa.../modules/ext.templateDataGenerator.ui.js
2016-08-23 17:35:35 +00:00

317 lines
9.2 KiB
JavaScript

( function () {
'use strict';
/**
* TemplateData Generator data model.
* This singleton is independent of any UI; it expects
* a templatedata string, converts it into the object
* model and manages it, fully event-driven.
*
* @class
* @singleton
*/
mw.libs.tdgUi = ( function () {
var isPageSubLevel,
isDocPage,
pageName,
parentPage,
$textbox,
// ooui Window Manager
sourceHandler,
tdgDialog,
messageDialog,
windowManager,
// Edit window elements
editOpenDialogButton,
editNoticeLabel,
editArea = {
/**
* Display error message in the edit window
*
* @param {string} msg Message to display
* @param {string} [type='error'] Message type 'notice' or 'warning' or 'error'
* @param {boolean} [parseHTML] The message should be parsed
*/
setNoticeMessage: function ( msg, type, parseHTML ) {
type = type || 'error';
editNoticeLabel.$element
.toggleClass( 'errorbox', type === 'error' )
.toggleClass( 'warningbox', type === 'warning' );
if ( parseHTML ) {
// OOUI's label elements do not parse strings and display them
// as-is. If the message contains html that should be parsed,
// we have to transform it into a jQuery object
msg = $( '<span>' ).append( $.parseHTML( msg ) );
}
editNoticeLabel.setLabel( msg );
editNoticeLabel.toggle( true );
},
/**
* Reset the error message in the edit window
*/
resetNoticeMessage: function () {
editNoticeLabel.setLabel( '' );
editNoticeLabel.toggle( false );
}
},
/**
* Open the templatedata edit dialog
*
* @method openEditDialog
* @param {mw.TemplateData.Model} dataModel The data model
* associated with this edit dialog.
*/
openEditDialog = function ( dataModel ) {
// Open the edit dialog
windowManager.openWindow( tdgDialog, {
model: dataModel
} );
},
/**
* Respond to edit dialog button click.
*
* @method onEditOpenDialogButton
*/
onEditOpenDialogButton = function () {
// Reset notice message
editArea.resetNoticeMessage();
// Build the model
sourceHandler.buildModel( $textbox.val() )
.then(
// Success
function ( model ) {
openEditDialog( model );
},
// Failure
function () {
// Open a message dialog
windowManager.openWindow( messageDialog, {
title: mw.msg( 'templatedata-modal-title' ),
message: mw.msg( 'templatedata-errormsg-jsonbadformat' ),
actions: [
{
action: 'accept',
label: mw.msg( 'templatedata-modal-json-error-replace' ),
flags: [ 'primary', 'destructive' ]
},
{
action: 'reject',
label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ),
flags: 'safe'
}
]
} )
.then( function ( opening ) {
return opening;
} )
.then( function ( opened ) {
return opened;
} )
.then( function ( data ) {
var model;
if ( data && data.action === 'accept' ) {
// Open the dialog with an empty model
model = new mw.TemplateData.Model.static.newFromObject(
{ params: {} },
sourceHandler.getTemplateSourceCodeParams()
);
openEditDialog( model );
}
} );
}
);
},
/**
* Replace the old templatedata string with the new one, or
* insert the new one into the page if an old one doesn't exist
*
* @method replaceTemplateData
* @param {Object} newTemplateData New templatedata
* @return {string} Full wikitext content with the new templatedata
* string.
*/
replaceTemplateData = function ( newTemplateData ) {
var finalOutput,
fullWikitext = $textbox.val(),
endNoIncludeLength = '</noinclude>'.length,
// NB: This pattern contains no matching groups: (). This avoids
// corruption if the template data JSON contains $1 etc.
templatedataPattern = /<templatedata>[\s\S]*?<\/templatedata>/i;
if ( fullWikitext.match( templatedataPattern ) ) {
// <templatedata> exists. Replace it
finalOutput = fullWikitext.replace(
templatedataPattern,
'<templatedata>\n' + JSON.stringify( newTemplateData, null, '\t' ) + '\n</templatedata>'
);
} else {
finalOutput = fullWikitext;
if ( finalOutput.substr( -1 ) !== '\n' ) {
finalOutput += '\n';
}
if ( !isPageSubLevel ) {
if ( finalOutput.substr( -endNoIncludeLength - 1 ) === '</noinclude>\n' ) {
finalOutput = finalOutput.substr( 0, finalOutput.length - endNoIncludeLength - 1 );
} else {
finalOutput += '<noinclude>\n';
}
}
finalOutput += '<templatedata>\n' +
JSON.stringify( newTemplateData, null, '\t' ) +
'\n</templatedata>\n';
if ( !isPageSubLevel ) {
finalOutput += '</noinclude>\n';
}
}
return finalOutput;
},
/**
* Respond to edit dialog apply event
*
* @method onDialogApply
* @param {Object} templateData New templatedata
*/
onDialogApply = function ( templateData ) {
if (
Object.keys( templateData ).length > 1 ||
Object.keys( templateData.params ).length > 0
) {
$textbox.val( replaceTemplateData( templateData ) );
} else {
windowManager.closeWindow( windowManager.getCurrentWindow() );
windowManager.openWindow( messageDialog, {
title: mw.msg( 'templatedata-modal-title' ),
message: mw.msg( 'templatedata-errormsg-insertblank' ),
actions: [
{
label: mw.msg( 'templatedata-modal-button-cancel' ),
flags: [ 'primary', 'safe' ]
},
{
action: 'apply',
label: mw.msg( 'templatedata-modal-button-apply' )
}
]
} )
.then( function ( opening ) { return opening; } )
.then( function ( opened ) { return opened; } )
.then( function ( data ) {
if ( data && data.action === 'apply' ) {
$textbox.val( replaceTemplateData( templateData ) );
}
} );
}
};
return {
/**
* Initialize UI
*
* @param {jQuery} $container The container to attach UI buttons to
* @param {jQuery} $editorTextbox The editor textbox to take the
* current wikitext from.
*/
init: function ( $container, $editorTextbox, userConfig ) {
var $helpLink, relatedPage,
config = userConfig;
$textbox = $editorTextbox;
pageName = config.pageName;
parentPage = config.parentPage;
isPageSubLevel = !!config.isPageSubLevel;
isDocPage = !!config.isDocPage;
editOpenDialogButton = new OO.ui.ButtonWidget( {
label: mw.msg( 'templatedata-editbutton' )
} );
editNoticeLabel = new OO.ui.LabelWidget( {
classes: [ 'tdg-editscreen-error-msg' ]
} )
.toggle( false );
$helpLink = $( '<a>' )
.attr( {
href: mw.msg( 'templatedata-helplink-target' ),
target: '_blank'
} )
.addClass( 'tdg-editscreen-main-helplink' )
.text( mw.msg( 'templatedata-helplink' ) );
// Dialog
windowManager = new OO.ui.WindowManager();
tdgDialog = new mw.TemplateData.Dialog( config );
messageDialog = new OO.ui.MessageDialog();
windowManager.addWindows( [ tdgDialog, messageDialog ] );
sourceHandler = new mw.TemplateData.SourceHandler( {
fullPageName: pageName,
parentPage: parentPage,
isPageSubLevel: isPageSubLevel
} );
// Check if there's already a templatedata in a related page
// TODO: Hard-coding 'doc' is dangerous for i18n. We need to find
// a better way to define 'related' pages for a template.
relatedPage = isDocPage ? parentPage : pageName + '/doc';
sourceHandler.getApi( relatedPage )
.then( function ( result ) {
var msg, matches, content,
response = result.query.pages[ result.query.pageids[ 0 ] ];
// HACK: When checking whether a related page (parent for /doc page or
// vice versa) already has a templatedata string, we shouldn't
// ask for the 'templatedata' action but rather the actual content
// of the related page, otherwise we get embedded templatedata and
// wrong information is presented.
if ( response.missing === undefined ) {
content = response.revisions[ 0 ][ '*' ];
matches = content.match( /<templatedata>/i );
// There's a templatedata string
if ( matches ) {
// HACK: Setting a link in the messages doesn't work. The bug report offers
// a somewhat hacky work around that includes setting a separate message
// to be parsed.
// https://phabricator.wikimedia.org/T49395#490610
msg = mw.message( 'templatedata-exists-on-related-page', relatedPage ).plain();
mw.messages.set( { 'templatedata-string-exists-hack-message': msg } );
msg = mw.message( 'templatedata-string-exists-hack-message' ).parse();
editArea.setNoticeMessage( msg, 'warning', true );
}
}
} );
// Prepend to container
$container
.prepend(
$( '<div>' )
.addClass( 'tdg-editscreen-main' )
.append(
editOpenDialogButton.$element,
$helpLink,
editNoticeLabel.$element
)
);
$( 'body' )
.append( windowManager.$element );
// UI events
editOpenDialogButton.connect( this, { click: onEditOpenDialogButton } );
tdgDialog.connect( this, {
apply: onDialogApply
} );
}
};
}() );
}() );