Esanders has uploaded a new change for review.
https://gerrit.wikimedia.org/r/188029
Change subject: Unify data transfer handlers for paste and drop
......................................................................
Unify data transfer handlers for paste and drop
Change-Id: I5690076fd594beb01ac15247dc86e29f958f8542
---
M src/ce/ve.ce.Surface.js
M src/ui/ve.ui.DataTransferHandlerFactory.js
2 files changed, 130 insertions(+), 81 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor
refs/changes/29/188029/1
diff --git a/src/ce/ve.ce.Surface.js b/src/ce/ve.ce.Surface.js
index 233bb5b..e1288fc 100644
--- a/src/ce/ve.ce.Surface.js
+++ b/src/ce/ve.ce.Surface.js
@@ -992,18 +992,41 @@
*/
ve.ce.Surface.prototype.onDocumentDrop = function ( e ) {
// Properties may be nullified by other events, so cache before
setTimeout
- var selectionJSON, dragSelection, dragRange, originFragment, originData,
+ var i, l, selectionJSON, dragSelection, dragRange, originFragment,
originData,
targetRange, targetOffset, targetFragment, dragHtml, dragText,
- i, l, name, insert, item,
- fileHandlers = [],
+ items = [],
dataTransfer = e.originalEvent.dataTransfer,
- items = dataTransfer && ( dataTransfer.items ||
dataTransfer.files ),
$dropTarget = this.$lastDropTarget,
dropPosition = this.lastDropPosition;
// Prevent native drop event from modifying view
e.preventDefault();
+ // Determine drop position
+ if ( this.relocatingNode && !this.relocatingNode.getModel().isContent()
) {
+ // Block level drag and drop: use the lastDropTarget to get the
targetOffset
+ if ( $dropTarget ) {
+ targetRange = $dropTarget.data( 'view'
).getModel().getOuterRange();
+ if ( dropPosition === 'top' ) {
+ targetOffset = targetRange.start;
+ } else {
+ targetOffset = targetRange.end;
+ }
+ } else {
+ return;
+ }
+ } else {
+ targetOffset = this.getOffsetFromCoords(
+ e.originalEvent.pageX - this.$document.scrollLeft(),
+ e.originalEvent.pageY - this.$document.scrollTop()
+ );
+ if ( targetOffset === -1 ) {
+ return;
+ }
+ }
+ targetFragment = this.getModel().getLinearFragment( new ve.Range(
targetOffset ) );
+
+ // Get source range from drag data
try {
selectionJSON = dataTransfer.getData(
'application-x/VisualEditor' );
} catch ( err ) {
@@ -1014,7 +1037,6 @@
selectionJSON = null;
}
}
-
if ( this.relocatingNode ) {
dragRange = this.relocatingNode.getModel().getOuterRange();
} else if ( selectionJSON ) {
@@ -1022,24 +1044,31 @@
if ( dragSelection instanceof ve.dm.LinearSelection ) {
dragRange = dragSelection.getRange();
}
+ }
+
+ // Internal drop
+ if ( dragRange ) {
+ // Get a fragment and data of the node being dragged
+ originFragment = this.getModel().getLinearFragment( dragRange );
+ originData = originFragment.getData();
+
+ // Remove node from old location
+ originFragment.removeContent();
+
+ // Re-insert data at new location
+ targetFragment.insertContent( originData );
} else {
- if ( items && items.length ) {
- for ( i = 0, l = items.length; i < l; i++ ) {
- if ( items[i].kind ) {
- item = items[i];
- } else {
- // Create fake DataTransferItem from
file
- item = new ve.ui.DataTransferItem(
items[i] );
- }
- name =
ve.ui.dataTransferHandlerFactory.getHandlerNameForItem( item );
- if ( name ) {
- fileHandlers.push(
-
ve.ui.dataTransferHandlerFactory.create( name, this.surface, item )
- );
- }
+ // External drop
+ if ( dataTransfer && dataTransfer.items ) {
+ items = dataTransfer.items;
+ } else if ( dataTransfer && dataTransfer.files ) {
+ for ( i = 0, l = dataTransfer.files.length; i < l; i++
) {
+ // Create fake DataTransferItem from file
+ items.push( new ve.ui.DataTransferItem(
dataTransfer.files[i] ) );
}
}
- if ( !fileHandlers.length ) {
+ // Insert files
+ if ( !this.handleDataTransferItems( items, targetFragment ) ) {
try {
dragHtml = dataTransfer.getData( 'text/html' );
if ( !dragHtml ) {
@@ -1048,59 +1077,12 @@
} catch ( err ) {
dragText = dataTransfer.getData( 'text' );
}
- }
- }
-
- if ( ( dragRange && !dragRange.isCollapsed() ) || fileHandlers.length
|| dragHtml || dragText ) {
- if ( this.relocatingNode &&
!this.relocatingNode.getModel().isContent() ) {
- // Block level drag and drop: use the lastDropTarget to
get the targetOffset
- if ( $dropTarget ) {
- targetRange = $dropTarget.data( 'view'
).getModel().getOuterRange();
- if ( dropPosition === 'top' ) {
- targetOffset = targetRange.start;
- } else {
- targetOffset = targetRange.end;
- }
- } else {
- return;
+ // Insert HTML/text
+ if ( dragHtml ) {
+ targetFragment.insertHtml( dragHtml,
this.getSurface().getImportRules() );
+ } else if ( dragText ) {
+ targetFragment.insertContent( dragText );
}
- } else {
- targetOffset = this.getOffsetFromCoords(
- e.originalEvent.pageX -
this.$document.scrollLeft(),
- e.originalEvent.pageY -
this.$document.scrollTop()
- );
- if ( targetOffset === -1 ) {
- return;
- }
- }
-
- targetFragment = this.getModel().getLinearFragment( new
ve.Range( targetOffset ) );
-
- if ( dragRange ) {
- // Get a fragment and data of the node being dragged
- originFragment = this.getModel().getLinearFragment(
dragRange );
- originData = originFragment.getData();
-
- // Remove node from old location
- originFragment.removeContent();
-
- // Re-insert data at new location
- targetFragment.insertContent( originData );
- } else if ( fileHandlers.length ) {
- insert = function ( docOrData ) {
- if ( docOrData instanceof ve.dm.Document ) {
-
targetFragment.collapseToEnd().insertDocument( docOrData );
- } else {
-
targetFragment.collapseToEnd().insertContent( docOrData );
- }
- };
- for ( i = 0, l = fileHandlers.length; i < l; i++ ) {
- fileHandlers[i].getInsertableData().done(
insert );
- }
- } else if ( dragHtml ) {
- targetFragment.insertHtml( dragHtml,
this.getSurface().getImportRules() );
- } else if ( dragText ) {
- targetFragment.insertContent( dragText );
}
}
this.endRelocation();
@@ -1623,8 +1605,9 @@
ve.ce.Surface.prototype.afterPaste = function () {
var clipboardKey, clipboardId, clipboardIndex, range,
$elements, parts, pasteData, slice, tx, internalListRange,
- data, doc, htmlDoc,
+ data, doc, htmlDoc, $images, i,
context, left, right, contextRange,
+ items = [],
importantSpan = 'span[id],span[typeof],span[rel]',
importRules = this.getSurface().getImportRules(),
beforePasteData = this.beforePasteData || {},
@@ -1783,6 +1766,17 @@
// in edge cases (e.g. pasting a single MWReference)
htmlDoc = ve.createDocumentFromHtml(
this.$pasteTarget.html() );
}
+ // Some browsers don't provide pasted image data through the
clipboardData API and
+ // instead create img tags with data URLs, so detect those here
+ $images = $( htmlDoc.body ).find( 'img[src^=data\\:]' );
+ if ( $images.length ) {
+ for ( i = 0; i < $images.length; i++ ) {
+ items.push( new ve.ui.DataTransferItem(
$images.eq( i ).attr( 'src' ) ) );
+ }
+ }
+ if ( this.handleDataTransferItems( items ) ) {
+ return;
+ }
// External paste
doc = ve.dm.converter.getModelFromDom( htmlDoc,
this.getModel().getDocument().getHtmlDocument() );
data = doc.data;
@@ -1869,6 +1863,38 @@
};
/**
+ * Handle the insertion of data tranfer items
+ *
+ * @param {DataTransferItemList|DataTransferItem[]} items Data transfer items
+ * @param {ve.dm.SurfaceFragment} targetFragment Fragment to inserto data
items at, defaults to current selection
+ * @return {boolean} One more items was handled
+ */
+ve.ce.Surface.prototype.handleDataTransferItems = function ( items,
targetFragment ) {
+ var i, l, name,
+ handled = false;
+
+ targetFragment = targetFragment || this.getModel().getFragment();
+
+ function insert( docOrData ) {
+ if ( docOrData instanceof ve.dm.Document ) {
+ targetFragment.collapseToEnd().insertDocument(
docOrData );
+ } else {
+ targetFragment.collapseToEnd().insertContent( docOrData
);
+ }
+ }
+
+ for ( i = 0, l = items.length; i < l; i++ ) {
+ name = ve.ui.dataTransferHandlerFactory.getHandlerNameForItem(
items[i] );
+ if ( name ) {
+ ve.ui.dataTransferHandlerFactory.create( name,
this.surface, items[i] )
+ .getInsertableData().done( insert );
+ handled = true;
+ }
+ }
+ return handled;
+};
+
+/**
* Select all the contents within the current context
*/
ve.ce.Surface.prototype.selectAll = function () {
diff --git a/src/ui/ve.ui.DataTransferHandlerFactory.js
b/src/ui/ve.ui.DataTransferHandlerFactory.js
index c498104..ebc7dfb 100644
--- a/src/ui/ve.ui.DataTransferHandlerFactory.js
+++ b/src/ui/ve.ui.DataTransferHandlerFactory.js
@@ -68,23 +68,46 @@
ve.ui.dataTransferHandlerFactory = new ve.ui.DataTransferHandlerFactory();
/**
- * Fake data transfer item from a file
+ * Fake data transfer item from a blob or data URI
*
* @class
* @constructor
- * @param {File} file Data transfer file
+ * @param {Blob|string} blobOrDataUri Data transfer blob or data URI
*/
-ve.ui.DataTransferItem = function VeUiDataTransferItem( file ) {
+ve.ui.DataTransferItem = function VeUiDataTransferItem( blobOrDataUri ) {
+ if ( typeof blobOrDataUri === 'string' ) {
+ // Data URI: extra metadata, but don't convert until
+ // getAsFile is called
+ this.dataUri = blobOrDataUri;
+ this.type = blobOrDataUri.match( /^data:([^;,]+)/ )[1];
+ } else {
+ this.blob = blobOrDataUri;
+ this.type = blobOrDataUri.type;
+ }
this.kind = 'file';
- this.type = file.type;
- this.file = file;
};
/**
- * Get file object
+ * Get file blob
*
- * @return {File} File object
+ * Generically getAsFile returns a Blob, which could be a File.
+ *
+ * @return {Blob} File blob
*/
ve.ui.DataTransferItem.prototype.getAsFile = function () {
- return this.file;
+ var parts, binary, array, i;
+ if ( !this.blob ) {
+ parts = this.dataUri.split( ',' );
+ delete this.dataUri;
+ binary = atob( parts[1] );
+ array = [];
+ for ( i = 0; i < binary.length; i++ ) {
+ array.push( binary.charCodeAt( i ) );
+ }
+ this.blob = new Blob(
+ [ new Uint8Array( array ) ],
+ { type: this.type }
+ );
+ }
+ return this.blob;
};
--
To view, visit https://gerrit.wikimedia.org/r/188029
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I5690076fd594beb01ac15247dc86e29f958f8542
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits