From 94d0e73a6b0dec876a00607ef2232b8dadbf7128 Mon Sep 17 00:00:00 2001 From: Ed Sanders Date: Mon, 5 Dec 2016 12:37:01 +0000 Subject: [PATCH] Move all init code over to target Change-Id: I9ba06bb3a57a7683e246461341c67462cab465d0 --- extension.json | 1 - modules/ext.templateDataGenerator.editPage.js | 3 +- modules/ext.templateDataGenerator.target.js | 280 +++++++++++++++- modules/ext.templateDataGenerator.ui.js | 308 ------------------ 4 files changed, 276 insertions(+), 316 deletions(-) delete mode 100644 modules/ext.templateDataGenerator.ui.js diff --git a/extension.json b/extension.json index a946024c..ad6d70a6 100644 --- a/extension.json +++ b/extension.json @@ -74,7 +74,6 @@ "styles": "modules/ext.templateDataGenerator.ui.css", "scripts": [ "modules/ext.templateDataGenerator.target.js", - "modules/ext.templateDataGenerator.ui.js", "modules/widgets/ext.templateDataGenerator.paramSelectWidget.js", "modules/widgets/ext.templateDataGenerator.paramWidget.js", "modules/widgets/ext.templateDataGenerator.paramImportWidget.js", diff --git a/modules/ext.templateDataGenerator.editPage.js b/modules/ext.templateDataGenerator.editPage.js index 40c24116..cf67508a 100644 --- a/modules/ext.templateDataGenerator.editPage.js +++ b/modules/ext.templateDataGenerator.editPage.js @@ -41,8 +41,7 @@ // Textbox wikitext editor if ( $textbox.length ) { // Prepare the editor - target = new mw.TemplateData.TextareaTarget( $textbox ), - mw.libs.tdgUi.init( target, config ); + target = new mw.TemplateData.TextareaTarget( $textbox, config ); $( '#mw-content-text' ).prepend( target.$element ); } diff --git a/modules/ext.templateDataGenerator.target.js b/modules/ext.templateDataGenerator.target.js index db1c7315..c9b941c8 100644 --- a/modules/ext.templateDataGenerator.target.js +++ b/modules/ext.templateDataGenerator.target.js @@ -7,16 +7,94 @@ * @mixin OO.EventEmitter * * @constructor + * @param {Object} config Configuration options */ -mw.TemplateData.Target = function mwTemplateDataTarget() { +mw.TemplateData.Target = function mwTemplateDataTarget( config ) { + var $helpLink, relatedPage, + target = this; + // Parent constructor mw.TemplateData.Target.super.apply( this, arguments ); // Mixin constructor OO.EventEmitter.call( this ); - this.$element.addClass( 'tdg-editscreen-main' ); - // TODO: Move more init code into this class + this.pageName = config.pageName; + this.parentPage = config.parentPage; + this.isPageSubLevel = !!config.isPageSubLevel; + this.isDocPage = !!config.isDocPage; + + this.editOpenDialogButton = new OO.ui.ButtonWidget( { + label: mw.msg( 'templatedata-editbutton' ) + } ); + + this.editNoticeLabel = new OO.ui.LabelWidget( { + classes: [ 'tdg-editscreen-error-msg' ] + } ) + .toggle( false ); + + $helpLink = $( '' ) + .attr( { + href: mw.msg( 'templatedata-helplink-target' ), + target: '_blank' + } ) + .addClass( 'tdg-editscreen-main-helplink' ) + .text( mw.msg( 'templatedata-helplink' ) ); + + this.windowManager = OO.ui.getWindowManager(); + + // Dialog + this.tdgDialog = new mw.TemplateData.Dialog( config ); + this.windowManager.addWindows( [ this.tdgDialog ] ); + + this.sourceHandler = new mw.TemplateData.SourceHandler( { + fullPageName: this.pageName, + parentPage: this.parentPage, + isPageSubLevel: this.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 = this.isDocPage ? this.parentPage : this.pageName + '/doc'; + this.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( //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(); + + target.setNoticeMessage( msg, 'warning', true ); + } + } + } ); + + // Events + this.editOpenDialogButton.connect( this, { click: 'onEditOpenDialogButton' } ); + this.tdgDialog.connect( this, { apply: 'onDialogApply' } ); + + this.$element + .addClass( 'tdg-editscreen-main' ) + .append( + this.editOpenDialogButton.$element, + $helpLink, + this.editNoticeLabel.$element + ); }; /* Inheritance */ @@ -45,6 +123,197 @@ mw.TemplateData.Target.prototype.getWikitext = null; */ mw.TemplateData.Target.prototype.setWikitext = null; +/** + * Display error message in the edit window + * + * @method setNoticeMessage + * @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 + */ +mw.TemplateData.Target.prototype.setNoticeMessage = function ( msg, type, parseHTML ) { + type = type || 'error'; + this.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 = $( '' ).append( $.parseHTML( msg ) ); + } + this.editNoticeLabel.setLabel( msg ); + this.editNoticeLabel.toggle( true ); +}, + +/** + * Reset the error message in the edit window + * + * @method resetNoticeMessage + */ +mw.TemplateData.Target.prototype.resetNoticeMessage = function () { + this.editNoticeLabel.setLabel( '' ); + this.editNoticeLabel.toggle( false ); +}; + +/** + * Open the templatedata edit dialog + * + * @method openEditDialog + * @param {mw.TemplateData.Model} dataModel The data model + * associated with this edit dialog. + */ +mw.TemplateData.Target.prototype.openEditDialog = function ( dataModel ) { + // Open the edit dialog + this.windowManager.openWindow( 'TemplateDataDialog', { + model: dataModel + } ); +}; + +/** + * Respond to edit dialog button click. + * + * @method onEditOpenDialogButton + */ +mw.TemplateData.Target.prototype.onEditOpenDialogButton = function () { + var target = this; + + // Reset notice message + this.resetNoticeMessage(); + + this.originalWikitext = this.getWikitext(); + + // Build the model + this.sourceHandler.buildModel( this.originalWikitext ) + .then( + // Success + function ( model ) { + target.openEditDialog( model ); + }, + // Failure + function () { + // Open a message dialog + target.windowManager.openWindow( 'messageDialog', { + title: mw.msg( 'templatedata-modal-title' ), + message: mw.msg( 'templatedata-errormsg-jsonbadformat' ), + verbose: true, + 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 ( opened ) { + return opened.then( function ( closing ) { + return closing.then( function ( data ) { + var model; + if ( data && data.action === 'accept' ) { + // Open the dialog with an empty model + model = mw.TemplateData.Model.static.newFromObject( + { params: {} }, + target.sourceHandler.getTemplateSourceCodeParams() + ); + target.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. + */ +mw.TemplateData.Target.prototype.replaceTemplateData = function ( newTemplateData ) { + var finalOutput, + endNoIncludeLength = ''.length, + // NB: This pattern contains no matching groups: (). This avoids + // corruption if the template data JSON contains $1 etc. + templatedataPattern = /[\s\S]*?<\/templatedata>/i; + + if ( this.originalWikitext.match( templatedataPattern ) ) { + // exists. Replace it + finalOutput = this.originalWikitext.replace( + templatedataPattern, + '\n' + JSON.stringify( newTemplateData, null, '\t' ) + '\n' + ); + } else { + finalOutput = this.originalWikitext; + if ( finalOutput.substr( -1 ) !== '\n' ) { + finalOutput += '\n'; + } + + if ( !this.isPageSubLevel ) { + if ( finalOutput.substr( -endNoIncludeLength - 1 ) === '\n' ) { + finalOutput = finalOutput.substr( 0, finalOutput.length - endNoIncludeLength - 1 ); + } else { + finalOutput += '\n'; + } + } + finalOutput += '\n' + + JSON.stringify( newTemplateData, null, '\t' ) + + '\n\n'; + if ( !this.isPageSubLevel ) { + finalOutput += '\n'; + } + } + return finalOutput; +}; + +/** + * Respond to edit dialog apply event + * + * @method onDialogApply + * @param {Object} templateData New templatedata + */ +mw.TemplateData.Target.prototype.onDialogApply = function ( templateData ) { + var target = this; + + if ( + Object.keys( templateData ).length > 1 || + Object.keys( templateData.params ).length > 0 + ) { + this.setWikitext( this.replaceTemplateData( templateData ) ); + } else { + this.windowManager.closeWindow( this.windowManager.getCurrentWindow() ); + this.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' ) { + target.setWikitext( target.replaceTemplateData( templateData ) ); + } + } ); + } +}; + /** * Textarea target * @@ -53,10 +322,11 @@ mw.TemplateData.Target.prototype.setWikitext = null; * * @constructor * @param {jQuery} $textarea Editor textarea + * @param {Object} config Configuration options */ -mw.TemplateData.TextareaTarget = function mwTemplateDataTextareaTarget( $textarea ) { +mw.TemplateData.TextareaTarget = function mwTemplateDataTextareaTarget( $textarea, config ) { // Parent constructor - mw.TemplateData.TextareaTarget.super.call( this ); + mw.TemplateData.TextareaTarget.super.call( this, config ); this.$textarea = $textarea; }; diff --git a/modules/ext.templateDataGenerator.ui.js b/modules/ext.templateDataGenerator.ui.js deleted file mode 100644 index 0a7ce59c..00000000 --- a/modules/ext.templateDataGenerator.ui.js +++ /dev/null @@ -1,308 +0,0 @@ -( 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, - target, - originalWikitext, - // ooui Window Manager - sourceHandler, - tdgDialog, - // Edit window elements - editOpenDialogButton, - editNoticeLabel, - editArea, openEditDialog, onEditOpenDialogButton, - replaceTemplateData, onDialogApply, - windowManager = OO.ui.getWindowManager(); - - 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 = $( '' ).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(); - - originalWikitext = target.getWikitext(); - - // Build the model - sourceHandler.buildModel( originalWikitext ) - .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' ), - verbose: true, - 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 ( opened ) { - return opened.then( function ( closing ) { - return closing.then( function ( data ) { - var model; - if ( data && data.action === 'accept' ) { - // Open the dialog with an empty model - model = 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, - endNoIncludeLength = ''.length, - // NB: This pattern contains no matching groups: (). This avoids - // corruption if the template data JSON contains $1 etc. - templatedataPattern = /[\s\S]*?<\/templatedata>/i; - - if ( originalWikitext.match( templatedataPattern ) ) { - // exists. Replace it - finalOutput = originalWikitext.replace( - templatedataPattern, - '\n' + JSON.stringify( newTemplateData, null, '\t' ) + '\n' - ); - } else { - finalOutput = originalWikitext; - if ( finalOutput.substr( -1 ) !== '\n' ) { - finalOutput += '\n'; - } - - if ( !isPageSubLevel ) { - if ( finalOutput.substr( -endNoIncludeLength - 1 ) === '\n' ) { - finalOutput = finalOutput.substr( 0, finalOutput.length - endNoIncludeLength - 1 ); - } else { - finalOutput += '\n'; - } - } - finalOutput += '\n' + - JSON.stringify( newTemplateData, null, '\t' ) + - '\n\n'; - if ( !isPageSubLevel ) { - finalOutput += '\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 - ) { - target.setWikitext( 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' ) { - target.setWikitext( replaceTemplateData( templateData ) ); - } - } ); - } - }; - - return { - /** - * Initialize UI - * - * @param {mw.TemplateData.Target} target Edit UI target - * @param {Object} userConfig Config options - */ - init: function ( editTarget, userConfig ) { - var $helpLink, relatedPage, - config = userConfig; - - target = editTarget; - - 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 = $( '' ) - .attr( { - href: mw.msg( 'templatedata-helplink-target' ), - target: '_blank' - } ) - .addClass( 'tdg-editscreen-main-helplink' ) - .text( mw.msg( 'templatedata-helplink' ) ); - - // Dialog - tdgDialog = new mw.TemplateData.Dialog( config ); - windowManager.addWindows( [ tdgDialog ] ); - - 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( //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 ); - } - } - } ); - - target.$element - .append( - editOpenDialogButton.$element, - $helpLink, - editNoticeLabel.$element - ); - - // UI events - editOpenDialogButton.connect( this, { click: onEditOpenDialogButton } ); - - tdgDialog.connect( this, { - apply: onDialogApply - } ); - } - }; - }() ); -}() );