jenkins-bot has submitted this change and it was merged. Change subject: Create base MWExtensionNode for simple extension support ......................................................................
Create base MWExtensionNode for simple extension support MWMath and other simple extensions all behave in a similar way, e.g. <tagname>Foreign syntax</tagname>. This creates a base class that should make supporting such extensions, and editing their contents in a plain text box, very simple. Change-Id: Icc0acb33fe32704f71dacb552d9dfa3142eaef2b --- M VisualEditor.php A modules/ve-mw/ce/nodes/ve.ce.MWExtensionNode.js M modules/ve-mw/ce/nodes/ve.ce.MWMathNode.js A modules/ve-mw/dm/nodes/ve.dm.MWExtensionNode.js M modules/ve-mw/dm/nodes/ve.dm.MWMathNode.js A modules/ve-mw/ui/inspectors/ve.ui.MWExtensionInspector.js M modules/ve-mw/ui/inspectors/ve.ui.MWMathInspector.js 7 files changed, 335 insertions(+), 233 deletions(-) Approvals: Catrope: Looks good to me, approved jenkins-bot: Verified diff --git a/VisualEditor.php b/VisualEditor.php index c222525..822b4fc 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -711,8 +711,11 @@ 'ext.visualEditor.experimental' => $wgVisualEditorResourceTemplate + array( 'scripts' => array( + 've-mw/dm/nodes/ve.dm.MWExtensionNode.js', + 've-mw/ce/nodes/ve.ce.MWExtensionNode.js', 've-mw/dm/nodes/ve.dm.MWMathNode.js', 've-mw/ce/nodes/ve.ce.MWMathNode.js', + 've-mw/ui/inspectors/ve.ui.MWExtensionInspector.js', 've-mw/ui/inspectors/ve.ui.MWMathInspector.js', 've-mw/ui/tools/buttons/ve.ui.MWMathButtonTool.js', 've/dm/annotations/ve.dm.LanguageAnnotation.js', diff --git a/modules/ve-mw/ce/nodes/ve.ce.MWExtensionNode.js b/modules/ve-mw/ce/nodes/ve.ce.MWExtensionNode.js new file mode 100644 index 0000000..b0d17fa --- /dev/null +++ b/modules/ve-mw/ce/nodes/ve.ce.MWExtensionNode.js @@ -0,0 +1,97 @@ +/*! + * VisualEditor ContentEditable MWExtensionNode class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/*global mw */ + +/** + * ContentEditable MediaWiki extension node. + * + * @class + * @abstract + * @extends ve.ce.LeafNode + * @mixins ve.ce.FocusableNode + * @mixins ve.ce.ProtectedNode + * @mixins ve.ce.GeneratedContentNode + * + * @constructor + * @param {ve.dm.MWExtensionNode} model Model to observe + * @param {Object} [config] Config options + */ +ve.ce.MWExtensionNode = function VeCeMWExtensionNode( model, config ) { + // Parent constructor + ve.ce.LeafNode.call( this, model, config ); + + // Mixin constructors + ve.ce.FocusableNode.call( this ); + ve.ce.ProtectedNode.call( this ); + ve.ce.GeneratedContentNode.call( this ); + + // Events + this.model.connect( this, { 'update': 'onUpdate' } ); + this.$.on( 'click', ve.bind( this.onClick, this ) ); + + // DOM Changes + this.$.addClass( 've-ce-mwExtensionNode' ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.ce.MWExtensionNode, ve.ce.LeafNode ); + +ve.mixinClass( ve.ce.MWExtensionNode, ve.ce.FocusableNode ); +ve.mixinClass( ve.ce.MWExtensionNode, ve.ce.ProtectedNode ); +ve.mixinClass( ve.ce.MWExtensionNode, ve.ce.GeneratedContentNode ); + +/* Methods */ + +/** */ +ve.ce.MWExtensionNode.prototype.generateContents = function () { + var deferred = $.Deferred(); + $.ajax( { + 'url': mw.util.wikiScript( 'api' ), + 'data': { + 'action': 'visualeditor', + 'paction': 'parsefragment', + 'page': mw.config.get( 'wgRelevantPageName' ), + 'wikitext': + '<' + this.getModel().constructor.static.extensionName + '>' + + this.getModel().getAttribute( 'mw' ).body.extsrc + + '</' + this.getModel().constructor.static.extensionName + '>', + 'token': mw.user.tokens.get( 'editToken' ), + 'format': 'json' + }, + 'dataType': 'json', + 'type': 'POST', + // Wait up to 100 seconds before giving up + 'timeout': 100000, + 'cache': 'false', + 'success': ve.bind( this.onParseSuccess, this, deferred ), + 'error': ve.bind( this.onParseError, this, deferred ) + } ); + return deferred.promise(); +}; + +/** + * Handle a successful response from the parser for the wikitext fragment. + * + * @param {jQuery.Deferred} deferred The Deferred object created by generateContents + * @param {Object} response Response data + */ +ve.ce.MWExtensionNode.prototype.onParseSuccess = function ( deferred, response ) { + var data = response.visualeditor, contentNodes = $( data.content ).get(); + deferred.resolve( contentNodes ); +}; + +/** + * Handle an unsuccessful response from the parser for the wikitext fragment. + * + * @param {jQuery.Deferred} deferred The promise object created by generateContents + * @param {Object} response Response data + */ +ve.ce.MWExtensionNode.prototype.onParseError = function ( deferred ) { + deferred.reject(); +}; diff --git a/modules/ve-mw/ce/nodes/ve.ce.MWMathNode.js b/modules/ve-mw/ce/nodes/ve.ce.MWMathNode.js index 17ccf52..f4b593c 100644 --- a/modules/ve-mw/ce/nodes/ve.ce.MWMathNode.js +++ b/modules/ve-mw/ce/nodes/ve.ce.MWMathNode.js @@ -5,41 +5,21 @@ * @license The MIT License (MIT); see LICENSE.txt */ -/*global mw, MathJax */ +/*global MathJax */ /** * ContentEditable MediaWiki math node. * * @class - * @extends ve.ce.LeafNode - * @mixins ve.ce.FocusableNode - * @mixins ve.ce.ProtectedNode - * @mixins ve.ce.GeneratedContentNode + * @extends ve.ce.MWExtensionNode * * @constructor * @param {ve.dm.MWMathNode} model Model to observe * @param {Object} [config] Config options */ ve.ce.MWMathNode = function VeCeMWMathNode( model, config ) { - var $wrapper; - // Parent constructor - ve.ce.LeafNode.call( this, model, config ); - - // Wrap image - this.$image = this.$; - $wrapper = $( '<span> '); - this.$.wrap( $wrapper ); - this.$ = $wrapper; - - // Mixin constructors - ve.ce.FocusableNode.call( this ); - ve.ce.ProtectedNode.call( this ); - ve.ce.GeneratedContentNode.call( this ); - - // Events - this.model.connect( this, { 'update': 'onUpdate' } ); - this.$.on( 'click', ve.bind( this.onClick, this ) ); + ve.ce.MWExtensionNode.call( this, model, config ); // DOM Changes this.$.addClass( 've-ce-mwMathNode' ); @@ -47,50 +27,15 @@ /* Inheritance */ -ve.inheritClass( ve.ce.MWMathNode, ve.ce.LeafNode ); - -ve.mixinClass( ve.ce.MWMathNode, ve.ce.FocusableNode ); -ve.mixinClass( ve.ce.MWMathNode, ve.ce.ProtectedNode ); -ve.mixinClass( ve.ce.MWMathNode, ve.ce.GeneratedContentNode ); +ve.inheritClass( ve.ce.MWMathNode, ve.ce.MWExtensionNode ); /* Static Properties */ ve.ce.MWMathNode.static.name = 'mwMath'; -ve.ce.MWMathNode.static.tagName = 'img'; - /* Methods */ /** */ -ve.ce.MWMathNode.prototype.generateContents = function () { - var deferred = $.Deferred(); - $.ajax( { - 'url': mw.util.wikiScript( 'api' ), - 'data': { - 'action': 'visualeditor', - 'paction': 'parsefragment', - 'page': mw.config.get( 'wgRelevantPageName' ), - 'wikitext': '<math>' + this.getModel().getAttribute( 'mw' ).body.extsrc + '</math>', - 'token': mw.user.tokens.get( 'editToken' ), - 'format': 'json' - }, - 'dataType': 'json', - 'type': 'POST', - // Wait up to 100 seconds before giving up - 'timeout': 100000, - 'cache': 'false', - 'success': ve.bind( this.onParseSuccess, this, deferred ), - 'error': ve.bind( this.onParseError, this, deferred ) - } ); - return deferred.promise(); -}; - -/** - * Handle a successful response from the parser for the wikitext fragment. - * - * @param {jQuery.Deferred} deferred The Deferred object created by generateContents - * @param {Object} response Response data - */ ve.ce.MWMathNode.prototype.onParseSuccess = function ( deferred, response ) { var data = response.visualeditor, contentNodes = $( data.content ).get(); // HACK: unwrap paragraph from PHP parser @@ -105,36 +50,6 @@ this.emit( 'rerender' ); }, this ) ); } -}; - -/** - * Handle an unsuccessful response from the parser for the wikitext fragment. - * - * @param {jQuery.Deferred} deferred The promise object created by generateContents - * @param {Object} response Response data - */ -ve.ce.MWMathNode.prototype.onParseError = function ( deferred ) { - deferred.reject(); -}; - -/** - * Handle the mouse click. - * - * @method - * @param {jQuery.Event} e Click event - */ -ve.ce.MWMathNode.prototype.onClick = function ( e ) { - var surfaceModel = this.getRoot().getSurface().getModel(), - selectionRange = surfaceModel.getSelection(), - nodeRange = this.model.getOuterRange(); - - surfaceModel.getFragment( - e.shiftKey ? - ve.Range.newCoveringRange( - [ selectionRange, nodeRange ], selectionRange.from > nodeRange.from - ) : - nodeRange - ).select(); }; /* Registration */ diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWExtensionNode.js b/modules/ve-mw/dm/nodes/ve.dm.MWExtensionNode.js new file mode 100644 index 0000000..54becf6 --- /dev/null +++ b/modules/ve-mw/dm/nodes/ve.dm.MWExtensionNode.js @@ -0,0 +1,99 @@ +/*! + * VisualEditor DataModel MWExtensionNode class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * DataModel MediaWiki Extension node. + * + * @class + * @extends ve.dm.LeafNode + * @mixins ve.dm.GeneratedContentNode + * + * @constructor + */ +ve.dm.MWExtensionNode = function VeDmMWExtensionNode( length, element ) { + // Parent constructor + ve.dm.LeafNode.call( this, 0, element ); + + // Mixin constructors + ve.dm.GeneratedContentNode.call( this ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.dm.MWExtensionNode, ve.dm.LeafNode ); + +ve.mixinClass( ve.dm.MWExtensionNode, ve.dm.GeneratedContentNode ); + +/* Static members */ + +ve.dm.MWExtensionNode.static.enableAboutGrouping = true; + +ve.dm.MWExtensionNode.static.matchTagNames = null; + +ve.dm.MWExtensionNode.static.isContent = true; + +ve.dm.MWExtensionNode.static.tagName = null; + +/** + * Name of the extension and the parser tag name. + * @static + * @property {string} static.extensionName + * @inheritable + */ +ve.dm.MWExtensionNode.static.extensionName = null; + +ve.dm.MWExtensionNode.static.getMatchRdfaTypes = function () { + return [ 'mw:Extension/' + this.extensionName ]; +}; + +ve.dm.MWExtensionNode.static.toDataElement = function ( domElements, converter ) { + var dataElement, index, + mwDataJSON = domElements[0].getAttribute( 'data-mw' ), + mwData = mwDataJSON ? JSON.parse( mwDataJSON ) : {}; + + dataElement = { + 'type': this.name, + 'attributes': { + 'mw': mwData, + 'originalDomElements': ve.copy( domElements ), + 'originalMw': mwDataJSON + } + }; + + index = this.storeDomElements( dataElement, domElements, converter.getStore() ); + dataElement.attributes.originalIndex = index; + + return dataElement; +}; + +ve.dm.MWExtensionNode.static.toDomElements = function ( dataElement, doc, converter ) { + var el, + index = converter.getStore().indexOfHash( ve.getHash( this.getHashObject( dataElement ) ) ), + originalMw = dataElement.attributes.originalMw; + + // If the transclusion is unchanged just send back the + // original DOM elements so selser can skip over it + if ( + index === dataElement.attributes.originalIndex || + ( originalMw && ve.compare( dataElement.attributes.mw, JSON.parse( originalMw ) ) ) + ) { + // The object in the store is also used for CE rendering so return a copy + return ve.copyDomElements( dataElement.attributes.originalDomElements, doc ); + } else { + el = doc.createElement( this.tagName ); + el.setAttribute( 'typeof', this.getMatchRdfaTypes()[0] ); + el.setAttribute( 'data-mw', JSON.stringify( dataElement.attributes.mw ) ); + return [ el ]; + } +}; + +ve.dm.MWExtensionNode.static.getHashObject = function ( dataElement ) { + return { + type: dataElement.type, + mw: dataElement.attributes.mw + }; +}; diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWMathNode.js b/modules/ve-mw/dm/nodes/ve.dm.MWMathNode.js index fd70b1a..622f13a 100644 --- a/modules/ve-mw/dm/nodes/ve.dm.MWMathNode.js +++ b/modules/ve-mw/dm/nodes/ve.dm.MWMathNode.js @@ -9,84 +9,26 @@ * DataModel MediaWiki math node. * * @class - * @extends ve.dm.LeafNode - * @mixins ve.dm.GeneratedContentNode + * @extends ve.dm.MWExtensionNode * * @constructor */ ve.dm.MWMathNode = function VeDmMWMathNode( length, element ) { // Parent constructor - ve.dm.LeafNode.call( this, 0, element ); - - // Mixin constructors - ve.dm.GeneratedContentNode.call( this ); + ve.dm.MWExtensionNode.call( this, 0, element ); }; /* Inheritance */ -ve.inheritClass( ve.dm.MWMathNode, ve.dm.LeafNode ); - -ve.mixinClass( ve.dm.MWMathNode, ve.dm.GeneratedContentNode ); +ve.inheritClass( ve.dm.MWMathNode, ve.dm.MWExtensionNode ); /* Static members */ ve.dm.MWMathNode.static.name = 'mwMath'; -ve.dm.MWMathNode.static.enableAboutGrouping = true; +ve.dm.MWMathNode.static.tagName = 'img'; -ve.dm.MWMathNode.static.matchTagNames = null; - -ve.dm.MWMathNode.static.matchRdfaTypes = [ 'mw:Extension/math' ]; - -ve.dm.MWMathNode.static.isContent = true; - -ve.dm.MWMathNode.static.toDataElement = function ( domElements, converter ) { - var dataElement, index, - mwDataJSON = domElements[0].getAttribute( 'data-mw' ), - mwData = mwDataJSON ? JSON.parse( mwDataJSON ) : {}; - - dataElement = { - 'type': 'mwMath', - 'attributes': { - 'mw': mwData, - 'originalDomElements': ve.copy( domElements ), - 'originalMw': mwDataJSON - } - }; - - index = this.storeDomElements( dataElement, domElements, converter.getStore() ); - dataElement.attributes.originalIndex = index; - - return dataElement; -}; - -ve.dm.MWMathNode.static.toDomElements = function ( dataElement, doc, converter ) { - var el, - index = converter.getStore().indexOfHash( ve.getHash( this.getHashObject( dataElement ) ) ), - originalMw = dataElement.attributes.originalMw; - - // If the transclusion is unchanged just send back the - // original DOM elements so selser can skip over it - if ( - index === dataElement.attributes.originalIndex || - ( originalMw && ve.compare( dataElement.attributes.mw, JSON.parse( originalMw ) ) ) - ) { - // The object in the store is also used for CE rendering so return a copy - return ve.copyDomElements( dataElement.attributes.originalDomElements, doc ); - } else { - el = doc.createElement( 'img' ); - el.setAttribute( 'typeof', 'mw:Extension/Math' ); - el.setAttribute( 'data-mw', JSON.stringify( dataElement.attributes.mw ) ); - return [ el ]; - } -}; - -ve.dm.MWMathNode.static.getHashObject = function ( dataElement ) { - return { - type: dataElement.type, - mw: dataElement.attributes.mw - }; -}; +ve.dm.MWMathNode.static.extensionName = 'math'; /* Registration */ diff --git a/modules/ve-mw/ui/inspectors/ve.ui.MWExtensionInspector.js b/modules/ve-mw/ui/inspectors/ve.ui.MWExtensionInspector.js new file mode 100644 index 0000000..8367a18 --- /dev/null +++ b/modules/ve-mw/ui/inspectors/ve.ui.MWExtensionInspector.js @@ -0,0 +1,118 @@ +/*! + * VisualEditor UserInterface MWExtensionInspector class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * MediaWiki extension inspector. + * + * @class + * @abstract + * @extends ve.ui.Inspector + * + * @constructor + * @param {ve.ui.Surface} surface + * @param {Object} [config] Config options + */ +ve.ui.MWExtensionInspector = function VeUiMWExtensionInspector( surface, config ) { + // Parent constructor + ve.ui.Inspector.call( this, surface, config ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.ui.MWExtensionInspector, ve.ui.Inspector ); + +/* Static properties */ + +ve.ui.MWExtensionInspector.static.nodeView = null; + +ve.ui.MWExtensionInspector.static.nodeModel = null; + +/* Methods */ + +/** + * Handle frame ready events. + * + * @method + */ +ve.ui.MWExtensionInspector.prototype.initialize = function () { + // Parent method + ve.ui.Inspector.prototype.initialize.call( this ); + + this.input = new ve.ui.TextInputWidget( { + '$$': this.frame.$$, + 'overlay': this.surface.$localOverlay, + 'multiline': true + } ); + this.input.$.addClass( 've-ui-mwExtensionInspector-input' ); + + // Initialization + this.$form.append( this.input.$ ); +}; + + +/** + * Handle the inspector being opened. + */ +ve.ui.MWExtensionInspector.prototype.onOpen = function () { + var extsrc = ''; + + // Parent method + ve.ui.Inspector.prototype.onOpen.call( this ); + + this.node = this.surface.getView().getFocusedNode(); + + if ( this.node ) { + extsrc = this.node.getModel().getAttribute( 'mw' ).body.extsrc; + } + + // Wait for animation to complete + setTimeout( ve.bind( function () { + // Setup input text + this.input.setValue( extsrc ); + this.input.$input.focus().select(); + }, this ), 200 ); +}; + +/** + * Handle the inspector being closed. + * + * @param {string} action Action that caused the window to be closed + */ +ve.ui.MWExtensionInspector.prototype.onClose = function ( action ) { + var mw, + surfaceModel = this.surface.getModel(); + + // Parent method + ve.ui.Inspector.prototype.onClose.call( this, action ); + + if ( this.node instanceof this.constructor.static.nodeView ) { + mw = this.node.getModel().getAttribute( 'mw' ); + mw.body.extsrc = this.input.getValue(); + surfaceModel.change( + ve.dm.Transaction.newFromAttributeChanges( + surfaceModel.getDocument(), this.node.getOuterRange().start, { 'mw': mw } + ) + ); + } else { + mw = { + 'name': this.constructor.static.nodeModel.static.extensionName, + 'attrs': {}, + 'body': { + 'extsrc': this.input.getValue() + } + }; + surfaceModel.getFragment().collapseRangeToEnd().insertContent( [ + { + 'type': this.constructor.static.nodeModel.static.name, + 'attributes': { + 'mw': mw + } + }, + { 'type': '/' + this.constructor.static.nodeModel.static.name } + ] ); + } +}; diff --git a/modules/ve-mw/ui/inspectors/ve.ui.MWMathInspector.js b/modules/ve-mw/ui/inspectors/ve.ui.MWMathInspector.js index 5f9dfc6..6b92c05 100644 --- a/modules/ve-mw/ui/inspectors/ve.ui.MWMathInspector.js +++ b/modules/ve-mw/ui/inspectors/ve.ui.MWMathInspector.js @@ -9,7 +9,7 @@ * MediaWiki math inspector. * * @class - * @extends ve.ui.Inspector + * @extends ve.ui.MWExtensionInspector * * @constructor * @param {ve.ui.Surface} surface @@ -17,12 +17,12 @@ */ ve.ui.MWMathInspector = function VeUiMWMathInspector( surface, config ) { // Parent constructor - ve.ui.Inspector.call( this, surface, config ); + ve.ui.MWExtensionInspector.call( this, surface, config ); }; /* Inheritance */ -ve.inheritClass( ve.ui.MWMathInspector, ve.ui.Inspector ); +ve.inheritClass( ve.ui.MWMathInspector, ve.ui.MWExtensionInspector ); /* Static properties */ @@ -30,90 +30,18 @@ ve.ui.MWMathInspector.static.titleMessage = 'visualeditor-mwmathinspector-title'; +ve.ui.MWMathInspector.static.nodeView = ve.ce.MWMathNode; + +ve.ui.MWMathInspector.static.nodeModel = ve.dm.MWMathNode; + /* Methods */ -/** - * Handle frame ready events. - * - * @method - */ +/** */ ve.ui.MWMathInspector.prototype.initialize = function () { // Parent method - ve.ui.Inspector.prototype.initialize.call( this ); + ve.ui.MWExtensionInspector.prototype.initialize.call( this ); - this.input = new ve.ui.TextInputWidget( { - '$$': this.frame.$$, - 'overlay': this.surface.$localOverlay, - 'multiline': true - } ); this.input.$.addClass( 've-ui-mwMathInspector-input' ); - - // Initialization - this.$form.append( this.input.$ ); -}; - - -/** - * Handle the inspector being opened. - */ -ve.ui.MWMathInspector.prototype.onOpen = function () { - var extsrc = ''; - - // Parent method - ve.ui.Inspector.prototype.onOpen.call( this ); - - this.node = this.surface.getView().getFocusedNode(); - - if ( this.node ) { - extsrc = this.node.getModel().getAttribute( 'mw' ).body.extsrc; - } - - // Wait for animation to complete - setTimeout( ve.bind( function () { - // Setup input text - this.input.setValue( extsrc ); - this.input.$input.focus().select(); - }, this ), 200 ); -}; - -/** - * Handle the inspector being closed. - * - * @param {string} action Action that caused the window to be closed - */ -ve.ui.MWMathInspector.prototype.onClose = function ( action ) { - var mw, - surfaceModel = this.surface.getModel(); - - // Parent method - ve.ui.Inspector.prototype.onClose.call( this, action ); - - if ( this.node instanceof ve.ce.MWMathNode ) { - mw = this.node.getModel().getAttribute( 'mw' ); - mw.body.extsrc = this.input.getValue(); - surfaceModel.change( - ve.dm.Transaction.newFromAttributeChanges( - surfaceModel.getDocument(), this.node.getOuterRange().start, { 'mw': mw } - ) - ); - } else { - mw = { - 'name': 'math', - 'attrs': {}, - 'body': { - 'extsrc': this.input.getValue() - } - }; - surfaceModel.getFragment().collapseRangeToEnd().insertContent( [ - { - 'type': 'mwMath', - 'attributes': { - 'mw': mw - } - }, - { 'type': '/mwMath' } - ] ); - } }; /* Registration */ -- To view, visit https://gerrit.wikimedia.org/r/77507 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Icc0acb33fe32704f71dacb552d9dfa3142eaef2b Gerrit-PatchSet: 8 Gerrit-Project: mediawiki/extensions/VisualEditor Gerrit-Branch: master Gerrit-Owner: Esanders <esand...@wikimedia.org> Gerrit-Reviewer: Catrope <roan.katt...@gmail.com> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits