diff --git a/Math.hooks.php b/Math.hooks.php index a4ab39e..2a1827b 100644 --- a/Math.hooks.php +++ b/Math.hooks.php @@ -151,6 +151,28 @@ class MathHooks { return true; } + public static function onGetBetaPreferences( $user, &$preferences ) { + global $wgExtensionAssetsPath; + if ( class_exists( 'VisualEditorHooks' ) ) { + // Add beta feature if VisualEditor is installed + $dir = RequestContext::getMain()->getLanguage()->getDir(); + $preferences['math-enable-visualeditor'] = array( + 'version' => '1.0', + 'label-message' => 'math-preference-mwmathinspector-label', + 'desc-message' => 'math-preference-mwmathinspector-description', + 'screenshot' => $wgExtensionAssetsPath . + "/Math/betafeatures-icon-VisualEditor-formulae-$dir.svg", + 'info-message' => 'math-preference-mwmathinspector-info-link', + 'discussion-message' => 'math-preference-mwmathinspector-info-link', + 'requirements' => array( + 'betafeatures' => array( + 'visualeditor-enable', + ), + ), + ); + } + } + /** * List of message keys for the various math output settings. * diff --git a/Math.i18n.php b/Math.i18n.php index b3bae14..82aab84 100644 --- a/Math.i18n.php +++ b/Math.i18n.php @@ -40,7 +40,14 @@ $messages['en'] = array( 'math_latexml_invalidresponse' => 'LaTeXML Invalid response (\'$2\') from server \'$1\':', 'math_latexml_invalidxml' => 'LaTeXML MathML is invalid XML.', 'math_latexml_invalidjson' => 'LaTeXML Server response is invalid JSON.', - 'math_latexml_xmlversion' => 'Warning: XML type check skipped! Check if your MediaWiki installation is version wmf/1.22wmf7 or newer.' + 'math_latexml_xmlversion' => 'Warning: XML type check skipped! Check if your MediaWiki installation is version wmf/1.22wmf7 or newer.', + + // VisualEditor math plugin + 'math-visualeditor-mwmathinspector-title' => 'Formula', + 'math-preference-mwmathinspector-description' => 'Add experimental support to VisualEditor for creating and editing of mathematical formulae for testing, ahead of general release. Please remember to always review your changes before saving when using experimental features.', + 'math-preference-mwmathinspector-discussion-link' => '//mediawiki.org/wiki/Special:MyLanguage/Talk:VisualEditor/Beta_Features/Formulae', + 'math-preference-mwmathinspector-info-link' => '//mediawiki.org/wiki/Special:MyLanguage/VisualEditor/Beta_Features/Formulae', + 'math-preference-mwmathinspector-label' => 'VisualEditor formulae editing', ); /** Message documentation (Message documentation) @@ -165,6 +172,21 @@ This message follows the message {{msg-mw|Math failure}}.', This message follows the message {{msg-mw|Math failure}}.', 'math_latexml_xmlversion' => 'Warning that XML checking of MathML requires wmf/1.22wmf7 or newer.', + 'math-visualeditor-mwmathinspector-title' => 'Title for the inspector to edit formula blocks. +{{Identical|Formula}}', + 'math-preference-mwmathinspector-description' => 'Used in [[Special:Preferences]]. + +Used as description for the checkbox to enable editing of mathematical formulae in VisualEditor. + +The label for this checkbox is {{msg-mw|Visualeditor-preference-mwmath-label}}.', + 'math-preference-mwmathinspector-discussion-link' => '{{optional|Used on [[Special:Preferences]] as a link to a page where users can discuss this Beta Feature. Defaults to a page on MediaWiki.org.}}', + 'math-preference-mwmathinspector-info-link' => '{{optional|Used on [[Special:Preferences]] as a link to a page where users can learn about this Beta Feature. Defaults to a page on MediaWiki.org.}}', + 'math-preference-mwmathinspector-label' => 'Used in [[Special:Preferences]]. + +Used as label for checkbox to enable editing of mathematical formulae in VisualEditor. + +The description for this checkbox is: +* {{msg-mw|Visualeditor-preference-mwmath-description}}', ); /** Achinese (Acèh) diff --git a/Math.php b/Math.php index 5d4d93e..2ecd9cc 100644 --- a/Math.php +++ b/Math.php @@ -147,6 +147,7 @@ $wgDefaultUserOptions['math'] = MW_MATH_PNG; $wgExtensionFunctions[] = 'MathHooks::setup'; $wgHooks['ParserFirstCallInit'][] = 'MathHooks::onParserFirstCallInit'; +$wgHooks['GetBetaFeaturePreferences'][] = 'MathHooks::onGetBetaPreferences'; $wgHooks['GetPreferences'][] = 'MathHooks::onGetPreferences'; $wgHooks['LoadExtensionSchemaUpdates'][] = 'MathHooks::onLoadExtensionSchemaUpdates'; $wgHooks['ParserTestTables'][] = 'MathHooks::onParserTestTables'; @@ -395,3 +396,26 @@ $wgResourceModules += array( 'scripts' => array( 'Fraktur/Bold/BasicLatin.js', 'Fraktur/Bold/Other.js', 'Fraktur/Bold/PUA.js', 'Fraktur/Regular/BasicLatin.js', 'Fraktur/Regular/Other.js', 'Fraktur/Regular/PUA.js', 'SansSerif/Bold/BasicLatin.js', 'SansSerif/Bold/CombDiacritMarks.js', 'SansSerif/Bold/Other.js', 'SansSerif/Italic/BasicLatin.js', 'SansSerif/Italic/CombDiacritMarks.js', 'SansSerif/Italic/Other.js', 'SansSerif/Regular/BasicLatin.js', 'SansSerif/Regular/CombDiacritMarks.js', 'SansSerif/Regular/Other.js', 'Script/Regular/BasicLatin.js', 'Typewriter/Regular/BasicLatin.js', 'Typewriter/Regular/CombDiacritMarks.js', 'Typewriter/Regular/Other.js' ) ) + $moduleTemplateSVG ); + +$moduleTemplate = array( + 'localBasePath' => dirname( __FILE__ ) . '/modules', + 'remoteExtPath' => 'Math/modules', +); + +$wgResourceModules['ext.math.visualEditor'] = array( + 'scripts' => array( + 'VisualEditor/ve.dm.MWMathNode.js', + 'VisualEditor/ve.ce.MWMathNode.js', + 'VisualEditor/ve.ui.MWMathInspector.js', + 'VisualEditor/ve.ui.MWMathInspectorTool.js', + ), + 'dependencies' => array( + 'ext.visualEditor.mwcore', + ), + 'messages' => array( + 'math-visualeditor-mwmathinspector-title', + ), + 'targets' => array( 'desktop', 'mobile' ), +) + $moduleTemplate; + +$wgVisualEditorPreferenceModules['math-enable-visualeditor'] = 'ext.math.visualEditor'; diff --git a/betafeatures-icon-VisualEditor-formulae-ltr.svg b/betafeatures-icon-VisualEditor-formulae-ltr.svg new file mode 100644 index 0000000..72d66ae --- /dev/null +++ b/betafeatures-icon-VisualEditor-formulae-ltr.svg @@ -0,0 +1,184 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/betafeatures-icon-VisualEditor-formulae-rtl.svg b/betafeatures-icon-VisualEditor-formulae-rtl.svg new file mode 100644 index 0000000..15a2aa9 --- /dev/null +++ b/betafeatures-icon-VisualEditor-formulae-rtl.svg @@ -0,0 +1,238 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/modules/VisualEditor/ve.ce.MWMathNode.css b/modules/VisualEditor/ve.ce.MWMathNode.css new file mode 100644 index 0000000..4fa349e --- /dev/null +++ b/modules/VisualEditor/ve.ce.MWMathNode.css @@ -0,0 +1,3 @@ +.ve-ce-mwMathNode { + display: inline-block; +} diff --git a/modules/VisualEditor/ve.ce.MWMathNode.js b/modules/VisualEditor/ve.ce.MWMathNode.js new file mode 100644 index 0000000..bf529dd --- /dev/null +++ b/modules/VisualEditor/ve.ce.MWMathNode.js @@ -0,0 +1,65 @@ +/*! + * VisualEditor ContentEditable MWMathNode class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/*global MathJax, ve, OO */ + +/** + * ContentEditable MediaWiki math node. + * + * @class + * @extends ve.ce.MWExtensionNode + * + * @constructor + * @param {ve.dm.MWMathNode} model Model to observe + * @param {Object} [config] Configuration options + */ +ve.ce.MWMathNode = function VeCeMWMathNode( model, config ) { + // Parent constructor + ve.ce.MWExtensionNode.call( this, model, config ); + + // DOM changes + this.$element.addClass( 've-ce-mwMathNode' ); +}; + +/* Inheritance */ + +OO.inheritClass( ve.ce.MWMathNode, ve.ce.MWExtensionNode ); + +/* Static Properties */ + +ve.ce.MWMathNode.static.name = 'mwMath'; + +/* Methods */ + +/** */ +ve.ce.MWMathNode.prototype.onParseSuccess = function ( deferred, response ) { + var data = response.visualeditor, contentNodes = this.$( data.content ).get(); + if ( contentNodes[0] && contentNodes[0].childNodes ) { + contentNodes = Array.prototype.slice.apply( contentNodes[0].childNodes ); + } + deferred.resolve( contentNodes ); +}; + +/** */ +ve.ce.MWExtensionNode.prototype.afterRender = function ( domElements ) { + if ( this.$( domElements ).is( 'span.tex' ) ) { + // MathJax + MathJax.Hub.Queue( + [ 'Typeset', MathJax.Hub, this.$element[0] ], + [ this, this.emit, 'rerender' ] + ); + } else { + // Rerender after image load + this.$element.find( 'img.tex' ).on( 'load', ve.bind( function () { + this.emit( 'rerender' ); + }, this ) ); + } +}; + +/* Registration */ + +ve.ce.nodeFactory.register( ve.ce.MWMathNode ); diff --git a/modules/VisualEditor/ve.dm.MWMathNode.js b/modules/VisualEditor/ve.dm.MWMathNode.js new file mode 100644 index 0000000..3af2817 --- /dev/null +++ b/modules/VisualEditor/ve.dm.MWMathNode.js @@ -0,0 +1,39 @@ +/*! + * VisualEditor DataModel MWMathNode class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/*global ve, OO */ + +/** + * DataModel MediaWiki math node. + * + * @class + * @extends ve.dm.MWExtensionNode + * + * @constructor + * @param {number} [length] Length of content data (ignored, forced to 0) + * @param {Object} [element] Reference to element in linear model + */ +ve.dm.MWMathNode = function VeDmMWMathNode( length, element ) { + // Parent constructor + ve.dm.MWExtensionNode.call( this, 0, element ); +}; + +/* Inheritance */ + +OO.inheritClass( ve.dm.MWMathNode, ve.dm.MWExtensionNode ); + +/* Static members */ + +ve.dm.MWMathNode.static.name = 'mwMath'; + +ve.dm.MWMathNode.static.tagName = 'img'; + +ve.dm.MWMathNode.static.extensionName = 'math'; + +/* Registration */ + +ve.dm.modelRegistry.register( ve.dm.MWMathNode ); diff --git a/modules/VisualEditor/ve.ui.MWMathInspector.js b/modules/VisualEditor/ve.ui.MWMathInspector.js new file mode 100644 index 0000000..37eb427 --- /dev/null +++ b/modules/VisualEditor/ve.ui.MWMathInspector.js @@ -0,0 +1,117 @@ +/*! + * VisualEditor UserInterface MWMathInspector class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/*global ve, OO */ + +/** + * MediaWiki math inspector. + * + * @class + * @extends ve.ui.MWExtensionInspector + * + * @constructor + * @param {ve.ui.WindowSet} windowSet Window set this inspector is part of + * @param {Object} [config] Configuration options + */ +ve.ui.MWMathInspector = function VeUiMWMathInspector( windowSet, config ) { + // Parent constructor + ve.ui.MWExtensionInspector.call( this, windowSet, config ); + + this.onChangeHandler = ve.debounce( ve.bind( this.updatePreview, this ), 250 ); +}; + +/* Inheritance */ + +OO.inheritClass( ve.ui.MWMathInspector, ve.ui.MWExtensionInspector ); + +/* Static properties */ + +ve.ui.MWMathInspector.static.name = 'math'; + +ve.ui.MWMathInspector.static.icon = 'math'; + +ve.ui.MWMathInspector.static.titleMessage = 'math-visualeditor-mwmathinspector-title'; + +ve.ui.MWMathInspector.static.nodeView = ve.ce.MWMathNode; + +ve.ui.MWMathInspector.static.nodeModel = ve.dm.MWMathNode; + +/* Methods */ + +/** + * Update the math node rendering to reflect the content entered into the inspector. + */ +ve.ui.MWMathInspector.prototype.updatePreview = function () { + var newsrc = this.input.getValue(); + if ( this.visible ) { + this.node.update( { 'extsrc': newsrc } ); + } +}; + +/** + * @inheritdoc + */ +ve.ui.MWMathInspector.prototype.setup = function ( data ) { + // Parent method + ve.ui.MWExtensionInspector.prototype.setup.call( this, data ); + + var mw, surfaceModel = this.surface.getModel(); + + this.node = this.surface.getView().getFocusedNode(); + if ( !this.node ) { + // Create a dummy node, needed for live preview + mw = { + 'name': 'math', + 'attrs': {}, + 'body': { + 'extsrc': '' + } + }; + surfaceModel.getFragment().collapseRangeToEnd().insertContent( [ + { + 'type': 'mwMath', + 'attributes': { + 'mw': mw + } + }, + { 'type': '/mwMath' } + ] ); + this.node = this.surface.getView().getFocusedNode(); + } + + this.input.on( 'change', this.onChangeHandler ); + + // Override directionality settings, inspector's input + // should always be LTR: + this.input.setRTL( false ); +}; + +/** + * @inheritdoc + */ +ve.ui.MWMathInspector.prototype.teardown = function ( data ) { + var newsrc = this.input.getValue(), + surfaceModel = this.surface.getModel(); + + this.input.off( 'change', this.onChangeHandler ); + + if ( newsrc !== '' ) { + // Parent method + ve.ui.MWExtensionInspector.prototype.teardown.call( this, data ); + } else { + // The user tried to empty the node, remove it + surfaceModel.change( ve.dm.Transaction.newFromRemoval( + surfaceModel.getDocument(), this.node.getOuterRange() + ) ); + // Grandparent method; we're overriding the parent behavior in this case + ve.ui.Inspector.prototype.teardown.call( this, data ); + } +}; + +/* Registration */ + +ve.ui.inspectorFactory.register( ve.ui.MWMathInspector ); diff --git a/modules/VisualEditor/ve.ui.MWMathInspectorTool.js b/modules/VisualEditor/ve.ui.MWMathInspectorTool.js new file mode 100644 index 0000000..3e1e66a --- /dev/null +++ b/modules/VisualEditor/ve.ui.MWMathInspectorTool.js @@ -0,0 +1,29 @@ +/*! + * VisualEditor MediaWiki UserInterface math tool class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/*global ve, OO */ + +/** + * MediaWiki UserInterface math tool. + * + * @class + * @extends ve.ui.InspectorTool + * @constructor + * @param {OO.ui.ToolGroup} toolGroup + * @param {Object} [config] Configuration options + */ +ve.ui.MWMathInspectorTool = function VeUiMWMathInspectorTool( toolGroup, config ) { + ve.ui.InspectorTool.call( this, toolGroup, config ); +}; +OO.inheritClass( ve.ui.MWMathInspectorTool, ve.ui.InspectorTool ); +ve.ui.MWMathInspectorTool.static.name = 'math'; +ve.ui.MWMathInspectorTool.static.group = 'object'; +ve.ui.MWMathInspectorTool.static.icon = 'math'; +ve.ui.MWMathInspectorTool.static.titleMessage = 'math-visualeditor-mwmathinspector-title'; +ve.ui.MWMathInspectorTool.static.inspector = 'math'; +ve.ui.MWMathInspectorTool.static.modelClasses = [ ve.dm.MWMathNode ]; +ve.ui.toolFactory.register( ve.ui.MWMathInspectorTool );