/*! * VisualEditor MediaWiki Initialization DesktopWikitextArticleTarget class. * * @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * * @class * @extends ve.init.mw.DesktopArticleTarget * * @constructor * @param {Object} [config] Configuration options */ ve.init.mw.DesktopWikitextArticleTarget = function VeInitMwDesktopWikitextArticleTarget( config ) { // Parent constructor ve.init.mw.DesktopWikitextArticleTarget.super.call( this, config ); }; /* Inheritance */ OO.inheritClass( ve.init.mw.DesktopWikitextArticleTarget, ve.init.mw.DesktopArticleTarget ); /* Events */ /* Static Properties */ ve.init.mw.DesktopWikitextArticleTarget.static.trackingName = 'desktopWikitext'; ve.init.mw.DesktopWikitextArticleTarget.static.importRules = ve.extendObject( {}, ve.init.mw.DesktopWikitextArticleTarget.static.importRules, { all: { keepEmptyContentBranches: true } } ); /* Methods */ /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.switchToWikitextEditor = function ( discardChanges, modified ) { var dataPromise, target = this; if ( discardChanges ) { dataPromise = mw.libs.ve.targetLoader.requestPageData( 'source', this.pageName, null, // We may have this.section but VE is always full page at the moment this.requestedRevId, this.constructor.name ).then( function ( response ) { return response; }, function () { // TODO: Some sort of progress bar? ve.init.mw.DesktopWikitextArticleTarget.super.prototype.switchToWikitextEditor.call( target, discardChanges, modified ); // Keep everything else waiting so our error handler can do its business return $.Deferred().promise(); } ); } else { this.serialize( this.getDocToSave() ); dataPromise = this.serializing.then( function ( response ) { // HACK - add parameters the API doesn't provide for a VE->WT switch var data = response.visualeditoredit; data.etag = target.etag; data.fromEditedState = modified; data.notices = target.remoteNotices; data.protectedClasses = target.protectedClasses; data.basetimestamp = target.baseTimeStamp; data.starttimestamp = target.startTimeStamp; data.oldid = target.revid; return response; } ); } this.setMode( 'source' ); this.reloadSurface( dataPromise ); }; /** * Switch to the visual editor. */ ve.init.mw.DesktopWikitextArticleTarget.prototype.switchToVisualEditor = function () { var dataPromise, windowManager, switchWindow, target = this; if ( this.section !== null ) { // WT -> VE switching is not yet supported in sections, so // show a discard-only confirm dialog, then reload the whole page. windowManager = new OO.ui.WindowManager(); switchWindow = new mw.libs.ve.SwitchConfirmDialog(); $( 'body' ).append( windowManager.$element ); windowManager.addWindows( [ switchWindow ] ); windowManager.openWindow( switchWindow, { mode: 'simple' } ) .then( function ( opened ) { return opened; } ) .then( function ( closing ) { return closing; } ) .then( function ( data ) { if ( data && data.action === 'discard' ) { target.section = null; target.setMode( 'visual' ); target.reloadSurface(); } windowManager.destroy(); } ); } else { dataPromise = mw.libs.ve.targetLoader.requestParsoidData( this.pageName, this.revid, this.constructor.name, this.edited, this.getDocToSave() ); this.setMode( 'visual' ); this.reloadSurface( dataPromise ); } }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.editSource = function () { // Don't bother with a confirm dialog when switching to the new wikitext editor. // Second argument (modified) is never checked if we are keeping changes, so // don't bother computing it. this.switchToWikitextEditor( false ); }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.onWindowPopState = function ( e ) { var veaction; if ( !this.verifyPopState( e.state ) ) { return; } // Parent method ve.init.mw.DesktopWikitextArticleTarget.super.prototype.onWindowPopState.apply( this, arguments ); veaction = this.currentUri.query.veaction; if ( this.active ) { if ( veaction === 'editsource' && this.mode === 'visual' ) { this.actFromPopState = true; this.switchToWikitextEditor(); } else if ( veaction === 'edit' && this.mode === 'source' ) { this.actFromPopState = true; this.switchToVisualEditor(); } } }; /** * Reload the target surface in the new editor mode * * @param {jQuery.Promise} [dataPromise] Data promise, if any */ ve.init.mw.DesktopWikitextArticleTarget.prototype.reloadSurface = function ( dataPromise ) { var target = this; // Create progress - will be discarded when surface is destroyed. this.getSurface().createProgress( $.Deferred().promise(), ve.msg( this.mode === 'source' ? 'visualeditor-mweditmodesource-progress' : 'visualeditor-mweditmodeve-progress' ), true /* non-cancellable */ ); this.activating = true; this.activatingDeferred = $.Deferred(); this.load( dataPromise ); this.activatingDeferred.done( function () { target.updateHistoryState(); target.afterActivate(); target.setupTriggerListeners(); } ); this.toolbarSetupDeferred.resolve(); }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.setupToolbar = function ( surface ) { var actionGroups; // Parent method ve.init.mw.DesktopWikitextArticleTarget.super.prototype.setupToolbar.apply( this, arguments ); if ( this.mode === 'source' ) { // HACK: Replace source button with VE button. This should be via the registry, // or we should have a toggle tool. actionGroups = ve.copy( this.constructor.static.actionGroups ); actionGroups[ 2 ].include[ 0 ] = 'editModeVisual'; this.getActions().setup( actionGroups, surface ); } }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.parseHtml = function ( content ) { var doc; if ( this.mode === 'source' ) { doc = ve.createDocumentFromHtml( '' ); content.split( '\n' ).forEach( function ( line ) { var p = doc.createElement( 'p' ); p.appendChild( doc.createTextNode( line ) ); doc.body.appendChild( p ); } ); return doc; } else { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.parseHtml.apply( this, arguments ); } }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.createTargetWidget = function ( dmDoc, config ) { if ( this.mode === 'source' ) { return new ve.ui.MWTargetWidget( dmDoc, ve.extendObject( { commandRegistry: ve.ui.commandRegistry, sequenceRegistry: ve.ui.sequenceRegistry, dataTransferHandlerFactory: ve.ui.dataTransferHandlerFactory }, config ) ); } else { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.createTargetWidget.apply( this, arguments ); } }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.createSurface = function ( dmDoc, config ) { // Use a regular surface in target widgets if ( this.mode !== 'source' || ( config && config.inTargetWidget ) ) { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.createSurface.apply( this, arguments ); } else { return new ve.ui.MWDesktopWikitextSurface( dmDoc, this.getSurfaceConfig( { commandRegistry: ve.ui.wikitextCommandRegistry, sequenceRegistry: ve.ui.wikitextSequenceRegistry, dataTransferHandlerFactory: ve.ui.wikitextDataTransferHandlerFactory } ) ); } }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.restoreEditSection = function () { if ( this.mode !== 'source' ) { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.restoreEditSection.apply( this, arguments ); } }; /** * Get a wikitext fragment from a document * * @param {ve.dm.Document} doc Document * @param {boolean} [useRevision=true] Whether to use the revision ID + ETag * @return {jQuery.Promise} Abortable promise which resolves with a wikitext string */ ve.init.mw.DesktopWikitextArticleTarget.prototype.getWikitextFragment = function ( doc, useRevision ) { var promise, xhr, params = { action: 'visualeditoredit', token: this.editToken, paction: 'serialize', html: ve.dm.converter.getDomFromModel( doc ).body.innerHTML, page: this.pageName }; if ( useRevision === undefined || useRevision ) { params.oldid = this.revid; params.etag = this.etag; } xhr = new mw.Api().post( params, { contentType: 'multipart/form-data' } ); promise = xhr.then( function ( response ) { if ( response.visualeditoredit ) { return response.visualeditoredit.content; } return $.Deferred().reject(); } ); promise.abort = function () { xhr.abort(); }; return promise; }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.createModelFromDom = function ( doc ) { var i, l, conf, children, data; if ( this.mode !== 'source' ) { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.createModelFromDom.apply( this, arguments ); } conf = mw.config.get( 'wgVisualEditor' ); children = doc.body.children; data = []; // Wikitext documents are just plain text paragraphs, so we can just do a simple manual conversion. for ( i = 0, l = children.length; i < l; i++ ) { data.push( { type: 'paragraph' } ); ve.batchPush( data, children[ i ].textContent.split( '' ) ); data.push( { type: '/paragraph' } ); } data.push( { type: 'internalList' }, { type: '/internalList' } ); return new ve.dm.Document( data, doc, null, null, null, conf.pageLanguageCode, conf.pageLanguageDir ); }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.prepareCacheKey = function () { if ( this.mode !== 'source' ) { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.prepareCacheKey.apply( this, arguments ); } // else: No need, just wikitext }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.createDocToSave = function () { var i, l, text, data; if ( this.mode !== 'source' ) { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.createDocToSave.apply( this, arguments ); } text = ''; data = this.getSurface().getModel().getDocument().data.data; for ( i = 0, l = data.length; i < l; i++ ) { if ( data[ i ].type === '/paragraph' && data[ i + 1 ].type === 'paragraph' ) { text += '\n'; } else if ( !data[ i ].type ) { text += data[ i ]; } } return text; }; /** * @inheritdoc */ ve.init.mw.DesktopWikitextArticleTarget.prototype.tryWithPreparedCacheKey = function ( doc, options ) { var data, postData; if ( this.mode === 'source' ) { data = { wikitext: doc, format: 'json' }; if ( this.section !== null ) { data.section = this.section; } postData = ve.extendObject( {}, options, data ); if ( data.token ) { return new mw.Api().post( postData, { contentType: 'multipart/form-data' } ); } return new mw.Api().postWithToken( 'csrf', postData, { contentType: 'multipart/form-data' } ); } else { // Parent method return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.tryWithPreparedCacheKey.apply( this, arguments ); } }; /* Registration */ ve.init.mw.targetFactory.register( ve.init.mw.DesktopWikitextArticleTarget ); /** * MediaWiki UserInterface edit mode visual tool. * * @class * @extends ve.ui.MWEditModeTool * @constructor * @param {OO.ui.ToolGroup} toolGroup * @param {Object} [config] Config options */ ve.ui.MWEditModeVisualTool = function VeUiMWEditModeVisualTool() { ve.ui.MWEditModeVisualTool.super.apply( this, arguments ); }; OO.inheritClass( ve.ui.MWEditModeVisualTool, ve.ui.MWEditModeTool ); ve.ui.MWEditModeVisualTool.static.name = 'editModeVisual'; ve.ui.MWEditModeVisualTool.static.icon = 'edit'; ve.ui.MWEditModeVisualTool.static.title = OO.ui.deferMsg( 'visualeditor-mweditmodeve-tool' ); /** * @inheritdoc */ ve.ui.MWEditModeVisualTool.prototype.onSelect = function () { this.toolbar.getTarget().switchToVisualEditor(); this.setActive( false ); }; /** * @inheritdoc */ ve.ui.MWEditModeVisualTool.prototype.onUpdateState = function () { // Parent method ve.ui.MWEditModeVisualTool.super.prototype.onUpdateState.apply( this, arguments ); this.setDisabled( ve.init.target.modes.indexOf( 'visual' ) === -1 ); }; ve.ui.toolFactory.register( ve.ui.MWEditModeVisualTool );