Esanders has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/175334

Change subject: Create ve.ui.Sequence which matches commands to typed text
......................................................................

Create ve.ui.Sequence which matches commands to typed text

Register two sequences:
- '<p>* ' creates a bullet list
- '<p>1. ' creates a numbered list (note this isn't '#' as that
  is MW-specific)

Change-Id: I6a4d71d845ecde460193466d9d8d12a0c9050c98
---
M .docs/eg-iframe.html
M build/modules.json
M demos/ve/desktop.html
M demos/ve/mobile.html
M src/ce/ve.ce.Surface.js
M src/ui/actions/ve.ui.ListAction.js
M src/ui/ve.ui.CommandRegistry.js
A src/ui/ve.ui.Sequence.js
A src/ui/ve.ui.SequenceRegistry.js
M tests/index.html
10 files changed, 252 insertions(+), 13 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor 
refs/changes/34/175334/1

diff --git a/.docs/eg-iframe.html b/.docs/eg-iframe.html
index 9562a05..d1d726e 100644
--- a/.docs/eg-iframe.html
+++ b/.docs/eg-iframe.html
@@ -284,6 +284,8 @@
                <script src="../src/ui/ve.ui.CommandRegistry.js"></script>
                <script src="../src/ui/ve.ui.Trigger.js"></script>
                <script src="../src/ui/ve.ui.TriggerRegistry.js"></script>
+               <script src="../src/ui/ve.ui.Sequence.js"></script>
+               <script src="../src/ui/ve.ui.SequenceRegistry.js"></script>
                <script src="../src/ui/ve.ui.Action.js"></script>
                <script src="../src/ui/ve.ui.ActionFactory.js"></script>
                <script src="../src/ui/ve.ui.FileDropHandler.js"></script>
diff --git a/build/modules.json b/build/modules.json
index c96abff..6a5eab1 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -310,6 +310,8 @@
                        "src/ui/ve.ui.CommandRegistry.js",
                        "src/ui/ve.ui.Trigger.js",
                        "src/ui/ve.ui.TriggerRegistry.js",
+                       "src/ui/ve.ui.Sequence.js",
+                       "src/ui/ve.ui.SequenceRegistry.js",
                        "src/ui/ve.ui.Action.js",
                        "src/ui/ve.ui.ActionFactory.js",
                        "src/ui/ve.ui.FileDropHandler.js",
diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html
index 41ab04d..9038e66 100644
--- a/demos/ve/desktop.html
+++ b/demos/ve/desktop.html
@@ -297,6 +297,8 @@
                <script src="../../src/ui/ve.ui.CommandRegistry.js"></script>
                <script src="../../src/ui/ve.ui.Trigger.js"></script>
                <script src="../../src/ui/ve.ui.TriggerRegistry.js"></script>
+               <script src="../../src/ui/ve.ui.Sequence.js"></script>
+               <script src="../../src/ui/ve.ui.SequenceRegistry.js"></script>
                <script src="../../src/ui/ve.ui.Action.js"></script>
                <script src="../../src/ui/ve.ui.ActionFactory.js"></script>
                <script src="../../src/ui/ve.ui.FileDropHandler.js"></script>
diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html
index ed878ed..a437661 100644
--- a/demos/ve/mobile.html
+++ b/demos/ve/mobile.html
@@ -298,6 +298,8 @@
                <script src="../../src/ui/ve.ui.CommandRegistry.js"></script>
                <script src="../../src/ui/ve.ui.Trigger.js"></script>
                <script src="../../src/ui/ve.ui.TriggerRegistry.js"></script>
+               <script src="../../src/ui/ve.ui.Sequence.js"></script>
+               <script src="../../src/ui/ve.ui.SequenceRegistry.js"></script>
                <script src="../../src/ui/ve.ui.Action.js"></script>
                <script src="../../src/ui/ve.ui.ActionFactory.js"></script>
                <script src="../../src/ui/ve.ui.FileDropHandler.js"></script>
diff --git a/src/ce/ve.ce.Surface.js b/src/ce/ve.ce.Surface.js
index 9a442db..e788610 100644
--- a/src/ce/ve.ce.Surface.js
+++ b/src/ce/ve.ce.Surface.js
@@ -2245,6 +2245,9 @@
                        } finally {
                                this.decRenderLock();
                        }
+                       setTimeout( function () {
+                               surface.checkSequences();
+                       } );
                        return;
                }
 
