jenkins-bot has submitted this change and it was merged.
Change subject: Allow node relocation
......................................................................
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
---
M VisualEditor.php
M demos/ve/index.php
M modules/ve/ce/nodes/ve.ce.ImageNode.js
M modules/ve/ce/nodes/ve.ce.MWImageNode.js
M modules/ve/ce/styles/ve.ce.Node.css
A modules/ve/ce/ve.ce.RelocatableNode.js
M modules/ve/ce/ve.ce.Surface.js
M modules/ve/ce/ve.ce.js
M modules/ve/test/index.php
M modules/ve/ui/ve.ui.Context.js
10 files changed, 219 insertions(+), 53 deletions(-)
Approvals:
Christian: Looks good to me, approved
jenkins-bot: Verified
diff --git a/VisualEditor.php b/VisualEditor.php
index a3f5a96..0f43909 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -303,6 +303,7 @@
'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',
diff --git a/demos/ve/index.php b/demos/ve/index.php
index fecb8fe..ee66e05 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -182,6 +182,7 @@
<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>
diff --git a/modules/ve/ce/nodes/ve.ce.ImageNode.js
b/modules/ve/ce/nodes/ve.ce.ImageNode.js
index 503eaec..6c32e9c 100644
--- a/modules/ve/ce/nodes/ve.ce.ImageNode.js
+++ b/modules/ve/ce/nodes/ve.ce.ImageNode.js
@@ -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 @@
// 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.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 @@
* @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 */
diff --git a/modules/ve/ce/nodes/ve.ce.MWImageNode.js
b/modules/ve/ce/nodes/ve.ce.MWImageNode.js
index ede3591..0fea716 100644
--- a/modules/ve/ce/nodes/ve.ce.MWImageNode.js
+++ b/modules/ve/ce/nodes/ve.ce.MWImageNode.js
@@ -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();
};
diff --git a/modules/ve/ce/styles/ve.ce.Node.css
b/modules/ve/ce/styles/ve.ce.Node.css
index 47d5686..37f3010 100644
--- a/modules/ve/ce/styles/ve.ce.Node.css
+++ b/modules/ve/ce/styles/ve.ce.Node.css
@@ -16,6 +16,10 @@
right: 0 !important;
}
+.ve-ce-imageNode {
+ cursor: default;
+}
+
.ve-ce-alienNode {
z-index: 0;
}
diff --git a/modules/ve/ce/ve.ce.RelocatableNode.js
b/modules/ve/ce/ve.ce.RelocatableNode.js
new file mode 100644
index 0000000..e51abba
--- /dev/null
+++ b/modules/ve/ce/ve.ce.RelocatableNode.js
@@ -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;
+ }
+};
diff --git a/modules/ve/ce/ve.ce.Surface.js b/modules/ve/ce/ve.ce.Surface.js
index 68b899b..fbf1f0c 100644
--- a/modules/ve/ce/ve.ce.Surface.js
+++ b/modules/ve/ce/ve.ce.Surface.js
@@ -32,6 +32,7 @@
this.clipboard = {};
this.renderingEnabled = true;
this.dragging = false;
+ this.relocating = false;
this.selecting = false;
this.$phantoms = $( '<div>' );
this.$pasteTarget = $( '<div>' );
@@ -54,7 +55,8 @@
'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 ) );
@@ -81,6 +83,14 @@
/**
* @event selectionEnd
+ */
+
+/**
+ * @event relocationStart
+ */
+
+/**
+ * @event relocationEnd
*/
/* Static Properties */
@@ -259,14 +269,70 @@
};
/**
- * 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 @@
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 */
/**
diff --git a/modules/ve/ce/ve.ce.js b/modules/ve/ce/ve.ce.js
index 1c7511f..1192f35 100644
--- a/modules/ve/ce/ve.ce.js
+++ b/modules/ve/ce/ve.ce.js
@@ -217,7 +217,7 @@
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();
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 03740e4..5eb2d91 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -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>
diff --git a/modules/ve/ui/ve.ui.Context.js b/modules/ve/ui/ve.ui.Context.js
index af12b19..4e3d34e 100644
--- a/modules/ve/ui/ve.ui.Context.js
+++ b/modules/ve/ui/ve.ui.Context.js
@@ -20,6 +20,7 @@
this.visible = false;
this.showing = false;
this.selecting = false;
+ this.relocating = false;
this.selection = null;
this.toolbar = null;
this.$ = $( '<div>' );
@@ -41,7 +42,9 @@
} );
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 @@
* 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.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();
};
--
To view, visit https://gerrit.wikimedia.org/r/58649
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I8703adfb707af2c3224431afc3418356ac2c686c
Gerrit-PatchSet: 14
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Christian <[email protected]>
Gerrit-Reviewer: Inez <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits