ve.ui.Context: Add embedding feature

Objectives:

* Make the context menu display in the top right corner of the currently
  focused inspectable node (if there is one)
* Prevent clicking on anything to do with the toolbar or popup from doing
  anything at all, ever

Bonus:

* While we are using the clever feature in jQuery's on method which allows
  passing boolean false to cancel the event - may as well do that in
  ve.ui.Dialog as well

Changes:

ve.ui.FocusableNode
* Add ability to specify the focusable element so that dimensions can be
  derived from it

ve.ce.Surface
* Add quotes to object keys

ve.ui.MediaDialog
* Change association from being MW specific to handling images in general

ve.ui.Context
* Add embedded styles for context
* Add embedded mode, which is triggered when the context is a single
  focusable node, and the node is large enough to fit the context
  reasonably

ve.ui.Dialog
* Inline mousedown handler

ve.ui.Toolbar, ve.ui.PopupWidget
* Cancel stray mousedown events

Change-Id: I4b25d33f64b4bcb8a3ecfd7e9728f54a2d4886f3
This commit is contained in:
Trevor Parscal 2013-05-15 16:31:02 -07:00
parent a47619cd04
commit 55b5f30edb
8 changed files with 71 additions and 23 deletions

View file

@ -8,14 +8,23 @@
/**
* ContentEditable resizable node.
*
* Focusable elements have a special treatment by ve.ce.Surface. When the user selects only a single
* node, if it is focusable, the surface will set the focusable node's focused state. Other systems,
* such as the context, may also use a focusable node's $focusable property as a hint of where the
* primary element in the node is. Typically, and by default, the primary element is the root
* element, but in some cases it may need to be configured to be a specific child element within the
* node's DOM rendering.
*
* @class
* @abstract
*
* @constructor
* @param {jQuery} [$focusable] Primary element user is focusing on
*/
ve.ce.FocusableNode = function VeCeFocusableNode() {
ve.ce.FocusableNode = function VeCeFocusableNode( $focusable ) {
// Properties
this.focused = false;
this.$focusable = $focusable || this.$;
};
/* Events */

View file

@ -177,8 +177,8 @@ ve.ce.Surface.getSelectionRect = function () {
};
} else {
return {
start: sel.getStartDocumentPos(),
end: sel.getEndDocumentPos()
'start': sel.getStartDocumentPos(),
'end': sel.getEndDocumentPos()
};
}
};

View file

@ -30,7 +30,9 @@ ve.ui.MediaDialog.static.titleMessage = 'visualeditor-dialog-media-title';
ve.ui.MediaDialog.static.icon = 'picture';
ve.ui.MediaDialog.static.modelClasses = [ ve.dm.MWInlineImageNode ];
ve.ui.MediaDialog.static.modelClasses = [ ve.dm.ImageNode ];
/* Methods */
/* Registration */

View file

@ -35,3 +35,16 @@
.ve-ui-context-menu .ve-ui-buttonTool-active {
background-image: none;
}
.ve-ui-context-embed .ve-ui-popupWidget-callout {
display: none;
}
.ve-ui-context-embed .ve-ui-popupWidget-body {
margin-top: 0.25em;
margin-left: -1.3em;
}
.ve-ui-context-embed .ve-ui-context-menu {
right: 0;
}

View file