@@ -2311,6 +2314,24 @@
                ve.dm.Transaction.newFromReplacement( this.documentView.model, 
replacementRange, data ),
                new ve.dm.LinearSelection( this.documentView.model, newRange )
        );
+       this.queueCheckSequences = true;
+       setTimeout( function () {
+               surface.checkSequences();
+       } );
+};
+
+/**
+ * Check the current surface offset for sequence matches
+ */
+ve.ce.Surface.prototype.checkSequences = function () {
+       var i, surfaceModel = this.surface.getModel(),
+               sequences = ve.ui.sequenceRegistry.findMatching( 
surfaceModel.getDocument().data, surfaceModel.getSelection().getRange().end );
+
+       // sequences.length will likely be 0 or 1 so don't cache
+       for ( i = 0; i < sequences.length; i++ ) {
+               sequences[i].execute( this.surface );
+       }
+       this.showSelection( this.surface.getModel().getSelection() );
 };
 
 /**
diff --git a/src/ui/actions/ve.ui.ListAction.js 
b/src/ui/actions/ve.ui.ListAction.js
index 920dfe7..f01b231 100644
--- a/src/ui/actions/ve.ui.ListAction.js
+++ b/src/ui/actions/ve.ui.ListAction.js
@@ -31,32 +31,61 @@
  * @static
  * @property
  */
-ve.ui.ListAction.static.methods = [ 'wrap', 'unwrap', 'toggle' ];
+ve.ui.ListAction.static.methods = [ 'wrap', 'unwrap', 'toggle', 'wrapOnce' ];
 
 /* Methods */
 
 /**
- * Toggle a list around content.
+ * Check if the current selection is wrapped in a list of a given style
  *
  * @method
- * @param {string} style List style, e.g. 'number' or 'bullet'
- * @return {boolean} Action was executed
+ * @param {string|null} style List style, e.g. 'number' or 'bullet', or null 
for any style
+ * @return {boolean} Current selection is all wrapped in a list
  */
-ve.ui.ListAction.prototype.toggle = function ( style ) {
+ve.ui.ListAction.prototype.allWrapped = function ( style ) {
        var i, len,
+               attributes = style ? { style: style } : undefined,
                nodes = this.surface.getModel().getFragment().getLeafNodes(),
                all = !!nodes.length;
 
        for ( i = 0, len = nodes.length; i < len; i++ ) {
                if (
                        ( len === 1 || !nodes[i].range || 
nodes[i].range.getLength() ) &&
-                       !nodes[i].node.hasMatchingAncestor( 'list', { style: 
style } )
+                       !nodes[i].node.hasMatchingAncestor( 'list', attributes )
                ) {
                        all = false;
                        break;
                }
        }
-       return this[all ? 'unwrap' : 'wrap']( style );
+       return all;
+};
+
+/**
+ * Toggle a list around content.
+ *
+ * @method
+ * @param {string} style List style, e.g. 'number' or 'bullet'
+ * @param {boolean} noBreakpoints Don't create breakpoints
+ * @return {boolean} Action was executed
+ */
+ve.ui.ListAction.prototype.toggle = function ( style, noBreakpoints ) {
+       return this[this.allWrapped( style ) ? 'unwrap' : 'wrap']( style, 
noBreakpoints );
+};
+
+/**
+ * Add a list around content only if it has no list already.
+ *
+ * @method
+ * @param {string} style List style, e.g. 'number' or 'bullet'
+ * @param {boolean} noBreakpoints Don't create breakpoints
+ * @return {boolean} Action was executed
+ */
+ve.ui.ListAction.prototype.wrapOnce = function ( style, noBreakpoints ) {
+       // Check for a list of any style
+       if ( !this.allWrapped() ) {
+               return this.wrap( style, noBreakpoints );
+       }
+       return false;
 };
 
 /**
@@ -66,9 +95,10 @@
  *
  * @method
  * @param {string} style List style, e.g. 'number' or 'bullet'
+ * @param {boolean} noBreakpoints Don't create breakpoints
  * @return {boolean} Action was executed
  */
-ve.ui.ListAction.prototype.wrap = function ( style ) {
+ve.ui.ListAction.prototype.wrap = function ( style, noBreakpoints ) {
        var tx, i, previousList, groupRange, group, range,
                surfaceModel = this.surface.getModel(),
                documentModel = surfaceModel.getDocument(),
@@ -81,7 +111,9 @@
 
        range = selection.getRange();
 
-       surfaceModel.breakpoint();
+       if ( !noBreakpoints ) {
+               surfaceModel.breakpoint();
+       }
 
        // TODO: Would be good to refactor at some point and avoid/abstract 
path split for block slug
        // and not block slug.
@@ -153,7 +185,9 @@
                        }
                }
        }
