Trevor Parscal has uploaded a new change for review.

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


Change subject: Outline controls
......................................................................

Outline controls

Objectives:

* Allow reordering items in outline widgets using an outline control widget
* Use an outline control widget to reorder transclusion parts

Changes:

ve.ui.SelectWidget.js
* Emit add and remove events

ve.ui.OutlineItemWidget.js
* Add movable config options
* Add isMovable method

ve.ui.OutlineControlsWidget.js
* New class
* Displays move up/down buttons which are synchronized with an outline widget
* Doesn't actually move items (since an outline widget is probably data-driven) 
just emits events

ve.ui.Widget.css
* Add disabled style for icon button widgets
* Add styles for outline controls widget

ve.ui.Icons*.css
* Add missing icon styles

ve.ui.Dialog.css
* Add styles for outline and controls in editable paged dialogs

ve.ui.GroupElement.js
* Fix bug where items are insertions are in the wrong place when "moving" them

ve.ui.PagedDialog.js
* Add editable config option which shows outline controls under the outline
* Pass through movable config option when creating pages

ve.ui.MWTranclusionDialog.js
* Configure paged dialog outline as editable
* Add initialize method to connect outline controls widget events
* Make addPart method automatically add parameters when templates are added
* Add handler for outline controls move event which re-orders parts
* Make parts movable (params are automatically ordered, so they aren't movable)

ve.dm.MWTransclusionModel.js
* Add addPart method and use it within the addContent and addTemplate methods
* Fix documentation lies
* Add getPartFromId method

*.php
* Add links to new files and messages

Change-Id: I919d4c3e9b85d07a97a99c0b2e8739a859bdf2b1
---
M VisualEditor.i18n.php
M VisualEditor.php
M demos/ve/index.php
M modules/ve/dm/models/ve.dm.MWTransclusionModel.js
M modules/ve/test/index.php
M modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
M modules/ve/ui/dialogs/ve.ui.PagedDialog.js
M modules/ve/ui/elements/ve.ui.GroupElement.js
M modules/ve/ui/styles/ve.ui.Dialog.css
M modules/ve/ui/styles/ve.ui.Icons-raster.css
M modules/ve/ui/styles/ve.ui.Icons-vector.css
M modules/ve/ui/styles/ve.ui.Widget.css
A modules/ve/ui/widgets/ve.ui.OutlineControlsWidget.js
M modules/ve/ui/widgets/ve.ui.OutlineItemWidget.js
M modules/ve/ui/widgets/ve.ui.SelectWidget.js
15 files changed, 296 insertions(+), 14 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/04/68604/1

diff --git a/VisualEditor.i18n.php b/VisualEditor.i18n.php
index aeca79e..6e04e76 100644
--- a/VisualEditor.i18n.php
+++ b/VisualEditor.i18n.php
@@ -99,6 +99,8 @@
        'visualeditor-notification-created' => '$1 has been created.',
        'visualeditor-notification-restored' => '$1 has been restored.',
        'visualeditor-notification-saved' => 'Your changes to $1 have been 
saved.',
+       'visualeditor-outline-control-move-up' => 'Move item up',
+       'visualeditor-outline-control-move-down' => 'Move item down',
        'visualeditor-preference-enable' => 'Enable VisualEditor (only in the 
[[{{MediaWiki:Visualeditor-mainnamespacepagelink}}|main]] and 
[[{{MediaWiki:Visualeditor-usernamespacepagelink}}|user]] namespaces)',
        'visualeditor-savedialog-label-create' => 'Create page',
        'visualeditor-savedialog-label-report' => 'Report problem',
diff --git a/VisualEditor.php b/VisualEditor.php
index 0b82ce0..9eb9c9f 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -393,6 +393,7 @@
                        've/ui/widgets/ve.ui.TextInputWidget.js',
                        've/ui/widgets/ve.ui.OutlineItemWidget.js',
                        've/ui/widgets/ve.ui.OutlineWidget.js',
+                       've/ui/widgets/ve.ui.OutlineControlsWidget.js',
                        've/ui/widgets/ve.ui.MenuItemWidget.js',
                        've/ui/widgets/ve.ui.MenuSectionItemWidget.js',
                        've/ui/widgets/ve.ui.MenuWidget.js',
@@ -537,6 +538,8 @@
                        'visualeditor-notification-created',
                        'visualeditor-notification-restored',
                        'visualeditor-notification-saved',
