mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-29 08:34:54 +00:00
867ec44a9f
When the toolbar animates, the position of the surface changes. ProtectedNode now listens for the toolbarPosition event and repositions the phantoms. I also tried moving the localOverlay to be a sibling of the surface (instead of child of body), but the same problems persisted. Bug: 49853 Change-Id: I8ae1c8f66c6083b4ffee7107a78a298126e1064c
258 lines
5.6 KiB
JavaScript
258 lines
5.6 KiB
JavaScript
/*!
|
|
* VisualEditor ContentEditable ProtectedNode class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* ContentEditable relocatable node.
|
|
*
|
|
* @class
|
|
* @abstract
|
|
*
|
|
* @constructor
|
|
*/
|
|
ve.ce.ProtectedNode = function VeCeProtectedNode() {
|
|
// Properties
|
|
this.$phantoms = $( [] );
|
|
this.$shields = $( [] );
|
|
this.isSetup = false;
|
|
|
|
// Events
|
|
this.connect( this, {
|
|
'setup': 'onProtectedSetup',
|
|
'teardown': 'onProtectedTeardown'
|
|
} );
|
|
|
|
// Initialization
|
|
this.$.addClass( 've-ce-protectedNode' );
|
|
};
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ce.ProtectedNode.static = {};
|
|
|
|
/**
|
|
* Template for shield elements.
|
|
*
|
|
* Uses data URI to inject a 1x1 transparent GIF image into the DOM.
|
|
*
|
|
* @property {jQuery}
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
ve.ce.ProtectedNode.static.$shieldTemplate = $( '<img>' )
|
|
.addClass( 've-ce-protectedNode-shield' )
|
|
.attr( 'src', 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' );
|
|
|
|
/**
|
|
* Phantom element template.
|
|
*
|
|
* @property {jQuery}
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
ve.ce.ProtectedNode.static.$phantomTemplate = $( '<div>' )
|
|
.addClass( 've-ce-protectedNode-phantom' )
|
|
.attr( 'draggable', false );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Handle setup events.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onProtectedSetup = function () {
|
|
var $shield, surfaceModel, surface,
|
|
node = this,
|
|
$shieldTemplate = this.constructor.static.$shieldTemplate;
|
|
|
|
if ( this.isSetup ) {
|
|
return;
|
|
}
|
|
this.isSetup = true;
|
|
|
|
surfaceModel = this.getRoot().getSurface().getModel();
|
|
surface = this.getRoot().getSurface().getSurface();
|
|
|
|
// Events
|
|
this.$.on( 'mouseenter.ve-ce-protectedNode', ve.bind( this.onProtectedMouseEnter, this ) );
|
|
surfaceModel.connect( this, { 'change': 'onSurfaceModelChange' } );
|
|
surface.connect( this, { 'toolbarPosition': 'positionPhantoms' } );
|
|
|
|
// Shields
|
|
this.$.add( this.$.find( '*' ) ).each( function () {
|
|
var $this = $( this );
|
|
if ( this.nodeType === Node.ELEMENT_NODE ) {
|
|
if (
|
|
( $this.css( 'float' ) === 'none' || $this.css( 'float' ) === '' ) &&
|
|
!$this.hasClass( 've-ce-protectedNode' )
|
|
) {
|
|
return;
|
|
}
|
|
$shield = $shieldTemplate.clone().appendTo( $this );
|
|
node.$shields = node.$shields.add( $shield );
|
|
}
|
|
} );
|
|
|
|
};
|
|
|
|
/**
|
|
* Handle teardown events.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onProtectedTeardown = function () {
|
|
var surfaceModel;
|
|
|
|
if ( !this.isSetup ) {
|
|
return;
|
|
}
|
|
this.isSetup = false;
|
|
|
|
surfaceModel = this.getRoot().getSurface().getModel();
|
|
|
|
// Events
|
|
this.$.off( '.ve-ce-protectedNode' );
|
|
surfaceModel.disconnect( this, { 'change': 'onSurfaceModelChange' } );
|
|
|
|
// Shields
|
|
this.$shields.remove();
|
|
this.$shields = $( [] );
|
|
|
|
// Phantoms
|
|
this.clearPhantoms();
|
|
};
|
|
|
|
/**
|
|
* Handle phantom mouse down events.
|
|
*
|
|
* @method
|
|
* @param {jQuery.Event} e Mouse down event
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onPhantomMouseDown = function ( e ) {
|
|
var surfaceModel = this.getRoot().getSurface().getModel(),
|
|
selectionRange = surfaceModel.getSelection(),
|
|
nodeRange = this.model.getOuterRange();
|
|
|
|
surfaceModel.getFragment(
|
|
e.shiftKey ?
|
|
ve.Range.newCoveringRange(
|
|
[ selectionRange, nodeRange ], selectionRange.from > nodeRange.from
|
|
) :
|
|
nodeRange
|
|
).select();
|
|
|
|
e.preventDefault();
|
|
};
|
|
|
|
/**
|
|
* Handle mouse enter events.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onProtectedMouseEnter = function () {
|
|
if ( !this.root.getSurface().dragging ) {
|
|
this.createPhantoms();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle surface mouse move events.
|
|
*
|
|
* @method
|
|
* @param {jQuery.Event} e Mouse move event
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onSurfaceMouseMove = function ( e ) {
|
|
var $target = $( e.target );
|
|
if (
|
|
!$target.hasClass( 've-ce-protectedNode-phantom' ) &&
|
|
$target.closest( '.ve-ce-protectedNode' ).length === 0
|
|
) {
|
|
this.clearPhantoms();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle surface mouse out events.
|
|
*
|
|
* @method
|
|
* @param {jQuery.Event} e
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onSurfaceMouseOut = function ( e ) {
|
|
if ( e.toElement === null ) {
|
|
this.clearPhantoms();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle surface model change events
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.onSurfaceModelChange = function () {
|
|
if ( this.$phantoms.length ) {
|
|
this.positionPhantoms();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates phantoms
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.createPhantoms = function () {
|
|
var $phantomTemplate = this.constructor.static.$phantomTemplate,
|
|
surface = this.root.getSurface();
|
|
|
|
this.$.find( '.ve-ce-protectedNode-shield' ).each(
|
|
ve.bind( function () {
|
|
this.$phantoms = this.$phantoms.add(
|
|
$phantomTemplate.clone().on( 'mousedown', ve.bind( this.onPhantomMouseDown, this ) )
|
|
);
|
|
}, this )
|
|
);
|
|
this.positionPhantoms();
|
|
surface.replacePhantoms( this.$phantoms );
|
|
|
|
surface.$.on( {
|
|
'mousemove.ve-ce-protectedNode': ve.bind( this.onSurfaceMouseMove, this ),
|
|
'mouseout.ve-ce-protectedNode': ve.bind( this.onSurfaceMouseOut, this )
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Positions phantoms
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.positionPhantoms = function () {
|
|
this.$.find( '.ve-ce-protectedNode-shield' ).each(
|
|
ve.bind( function ( i, element ) {
|
|
var $shield = $( element ),
|
|
offset = $shield.offset();
|
|
this.$phantoms.eq( i ).css( {
|
|
'top': offset.top,
|
|
'left': offset.left,
|
|
'height': $shield.height(),
|
|
'width': $shield.width(),
|
|
'background-position': -offset.left + 'px ' + -offset.top + 'px'
|
|
} );
|
|
}, this )
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Clears all phantoms and unbinds .ve-ce-protectedNode namespace event handlers
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ce.ProtectedNode.prototype.clearPhantoms = function () {
|
|
var surface = this.root.getSurface();
|
|
surface.replacePhantoms( null );
|
|
surface.$.unbind( '.ve-ce-protectedNode' );
|
|
this.$phantoms = $( [] );
|
|
};
|