-       surfaceModel.breakpoint();
+       if ( !noBreakpoints ) {
+               surfaceModel.breakpoint();
+       }
        this.surface.getView().focus();
        return true;
 };
@@ -164,9 +198,10 @@
  * TODO: Refactor functionality into {ve.dm.SurfaceFragment}.
  *
  * @method
+ * @param {boolean} noBreakpoints Don't create breakpoints
  * @return {boolean} Action was executed
  */
-ve.ui.ListAction.prototype.unwrap = function () {
+ve.ui.ListAction.prototype.unwrap = function ( noBreakpoints ) {
        var node,
                surfaceModel = this.surface.getModel(),
                documentModel = surfaceModel.getDocument();
@@ -175,13 +210,18 @@
                return false;
        }
 
-       surfaceModel.breakpoint();
+       if ( !noBreakpoints ) {
+               surfaceModel.breakpoint();
+       }
 
        do {
                node = documentModel.getBranchNodeFromOffset( 
surfaceModel.getSelection().getRange().start );
        } while ( node.hasMatchingAncestor( 'list' ) && this.surface.execute( 
'indentation', 'decrease' ) );
 
-       surfaceModel.breakpoint();
+       if ( !noBreakpoints ) {
+               surfaceModel.breakpoint();
+       }
+
        this.surface.getView().focus();
        return true;
 };
diff --git a/src/ui/ve.ui.CommandRegistry.js b/src/ui/ve.ui.CommandRegistry.js
index d9277ef..9235d9a 100644
--- a/src/ui/ve.ui.CommandRegistry.js
+++ b/src/ui/ve.ui.CommandRegistry.js
@@ -124,6 +124,18 @@
 );
 ve.ui.commandRegistry.register(
        new ve.ui.Command(
+               'numberWrapOnce', 'list', 'wrapOnce',
+               { args: ['number', true], supportedSelections: ['linear'] }
+       )
+);
+ve.ui.commandRegistry.register(
+       new ve.ui.Command(
+               'bulletWrapOnce', 'list', 'wrapOnce',
+               { args: ['bullet', true], supportedSelections: ['linear'] }
+       )
+);
+ve.ui.commandRegistry.register(
+       new ve.ui.Command(
                'commandHelp', 'window', 'open', { args: ['commandHelp'] }
        )
 );