+                       'visualeditor-outline-control-move-up',
+                       'visualeditor-outline-control-move-down',
                        'visualeditor-savedialog-label-create',
                        'visualeditor-savedialog-label-report',
                        'visualeditor-savedialog-label-resolve-conflict',
diff --git a/demos/ve/index.php b/demos/ve/index.php
index 2334670..a1d3d34 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -268,6 +268,7 @@
                <script 
src="../../modules/ve/ui/widgets/ve.ui.TextInputWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.OutlineItemWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.OutlineWidget.js"></script>
+               <script 
src="../../modules/ve/ui/widgets/ve.ui.OutlineControlsWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.MenuItemWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.MenuSectionItemWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.MenuWidget.js"></script>
diff --git a/modules/ve/dm/models/ve.dm.MWTransclusionModel.js 
b/modules/ve/dm/models/ve.dm.MWTransclusionModel.js
index c88cd89..9eca138 100644
--- a/modules/ve/dm/models/ve.dm.MWTransclusionModel.js
+++ b/modules/ve/dm/models/ve.dm.MWTransclusionModel.js
@@ -205,8 +205,7 @@
  */
 ve.dm.MWTransclusionModel.prototype.addContent = function ( value, index ) {
        var part = new ve.dm.MWTransclusionContentModel( this, value );
-       this.parts.splice( index === undefined ? this.parts.length : index, 0, 
part );
-       this.emit( 'add', part );
+       this.addPart( part, index );
        return part;
 };
 
@@ -224,16 +223,28 @@
        if ( this.specs.hasOwnProperty( title ) ) {
                part.getSpec().extend( this.specs[title] );
        }
