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 );
}
- } );
+ }
};
/*