Single-click insertion

Objectives:

* Reduce the number of clicks and mouse maneuvers required to insert
  media, references or template parameters
* Make use of highlighting with mouse movement or arrow key presses,
  similar to menus, to suggest action when clicked
* Improve the way media search results look and feel

Changes:

ve.ui.SelectWidget.js
* Add mouseleave handler to un-highlight when the mouse exits the widget
* Document highlight events (already being emitted)

ve.ui.SearchWidget.js
* Propagate both select and highlight events from results widget
* Make arrow keys change highlight instead of selection
* Get rid of enter event, make enter key select highlighted item instead
* Provide direct access to results widget through getResults method

ve.ui.MenuWidget.js
* Use the selected item as a starting point if nothing is currently
  highlighted when adjusting the highlight position

ve.ui.Dialog.js
* Add footless option to hide the foot element and make the body extend
  all the way down to the bottom
* Remove applyButton, which only some dialogs need, and should be creating
  themselves, along with other buttons as needed

ve.ui.Widget.css
* Change highlight and selected colors of option widgets to match other
  selection colors used elsewhere
* Leave selected and highlighted widget looking selected

ve.ui.Frame.css
* Add background color to combat any color that might have been applied to
  the frame body in the imported CSS from the parent frame

ve.ui.Dialog.css
* Add rules for footless mode

ve.ui.MWReferenceResultWidget.js,
ve.ui.MWParameterResultWidget.js,
ve.ui.MWMediaResultWidget.js
* Allow highlighting

ve.ui.MWParamterSearchWidget.js
* Switch from selecting the first item when filtering to highlighting

ve-mw/ve.ui.Widget.js
* Adjust media result widget styling to better match other elements

ve.ui.MWTransclusionDialog.js,
ve.ui.MWReferenceListDialog.js,
ve.ui.MWReferenceEditDialog.js,
ve.ui.MWMetaDialog.js
ve.ui.MWMediaEditDialog.js
* Add apply button, as per it being removed from parent class

ve.ui.MWTransclusionDialog.js,
ve.ui.MWReferenceInsertDialog.js,
ve.ui.MWMediaInsertDialog.js
* Insert parameter/reference/media on select, instead of clicking an
  insert button
* Use 'insert' instead of 'apply' as argument for close method

Bug: 50774
Bug: 51143
Change-Id: Ia18e79f1f8df2540f465468edb01f5ce989bf843
This commit is contained in:
Trevor Parscal 2013-07-15 14:07:53 -07:00 committed by Trevor Parscal
parent 3d214d97b3
commit 130e446e52
18 changed files with 198 additions and 90 deletions

View file