+       this.addPart( part, index );
+       return part;
+};
+
+/**
+ * Add part.
+ *
+ * @method
+ * @param {ve.dm.MWTransclusionPartModel} part Part to add
+ * @param {number} [index] Specific index to add content at
+ * @emits add
+ */
+ve.dm.MWTransclusionModel.prototype.addPart = function ( part, index ) {
        this.parts.splice( index === undefined ? this.parts.length : index, 0, 
part );
        this.emit( 'add', part );
-       return part;
 };
 
 /**
  * Remove a part.
  *
  * @method
- * @param {ve.dm.MWTransclusionPartModel} part Template part
+ * @param {ve.dm.MWTransclusionPartModel} part Part to remove
  * @emits remove
  */
 ve.dm.MWTransclusionModel.prototype.removePart = function ( part ) {
@@ -255,6 +266,24 @@
 };
 
 /**
+ * Get part by its ID.
+ *
+ * @method
+ * @param {string} id Part ID
+ * @returns {ve.dm.MWTransclusionPartModel|null} Part with matching ID, if 
found
+ */
+ve.dm.MWTransclusionModel.prototype.getPartFromId = function ( id ) {
+       var i, len;
+
+       for ( i = 0, len = this.parts.length; i < len; i++ ) {
+               if ( this.parts[i].getId() === id ) {
+                       return this.parts[i];
+               }
+       }
+       return null;
+};
+
+/**
  * Get a template specification.
  *
  * @method
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 3ce587d..ac64440 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -221,6 +221,7 @@
                <script 
src="../../ve/ui/widgets/ve.ui.TextInputWidget.js"></script>
                <script 
src="../../ve/ui/widgets/ve.ui.OutlineItemWidget.js"></script>
                <script 
src="../../ve/ui/widgets/ve.ui.OutlineWidget.js"></script>
+               <script 
src="../../ve/ui/widgets/ve.ui.OutlineControlsWidget.js"></script>
                <script 
src="../../ve/ui/widgets/ve.ui.MenuItemWidget.js"></script>
                <script 
src="../../ve/ui/widgets/ve.ui.MenuSectionItemWidget.js"></script>
                <script src="../../ve/ui/widgets/ve.ui.MenuWidget.js"></script>
diff --git a/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js 
b/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
index ab7b41d..e50f76b 100644
--- a/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
+++ b/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
@@ -19,6 +19,9 @@
  * @param {Object} [config] Config options
  */
 ve.ui.MWTransclusionDialog = function VeUiMWTransclusionDialog( surface, 
config ) {
+       // Configuration initialization
+       config = ve.extendObject( {}, config, { 'editable': true } );
+
        // Parent constructor
        ve.ui.PagedDialog.call( this, surface, config );
 
@@ -40,6 +43,18 @@
 ve.ui.MWTransclusionDialog.static.modelClasses = [ ve.dm.MWTransclusionNode ];
 
 /* Methods */
+
+/**
+ * Handle frame ready events.
+ *
+ * @method
+ */
+ve.ui.MWTransclusionDialog.prototype.initialize = function () {
+       // Call parent method
+       ve.ui.PagedDialog.prototype.initialize.call( this );
+
+       this.outlineControlsWidget.connect( this, { 'move': 
'onOutlineControlsMove' } );
+};
 
 /**
  * Handle frame open events.
@@ -106,17 +121,27 @@
  * @param {ve.dm.MWTransclusionPartModel} part Added part
  */
 ve.ui.MWTransclusionDialog.prototype.onAddPart = function ( part ) {
-       var page;
+       var i, len, page, params, param, names;
 
        if ( part instanceof ve.dm.MWTemplateModel ) {
                page = this.getTemplatePage( part );
-               part.connect( this, { 'add': 'onAddParameter', 'remove': 
'onRemoveParameter' } );
        } else if ( part instanceof ve.dm.MWTransclusionContentModel ) {
                page = this.getContentPage( part );
        }
-       page.index = this.getPageIndex( part ) + 1;
+       page.index = this.getPageIndex( part );
        if ( page ) {
                this.addPage( part.getId(), page );
+               if ( part instanceof ve.dm.MWTemplateModel ) {
+                       names = part.getParameterNames();
+                       params = part.getParameters();
+                       for ( i = 0, len = names.length; i < len; i++ ) {
+                               param = params[names[i]];
+                               page = this.getParameterPage( param );
+                               page.index = this.getPageIndex( param ) + 1;
+                               this.addPage( param.getId(), page );
+                       }
+                       part.connect( this, { 'add': 'onAddParameter', 
'remove': 'onRemoveParameter' } );
+               }
        }
 };
 
@@ -161,6 +186,27 @@
        this.removePage( param.getId() );
        // Return to template page
        this.setPageByName( param.getTemplate().getId() );
+};
+
+/**
+ * Handle outline controls move events.
+ *
+ * @method
+ * @param {number} places Number of places to move the selected item.
+ */
+ve.ui.MWTransclusionDialog.prototype.onOutlineControlsMove = function ( places 
) {
+       var part, index, name,
+               parts = this.transclusion.getParts(),
+               item = this.outlineWidget.getSelectedItem();
+
+       if ( item ) {
+               name = item.getData();
+               part = this.transclusion.getPartFromId( name );
+               index = ve.indexOf( part, parts );
+               this.transclusion.removePart( part );
+               this.transclusion.addPart( part, index + places );
+               this.setPageByName( name );
+       }
 };
 
 /**
@@ -284,7 +330,8 @@
        return {
                'label': ve.msg( 'visualeditor-dialog-transclusion-content' ),
                'icon': 'source',
-               '$content': valueFieldset.$.add( optionsFieldset.$ )
+               '$content': valueFieldset.$.add( optionsFieldset.$ ),
+               'moveable': true
        };
 };
 
@@ -360,7 +407,8 @@
        return {
                'label': label,
                'icon': 'template',
-               '$content': infoFieldset.$.add( addParameterFieldset.$ ).add( 
optionsFieldset.$ )
+               '$content': infoFieldset.$.add( addParameterFieldset.$ ).add( 
optionsFieldset.$ ),
+               'moveable': true
        };
 };
 
diff --git a/modules/ve/ui/dialogs/ve.ui.PagedDialog.js 
b/modules/ve/ui/dialogs/ve.ui.PagedDialog.js
index 735cc72..a832c8c 100644
--- a/modules/ve/ui/dialogs/ve.ui.PagedDialog.js
+++ b/modules/ve/ui/dialogs/ve.ui.PagedDialog.js
@@ -19,12 +19,17 @@
  * @constructor
  * @param {ve.ui.Surface} surface
  * @param {Object} [config] Config options
+ * @cfg {boolean} [editable] Show controls for adding, removing and reordering 
items in the outline
  */
 ve.ui.PagedDialog = function VeUiPagedDialog( surface, config ) {
+       // Configuration initialization
+       config = config || {};
+
        // Parent constructor
        ve.ui.Dialog.call( this, surface, config );
 
        // Properties
+       this.editable = !!config.editable;
        this.pages = {};
        this.currentPageName = null;
 };