@ -21,6 +21,7 @@ ve.ui.Context = function VeUiContext( surface ) {
this.showing = false;
this.selecting = false;
this.relocating = false;
this.embedded = false;
this.selection = null;
this.toolbar = null;
this.$ = $( '<div>' );
@ -53,6 +54,8 @@ ve.ui.Context = function VeUiContext( surface ) {
'resize': ve.bind( this.update, this ),
'focus': ve.bind( this.onWindowFocus, this )
} );
this.$.add( this.$menu )
.on( 'mousedown', false );
};
/* Methods */
@ -246,14 +249,25 @@ ve.ui.Context.prototype.update = function () {
* @chainable
*/
ve.ui.Context.prototype.updateDimensions = function ( transition ) {
var position, $container,
inspector = this.inspectors.getCurrent();
var position, $container, focusableOffset, focusableWidth,
inspector = this.inspectors.getCurrent(),
focusedNode = this.surface.getView().getFocusedNode();
// Get cursor position
position = ve.ce.Surface.getSelectionRect();
position = position && position.end;
if ( position ) {
$container = inspector ? this.inspectors.$ : this.$menu;
if ( this.embedded ) {
focusableOffset = focusedNode.$focusable.offset();
focusableWidth = focusedNode.$focusable.outerWidth();
$container = this.$menu;
position = { 'x': focusableOffset.left + focusableWidth, 'y': focusableOffset.top };
this.popup.align = 'right';
} else {
position = position && position.end;
$container = inspector ? this.inspectors.$ : this.$menu;
this.popup.align = 'center';
}
this.$.css( { 'left': position.x, 'top': position.y } );
this.popup.display(
position.x,
@ -274,7 +288,8 @@ ve.ui.Context.prototype.updateDimensions = function ( transition ) {
* @chainable
*/
ve.ui.Context.prototype.show = function ( transition ) {
var inspector = this.inspectors.getCurrent();
var inspector = this.inspectors.getCurrent(),
focusedNode = this.surface.getView().getFocusedNode();
if ( !this.showing ) {
this.showing = true;
@ -294,6 +309,16 @@ ve.ui.Context.prototype.show = function ( transition ) {
}, this ), 200 );
} else {
this.inspectors.$.hide();
if (
focusedNode &&
focusedNode.$focusable.outerHeight() > this.$menu.outerHeight() * 2
) {
this.$.addClass( 've-ui-context-embed' );
this.embedded = true;
} else {
this.$.removeClass( 've-ui-context-embed' );
this.embedded = false;
}
this.$menu.show();
}

View file

@ -24,7 +24,7 @@ ve.ui.Dialog = function VeUiDialog( surface ) {
// Initialization
this.$.addClass( 've-ui-dialog' );
this.$.on( 'mousedown', ve.bind( this.onMouseDown, this ) );
this.$.on( 'mousedown', false );
};
/* Inheritance */
@ -33,16 +33,6 @@ ve.inheritClass( ve.ui.Dialog, ve.ui.Window );
/* Methods */
/**
* Handle mouse down events.
*
* @method
* @param {jQuery.Event} e Mouse down event
*/
ve.ui.Dialog.prototype.onMouseDown = function () {
return false;
};
/**
* Handle close button click events.
*

View file

@ -32,6 +32,7 @@ ve.ui.Toolbar = function VeUiToolbar( surface, options ) {
this.surface = surface;
this.$bar = this.$$( '<div>' );
this.$tools = this.$$( '<div>' );
this.$actions = this.$$( '<div>' );
this.floating = false;
this.$window = null;
this.windowEvents = {
@ -39,11 +40,15 @@ ve.ui.Toolbar = function VeUiToolbar( surface, options ) {
'scroll': ve.bind( this.onWindowScroll, this )
};
// Events
this.$
.add( this.$bar ).add( this.$tools ).add( this.$actions )
.on( 'mousedown', false );
// Initialization
this.$tools.addClass( 've-ui-toolbar-tools' );
this.$bar.addClass( 've-ui-toolbar-bar' ).append( this.$tools );
if ( options.actions ) {
this.$actions = this.$$( '<div>' );
this.$actions.addClass( 've-ui-toolbar-actions' );
this.$bar.append( this.$actions );
}
@ -160,6 +165,7 @@ ve.ui.Toolbar.prototype.addTools = function ( tools ) {
group = tools[i];
// Create group
$group = this.$$( '<div class="ve-ui-toolbar-group"></div>' )
.on( 'mousedown', false )
.addClass( 've-ui-toolbar-group-' + group.name );
if ( group.label ) {
$group.append(

View file

@ -29,6 +29,11 @@ ve.ui.PopupWidget = function VeUiPopupWidget( config ) {
this.transitionTimeout = null;
this.align = config.align || 'center';
// Events
this.$body.on( 'blur', ve.bind( this.onPopupBlur, this ) );
this.$.add( this.$body ).add( this.$callout )
.on( 'mousedown', false );
// Initialization
this.$
.addClass( 've-ui-popupWidget' )
@ -37,8 +42,6 @@ ve.ui.PopupWidget = function VeUiPopupWidget( config ) {
this.$body.addClass( 've-ui-popupWidget-body' )
);
// Auto hide popup
this.$body.on( 'blur', ve.bind( this.onPopupBlur, this ) );
};
/* Inheritance */