2012-07-19 00:11:26 +00:00
|
|
|
/**
|
|
|
|
* VisualEditor user interface Context class.
|
2012-07-19 21:25:16 +00:00
|
|
|
*
|
2012-07-19 00:11:26 +00:00
|
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
2011-11-29 23:29:02 +00:00
|
|
|
/**
|
2012-02-06 23:50:56 +00:00
|
|
|
* Creates an ve.ui.Context object.
|
2012-06-20 01:20:28 +00:00
|
|
|
*
|
2011-11-29 23:29:02 +00:00
|
|
|
* @class
|
|
|
|
* @constructor
|
|
|
|
* @param {jQuery} $overlay DOM selection to add nodes to
|
|
|
|
*/
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context = function( surfaceView, $overlay ) {
|
2011-12-09 01:28:44 +00:00
|
|
|
// Inheritance
|
|
|
|
if ( !surfaceView ) {
|
|
|
|
return;
|
|
|
|
}
|
2011-12-02 00:30:50 +00:00
|
|
|
|
|
|
|
// Properties
|
2011-11-29 23:29:02 +00:00
|
|
|
this.surfaceView = surfaceView;
|
2011-12-09 01:28:44 +00:00
|
|
|
this.inspectors = {};
|
|
|
|
this.inspector = null;
|
|
|
|
this.position = null;
|
2012-06-20 01:20:28 +00:00
|
|
|
this.clicking = false;
|
2011-11-29 23:29:02 +00:00
|
|
|
this.$ = $( '<div class="es-contextView"></div>' ).appendTo( $overlay || $( 'body' ) );
|
2011-12-02 00:30:50 +00:00
|
|
|
this.$toolbar = $( '<div class="es-contextView-toolbar"></div>' );
|
2012-07-17 18:53:42 +00:00
|
|
|
|
|
|
|
// Create iframe which will contain context inspectors.
|
|
|
|
this.setupInspectorFrame();
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2011-12-09 01:28:44 +00:00
|
|
|
this.$icon = $( '<div class="es-contextView-icon"></div>' ).appendTo( this.$ );
|
2012-02-06 23:50:56 +00:00
|
|
|
this.toolbarView = new ve.ui.Toolbar(
|
2011-11-30 23:40:33 +00:00
|
|
|
this.$toolbar,
|
|
|
|
this.surfaceView,
|
2011-12-07 19:16:40 +00:00
|
|
|
[{ 'name': 'textStyle', 'items' : [ 'bold', 'italic', 'link', 'clear' ] }]
|
2011-11-30 23:40:33 +00:00
|
|
|
);
|
2012-02-06 23:50:56 +00:00
|
|
|
this.menuView = new ve.ui.Menu( [
|
2011-12-02 21:25:19 +00:00
|
|
|
// Example menu items
|
2011-12-07 19:16:40 +00:00
|
|
|
{ 'name': 'tools', '$': this.$toolbar }
|
2011-12-02 21:25:19 +00:00
|
|
|
],
|
|
|
|
null,
|
|
|
|
this.$
|
|
|
|
);
|
|
|
|
|
2011-11-29 23:48:11 +00:00
|
|
|
// Events
|
2011-12-02 21:25:19 +00:00
|
|
|
this.$icon.bind( {
|
2012-06-20 01:20:28 +00:00
|
|
|
'mousedown': ve.proxy( this.onMouseDown, this ),
|
|
|
|
'mouseup': ve.proxy( this.onMouseUp, this )
|
2011-11-29 23:48:11 +00:00
|
|
|
} );
|
2012-06-20 19:20:22 +00:00
|
|
|
this.surfaceView.getDocument().getDocumentNode().$.on( {
|
|
|
|
'focus': ve.proxy( this.onDocumentFocus, this ),
|
|
|
|
'blur': ve.proxy( this.onDocumentBlur, this )
|
|
|
|
} );
|
2011-12-09 01:28:44 +00:00
|
|
|
|
2012-07-17 18:53:42 +00:00
|
|
|
// Intitialize link inspector
|
2012-02-06 23:50:56 +00:00
|
|
|
this.addInspector( 'link', new ve.ui.LinkInspector( this.toolbarView, this ) );
|
2011-11-29 23:29:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Methods */
|
|
|
|
|
2012-07-17 18:53:42 +00:00
|
|
|
ve.ui.Context.prototype.setupInspectorFrame = function() {
|
|
|
|
// Create and append an iframe for inspectors.
|
|
|
|
// Use of iframe is required to retain selection while inspector controls are focused.
|
|
|
|
this.$inspectors =
|
|
|
|
$( '<iframe class="es-contextView-inspectors"></iframe>' )
|
|
|
|
.attr({
|
|
|
|
'frameborder': '0'
|
|
|
|
})
|
|
|
|
.appendTo( this.$ );
|
|
|
|
|
|
|
|
// Stash iframe document reference to properly create & append elements.
|
|
|
|
this.inspectorDoc = this.$inspectors.prop( 'contentWindow' ).document;
|
|
|
|
|
|
|
|
// Cross browser trick to append content to an iframe
|
|
|
|
// Write a containing element to the iframe
|
|
|
|
this.inspectorDoc.write( '<div class="ve-inspector-wrapper"></div>' );
|
|
|
|
this.inspectorDoc.close();
|
|
|
|
this.$inspectorWrapper = $( this.inspectorDoc ).find( '.ve-inspector-wrapper' );
|
|
|
|
|
|
|
|
// Create style element in iframe document scope
|
|
|
|
var $styleLink =
|
|
|
|
$('<link />', this.inspectorDoc )
|
|
|
|
.attr( {
|
|
|
|
'rel': 'stylesheet',
|
|
|
|
'type': 'text/css',
|
|
|
|
'media': 'screen',
|
2012-07-20 23:59:59 +00:00
|
|
|
'href': ve.init.platform.getModulesUrl() + '/ve/ui/styles/ve.ui.Inspector.css'
|
2012-07-17 18:53:42 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
// Append inspector styles to iframe head
|
|
|
|
$( 'head', this.inspectorDoc ).append( $styleLink );
|
|
|
|
|
|
|
|
// Adjust iframe body styles.
|
|
|
|
$( 'body', this.inspectorDoc ).css( {
|
|
|
|
'padding': '0px 5px 10px 5px',
|
|
|
|
'margin': 0
|
|
|
|
} );
|
|
|
|
|
|
|
|
this.hideInspectorFrame();
|
|
|
|
};
|
|
|
|
|
2012-06-20 19:20:22 +00:00
|
|
|
ve.ui.Context.prototype.onDocumentFocus = function( event ) {
|
|
|
|
$( window ).bind( 'resize.ve-ui-context scroll.ve-ui-context', ve.proxy( this.set, this ) );
|
|
|
|
};
|
|
|
|
|
|
|
|
ve.ui.Context.prototype.onDocumentBlur = function( event ) {
|
|
|
|
$( window ).unbind( 'resize.ve-ui-context scroll.ve-ui-context' );
|
|
|
|
};
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
ve.ui.Context.prototype.onMouseDown = function( event ) {
|
|
|
|
this.clicking = true;
|
|
|
|
event.preventDefault();
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
ve.ui.Context.prototype.onMouseUp = function( event ) {
|
|
|
|
if ( this.clicking && event.which === 1 ) {
|
|
|
|
if ( this.inspector ) {
|
|
|
|
this.closeInspector();
|
|
|
|
} else {
|
|
|
|
if ( this.isMenuOpen() ) {
|
|
|
|
this.closeMenu();
|
|
|
|
} else {
|
|
|
|
this.openMenu();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.clicking = false;
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.getSurfaceView = function() {
|
2011-12-09 01:28:44 +00:00
|
|
|
return this.surfaceView;
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.openMenu = function() {
|
2011-12-09 01:28:44 +00:00
|
|
|
this.menuView.open();
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.closeMenu = function() {
|
2011-12-09 01:28:44 +00:00
|
|
|
this.menuView.close();
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.isMenuOpen = function() {
|
2011-12-09 01:28:44 +00:00
|
|
|
return this.menuView.isOpen();
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.set = function() {
|
2012-07-02 19:48:09 +00:00
|
|
|
if ( this.surfaceView.getModel().getSelection().getLength() > 0 ) {
|
|
|
|
this.positionIcon();
|
|
|
|
if ( this.position ) {
|
|
|
|
this.positionOverlay( this.menuView.$ );
|
|
|
|
if ( this.inspector ) {
|
|
|
|
this.positionOverlay ( this.$inspectors );
|
|
|
|
}
|
2011-12-09 01:28:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.positionIcon = function() {
|
2011-12-09 23:33:16 +00:00
|
|
|
this.$.removeClass( 'es-contextView-position-start es-contextView-position-end' );
|
2012-04-28 00:19:17 +00:00
|
|
|
|
2012-07-20 00:24:54 +00:00
|
|
|
var selection = this.surfaceView.model.getSelection(),
|
2012-04-28 00:19:17 +00:00
|
|
|
selectionRect = this.surfaceView.getSelectionRect();
|
2012-03-12 22:14:43 +00:00
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
if( selection.to > selection.from ) {
|
2012-03-15 22:06:40 +00:00
|
|
|
this.position = new ve.Position( selectionRect.end.x, selectionRect.end.y );
|
2012-03-12 22:14:43 +00:00
|
|
|
this.$.addClass( 'es-contextView-position-end' );
|
2012-04-28 00:19:17 +00:00
|
|
|
} else {
|
2012-03-15 22:06:40 +00:00
|
|
|
this.position = new ve.Position( selectionRect.start.x, selectionRect.start.y );
|
2012-03-12 22:14:43 +00:00
|
|
|
this.$.addClass( 'es-contextView-position-start' );
|
2011-11-29 23:29:02 +00:00
|
|
|
}
|
2012-03-12 22:14:43 +00:00
|
|
|
|
2012-04-28 00:19:17 +00:00
|
|
|
this.$.css( { 'left': this.position.left, 'top': this.position.top } );
|
|
|
|
this.$icon.fadeIn( 'fast' );
|
2011-12-09 01:28:44 +00:00
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.positionOverlay = function( $overlay ) {
|
2011-12-09 23:33:16 +00:00
|
|
|
this.$.removeClass( 'es-contextView-position-below es-contextView-position-above' );
|
2011-12-09 01:28:44 +00:00
|
|
|
var overlayMargin = 5,
|
|
|
|
overlayWidth = $overlay.outerWidth(),
|
|
|
|
overlayHeight = $overlay.outerHeight(),
|
|
|
|
$window = $( window ),
|
|
|
|
windowWidth = $window.width(),
|
|
|
|
windowHeight = $window.height(),
|
2012-06-20 01:20:28 +00:00
|
|
|
windowScrollTop = $window.scrollTop(),
|
|
|
|
selection = this.surfaceView.model.getSelection();
|
2011-12-09 01:28:44 +00:00
|
|
|
// Center align overlay
|
|
|
|
var overlayLeft = -Math.round( overlayWidth / 2 );
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2011-12-09 01:28:44 +00:00
|
|
|
// Adjust overlay left or right depending on viewport
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( ( this.position.left - overlayMargin ) + overlayLeft < 0 ) {
|
|
|
|
// Move right a bit past center
|
|
|
|
overlayLeft -= this.position.left + overlayLeft - overlayMargin;
|
|
|
|
} else if ( ( overlayMargin + this.position.left ) - overlayLeft > windowWidth ) {
|
|
|
|
// Move left a bit past center
|
|
|
|
overlayLeft += windowWidth - overlayMargin - ( this.position.left - overlayLeft );
|
|
|
|
}
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2011-12-09 01:28:44 +00:00
|
|
|
$overlay.css( 'left', overlayLeft );
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2011-12-09 01:28:44 +00:00
|
|
|
// Position overlay on top or bottom depending on viewport
|
2012-06-20 01:20:28 +00:00
|
|
|
if (
|
|
|
|
selection.from < selection.to &&
|
|
|
|
this.position.top + overlayHeight + ( overlayMargin * 2 ) < windowHeight + windowScrollTop
|
|
|
|
) {
|
2011-12-09 01:28:44 +00:00
|
|
|
this.$.addClass( 'es-contextView-position-below' );
|
|
|
|
} else {
|
|
|
|
this.$.addClass( 'es-contextView-position-above' );
|
2011-11-29 23:29:02 +00:00
|
|
|
}
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2011-11-29 23:29:02 +00:00
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.clear = function() {
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( this.inspector ) {
|
|
|
|
this.closeInspector();
|
|
|
|
}
|
2011-11-29 23:29:02 +00:00
|
|
|
this.$icon.hide();
|
2011-12-09 01:28:44 +00:00
|
|
|
this.menuView.close();
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.openInspector = function( name ) {
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( !( name in this.inspectors ) ) {
|
|
|
|
throw 'Missing inspector error. Can not open nonexistent inspector: ' + name;
|
|
|
|
}
|
|
|
|
this.inspectors[name].open();
|
2012-06-21 05:29:50 +00:00
|
|
|
this.resizeInspectorFrame( this.inspectors[name] );
|
2012-06-20 01:20:28 +00:00
|
|
|
this.positionOverlay( this.$inspectors );
|
2011-12-09 01:28:44 +00:00
|
|
|
this.inspector = name;
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.closeInspector = function( accept ) {
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( this.inspector ) {
|
2011-12-09 23:04:55 +00:00
|
|
|
this.inspectors[this.inspector].close( accept );
|
2012-06-21 05:29:50 +00:00
|
|
|
this.hideInspectorFrame();
|
2011-12-09 01:28:44 +00:00
|
|
|
this.inspector = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.getInspector = function( name ) {
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( name in this.inspectors ) {
|
|
|
|
return this.inspectors[name];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.addInspector = function( name, inspector ) {
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( name in this.inspectors ) {
|
|
|
|
throw 'Duplicate inspector error. Previous registration with the same name: ' + name;
|
|
|
|
}
|
2012-06-21 02:03:48 +00:00
|
|
|
inspector.$.hide();
|
2011-12-09 01:28:44 +00:00
|
|
|
this.inspectors[name] = inspector;
|
2012-07-17 18:53:42 +00:00
|
|
|
this.$inspectorWrapper.append( inspector.$ );
|
2012-06-21 05:29:50 +00:00
|
|
|
};
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2012-06-21 05:29:50 +00:00
|
|
|
ve.ui.Context.prototype.hideInspectorFrame = function ( inspector ) {
|
|
|
|
this.$inspectors.css({
|
|
|
|
'width': 0,
|
2012-07-25 19:40:56 +00:00
|
|
|
'height': 0,
|
|
|
|
'visibility': 'hidden'
|
2012-06-21 05:29:50 +00:00
|
|
|
});
|
|
|
|
};
|
2012-06-20 01:20:28 +00:00
|
|
|
|
2012-06-21 05:29:50 +00:00
|
|
|
ve.ui.Context.prototype.resizeInspectorFrame = function( inspector ){
|
|
|
|
this.$inspectors.css( {
|
|
|
|
'width': inspector.$.outerWidth( true ) + 10,
|
2012-07-25 19:40:56 +00:00
|
|
|
'height': inspector.$.outerHeight( true ) + 10,
|
|
|
|
'visibility': 'visible'
|
2012-06-21 05:29:50 +00:00
|
|
|
} );
|
2011-12-09 01:28:44 +00:00
|
|
|
};
|
|
|
|
|
2012-02-06 23:50:56 +00:00
|
|
|
ve.ui.Context.prototype.removeInspector = function( name ) {
|
2011-12-09 01:28:44 +00:00
|
|
|
if ( name in this.inspectors ) {
|
|
|
|
throw 'Missing inspector error. Can not remove nonexistent inspector: ' + name;
|
|
|
|
}
|
|
|
|
this.inspectors[name].detach();
|
|
|
|
delete this.inspectors[name];
|
|
|
|
this.inspector = null;
|
2011-11-29 23:29:02 +00:00
|
|
|
};
|