@@ -51,12 +56,24 @@
                [this.outlinePanel, this.pagesPanel], { '$$': this.frame.$$, 
'widths': [1, 2] }
        );
        this.outlineWidget = new ve.ui.OutlineWidget( { '$$': this.frame.$$ } );
+       if ( this.editable ) {
+               this.outlineControlsWidget = new ve.ui.OutlineControlsWidget(
+                       this.outlineWidget, { '$$': this.frame.$$ }
+               );
+       }
 
        // Events
        this.outlineWidget.connect( this, { 'select': 'onOutlineSelect' } );
 
        // Initialization
-       this.outlinePanel.$.append( this.outlineWidget.$ ).addClass( 
've-ui-pagedDialog-outlinePanel' );
+       this.outlinePanel.$
+               .addClass( 've-ui-pagedDialog-outlinePanel' )
+               .append( this.outlineWidget.$ );
+       if ( this.editable ) {
+               this.outlinePanel.$
+                       .addClass( 've-ui-pagedDialog-outlinePanel-editable' )
+                       .append( this.outlineControlsWidget.$ );
+       }
        this.pagesPanel.$.addClass( 've-ui-pagedDialog-pagesPanel' );
        this.$body.append( this.layout.$ );
 };
@@ -84,6 +101,7 @@
  * @param {number} [config.level=0] Indentation level
  * @param {number} [config.index] Specific index to insert page at
  * @param {jQuery} [config.$content] Page content
+ * @param {jQuery} [config.moveable] Allow page to be moved in the outline
  * @chainable
  */
 ve.ui.PagedDialog.prototype.addPage = function ( name, config ) {
@@ -99,7 +117,8 @@
                                '$$': this.frame.$$,
                                'label': config.label || name,
                                'level': config.level || 0,
-                               'icon': config.icon
+                               'icon': config.icon,
+                               'moveable': config.moveable
                        } )
                ],
                config.index