diff --git a/src/ui/ve.ui.Sequence.js b/src/ui/ve.ui.Sequence.js
new file mode 100644
index 0000000..276cbed
--- /dev/null
+++ b/src/ui/ve.ui.Sequence.js
@@ -0,0 +1,87 @@
+/*!
+ * VisualEditor UserInterface Sequence class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see 
http://ve.mit-license.org
+ */
+
+/**
+ * Key sequence.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.ui.Sequence = function VeUiSequence( name, commandName, data, strip ) {
+       this.name = name;
+       this.commandName = commandName;
+       this.data = data;
+       this.strip = strip;
+};
+
+/* Inheritance */
+
+OO.initClass( ve.ui.Sequence );
+
+/* Methods */
+
+/**
+ * Check if the sequence matches a given offset in the data
+ *
+ * @param {string|Array} data String or linear data
+ * @param {number} offset Offset
+ * @return {boolean} Sequence matches
+ */
+ve.ui.Sequence.prototype.match = function ( data, offset ) {
+       var i, j = offset - 1;
+
+       for ( i = this.data.length - 1; i >= 0; i--, j-- ) {
+               if ( typeof this.data[i] === 'string' ) {
+                       if ( this.data[i] !== data.getCharacterData( j ) ) {
+                               return false;
+                       }
+               } else if ( !ve.compare( this.data[i], data.getData( j ), true 
) ) {
+                       return false;
+               }
+       }
+       return true;
+};
+
+/**
+ * Execute the command associated with the sequence
+ *
+ * @param {ve.ui.Surface} surface surface
+ * @return {boolean} The command executed
+ * @throws {Error} Command not found
+ */
+ve.ui.Sequence.prototype.execute = function ( surface ) {
+       var range, executed, stripFragment,
+               surfaceModel = surface.getModel(),
+               command = ve.ui.commandRegistry.lookup( this.getCommandName() );
+
+       if ( !command ) {
+               throw new Error( 'Command not found: ' + this.getCommandName() 
) ;
+       }
+
+       if ( this.strip ) {
+               range = surfaceModel.getSelection().getRange();
+               stripFragment = surfaceModel.getLinearFragment( new ve.Range( 
range.end, range.end - this.strip ) );
+       }
+
+       surfaceModel.breakpoint();
+
+       executed = command.execute( surface );
+
+       if ( executed && stripFragment ) {
+               stripFragment.removeContent();
+       }
+
+       return executed;
+};
+
+ve.ui.Sequence.prototype.getName = function () {
+       return this.name;
+};
+
+ve.ui.Sequence.prototype.getCommandName = function () {
+       return this.commandName;
+};
diff --git a/src/ui/ve.ui.SequenceRegistry.js b/src/ui/ve.ui.SequenceRegistry.js
new file mode 100644
index 0000000..bdb791e
--- /dev/null
+++ b/src/ui/ve.ui.SequenceRegistry.js
@@ -0,0 +1,69 @@
+/*!
+ * VisualEditor UserInterface SequenceRegistry class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see 
http://ve.mit-license.org
+ */
+
+/**
+ * Sequence registry.
+ *
+ * @class
+ * @extends OO.Registry
+ * @constructor
+ */
+ve.ui.SequenceRegistry = function VeUiSequenceRegistry() {
+       // Parent constructor
+       ve.ui.SequenceRegistry.super.call( this );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.SequenceRegistry, OO.Registry );
+
+/**
+ * Register a sequence with the factory.
+ *
+ * @method
+ * @param {ve.ui.Sequence} sequence Sequence object
+ * @throws {Error} If sequence is not an instance of ve.ui.Sequence
+ */
+ve.ui.SequenceRegistry.prototype.register = function ( sequence ) {
+       // Validate arguments
+       if ( !( sequence instanceof ve.ui.Sequence ) ) {
+               throw new Error(
+                       'sequence must be an instance of ve.ui.Sequence, cannot 
be a ' + typeof sequence
+               );
+       }
+
+       ve.ui.SequenceRegistry.super.prototype.register.call( this, 
sequence.getName(), sequence );
+};
+
+/**
+ * Find sequence matches a given offset in the data
+ *
+ * @param {ve.dm.ElementLinearData} data Linear data
+ * @param {number} offset Offset
+ * @return {ve.ui.Sequence[]} Sequences which match
+ */
+ve.ui.SequenceRegistry.prototype.findMatching = function ( data, offset ) {
+       var name, sequences = [];
+       for ( name in this.registry ) {
+               if ( this.registry[name].match( data, offset ) ) {
+                       sequences.push( this.registry[name] );
+               }
+       }
+       return sequences;
+};
+
+/* Initialization */
+
+ve.ui.sequenceRegistry = new ve.ui.SequenceRegistry();
+
+/* Registrations */
+
+ve.ui.sequenceRegistry.register(
+       new ve.ui.Sequence( 'bulletStar', 'bulletWrapOnce', [ { type: 
'paragraph' }, '*', ' ' ], 2 )
+);
+ve.ui.sequenceRegistry.register(
+       new ve.ui.Sequence( 'numberDot', 'numberWrapOnce', [ { type: 
'paragraph' }, '1', '.', ' ' ], 3 )
+);
diff --git a/tests/index.html b/tests/index.html
index 4778413..69ca0c0 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -247,6 +247,8 @@
                <script src="../src/ui/ve.ui.CommandRegistry.js"></script>
                <script src="../src/ui/ve.ui.Trigger.js"></script>
                <script src="../src/ui/ve.ui.TriggerRegistry.js"></script>
+               <script src="../src/ui/ve.ui.Sequence.js"></script>
+               <script src="../src/ui/ve.ui.SequenceRegistry.js"></script>
                <script src="../src/ui/ve.ui.Action.js"></script>
                <script src="../src/ui/ve.ui.ActionFactory.js"></script>
                <script src="../src/ui/ve.ui.FileDropHandler.js"></script>

-- 
To view, visit https://gerrit.wikimedia.org/r/175334
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6a4d71d845ecde460193466d9d8d12a0c9050c98
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <esand...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to