mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-29 08:34:54 +00:00
1572ec1569
This is a major refactor of user interface context, frame, dialog and inspector classes, including adding several new classes which generalize managing inspectors/dialogs (which are now subclasses of window). New classes: * ve.ui.Window.js - base class for inspector and dialog classes * ve.ui.WindowSet.js - manages mutually exclusive windows, used by surface and context for dialogs and inspectors respectively * ve.ui.DialogFactory - generates dialogs * ve.ui.IconButtonWidget - used in inspector for buttons in the head Refactored classes: * ve.ui.Context - moved inspector management to window set * ve.ui.Frame - made iframes initialize asynchronously * ve.ui.Dialog and ve.ui.Inspector - moved initialization to async initialize method Other interesting bits: ve.ui.*Icons*.css, *.svg, *.png, *.ai * Merged icon stylesheets so all icons are available inside windows * Renamed inspector icon to window ve.ui.*.css * Reorganized styles so that different windows can include only what they need * Moved things to where they belonged (some things were in strange places) ve.init.Target.js, ve.init.mw.ViewPageTarget.js, ve.init.sa.Target.js * Removed dialog management - dialogs are managed by the surface now ve.ui.*Dialog.js * Renamed title message static property * Added registration ve.ui.*Inspector.js * Switch to accept surface object rather than context, which conforms to the more general window class without losing any functionality (in fact, most of the time the surface was what we actually wanted) ve.ui.MenuWidget.js, ve.ui.MWLinkTargetInputWidget.js * Using surface overly rather than passing an overlay around through constructors Change-Id: Ifd16a1003ff44c48ee7b2c66928cf9cc858b2564
315 lines
7 KiB
JavaScript
315 lines
7 KiB
JavaScript
/*!
|
|
* VisualEditor UserInterface Context class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* UserInterface context.
|
|
*
|
|
* @class
|
|
*
|
|
* @constructor
|
|
* @param {ve.Surface} surface
|
|
*/
|
|
ve.ui.Context = function VeUiContext( surface ) {
|
|
// Properties
|
|
this.surface = surface;
|
|
this.inspectors = {};
|
|
this.visible = false;
|
|
this.showing = false;
|
|
this.selecting = false;
|
|
this.selection = null;
|
|
this.toolbar = null;
|
|
this.inspectors = new ve.ui.WindowSet( surface, ve.ui.inspectorFactory );
|
|
this.$ = $( '<div>' );
|
|
this.$callout = $( '<div>' );
|
|
this.$body = $( '<div>' );
|
|
this.$menu = $( '<div>' );
|
|
|
|
// Initialization
|
|
this.inspectors.$.addClass( 've-ui-context-inspectors' );
|
|
this.$
|
|
.addClass( 've-ui-context' )
|
|
.append(
|
|
this.$callout.addClass( 've-ui-context-callout' ),
|
|
this.$body.addClass( 've-ui-context-body' )
|
|
);
|
|
this.$body.append(
|
|
this.$menu.addClass( 've-ui-context-menu' ),
|
|
this.inspectors.$.addClass( 've-ui-context-inspectors' )
|
|
);
|
|
|
|
// Events
|
|
this.surface.getModel().addListenerMethods( this, {
|
|
'change': 'onChange'
|
|
} );
|
|
this.surface.getView().addListenerMethods( this, {
|
|
'selectionStart': 'onSelectionStart',
|
|
'selectionEnd': 'onSelectionEnd'
|
|
} );
|
|
this.inspectors.addListenerMethods( this, {
|
|
'setup': 'onInspectorSetup',
|
|
'open': 'onInspectorOpen',
|
|
'close': 'onInspectorClose'
|
|
} );
|
|
$( window ).on( {
|
|
'resize': ve.bind( this.update, this ),
|
|
'focus': ve.bind( this.onWindowFocus, this )
|
|
} );
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Handle change events on the model.
|
|
*
|
|
* Changes are ignored while the user is selecting text.
|
|
*
|
|
* @method
|
|
* @param {ve.dm.Transaction} tx Change transaction
|
|
* @param {ve.Range} selection Change selection
|
|
*/
|
|
ve.ui.Context.prototype.onChange = function ( tx, selection ) {
|
|
if ( selection && selection.start === 0 ) {
|
|
return;
|
|
}
|
|
if ( selection && !this.selecting ) {
|
|
this.update();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle selection start events on the view.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.Context.prototype.onSelectionStart = function () {
|
|
this.selecting = true;
|
|
this.hide();
|
|
};
|
|
|
|
/**
|
|
* Handle selection end events on the view.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.Context.prototype.onSelectionEnd = function () {
|
|
this.selecting = false;
|
|
this.update();
|
|
};
|
|
|
|
/**
|
|
* Handle window focus events on the view.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.Context.prototype.onWindowFocus = function () {
|
|
this.hide();
|
|
};
|
|
|
|
/**
|
|
* Handle an inspector being setup.
|
|
*
|
|
* @method
|
|
* @param {ve.ui.Inspector} inspector Inspector that's been setup
|
|
*/
|
|
ve.ui.Context.prototype.onInspectorSetup = function () {
|
|
this.selection = this.surface.getModel().getSelection();
|
|
};
|
|
|
|
/**
|
|
* Handle an inspector being opened.
|
|
*
|
|
* @method
|
|
* @param {ve.ui.Inspector} inspector Inspector that's been opened
|
|
*/
|
|
ve.ui.Context.prototype.onInspectorOpen = function () {
|
|
this.show();
|
|
};
|
|
|
|
/**
|
|
* Handle an inspector being closed.
|
|
*
|
|
* @method
|
|
* @param {ve.ui.Inspector} inspector Inspector that's been opened
|
|
* @param {boolean} accept Changes have been accepted
|
|
*/
|
|
ve.ui.Context.prototype.onInspectorClose = function () {
|
|
this.update();
|
|
};
|
|
|
|
/**
|
|
* Gets the surface the context is being used in.
|
|
*
|
|
* @method
|
|
* @returns {ve.Surface} Surface of context
|
|
*/
|
|
ve.ui.Context.prototype.getSurface = function () {
|
|
return this.surface;
|
|
};
|
|
|
|
/**
|
|
* Destroy the context, removing all DOM elements.
|
|
*
|
|
* @method
|
|
* @returns {ve.ui.Context} Context UserInterface
|
|
* @chainable
|
|
*/
|
|
ve.ui.Context.prototype.destroy = function () {
|
|
this.$.remove();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Updates the context menu.
|
|
*
|
|
* @method
|
|
* @chainable
|
|
*/
|
|
ve.ui.Context.prototype.update = function () {
|
|
var items,
|
|
fragment = this.surface.getModel().getFragment(),
|
|
selection = fragment.getRange(),
|
|
inspector = this.inspectors.getCurrent();
|
|
|
|
if ( inspector && selection.equals( this.selection ) ) {
|
|
// There's an inspector, and the selection hasn't changed, update the position
|
|
this.show();
|
|
} else {
|
|
// No inspector is open, or the selection has changed, show a menu of available inspectors
|
|
items = ve.ui.inspectorFactory.getInspectorsForAnnotations( fragment.getAnnotations() );
|
|
if ( items.length ) {
|
|
// There's at least one inspectable annotation, build a menu and show it
|
|
this.$menu.empty();
|
|
this.toolbar = new ve.ui.Toolbar(
|
|
$( '<div class="ve-ui-context-toolbar"></div>' ),
|
|
this.surface,
|
|
[{ 'name': 'inspectors', 'items' : items }]
|
|
);
|
|
this.$menu.append( this.toolbar.$ );
|
|
this.show();
|
|
} else if ( this.visible ) {
|
|
// Nothing to inspect
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
// Remember selection for next time
|
|
this.selection = selection.clone();
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Shows the context menu.
|
|
*
|
|
* @method
|
|
* @chainable
|
|
*/
|
|
ve.ui.Context.prototype.show = function () {
|
|
var position, $container, width, height, bodyWidth, buffer, center, overlapRight, overlapLeft,
|
|
inspector = this.inspectors.getCurrent();
|
|
|
|
if ( !this.showing ) {
|
|
this.showing = true;
|
|
|
|
this.$.show();
|
|
|
|
// Show either inspector or menu
|
|
if ( inspector ) {
|
|
this.inspectors.$.show();
|
|
this.$menu.hide();
|
|
inspector.fitHeightToContents();
|
|
} else {
|
|
this.inspectors.$.hide();
|
|
this.$menu.show();
|
|
}
|
|
|
|
// Get dimensions
|
|
position = this.surface.getView().getSelectionRect().end;
|
|
$container = inspector ? this.inspectors.$ : this.$menu;
|
|
width = $container.outerWidth( true );
|
|
height = $container.outerHeight( true );
|
|
bodyWidth = $( 'body' ).width();
|
|
buffer = ( width / 2 ) + 20;
|
|
overlapRight = bodyWidth - ( position.x + buffer );
|
|
overlapLeft = position.x - buffer;
|
|
center = -( width / 2 );
|
|
|
|
// Prevent body from displaying off-screen
|
|
if ( overlapRight < 0 ) {
|
|
center += overlapRight;
|
|
} else if ( overlapLeft < 0 ) {
|
|
center -= overlapLeft;
|
|
}
|
|
|
|
// Move to just below the cursor
|
|
this.$.css( { 'left': position.x, 'width': width, 'top': position.y } );
|
|
|
|
if ( !this.visible ) {
|
|
this.$body
|
|
.css( { 'width': 0, 'height': 0, 'left': 0 } )
|
|
.addClass( 've-ui-context-body-transition' );
|
|
}
|
|
|
|
this.$body.css( { 'left': center, 'width': width, 'height': height } );
|
|
|
|
this.visible = true;
|
|
this.showing = false;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Hides the context menu.
|
|
*
|
|
* @method
|
|
* @chainable
|
|
*/
|
|
ve.ui.Context.prototype.hide = function () {
|
|
var inspector = this.inspectors.getCurrent();
|
|
|
|
if ( inspector ) {
|
|
// This will recurse, but inspector will be undefined next time
|
|
inspector.close();
|
|
return this;
|
|
}
|
|
|
|
this.$body
|
|
.removeClass( 've-ui-context-body-transition' )
|
|
.css( { 'width': 0, 'height': 0, 'left': 0 } );
|
|
this.inspectors.$.hide();
|
|
this.$menu.hide();
|
|
this.$.hide();
|
|
this.visible = false;
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Opens a given inspector.
|
|
*
|
|
* @method
|
|
* @param {string} name Symbolic name of inspector
|
|
* @chainable
|
|
*/
|
|
ve.ui.Context.prototype.openInspector = function ( name ) {
|
|
this.inspectors.open( name );
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Closes currently open inspector.
|
|
*
|
|
* @method
|
|
* @param {boolean} remove Remove annotation while closing
|
|
* @chainable
|
|
*/
|
|
ve.ui.Context.prototype.closeInspector = function ( remove ) {
|
|
this.inspectors.close( remove );
|
|
return this;
|
|
};
|