From 443c5438ab49150cc6b6b4184b98b3b9318f69a5 Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Wed, 7 Nov 2012 12:09:18 -0800 Subject: [PATCH] Refactored commands into a registry * Now ve.Factory inherits from the more general ve.Registry * New class ve.CommandRegistry * Refactored setupToolbar and command setup code into setupComands Change-Id: Ic548e5de95b77889727362d3e66d7be83c12a603 --- VisualEditor.php | 2 + demos/ve/index.php | 2 + modules/ve/test/index.html | 2 + modules/ve/ve.CommandRegistry.js | 68 ++++++++++++++ modules/ve/ve.Factory.js | 38 ++------ modules/ve/ve.Registry.js | 64 +++++++++++++ modules/ve/ve.Surface.js | 155 +++++++++---------------------- 7 files changed, 189 insertions(+), 142 deletions(-) create mode 100644 modules/ve/ve.CommandRegistry.js create mode 100644 modules/ve/ve.Registry.js diff --git a/VisualEditor.php b/VisualEditor.php index 6503a32816..693b1793fe 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -154,9 +154,11 @@ $wgResourceModules += array( 'ext.visualEditor.core' => $wgVisualEditorResourceTemplate + array( 'scripts' => array( // ve + 've/ve.Registry.js', 've/ve.Factory.js', 've/ve.Position.js', 've/ve.Command.js', + 've/ve.CommandRegistry.js', 've/ve.Range.js', 've/ve.Node.js', 've/ve.BranchNode.js', diff --git a/demos/ve/index.php b/demos/ve/index.php index d4dd7ed95f..acbb60d3f9 100644 --- a/demos/ve/index.php +++ b/demos/ve/index.php @@ -88,9 +88,11 @@ $html = '
' . file_get_contents( $page ) . '
'; + + diff --git a/modules/ve/test/index.html b/modules/ve/test/index.html index 52a2f519bc..32299dce7d 100644 --- a/modules/ve/test/index.html +++ b/modules/ve/test/index.html @@ -25,9 +25,11 @@ + + diff --git a/modules/ve/ve.CommandRegistry.js b/modules/ve/ve.CommandRegistry.js new file mode 100644 index 0000000000..4e46c41306 --- /dev/null +++ b/modules/ve/ve.CommandRegistry.js @@ -0,0 +1,68 @@ +/** + * VisualEditor CommandRegistry class. + * + * @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Command registry. + * + * @class + * @constructor + * @extends {ve.Registry} + */ +ve.CommandRegistry = function VeCommandRegistry() { + // Parent constructor + ve.Registry.call( this ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.CommandRegistry, ve.Registry ); + +/* Methods */ + +/** + * Register a constructor with the factory. + * + * @method + * @param {String|String[]} name Symbolic name or list of symbolic names + * @param {String|String[]} trigger Command string of keys that should trigger the command + * @param {String} action Action to execute when command is triggered + * @param {String} method Method to call on action when executing + * @param {Mixed} [...] Additional data to pass to the action when executing + * @throws 'trigger must be a string or array' + * @throws 'action must be a string' + * @throws 'method must be a string' + */ +ve.CommandRegistry.prototype.register = function ( name, trigger, action, method ) { + if ( typeof trigger !== 'string' && !ve.isArray( trigger ) ) { + throw new Error( 'trigger must be a string or array, cannot be a ' + typeof trigger ); + } + if ( typeof action !== 'string' ) { + throw new Error( 'action must be a string, cannot be a ' + typeof action ); + } + if ( typeof method !== 'string' ) { + throw new Error( 'method must be a string, cannot be a ' + typeof method ); + } + ve.Registry.prototype.register.call( + this, name, { 'trigger': trigger, 'action': Array.prototype.slice.call( arguments, 2 ) } + ); +}; + +/* Initialization */ + +ve.commandRegistry = new ve.CommandRegistry(); + +// TODO: Move these somewhere else + +ve.commandRegistry.register( + 'bold', ['cmd+b', 'ctrl+b'], 'annotation', 'toggle', 'textStyle/bold' +); +ve.commandRegistry.register( + 'italic', ['cmd+i', 'ctrl+i'], 'annotation', 'toggle', 'textStyle/italic' +); +ve.commandRegistry.register( 'link', ['cmd+k', 'ctrl+k'], 'inspector', 'open', 'link' ); +ve.commandRegistry.register( 'undo', ['cmd+z', 'ctrl+z'], 'history', 'undo' ); +ve.commandRegistry.register( 'redo', ['cmd+shift+z', 'ctrl+shift+z'], 'history', 'redo' ); diff --git a/modules/ve/ve.Factory.js b/modules/ve/ve.Factory.js index 79cf8519e2..d21aa76256 100644 --- a/modules/ve/ve.Factory.js +++ b/modules/ve/ve.Factory.js @@ -11,11 +11,11 @@ * @class * @abstract * @constructor - * @extends {ve.EventEmitter} + * @extends {ve.Registry} */ ve.Factory = function VeFactory() { // Parent constructor - ve.EventEmitter.call( this ); + ve.Registry.call( this ); // Properties this.registry = []; @@ -23,36 +23,23 @@ ve.Factory = function VeFactory() { /* Inheritance */ -ve.inheritClass( ve.Factory, ve.EventEmitter ); +ve.inheritClass( ve.Factory, ve.Registry ); /* Methods */ /** * Register a constructor with the factory. * - * Arguments will be passed through directly to the constructor. - * @see {ve.Factory.prototype.create} - * * @method * @param {String|String[]} name Symbolic name or list of symbolic names * @param {Function} constructor Constructor to use when creating object - * @throws 'Constructor must be a function, cannot be a string' + * @throws 'constructor must be a function' */ ve.Factory.prototype.register = function ( name, constructor ) { - var i, len; if ( typeof constructor !== 'function' ) { - throw new Error( 'Constructor must be a function, cannot be a ' + typeof constructor ); - } - if ( ve.isArray( name ) ) { - for ( i = 0, len = name.length; i < len; i++ ) { - this.register( name[i], constructor ); - } - } else if ( typeof name === 'string' ) { - this.registry[name] = constructor; - this.emit( 'register', name, constructor ); - } else { - throw new Error( 'Name must be a string or array of strings, cannot be a ' + typeof name ); + throw new Error( 'constructor must be a function, cannot be a ' + typeof constructor ); } + ve.Registry.prototype.register.call( this, name, constructor ); }; /** @@ -72,7 +59,7 @@ ve.Factory.prototype.create = function ( name ) { constructor = this.registry[name]; if ( constructor === undefined ) { - throw new Error( 'Unknown object name: ' + name ); + throw new Error( 'No class registered by that name: ' + name ); } // Convert arguments to array and shift the first argument (name) off @@ -87,14 +74,3 @@ ve.Factory.prototype.create = function ( name ) { constructor.apply( obj, args ); return obj; }; - -/** - * Gets a constructor for a given name. - * - * @method - * @param {String} name Object name - * @returns {Function|undefined} Constructor for name - */ -ve.Factory.prototype.lookup = function ( name ) { - return this.registry[name]; -}; diff --git a/modules/ve/ve.Registry.js b/modules/ve/ve.Registry.js new file mode 100644 index 0000000000..378ecae212 --- /dev/null +++ b/modules/ve/ve.Registry.js @@ -0,0 +1,64 @@ +/** + * VisualEditor Registry class. + * + * @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Generic object factory. + * + * @class + * @abstract + * @constructor + * @extends {ve.EventEmitter} + */ +ve.Registry = function VeRegistry() { + // Parent constructor + ve.EventEmitter.call( this ); + + // Properties + this.registry = []; +}; + +/* Inheritance */ + +ve.inheritClass( ve.Registry, ve.EventEmitter ); + +/* Methods */ + +/** + * Associate one or more symbolic names with some data. + * + * @method + * @param {String|String[]} name Symbolic name or list of symbolic names + * @param {Mixed} data Data to associate with symbolic name + * @throws 'name must be a string' + */ +ve.Registry.prototype.register = function ( name, data ) { + if ( typeof name !== 'string' && !ve.isArray( name ) ) { + throw new Error( 'name must be a string or array, cannot be a ' + typeof name ); + } + var i, len; + if ( ve.isArray( name ) ) { + for ( i = 0, len = name.length; i < len; i++ ) { + this.register( name[i], data ); + } + } else if ( typeof name === 'string' ) { + this.registry[name] = data; + this.emit( 'register', name, data ); + } else { + throw new Error( 'Name must be a string or array of strings, cannot be a ' + typeof name ); + } +}; + +/** + * Gets data for a given symbolic name. + * + * @method + * @param {String} name Symbolic name + * @returns {Mixed|undefined} Data associated with symbolic name + */ +ve.Registry.prototype.lookup = function ( name ) { + return this.registry[name]; +}; diff --git a/modules/ve/ve.Surface.js b/modules/ve/ve.Surface.js index 955df828ec..2915d2fe65 100644 --- a/modules/ve/ve.Surface.js +++ b/modules/ve/ve.Surface.js @@ -40,7 +40,7 @@ ve.Surface = function VeSurface( parent, dom, options ) { // Initialization this.setupToolbars(); - this.addCommands( this.options.commands ); + this.setupCommands(); ve.instances.push( this ); this.model.startHistoryTracking(); }; @@ -50,6 +50,7 @@ ve.Surface = function VeSurface( parent, dom, options ) { ve.Surface.defaultOptions = { 'toolbars': { 'top': { + 'float': true, 'tools': [ { 'name': 'history', 'items' : ['undo', 'redo'] }, { 'name': 'textStyle', 'items' : ['format'] }, @@ -62,50 +63,6 @@ ve.Surface.defaultOptions = { 'commands': ['bold', 'italic', 'link', 'undo', 'redo'] }; -/** - * Common commands that can be invoked by their symbolic names. - * - * @static - * @member - */ -ve.Surface.commands = { - 'bold': { - 'trigger': ['cmd+b', 'ctrl+b'], - 'action': ['annotation', 'toggle', 'textStyle/bold'] - }, - 'italic': { - 'trigger': ['cmd+i', 'ctrl+i'], - 'action': ['annotation', 'toggle', 'textStyle/italic'] - }, - 'link': { - 'trigger': ['cmd+k', 'ctrl+k'], - 'action': ['inspector', 'open', 'link'] - }, - 'undo': { - 'trigger': ['cmd+z', 'ctrl+z'], - 'action': ['history', 'undo'] - }, - 'redo': { - 'trigger': ['cmd+shift+z', 'ctrl+shift+z'], - 'action': ['history', 'redo'] - } -}; - -/* Static Methods */ - -/** - * Adds a command that can be referenced by a symbolic name. - * - * @static - * @method - * @param {String} name Symbolic name of command - * @param {String|String[]} trigger One or more canonical representations of keyboard triggers - * @param {Array} action Array containing the action name, method and additional arguments - */ -ve.Surface.registerCommand = function ( name, trigger, action ) { - ve.Surface.commands[name] = { 'trigger': trigger, 'action': action }; -}; - /* Methods */ /** @@ -176,53 +133,33 @@ ve.Surface.prototype.execute = function ( action, method ) { }; /** - * Adds a link between a keyboard trigger and an action. + * Adds all commands from initialization options. + * + * Commands must be registered through {ve.commandRegsitry} prior to constructing the surface. * * @method - * @param {String|String[]} trigger One or more canonical representations of keyboard triggers - * @param {Array} action Array containing the action name, method and additional arguments + * @param {String[]} commands Array of symbolic names of registered commands */ -ve.Surface.prototype.addCommand = function ( triggers, action ) { - var i, len, trigger; - if ( !ve.isArray( triggers ) ) { - triggers = [triggers]; - } - for ( i = 0, len = triggers.length; i < len; i++ ) { - // Normalize - trigger = ( new ve.Command( triggers[i] ) ).toString(); - // Validate - if ( trigger.length === 0 ) { - throw new Error( 'Incomplete command: ' + triggers[i] ); +ve.Surface.prototype.setupCommands = function () { + var i, iLen, j, jLen, triggers, trigger, command, + commands = this.options.commands; + for ( i = 0, iLen = commands.length; i < iLen; i++ ) { + command = ve.commandRegistry.lookup( commands[i] ); + if ( !command ) { + throw new Error( 'No command registered by that name: ' + commands[i] ); } - this.commands[trigger] = action; - } -}; - -/** - * Adds multiple links between a keyboard triggers and an actions. - * - * Each object's trigger and action properties will be passed directly into - * {ve.Surface.prototype.addCommand}. - * - * @method - * @param {String[]|Object[]} commands Array of symbolic names of known commands, or objects that - * each contain a trigger and action property - */ -ve.Surface.prototype.addCommands = function ( commands ) { - var i, len; - for ( i = 0, len = commands.length; i < len; i++ ) { - if ( typeof commands[i] === 'string' ) { - if ( !( commands[i] in ve.Surface.commands ) ) { - throw new Error( 'Unknown command: ' + commands[i] ); + triggers = command.trigger; + if ( !ve.isArray( triggers ) ) { + triggers = [triggers]; + } + for ( j = 0, jLen = triggers.length; j < jLen; j++ ) { + // Normalize + trigger = ( new ve.Command( triggers[j] ) ).toString(); + // Validate + if ( trigger.length === 0 ) { + throw new Error( 'Incomplete command: ' + triggers[j] ); } - this.addCommand( - ve.Surface.commands[commands[i]].trigger, - ve.Surface.commands[commands[i]].action - ); - } else if ( ve.isPlainObject( commands[i] ) ) { - this.addCommand( commands[i].trigger, commands[i].action ); - } else { - throw new Error( 'Invalid command, must be name of known command or command object' ); + this.commands[trigger] = command.action; } } }; @@ -235,36 +172,32 @@ ve.Surface.prototype.addCommands = function ( commands ) { * @method */ ve.Surface.prototype.setupToolbars = function () { - var surface = this; - - // Build each toolbar - $.each( this.options.toolbars, function ( name, config ) { - if ( config !== null ) { + var name, config, toolbar, + toolbars = this.options.toolbars; + for ( name in toolbars ) { + config = toolbars[name]; + if ( ve.isPlainObject( config ) ) { + this.toolbars[name] = toolbar = { '$': $( '
' ) }; if ( name === 'top' ) { - surface.toolbars[name] = { - '$wrapper': $( '
' ), - '$': $( '
' ) - .append( - $( '
' ), - $( '
' ), - $( '
' ) - ) - }; - surface.toolbars[name].$wrapper.append( surface.toolbars[name].$ ); - surface.$.before( surface.toolbars[name].$wrapper ); - + // Add extra sections to the toolbar + toolbar.$.append( + '
' + + '
' + + '
' + ); + // Wrap toolbar for floating + toolbar.$wrapper = $( '
' ) + .append( this.toolbars[name].$ ); + // Add toolbar to surface + this.$.before( toolbar.$wrapper ); if ( 'float' in config && config.float === true ) { // Float top toolbar - surface.floatTopToolbar(); + this.floatTopToolbar(); } } - surface.toolbars[name].instance = new ve.ui.Toolbar( - surface.toolbars[name].$, - surface, - config.tools - ); + toolbar.instance = new ve.ui.Toolbar( toolbar.$, this, config.tools ); } - } ); + } }; /*