@@ -169,7 +188,7 @@
  * @param {string} name Symbolic name of page
  */
 ve.ui.PagedDialog.prototype.setPage = function ( name ) {
-       if ( name in this.pages ) {
+       if ( this.pages[name] ) {
                this.currentPageName = name;
                this.pagesPanel.showItem( this.pages[name] );
                this.pages[name].$.find( ':input:first' ).focus();
diff --git a/modules/ve/ui/elements/ve.ui.GroupElement.js 
b/modules/ve/ui/elements/ve.ui.GroupElement.js
index 48845f5..fad63e9 100644
--- a/modules/ve/ui/elements/ve.ui.GroupElement.js
+++ b/modules/ve/ui/elements/ve.ui.GroupElement.js
@@ -42,15 +42,20 @@
  * @chainable
  */
 ve.ui.GroupElement.prototype.addItems = function ( items, index ) {
-       var i, len, item,
+       var i, len, item, currentIndex,
                $items = $( [] );
 
        for ( i = 0, len = items.length; i < len; i++ ) {
                item = items[i];
 
                // Check if item exists then remove it first, effectively 
"moving" it
-               if ( this.items.indexOf( item ) !== -1 ) {
+               currentIndex = this.items.indexOf( item );
+               if ( currentIndex >= 0 ) {
                        this.removeItems( [ item ] );
+                       // Adjust index to compensate for removal
+                       if ( currentIndex < index ) {
+                               index--;
+                       }
                }
                // Add the item
                $items = $items.add( item.$ );
diff --git a/modules/ve/ui/styles/ve.ui.Dialog.css 
b/modules/ve/ui/styles/ve.ui.Dialog.css
index 92bdb85..8c8bf45 100644
--- a/modules/ve/ui/styles/ve.ui.Dialog.css
+++ b/modules/ve/ui/styles/ve.ui.Dialog.css
@@ -120,6 +120,23 @@
        border-right: solid 1px #ddd;
 }
 
+.ve-ui-pagedDialog-outlinePanel-editable .ve-ui-outlineWidget {
+       position: absolute;
+       top: 0;
+       left: 0;
+       right: 0;
+       bottom: 3em;
+       overflow-y: auto;
+}
+
+.ve-ui-pagedDialog-outlinePanel .ve-ui-outlineControlsWidget {
+       position: absolute;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       box-shadow: 0 0 0.25em rgba(0,0,0,0.25);
+}
+
 .ve-ui-pagedDialog-pagesPanel .ve-ui-panelLayout {
        padding: 1.5em;
        width: 100%;
diff --git a/modules/ve/ui/styles/ve.ui.Icons-raster.css 
b/modules/ve/ui/styles/ve.ui.Icons-raster.css
index 27d9076..9d94bd7 100644
--- a/modules/ve/ui/styles/ve.ui.Icons-raster.css
+++ b/modules/ve/ui/styles/ve.ui.Icons-raster.css
@@ -239,3 +239,13 @@
        /* @embed */
        background-image: url(images/icons/search-big.png);
 }
+
+.ve-ui-icon-expand {
+       /* @embed */
+       background-image: url(images/icons/expand.png);
+}
+
+.ve-ui-icon-collapse {
+       /* @embed */
+       background-image: url(images/icons/collapse.png);
+}
diff --git a/modules/ve/ui/styles/ve.ui.Icons-vector.css 
b/modules/ve/ui/styles/ve.ui.Icons-vector.css
index 00dc6e1..17636ec 100644
--- a/modules/ve/ui/styles/ve.ui.Icons-vector.css
+++ b/modules/ve/ui/styles/ve.ui.Icons-vector.css
@@ -239,3 +239,13 @@
        /* @embed */
        background-image: url(images/icons/search-big.svg);
 }
+
+.ve-ui-icon-expand {
+       /* @embed */
+       background-image: url(images/icons/expand.svg);
+}
+
+.ve-ui-icon-collapse {
+       /* @embed */
+       background-image: url(images/icons/collapse.svg);
+}
diff --git a/modules/ve/ui/styles/ve.ui.Widget.css 
b/modules/ve/ui/styles/ve.ui.Widget.css
index 33ee614..557c403 100644
--- a/modules/ve/ui/styles/ve.ui.Widget.css
+++ b/modules/ve/ui/styles/ve.ui.Widget.css
@@ -25,6 +25,10 @@
        opacity: 1;
 }
 
+.ve-ui-iconButtonWidget.ve-ui-widget-disabled {
+       opacity: 0.2;
+}
+
 /* ve.ui.ButtonWidget */
 
 .ve-ui-buttonWidget {
@@ -202,6 +206,26 @@
        text-shadow: 0 1px 1px rgba(255,255,255,0.5);
 }
 
+/* ve.ui.OutlineControlsWidget */
+
+.ve-ui-outlineControlsWidget {
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+       height: 3em;
+       padding: 0.5em;
+       background-color: #fff;
+}
+
+.ve-ui-outlineControlsWidget-addButton {
+       float: left;
+}
+
+.ve-ui-outlineControlsWidget-upButton,
+.ve-ui-outlineControlsWidget-downButton {
+       float: right;
+}
+
 /* ve.ui.InputLabelWidget */
 
 .ve-ui-inputLabelWidget {
diff --git a/modules/ve/ui/widgets/ve.ui.OutlineControlsWidget.js 
b/modules/ve/ui/widgets/ve.ui.OutlineControlsWidget.js
new file mode 100644
index 0000000..c8b1f16
--- /dev/null
+++ b/modules/ve/ui/widgets/ve.ui.OutlineControlsWidget.js
@@ -0,0 +1,81 @@
+/*!
+ * VisualEditor UserInterface OutlineControlsWidget class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Creates an ve.ui.OutlineControlsWidget object.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {ve.ui.OutlineWidget} outline Outline to control
+ * @param {Object} [config] Config options
+ */
+ve.ui.OutlineControlsWidget = function VeUiOutlineControlsWidget( outline, 
config ) {
+       // Parent constructor
+       ve.ui.Widget.call( this, config );
+
+       // Properties
+       this.outline = outline;
+       this.upButton = new ve.ui.IconButtonWidget( {
+               '$$': this.$$, 'icon': 'collapse', 'title': ve.msg( 
'visualeditor-outline-control-move-up' )
+       } );
+       this.downButton = new ve.ui.IconButtonWidget( {
+               '$$': this.$$, 'icon': 'expand', 'title': ve.msg( 
'visualeditor-outline-control-move-down' )
+       } );
+
+       // Events
+       outline.connect( this, {
+               'select': 'onOutlineChange',
+               'add': 'onOutlineChange',
+               'remove': 'onOutlineChange'
+       } );
+       this.upButton.connect( this, { 'click': ['emit', 'move', -1] } );
+       this.downButton.connect( this, { 'click': ['emit', 'move', 1] } );
+
+       // Initialization
+       this.$.addClass( 've-ui-outlineControlsWidget' );
+       this.upButton.$.addClass( 've-ui-outlineControlsWidget-upButton' );
+       this.downButton.$.addClass( 've-ui-outlineControlsWidget-downButton' );
+       this.$.append( this.upButton.$, this.downButton.$ );
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.OutlineControlsWidget, ve.ui.Widget );
+
+/* Events */
+
+/**
+ * @event move
+ * @param {number} places Number of places to move
+ */
+
+/* Methods */
+
+ve.ui.OutlineControlsWidget.prototype.onOutlineChange = function () {
+       var i, len, item, firstMoveable, lastMoveable,
+               moveable = false,
+               items = this.outline.getItems(),
+               selectedItem = this.outline.getSelectedItem();
+
+       if ( selectedItem ) {
+               for ( i = 0, len = items.length; i < len; i++ ) {
+                       item = items[i];
+                       if ( item.isMoveable() ) {
+                               if ( item === selectedItem ) {
+                                       moveable = true;
+                               }
+                               if ( !firstMoveable ) {
+                                       firstMoveable = item;
+                               }
+                               lastMoveable = item;
+                       }
+               }
+       }
+       this.upButton.setDisabled( !moveable || selectedItem === firstMoveable 
);
+       this.downButton.setDisabled( !moveable || selectedItem === lastMoveable 
);
+};
diff --git a/modules/ve/ui/widgets/ve.ui.OutlineItemWidget.js 
b/modules/ve/ui/widgets/ve.ui.OutlineItemWidget.js
index 1eda48b..6ba9a98 100644
--- a/modules/ve/ui/widgets/ve.ui.OutlineItemWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.OutlineItemWidget.js
@@ -16,6 +16,7 @@
  * @param {Object} [config] Config options
  * @cfg {string} [icon] Symbolic name of icon
  * @cfg {number} [level] Indentation level
+ * @cfg {boolean} [moveable] Allow modification from outline controls
  */
 ve.ui.OutlineItemWidget = function VeUiOutlineItemWidget( data, config ) {
        // Config intialization
@@ -26,6 +27,7 @@
 
        // Properties
        this.level = 0;
+       this.moveable = !!config.moveable;
 
        // Initialization
        this.$.addClass( 've-ui-outlineItemWidget' );
@@ -50,6 +52,17 @@
 /* Methods */
 
 /**
+ * Check if item is moveable.
+ *
+ * Moveablilty is used by outline controls.
+ *
+ * @returns {boolean} Item is moveable
+ */
+ve.ui.OutlineItemWidget.prototype.isMoveable = function () {
+       return this.moveable;
+};
+
+/**
  * Get indentation level.
  *
  * @returns {number} Indentation level
diff --git a/modules/ve/ui/widgets/ve.ui.SelectWidget.js 
b/modules/ve/ui/widgets/ve.ui.SelectWidget.js
index 52d4ad8..92156a4 100644
--- a/modules/ve/ui/widgets/ve.ui.SelectWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.SelectWidget.js
@@ -56,6 +56,17 @@
  * @param {ve.ui.OptionWidget|null} item Selected item or null if no item is 
selected
  */
 
+/**
+ * @event add
+ * @param {ve.ui.OptionWidget[]} items Added items
+ * @param {number|undefined} index Index items were added at
+ */
+
+/**
+ * @event remove
+ * @param {ve.ui.OptionWidget[]} items Removed items
+ */
+
 /* Static Properties */
 
 ve.ui.SelectWidget.static.tagName = 'ul';
@@ -347,6 +358,8 @@
        }
        ve.ui.GroupElement.prototype.addItems.call( this, items, index );
 
+       this.emit( 'add', items, index );
+
        return this;
 };
 
@@ -372,6 +385,8 @@
        }
        ve.ui.GroupElement.prototype.removeItems.call( this, items );
 
+       this.emit( 'remove', items );
+
        return this;
 };
 
@@ -384,9 +399,13 @@
  * @chainable
  */
 ve.ui.SelectWidget.prototype.clearItems = function () {
+       var items = this.items.slice();
+
        // Clear all items
        this.hashes = {};
        ve.ui.GroupElement.prototype.clearItems.call( this );
 
+       this.emit( 'remove', items );
+
        return this;
 };

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I919d4c3e9b85d07a97a99c0b2e8739a859bdf2b1
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to