@ -6,7 +6,7 @@
*/ */
/** /**
* Document dialog. * Dialog for editing MediaWiki media objects.
* *
* @class * @class
* @extends ve.ui.MWDialog * @extends ve.ui.MWDialog
@ -54,10 +54,17 @@ ve.ui.MWMediaEditDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-media-content-section' ), 'label': ve.msg( 'visualeditor-dialog-media-content-section' ),
'icon': 'parameter' 'icon': 'parameter'
} ); } );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization // Initialization
this.$body.addClass( 've-ui-mwMediaEditDialog-body' ); this.$body.addClass( 've-ui-mwMediaEditDialog-body' );
this.$body.append( this.contentFieldset.$ ); this.$body.append( this.contentFieldset.$ );
this.$foot.append( this.applyButton.$ );
}; };
ve.ui.MWMediaEditDialog.prototype.onOpen = function () { ve.ui.MWMediaEditDialog.prototype.onOpen = function () {

View file

@ -5,11 +5,10 @@
* @license The MIT License (MIT); see LICENSE.txt * @license The MIT License (MIT); see LICENSE.txt
*/ */
/*global mw */
/** /**
* Dialog for inserting MediaWiki media objects.
*
* @class * @class
* @abstract
* @extends ve.ui.MWDialog * @extends ve.ui.MWDialog
* *
* @constructor * @constructor
@ -17,6 +16,9 @@
* @param {Object} [config] Config options * @param {Object} [config] Config options
*/ */
ve.ui.MWMediaInsertDialog = function VeUiMWMediaInsertDialog( surface, config ) { ve.ui.MWMediaInsertDialog = function VeUiMWMediaInsertDialog( surface, config ) {
// Configuration initialization
config = ve.extendObject( {}, config, { 'footless': true } );
// Parent constructor // Parent constructor
ve.ui.MWDialog.call( this, surface, config ); ve.ui.MWDialog.call( this, surface, config );
@ -36,9 +38,11 @@ ve.ui.MWMediaInsertDialog.static.icon = 'picture';
/* Methods */ /* Methods */
ve.ui.MWMediaInsertDialog.prototype.onSelect = function ( item ) { ve.ui.MWMediaInsertDialog.prototype.onSearchSelect = function ( item ) {
this.item = item; this.item = item;
this.applyButton.setDisabled( item === null ); if ( item ) {
this.close( 'insert' );
}
}; };
ve.ui.MWMediaInsertDialog.prototype.onOpen = function () { ve.ui.MWMediaInsertDialog.prototype.onOpen = function () {
@ -47,6 +51,8 @@ ve.ui.MWMediaInsertDialog.prototype.onOpen = function () {
// Initialization // Initialization
this.search.getQuery().$input.focus().select(); this.search.getQuery().$input.focus().select();
this.search.getResults().selectItem();
this.search.getResults().highlightItem();
}; };
ve.ui.MWMediaInsertDialog.prototype.onClose = function ( action ) { ve.ui.MWMediaInsertDialog.prototype.onClose = function ( action ) {
@ -55,7 +61,7 @@ ve.ui.MWMediaInsertDialog.prototype.onClose = function ( action ) {
// Parent method // Parent method
ve.ui.MWDialog.prototype.onClose.call( this ); ve.ui.MWDialog.prototype.onClose.call( this );
if ( action === 'apply' ) { if ( action === 'insert' ) {
info = this.item.imageinfo[0]; info = this.item.imageinfo[0];
this.surface.getModel().getFragment().insertContent( [ this.surface.getModel().getFragment().insertContent( [
{ {
@ -86,12 +92,9 @@ ve.ui.MWMediaInsertDialog.prototype.initialize = function () {
this.search = new ve.ui.MWMediaSearchWidget( { '$$': this.frame.$$ } ); this.search = new ve.ui.MWMediaSearchWidget( { '$$': this.frame.$$ } );
// Events // Events
this.search.connect( this, { 'select': 'onSelect' } ); this.search.connect( this, { 'select': 'onSearchSelect' } );
// Initialization // Initialization
this.applyButton.setDisabled( true ).setLabel(
mw.msg( 'visualeditor-dialog-media-insert-button' )
);
this.search.$.addClass( 've-ui-mwMediaInsertDialog-select' ); this.search.$.addClass( 've-ui-mwMediaInsertDialog-select' );
this.$body.append( this.search.$ ); this.$body.append( this.search.$ );
}; };

View file

@ -8,6 +8,8 @@
/*global mw*/ /*global mw*/
/** /**
* Dialog for editing MediaWiki page meta information.
*
* @class * @class
* @extends ve.ui.MWDialog * @extends ve.ui.MWDialog
* @mixins ve.ui.PagedDialog * @mixins ve.ui.PagedDialog
@ -85,6 +87,9 @@ ve.ui.MWMetaDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-meta-languages-label' ), 'label': ve.msg( 'visualeditor-dialog-meta-languages-label' ),
'icon': 'language' 'icon': 'language'
} ); } );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events // Events
this.categoryWidget.connect( this, { this.categoryWidget.connect( this, {
@ -94,6 +99,7 @@ ve.ui.MWMetaDialog.prototype.initialize = function () {
this.defaultSortInput.connect( this, { this.defaultSortInput.connect( this, {
'change': 'onDefaultSortChange' 'change': 'onDefaultSortChange'
} ); } );
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization // Initialization
this.categoryWidget.addItems( this.getCategoryItems() ); this.categoryWidget.addItems( this.getCategoryItems() );
@ -108,6 +114,7 @@ ve.ui.MWMetaDialog.prototype.initialize = function () {
this.pages.categories.$.append( this.categoriesFieldset.$, this.categoryOptionsFieldset.$ ); this.pages.categories.$.append( this.categoriesFieldset.$, this.categoryOptionsFieldset.$ );
this.categoriesFieldset.$.append( this.categoryWidget.$ ); this.categoriesFieldset.$.append( this.categoryWidget.$ );
this.categoryOptionsFieldset.$.append( this.defaultSortLabel.$, this.defaultSortInput.$ ); this.categoryOptionsFieldset.$.append( this.defaultSortLabel.$, this.defaultSortInput.$ );
this.$foot.append( this.applyButton.$ );
this.pages.languages.$.append( this.languagesFieldset.$ ); this.pages.languages.$.append( this.languagesFieldset.$ );

View file

@ -6,7 +6,7 @@
*/ */
/** /**
* Dialog for a MediaWiki reference. * Dialog for editing MediaWiki references.
* *
* @class * @class
* @extends ve.ui.MWDialog * @extends ve.ui.MWDialog
@ -68,6 +68,13 @@ ve.ui.MWReferenceEditDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' ) 'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' )
} ); } );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization // Initialization
this.optionsFieldset.$.append( this.optionsFieldset.$.append(
this.groupLabel.$, this.groupLabel.$,
@ -76,6 +83,7 @@ ve.ui.MWReferenceEditDialog.prototype.initialize = function () {
this.$body this.$body
.append( this.contentFieldset.$, this.optionsFieldset.$ ) .append( this.contentFieldset.$, this.optionsFieldset.$ )
.addClass( 've-ui-mwReferenceEditDialog-body' ); .addClass( 've-ui-mwReferenceEditDialog-body' );
this.$foot.append( this.applyButton.$ );
}; };
ve.ui.MWReferenceEditDialog.prototype.onOpen = function () { ve.ui.MWReferenceEditDialog.prototype.onOpen = function () {

View file

@ -5,11 +5,10 @@
* @license The MIT License (MIT); see LICENSE.txt * @license The MIT License (MIT); see LICENSE.txt
*/ */
/*global mw */
/** /**
* Dialog for inserting MediaWiki references.
*
* @class * @class
* @abstract
* @extends ve.ui.MWDialog * @extends ve.ui.MWDialog
* *
* @constructor * @constructor
@ -17,6 +16,9 @@
* @param {Object} [config] Config options * @param {Object} [config] Config options
*/ */
ve.ui.MWReferenceInsertDialog = function VeUiMWReferenceInsertDialog( surface, config ) { ve.ui.MWReferenceInsertDialog = function VeUiMWReferenceInsertDialog( surface, config ) {
// Configuration initialization
config = ve.extendObject( {}, config, { 'footless': true } );
// Parent constructor // Parent constructor
ve.ui.MWDialog.call( this, surface, config ); ve.ui.MWDialog.call( this, surface, config );
@ -43,9 +45,11 @@ ve.ui.MWReferenceInsertDialog.static.icon = 'reference';
* @param {string|Object|null} result Command string, reference attributes object, or null if * @param {string|Object|null} result Command string, reference attributes object, or null if
* nothing is selected * nothing is selected
*/ */
ve.ui.MWReferenceInsertDialog.prototype.onSelect = function ( result ) { ve.ui.MWReferenceInsertDialog.prototype.onSearchSelect = function ( result ) {
this.result = result; this.result = result;
this.applyButton.setDisabled( result === null ); if ( result ) {
this.close( 'insert' );
}
}; };
ve.ui.MWReferenceInsertDialog.prototype.onOpen = function () { ve.ui.MWReferenceInsertDialog.prototype.onOpen = function () {
@ -66,7 +70,7 @@ ve.ui.MWReferenceInsertDialog.prototype.onClose = function ( action ) {
// Parent method // Parent method
ve.ui.MWDialog.prototype.onClose.call( this ); ve.ui.MWDialog.prototype.onClose.call( this );
if ( action === 'apply' ) { if ( action === 'insert' ) {
doc = surfaceModel.getDocument(), doc = surfaceModel.getDocument(),
internalList = doc.getInternalList(); internalList = doc.getInternalList();
if ( create ) { if ( create ) {
@ -132,12 +136,9 @@ ve.ui.MWReferenceInsertDialog.prototype.initialize = function () {
this.search = new ve.ui.MWReferenceSearchWidget( this.surface, { '$$': this.frame.$$ } ); this.search = new ve.ui.MWReferenceSearchWidget( this.surface, { '$$': this.frame.$$ } );
// Events // Events
this.search.connect( this, { 'select': 'onSelect' } ); this.search.connect( this, { 'select': 'onSearchSelect' } );
// Initialization // Initialization
this.applyButton.setDisabled( true ).setLabel(
mw.msg( 'visualeditor-dialog-reference-insert-button' )
);
this.search.$.addClass( 've-ui-mwReferenceInsertDialog-select' ); this.search.$.addClass( 've-ui-mwReferenceInsertDialog-select' );
this.$body.append( this.search.$ ); this.$body.append( this.search.$ );
}; };

View file

@ -6,7 +6,7 @@
*/ */
/** /**
* Dialog for a MediaWiki references list. * Dialog for inserting and editing MediaWiki reference lists.
* *
* @class * @class
* @extends ve.ui.MWDialog * @extends ve.ui.MWDialog
@ -50,11 +50,19 @@ ve.ui.MWReferenceListDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' ) 'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' )
} ); } );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization // Initialization
this.optionsFieldset.$.append( this.groupLabel.$, this.groupInput.$ ); this.optionsFieldset.$.append( this.groupLabel.$, this.groupInput.$ );
this.$body this.$body
.append( this.optionsFieldset.$ ) .append( this.optionsFieldset.$ )
.addClass( 've-ui-mwReferenceListDialog-body' ); .addClass( 've-ui-mwReferenceListDialog-body' );
this.$foot.append( this.applyButton.$ );
}; };
ve.ui.MWReferenceListDialog.prototype.onOpen = function () { ve.ui.MWReferenceListDialog.prototype.onOpen = function () {

View file

@ -6,7 +6,7 @@
*/ */
/** /**
* Dialog for editing a MediaWiki transclusion. * Dialog for inserting and editing MediaWiki transclusions.
* *
* See https://raw.github.com/wikimedia/mediawiki-extensions-TemplateData/master/spec.templatedata.json * See https://raw.github.com/wikimedia/mediawiki-extensions-TemplateData/master/spec.templatedata.json
* for the latest version of the TemplateData specification. * for the latest version of the TemplateData specification.
@ -70,11 +70,20 @@ ve.ui.MWTransclusionDialog.prototype.initialize = function () {
// Setup for PagedDialog // Setup for PagedDialog
this.initializePages(); this.initializePages();
// Properties
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events // Events
this.outlineControlsWidget.connect( this, { this.outlineControlsWidget.connect( this, {
'move': 'onOutlineControlsMove', 'move': 'onOutlineControlsMove',
'add': 'onOutlineControlsAdd' 'add': 'onOutlineControlsAdd'
} ); } );
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization
this.$foot.append( this.applyButton.$ );
}; };
ve.ui.MWTransclusionDialog.prototype.onOpen = function () { ve.ui.MWTransclusionDialog.prototype.onOpen = function () {
@ -394,18 +403,15 @@ ve.ui.MWTransclusionDialog.prototype.getContentPage = function ( content ) {
* @param {ve.dm.MWTemplateModel} template Template model * @param {ve.dm.MWTemplateModel} template Template model
*/ */
ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) { ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) {
var infoFieldset, addParameterFieldset, addParameterSearch, addParameterButton, optionsFieldset, var infoFieldset, addParameterFieldset, addParameterSearch, optionsFieldset,
removeButton, removeButton,
spec = template.getSpec(), spec = template.getSpec(),
label = spec.getLabel(), label = spec.getLabel(),
description = spec.getDescription(); description = spec.getDescription();
function addParameter() { function addParameter( name ) {
var data, name, param, var param;
item = addParameterSearch.results.getSelectedItem();
data = item && item.getData();
name = data && data.name;
if ( name ) { if ( name ) {
param = new ve.dm.MWTemplateParameterModel( template, name ); param = new ve.dm.MWTemplateParameterModel( template, name );
template.addParameter( param ); template.addParameter( param );
@ -431,20 +437,8 @@ ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) {
} ); } );
addParameterFieldset.$.addClass( 've-ui-mwTransclusionDialog-addParameterFieldset' ); addParameterFieldset.$.addClass( 've-ui-mwTransclusionDialog-addParameterFieldset' );
addParameterSearch = new ve.ui.MWParameterSearchWidget( template, { '$$': this.frame.$$ } ); addParameterSearch = new ve.ui.MWParameterSearchWidget( template, { '$$': this.frame.$$ } );
addParameterButton = new ve.ui.ButtonWidget( { addParameterSearch.connect( this, { 'select': addParameter } );
'$$': this.frame.$$, addParameterFieldset.$.append( addParameterSearch.$ );
'label': ve.msg( 'visualeditor-dialog-transclusion-add-param' ),
'disabled': true
} );
addParameterButton.connect( this, { 'click': addParameter } );
addParameterSearch.connect( this, {
'enter': addParameter,
'select': function ( name ) {
var names = template.getParameterNames();
addParameterButton.setDisabled( !name || names.indexOf( name ) !== -1 );
}
} );
addParameterFieldset.$.append( addParameterSearch.$, addParameterButton.$ );
optionsFieldset = new ve.ui.FieldsetLayout( { optionsFieldset = new ve.ui.FieldsetLayout( {
'$$': this.frame.$$, '$$': this.frame.$$,

View file

@ -221,10 +221,20 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
box-shadow: inset 0 0 0 1px #ccc;
} }
.ve-ui-optionWidget-highlighted .ve-ui-mwMediaResultWidget-overlay,
.ve-ui-optionWidget-selected .ve-ui-mwMediaResultWidget-overlay { .ve-ui-optionWidget-selected .ve-ui-mwMediaResultWidget-overlay {
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5), inset 0 0 0 2px rgba(255,255,255,0.5); box-shadow: inset 0 0 0 1px #a7dcff;
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-highlighted {
box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-selected {
box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
} }
.ve-ui-mwMediaResultWidget-error .ve-ui-mwMediaResultWidget-thumbnail { .ve-ui-mwMediaResultWidget-error .ve-ui-mwMediaResultWidget-thumbnail {
@ -249,6 +259,14 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-highlighted .ve-ui-labeledElement-label {
background-color: rgba(0,0,0,0.75);
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-selected .ve-ui-labeledElement-label {
background-color: #000;
}
/* ve.ui.MWReferenceSearchWidget */ /* ve.ui.MWReferenceSearchWidget */
.ve-ui-mwReferenceSearchWidget-citation { .ve-ui-mwReferenceSearchWidget-citation {

View file

@ -43,10 +43,6 @@ ve.ui.MWMediaResultWidget = function VeUiMWMediaResultWidget( data, config ) {
ve.inheritClass( ve.ui.MWMediaResultWidget, ve.ui.OptionWidget ); ve.inheritClass( ve.ui.MWMediaResultWidget, ve.ui.OptionWidget );
/* Static Properties */
ve.ui.MWMediaResultWidget.static.highlightable = false;
/* Methods */ /* Methods */
ve.ui.MWMediaResultWidget.prototype.onThumbnailLoad = function () { ve.ui.MWMediaResultWidget.prototype.onThumbnailLoad = function () {

View file

@ -36,10 +36,6 @@ ve.ui.MWParameterResultWidget = function VeUiMWParameterResultWidget( data, conf
ve.inheritClass( ve.ui.MWParameterResultWidget, ve.ui.OptionWidget ); ve.inheritClass( ve.ui.MWParameterResultWidget, ve.ui.OptionWidget );
/* Static Properties */
ve.ui.MWParameterResultWidget.static.highlightable = false;
/* Methods */ /* Methods */
ve.ui.MWParameterResultWidget.prototype.buildLabel = function () { ve.ui.MWParameterResultWidget.prototype.buildLabel = function () {

View file

@ -153,6 +153,6 @@ ve.ui.MWParameterSearchWidget.prototype.addResults = function () {
this.results.addItems( items ); this.results.addItems( items );
if ( query.length ) { if ( query.length ) {
this.results.selectItem( this.results.getFirstSelectableItem() ); this.results.highlightItem( this.results.getFirstSelectableItem() );
} }
}; };

View file

@ -40,7 +40,3 @@ ve.ui.MWReferenceResultWidget = function VeUiMWReferenceResultWidget( data, conf
/* Inheritance */ /* Inheritance */
ve.inheritClass( ve.ui.MWReferenceResultWidget, ve.ui.OptionWidget ); ve.inheritClass( ve.ui.MWReferenceResultWidget, ve.ui.OptionWidget );
/* Static Properties */
ve.ui.MWReferenceResultWidget.static.highlightable = false;

View file

@ -95,6 +95,14 @@
bottom: 4.8em; bottom: 4.8em;
} }
.ve-ui-dialog-content-footless .ve-ui-window-body {
bottom: 0;
}
.ve-ui-dialog-content-footless .ve-ui-window-foot {
display: none;
}
.ve-ui-dialog-content .ve-ui-window-icon { .ve-ui-dialog-content .ve-ui-window-icon {
width: 2.4em; width: 2.4em;
height: 2.8em; height: 2.8em;

View file

@ -176,9 +176,12 @@
overflow: hidden; overflow: hidden;
} }
.ve-ui-optionWidget-highlighted, .ve-ui-optionWidget-highlighted {
background-color: #e1f3ff;
}
.ve-ui-optionWidget-selected { .ve-ui-optionWidget-selected {
background-color: #b3d6f6; background-color: #a7dcff;
} }
.ve-ui-optionWidget.ve-ui-widget-disabled { .ve-ui-optionWidget.ve-ui-widget-disabled {
@ -386,10 +389,6 @@
position: relative; position: relative;
} }
.ve-ui-menuItemWidget.ve-ui-optionWidget-selected:not(.ve-ui-optionWidget-highlighted) {
background-color: transparent;
}
.ve-ui-menuItemWidget .ve-ui-optionWidget-icon { .ve-ui-menuItemWidget .ve-ui-optionWidget-icon {
display: none; display: none;
} }

View file

@ -15,19 +15,26 @@
* @constructor * @constructor
* @param {ve.ui.Surface} surface * @param {ve.ui.Surface} surface
* @param {Object} [config] Config options * @param {Object} [config] Config options
* @cfg {boolean} [footless] Hide foot
*/ */
ve.ui.Dialog = function VeUiDialog( surface, config ) { ve.ui.Dialog = function VeUiDialog( surface, config ) {
// Configuration initialization
config = config || {};
// Parent constructor // Parent constructor
ve.ui.Window.call( this, surface, config ); ve.ui.Window.call( this, surface, config );
// Properties // Properties
this.visible = false; this.visible = false;
this.footless = !!config.footless;
this.onWindowMouseWheelHandler = ve.bind( this.onWindowMouseWheel, this ); this.onWindowMouseWheelHandler = ve.bind( this.onWindowMouseWheel, this );
this.onDocumentKeyDownHandler = ve.bind( this.onDocumentKeyDown, this ); this.onDocumentKeyDownHandler = ve.bind( this.onDocumentKeyDown, this );
// Events
this.$.on( 'mousedown', false );
// Initialization // Initialization
this.$.addClass( 've-ui-dialog' ); this.$.addClass( 've-ui-dialog' );
this.$.on( 'mousedown', false );
}; };
/* Inheritance */ /* Inheritance */
@ -45,15 +52,6 @@ ve.ui.Dialog.prototype.onCloseButtonClick = function () {
this.close( 'cancel' ); this.close( 'cancel' );
}; };
/**
* Handle apply button click events.
*
* @method
*/
ve.ui.Dialog.prototype.onApplyButtonClick = function () {
this.close( 'apply' );
};
/** /**
* Handle window mouse wheel events. * Handle window mouse wheel events.
* *
@ -142,22 +140,19 @@ ve.ui.Dialog.prototype.initialize = function () {
ve.ui.Window.prototype.initialize.call( this ); ve.ui.Window.prototype.initialize.call( this );
// Properties // Properties
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
this.closeButton = new ve.ui.IconButtonWidget( { this.closeButton = new ve.ui.IconButtonWidget( {
'$$': this.$$, 'title': ve.msg( 'visualeditor-dialog-action-close' ), 'icon': 'close' '$$': this.$$, 'title': ve.msg( 'visualeditor-dialog-action-close' ), 'icon': 'close'
} ); } );
// Events // Events
this.closeButton.connect( this, { 'click': 'onCloseButtonClick' } ); this.closeButton.connect( this, { 'click': 'onCloseButtonClick' } );
this.applyButton.connect( this, { 'click': 'onApplyButtonClick' } );
this.frame.$document.on( 'keydown', ve.bind( this.onFrameDocumentKeyDown, this ) ); this.frame.$document.on( 'keydown', ve.bind( this.onFrameDocumentKeyDown, this ) );
// Initialization // Initialization
this.frame.$content.addClass( 've-ui-dialog-content' ); this.frame.$content.addClass( 've-ui-dialog-content' );
if ( this.footless ) {
this.frame.$content.addClass( 've-ui-dialog-content-footless' );
}
this.closeButton.$.addClass( 've-ui-window-closeButton' ); this.closeButton.$.addClass( 've-ui-window-closeButton' );
this.applyButton.$.addClass( 've-ui-window-applyButton' );
this.$head.append( this.closeButton.$ ); this.$head.append( this.closeButton.$ );
this.$foot.append( this.applyButton.$ );
}; };

View file

@ -51,6 +51,9 @@ ve.ui.MenuWidget.prototype.onKeyDown = function ( e ) {
highlightItem = this.getHighlightedItem(); highlightItem = this.getHighlightedItem();
if ( !this.disabled && this.visible ) { if ( !this.disabled && this.visible ) {
if ( !highlightItem ) {
highlightItem = this.getSelectedItem();
}
switch ( e.keyCode ) { switch ( e.keyCode ) {
case ve.Keys.ENTER: case ve.Keys.ENTER:
this.selectItem( highlightItem ); this.selectItem( highlightItem );

View file

@ -35,9 +35,15 @@ ve.ui.SearchWidget = function VeUiSearchWidget( config ) {
this.$results = this.$$( '<div>' ); this.$results = this.$$( '<div>' );
// Events // Events
this.query.connect( this, { 'change': 'onQueryChange', 'enter': [ 'emit', 'enter' ] } ); this.query.connect( this, {
this.results.connect( this, { 'select': 'onResultsSelect' } ); 'change': 'onQueryChange',
this.query.$.on( 'keydown', ve.bind( this.onQueryKeydown, this ) ); 'enter': 'onQueryEnter'
} );
this.results.connect( this, {
'highlight': 'onResultsHighlight',
'select': 'onResultsSelect'
} );
this.query.$input.on( 'keydown', ve.bind( this.onQueryKeydown, this ) );
// Initialization // Initialization
this.$query this.$query
@ -58,12 +64,13 @@ ve.inheritClass( ve.ui.SearchWidget, ve.ui.Widget );
/* Events */ /* Events */
/** /**
* @event select * @event highlight
* @param {Object|null} item Item data or null if no item is selected * @param {Object|null} item Item data or null if no item is highlighted
*/ */
/** /**
* @event enter * @event select
* @param {Object|null} item Item data or null if no item is selected
*/ */
/* Methods */ /* Methods */
@ -75,12 +82,15 @@ ve.inheritClass( ve.ui.SearchWidget, ve.ui.Widget );
* @param {jQuery.Event} e Key down event * @param {jQuery.Event} e Key down event
*/ */
ve.ui.SearchWidget.prototype.onQueryKeydown = function ( e ) { ve.ui.SearchWidget.prototype.onQueryKeydown = function ( e ) {
var selectedItem, var highlightedItem,
dir = e.which === ve.Keys.DOWN ? 1 : ( e.which === ve.Keys.UP ? -1 : 0 ); dir = e.which === ve.Keys.DOWN ? 1 : ( e.which === ve.Keys.UP ? -1 : 0 );
if ( dir ) { if ( dir ) {
selectedItem = this.results.getSelectedItem(); highlightedItem = this.results.getHighlightedItem();
this.results.selectItem( this.results.getRelativeSelectableItem( selectedItem, dir ) ); if ( !highlightedItem ) {
highlightedItem = this.results.getSelectedItem();
}
this.results.highlightItem( this.results.getRelativeSelectableItem( highlightedItem, dir ) );
} }
}; };
@ -97,6 +107,30 @@ ve.ui.SearchWidget.prototype.onQueryChange = function () {
this.results.clearItems(); this.results.clearItems();
}; };
/**
* Handle select widget enter key events.
*
* Selects highlighted item.
*
* @method
* @param {string} value New value
*/
ve.ui.SearchWidget.prototype.onQueryEnter = function () {
// Reset
this.results.selectItem( this.results.getHighlightedItem() );
};
/**
* Handle select widget highlight events.
*
* @method
* @param {ve.ui.OptionWidget} item Highlighted item
* @emits highlight
*/
ve.ui.SearchWidget.prototype.onResultsHighlight = function ( item ) {
this.emit( 'highlight', item ? item.getData() : null );
};
/** /**
* Handle select widget select events. * Handle select widget select events.
* *
@ -117,3 +151,13 @@ ve.ui.SearchWidget.prototype.onResultsSelect = function ( item ) {
ve.ui.SearchWidget.prototype.getQuery = function () { ve.ui.SearchWidget.prototype.getQuery = function () {
return this.query; return this.query;
}; };
/**
* Get the results list.
*
* @method
* @returns {ve.ui.SelectWidget} Select list
*/
ve.ui.SearchWidget.prototype.getResults = function () {
return this.results;
};

View file

@ -36,7 +36,8 @@ ve.ui.SelectWidget = function VeUiSelectWidget( config ) {
'mousedown': ve.bind( this.onMouseDown, this ), 'mousedown': ve.bind( this.onMouseDown, this ),
'mouseup': ve.bind( this.onMouseUp, this ), 'mouseup': ve.bind( this.onMouseUp, this ),
'mousemove': ve.bind( this.onMouseMove, this ), 'mousemove': ve.bind( this.onMouseMove, this ),
'mouseover': ve.bind( this.onMouseOver, this ) 'mouseover': ve.bind( this.onMouseOver, this ),
'mouseleave': ve.bind( this.onMouseLeave, this )
} ); } );
// Initialization // Initialization
@ -51,6 +52,11 @@ ve.mixinClass( ve.ui.SelectWidget, ve.ui.GroupElement );
/* Events */ /* Events */
/**
* @event highlight
* @param {ve.ui.OptionWidget|null} item Highlighted item or null if no item is highlighted
*/
/** /**
* @event select * @event select
* @param {ve.ui.OptionWidget|null} item Selected item or null if no item is selected * @param {ve.ui.OptionWidget|null} item Selected item or null if no item is selected
@ -150,6 +156,20 @@ ve.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
return false; return false;
}; };
/**
* Handle mouse leave events.
*
* @method
* @private
* @param {jQuery.Event} e Mouse over event
*/
ve.ui.SelectWidget.prototype.onMouseLeave = function () {
if ( !this.disabled ) {
this.highlightItem();
}
return false;
};
/** /**
* Get the closest item to a jQuery.Event. * Get the closest item to a jQuery.Event.
* *
@ -225,6 +245,7 @@ ve.ui.SelectWidget.prototype.getItemFromData = function ( data ) {
* @method * @method
* @param {ve.ui.OptionWidget} [item] Item to highlight, omit to deselect all * @param {ve.ui.OptionWidget} [item] Item to highlight, omit to deselect all
* @param {boolean} [silent=false] Update UI only, do not emit `highlight` event * @param {boolean} [silent=false] Update UI only, do not emit `highlight` event
* @emits highlight
* @chainable * @chainable
*/ */
ve.ui.SelectWidget.prototype.highlightItem = function ( item, silent ) { ve.ui.SelectWidget.prototype.highlightItem = function ( item, silent ) {
@ -252,6 +273,7 @@ ve.ui.SelectWidget.prototype.highlightItem = function ( item, silent ) {
* @method * @method
* @param {ve.ui.OptionWidget} [item] Item to select, omit to deselect all * @param {ve.ui.OptionWidget} [item] Item to select, omit to deselect all
* @param {boolean} [silent=false] Update UI only, do not emit `select` event * @param {boolean} [silent=false] Update UI only, do not emit `select` event
* @emits select
* @chainable * @chainable
*/ */
ve.ui.SelectWidget.prototype.selectItem = function ( item, silent ) { ve.ui.SelectWidget.prototype.selectItem = function ( item, silent ) {
@ -334,6 +356,7 @@ ve.ui.SelectWidget.prototype.getFirstSelectableItem = function () {
* @method * @method
* @param {ve.ui.OptionWidget[]} items Items to add * @param {ve.ui.OptionWidget[]} items Items to add
* @param {number} [index] Index to insert items after * @param {number} [index] Index to insert items after
* @emits add
* @chainable * @chainable
*/ */
ve.ui.SelectWidget.prototype.addItems = function ( items, index ) { ve.ui.SelectWidget.prototype.addItems = function ( items, index ) {
@ -365,6 +388,7 @@ ve.ui.SelectWidget.prototype.addItems = function ( items, index ) {
* *
* @method * @method
* @param {ve.ui.OptionWidget[]} items Items to remove * @param {ve.ui.OptionWidget[]} items Items to remove
* @emits remove
* @chainable * @chainable
*/ */
ve.ui.SelectWidget.prototype.removeItems = function ( items ) { ve.ui.SelectWidget.prototype.removeItems = function ( items ) {
@ -394,6 +418,7 @@ ve.ui.SelectWidget.prototype.removeItems = function ( items ) {
* Items will be detached, not removed, so they can be used later. * Items will be detached, not removed, so they can be used later.
* *
* @method * @method
* @emits remove
* @chainable * @chainable
*/ */
ve.ui.SelectWidget.prototype.clearItems = function () { ve.ui.SelectWidget.prototype.clearItems = function () {