mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-24 22:35:41 +00:00
Allow node relocation
*.php * Added links to new file ve.ce.ImageNode.js * Added relocatable node mixin * Added $image reference to the actual img element, so if it's wrapped in a sub class the functionality in the parent class doesn't break. * Moved drag start event handling to relocatable node * Removed drag end binding, not needed. ve.ce.MWImageNode.js * Moved addClass to initialization section of constructor. * Copied 'view' data prop from image element to keep stuff working after the wrapping. ve.ce.Node.css * Switched to default (arrow) cursor for images. ve.ce.RelocatableNode.js * New mixing for nodes that should be relocatable * Added implementation for drag start, which tells the surface to allow dragging this node. ve.ce.Surface.js * Added relocation support, which is used by relocatable nodes * Split onDocumentDragDrop into onDocumentDragOver and onDocumentDrop which now have implementations that support relocation of nodes ve.ui.Context.js * Added relocation tracking to prevent context being shown while relocating Change-Id: I8703adfb707af2c3224431afc3418356ac2c686c
This commit is contained in:
parent
832137de50
commit
1878c7c5a8
|
@ -303,6 +303,7 @@ $wgResourceModules += array(
|
|||
've/ce/ve.ce.ContentBranchNode.js',
|
||||
've/ce/ve.ce.LeafNode.js',
|
||||
've/ce/ve.ce.FocusableNode.js',
|
||||
've/ce/ve.ce.RelocatableNode.js',
|
||||
've/ce/ve.ce.Surface.js',
|
||||
've/ce/ve.ce.SurfaceObserver.js',
|
||||
|
||||
|
|
|
@ -182,6 +182,7 @@ $html = file_get_contents( $page );
|
|||
<script src="../../modules/ve/ce/ve.ce.ContentBranchNode.js"></script>
|
||||
<script src="../../modules/ve/ce/ve.ce.LeafNode.js"></script>
|
||||
<script src="../../modules/ve/ce/ve.ce.FocusableNode.js"></script>
|
||||
<script src="../../modules/ve/ce/ve.ce.RelocatableNode.js"></script>
|
||||
<script src="../../modules/ve/ce/ve.ce.Surface.js"></script>
|
||||
<script src="../../modules/ve/ce/ve.ce.SurfaceObserver.js"></script>
|
||||
<script src="../../modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js"></script>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
* @class
|
||||
* @extends ve.ce.LeafNode
|
||||
* @mixins ve.ce.FocusableNode
|
||||
* @mixins ve.ce.RelocatableNode
|
||||
*
|
||||
* @constructor
|
||||
* @param {ve.dm.ImageNode} model Model to observe
|
||||
|
@ -21,18 +22,18 @@ ve.ce.ImageNode = function VeCeImageNode( model ) {
|
|||
|
||||
// Mixin constructors
|
||||
ve.ce.FocusableNode.call( this );
|
||||
ve.ce.RelocatableNode.call( this );
|
||||
|
||||
// Properties
|
||||
this.$image = this.$;
|
||||
|
||||
// Events
|
||||
this.model.addListenerMethod( this, 'update', 'onUpdate' );
|
||||
this.$.on( {
|
||||
'click': ve.bind( this.onClick, this ),
|
||||
'dragstart': ve.bind( this.onDragStart, this ),
|
||||
'dragend': ve.bind( this.onDragEnd, this )
|
||||
} );
|
||||
this.$image.on( 'click', ve.bind( this.onClick, this ) );
|
||||
|
||||
// Initialization
|
||||
ve.setDomAttributes( this.$[0], this.model.getAttributes(), ['src', 'width', 'height'] );
|
||||
this.$.addClass( 've-ce-imageNode' );
|
||||
ve.setDomAttributes( this.$image[0], this.model.getAttributes(), ['src', 'width', 'height'] );
|
||||
this.$image.addClass( 've-ce-imageNode' );
|
||||
};
|
||||
|
||||
/* Inheritance */
|
||||
|
@ -40,6 +41,7 @@ ve.ce.ImageNode = function VeCeImageNode( model ) {
|
|||
ve.inheritClass( ve.ce.ImageNode, ve.ce.LeafNode );
|
||||
|
||||
ve.mixinClass( ve.ce.ImageNode, ve.ce.FocusableNode );
|
||||
ve.mixinClass( ve.ce.ImageNode, ve.ce.RelocatableNode );
|
||||
|
||||
/* Static Properties */
|
||||
|
||||
|
@ -54,40 +56,17 @@ ve.ce.ImageNode.static.name = 'image';
|
|||
* @param {jQuery.Event} e Click event
|
||||
*/
|
||||
ve.ce.ImageNode.prototype.onClick = function ( e ) {
|
||||
var range,
|
||||
surfaceModel = this.getRoot().getSurface().getModel(),
|
||||
selection = surfaceModel.getSelection();
|
||||
var surfaceModel = this.getRoot().getSurface().getModel(),
|
||||
selectionRange = surfaceModel.getSelection(),
|
||||
nodeRange = this.model.getOuterRange();
|
||||
|
||||
range = new ve.Range(
|
||||
this.model.getOffset(),
|
||||
this.model.getOffset() + this.model.getOuterLength()
|
||||
);
|
||||
|
||||
if ( e.shiftKey ) {
|
||||
range = ve.Range.newCoveringRange( [ selection, range ], selection.from > range.from );
|
||||
}
|
||||
|
||||
this.getRoot().getSurface().getModel().change( null, range );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the dragstart.
|
||||
*
|
||||
* @method
|
||||
* @param {jQuery.Event} e Dragstart event
|
||||
*/
|
||||
ve.ce.ImageNode.prototype.onDragStart = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the dragend.
|
||||
*
|
||||
* @method
|
||||
* @param {jQuery.Event} e Dragstart event
|
||||
*/
|
||||
ve.ce.ImageNode.prototype.onDragEnd = function () {
|
||||
return false;
|
||||
surfaceModel.getFragment(
|
||||
e.shiftKey ?
|
||||
ve.Range.newCoveringRange(
|
||||
[ selectionRange, nodeRange ], selectionRange.from > nodeRange.from
|
||||
) :
|
||||
nodeRange
|
||||
).select();
|
||||
};
|
||||
|
||||
/* Registration */
|
||||
|
|
|
@ -17,13 +17,16 @@
|
|||
// Parent constructor
|
||||
ve.ce.ImageNode.call( this, model );
|
||||
|
||||
// Initialization
|
||||
this.$.addClass( 've-ce-MWImageNode' );
|
||||
// Properties
|
||||
this.$image = this.$;
|
||||
this.$ = $( '<' + ( model.getAttribute( 'isLinked' ) ? 'a' : 'span' ) + '>' );
|
||||
|
||||
// Initialization
|
||||
this.$.attr( 'contenteditable', false ).append( this.$image );
|
||||
this.$
|
||||
.attr( 'contenteditable', false )
|
||||
.addClass( 've-ce-mwImageNode' )
|
||||
.append( this.$image )
|
||||
.data( 'view', this.$image.data( 'view' ) );
|
||||
this.onUpdate();
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
right: 0 !important;
|
||||
}
|
||||
|
||||
.ve-ce-imageNode {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ve-ce-alienNode {
|
||||
z-index: 0;
|
||||
}
|
||||
|
|
58
modules/ve/ce/ve.ce.RelocatableNode.js
Normal file
58
modules/ve/ce/ve.ce.RelocatableNode.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*!
|
||||
* VisualEditor ContentEditable RelocatableNode 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
|
||||
* @param {jQuery} [$draggable=this.$] Draggable DOM element
|
||||
*/
|
||||
ve.ce.RelocatableNode = function VeCeRelocatableNode( $draggable ) {
|
||||
// Properties
|
||||
this.$draggable = $draggable || this.$;
|
||||
this.surface = null;
|
||||
|
||||
// Events
|
||||
this.$draggable.on( {
|
||||
'dragstart': ve.bind( this.onRelocatableDragStart, this ),
|
||||
'dragend': ve.bind( this.onRelocatableDragEnd, this )
|
||||
} );
|
||||
};
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Handle element drag start.
|
||||
*
|
||||
* @method
|
||||
* @param {jQuery.Event} e Drag start event
|
||||
*/
|
||||
ve.ce.RelocatableNode.prototype.onRelocatableDragStart = function () {
|
||||
// Store a copy of the surface, when dragend occurs the node will be detached
|
||||
this.surface = this.getRoot().getSurface();
|
||||
|
||||
if ( this.surface ) {
|
||||
// Allow dragging this node in the surface
|
||||
this.surface.startRelocation( this );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle element drag end.
|
||||
*
|
||||
* @method
|
||||
* @param {jQuery.Event} e Drag end event
|
||||
*/
|
||||
ve.ce.RelocatableNode.prototype.onRelocatableDragEnd = function () {
|
||||
if ( this.surface ) {
|
||||
this.surface.endRelocation();
|
||||
this.surface = null;
|
||||
}
|
||||
};
|
|
@ -32,6 +32,7 @@ ve.ce.Surface = function VeCeSurface( $container, model, surface ) {
|
|||
this.clipboard = {};
|
||||
this.renderingEnabled = true;
|
||||
this.dragging = false;
|
||||
this.relocating = false;
|
||||
this.selecting = false;
|
||||
this.$phantoms = $( '<div>' );
|
||||
this.$pasteTarget = $( '<div>' );
|
||||
|
@ -54,7 +55,8 @@ ve.ce.Surface = function VeCeSurface( $container, model, surface ) {
|
|||
'cut': ve.bind( this.onCut, this ),
|
||||
'copy': ve.bind( this.onCopy, this ),
|
||||
'paste': ve.bind( this.onPaste, this ),
|
||||
'dragover drop': ve.bind( this.onDocumentDragoverDrop, this )
|
||||
'dragover': ve.bind( this.onDocumentDragOver, this ),
|
||||
'drop': ve.bind( this.onDocumentDrop, this )
|
||||
} );
|
||||
if ( $.browser.msie ) {
|
||||
this.$.on( 'beforepaste', ve.bind( this.onPaste, this ) );
|
||||
|
@ -83,6 +85,14 @@ ve.inheritClass( ve.ce.Surface, ve.EventEmitter );
|
|||
* @event selectionEnd
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event relocationStart
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event relocationEnd
|
||||
*/
|
||||
|
||||
/* Static Properties */
|
||||
|
||||
/**
|
||||
|
@ -259,14 +269,70 @@ ve.ce.Surface.prototype.onDocumentMouseMove = function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Handle document dragover and drop events.
|
||||
* Handle document dragover events.
|
||||
*
|
||||
* Prevents native dragging and dropping of content.
|
||||
* Limits native drag and drop behavior.
|
||||
*
|
||||
* @method
|
||||
* @param {jQuery.Event} e Drag over/drop event
|
||||
* @param {jQuery.Event} e Drag over event
|
||||
*/
|
||||
ve.ce.Surface.prototype.onDocumentDragoverDrop = function () {
|
||||
ve.ce.Surface.prototype.onDocumentDragOver = function () {
|
||||
if ( !this.relocating ) {
|
||||
return false;
|
||||
} else if ( this.selecting ) {
|
||||
this.emit( 'selectionEnd' );
|
||||
this.selecting = false;
|
||||
this.dragging = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle document drop events.
|
||||
*
|
||||
* Limits native drag and drop behavior.
|
||||
*
|
||||
* TODO: Look into using drag and drop data transfer to embed the dragged element's original range
|
||||
* (for dragging within one document) and serialized linear model data (for dragging between
|
||||
* multiple documents) and use a special mimetype, like application-x/VisualEditor, to allow
|
||||
* dragover and drop events on the surface, removing the need to give the surface explicit
|
||||
* instructions to allow and prevent dragging and dropping a certain node.
|
||||
*
|
||||
* @method
|
||||
* @param {jQuery.Event} e Drag drop event
|
||||
*/
|
||||
ve.ce.Surface.prototype.onDocumentDrop = function ( e ) {
|
||||
var node = this.relocating;
|
||||
|
||||
if ( node ) {
|
||||
// Process drop operation after native drop has been prevented below
|
||||
setTimeout( ve.bind( function () {
|
||||
var dropPoint, nodeData, originFragment, targetFragment,
|
||||
nodeRange = node.getModel().getOuterRange();
|
||||
|
||||
// Get a fragment from the drop point
|
||||
dropPoint = rangy.positionFromPoint( e.originalEvent.pageX, e.originalEvent.pageY );
|
||||
if ( !dropPoint ) {
|
||||
// Getting position from point supported
|
||||
return false;
|
||||
}
|
||||
targetFragment = this.model.getFragment(
|
||||
new ve.Range( ve.ce.getOffset( dropPoint.node, dropPoint.offset ) ), false
|
||||
);
|
||||
|
||||
// Get a fragment and data of the node being dragged
|
||||
originFragment = this.model.getFragment( nodeRange, false );
|
||||
nodeData = originFragment.getData();
|
||||
|
||||
// Remove node from old location (auto-updates targetFragment's range)
|
||||
originFragment.removeContent().destroy();
|
||||
|
||||
// Re-insert node at new location and re-select it
|
||||
targetFragment.insertContent( nodeData );
|
||||
targetFragment.adjustRange( -nodeData.length, 0 ).select().destroy();
|
||||
targetFragment.destroy();
|
||||
}, this ) );
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -735,6 +801,34 @@ ve.ce.Surface.prototype.onUnlock = function () {
|
|||
this.surfaceObserver.start();
|
||||
};
|
||||
|
||||
/*! Relocation */
|
||||
|
||||
/**
|
||||
* Start a relocation action.
|
||||
*
|
||||
* @see ve.ce.RelocatableNode
|
||||
*
|
||||
* @method
|
||||
* @param {ve.ce.Node} node Node being relocated
|
||||
*/
|
||||
ve.ce.Surface.prototype.startRelocation = function ( node ) {
|
||||
this.relocating = node;
|
||||
this.emit( 'relocationStart', node );
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete a relocation action.
|
||||
*
|
||||
* @see ve.ce.RelocatableNode
|
||||
*
|
||||
* @method
|
||||
* @param {ve.ce.Node} node Node being relocated
|
||||
*/
|
||||
ve.ce.Surface.prototype.endRelocation = function () {
|
||||
this.emit( 'relocationEnd', this.relocating );
|
||||
this.relocating = null;
|
||||
};
|
||||
|
||||
/*! Utilities */
|
||||
|
||||
/**
|
||||
|
|
|
@ -217,7 +217,7 @@ ve.ce.getOffsetFromElementNode = function ( domNode, domOffset, addOuterLength )
|
|||
|
||||
if ( domOffset === 0 ) {
|
||||
node = $domNode.data( 'view' );
|
||||
if ( node ) {
|
||||
if ( node && node instanceof ve.ce.Node ) {
|
||||
nodeModel = $domNode.data( 'view' ).getModel();
|
||||
if ( addOuterLength === true ) {
|
||||
return nodeModel.getOffset() + nodeModel.getOuterLength();
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
<script src="../../ve/ce/ve.ce.ContentBranchNode.js"></script>
|
||||
<script src="../../ve/ce/ve.ce.LeafNode.js"></script>
|
||||
<script src="../../ve/ce/ve.ce.FocusableNode.js"></script>
|
||||
<script src="../../ve/ce/ve.ce.RelocatableNode.js"></script>
|
||||
<script src="../../ve/ce/ve.ce.Surface.js"></script>
|
||||
<script src="../../ve/ce/ve.ce.SurfaceObserver.js"></script>
|
||||
<script src="../../ve/ce/nodes/ve.ce.GeneratedContentNode.js"></script>
|
||||
|
|
|
@ -20,6 +20,7 @@ ve.ui.Context = function VeUiContext( surface ) {
|
|||
this.visible = false;
|
||||
this.showing = false;
|
||||
this.selecting = false;
|
||||
this.relocating = false;
|
||||
this.selection = null;
|
||||
this.toolbar = null;
|
||||
this.$ = $( '<div>' );
|
||||
|
@ -41,7 +42,9 @@ ve.ui.Context = function VeUiContext( surface ) {
|
|||
} );
|
||||
this.surface.getView().addListenerMethods( this, {
|
||||
'selectionStart': 'onSelectionStart',
|
||||
'selectionEnd': 'onSelectionEnd'
|
||||
'selectionEnd': 'onSelectionEnd',
|
||||
'relocationStart': 'onRelocationStart',
|
||||
'relocationEnd': 'onRelocationEnd'
|
||||
} );
|
||||
this.inspectors.addListenerMethods( this, {
|
||||
'setup': 'onInspectorSetup',
|
||||
|
@ -62,14 +65,14 @@ ve.ui.Context = function VeUiContext( surface ) {
|
|||
* Changes are ignored while the user is selecting text.
|
||||
*
|
||||
* @method
|
||||
* @param {ve.dm.Transaction} tx Change transaction
|
||||
* @param {ve.dm.Transaction[]} transactions Change transactions
|
||||
* @param {ve.Range} selection Change selection
|
||||
*/
|
||||
ve.ui.Context.prototype.onChange = function ( tx, selection ) {
|
||||
ve.ui.Context.prototype.onChange = function ( transactions, selection ) {
|
||||
if ( selection && selection.start === 0 ) {
|
||||
return;
|
||||
}
|
||||
if ( selection && !this.selecting ) {
|
||||
if ( selection && !this.selecting && !this.draggingAndDropping ) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
@ -91,6 +94,28 @@ ve.ui.Context.prototype.onSelectionStart = function () {
|
|||
*/
|
||||
ve.ui.Context.prototype.onSelectionEnd = function () {
|
||||
this.selecting = false;
|
||||
if ( !this.relocating ) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle selection start events on the view.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
ve.ui.Context.prototype.onRelocationStart = function () {
|
||||
this.relocating = true;
|
||||
this.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle selection end events on the view.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
ve.ui.Context.prototype.onRelocationEnd = function () {
|
||||
this.relocating = false;
|
||||
this.update();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue