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

Reply via email to