 0ddafc4bcd
			
		
	
	0ddafc4bcd
	
	
	
		
			
			Summary: Fixes T5917. Turns out that pasted files don't get a name - "what is data on a clipboard anyway?" / see https://code.google.com/p/chromium/issues/detail?id=361145 Test Plan: pasted a file and got the slightly better name "pasted_file" Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T5917 Differential Revision: https://secure.phabricator.com/D10310
		
			
				
	
	
		
			253 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @requires javelin-install
 | |
|  *           javelin-util
 | |
|  *           javelin-request
 | |
|  *           javelin-dom
 | |
|  *           javelin-uri
 | |
|  *           phabricator-file-upload
 | |
|  * @provides phabricator-drag-and-drop-file-upload
 | |
|  * @javelin
 | |
|  */
 | |
| 
 | |
| JX.install('PhabricatorDragAndDropFileUpload', {
 | |
| 
 | |
|   construct : function(node) {
 | |
|     this._node = node;
 | |
|   },
 | |
| 
 | |
|   events : [
 | |
|     'didBeginDrag',
 | |
|     'didEndDrag',
 | |
|     'willUpload',
 | |
|     'progress',
 | |
|     'didUpload',
 | |
|     'didError'],
 | |
| 
 | |
|   statics : {
 | |
|     isSupported : function() {
 | |
|       // TODO: Is there a better capability test for this? This seems okay in
 | |
|       // Safari, Firefox and Chrome.
 | |
| 
 | |
|       return !!window.FileList;
 | |
|     },
 | |
|     isPasteSupported : function() {
 | |
|       // TODO: Needs to check if event.clipboardData is available.
 | |
|       // Works in Chrome, doesn't work in Firefox 10.
 | |
|       return !!window.FileList;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   members : {
 | |
|     _node : null,
 | |
|     _depth : 0,
 | |
|     _updateDepth : function(delta) {
 | |
|       if (this._depth === 0 && delta > 0) {
 | |
|         this.invoke('didBeginDrag');
 | |
|       }
 | |
| 
 | |
|       this._depth += delta;
 | |
| 
 | |
|       if (this._depth === 0 && delta < 0) {
 | |
|         this.invoke('didEndDrag');
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     start : function() {
 | |
| 
 | |
|       // TODO: move this to JX.DOM.contains()?
 | |
|       function contains(container, child) {
 | |
|         do {
 | |
|           if (child === container) {
 | |
|             return true;
 | |
|           }
 | |
|           child = child.parentNode;
 | |
|         } while (child);
 | |
| 
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       // Firefox has some issues sometimes; implement this click handler so
 | |
|       // the user can recover. See T5188.
 | |
|       JX.DOM.listen(
 | |
|         this._node,
 | |
|         'click',
 | |
|         null,
 | |
|         JX.bind(this, function (e) {
 | |
|           if (this._depth) {
 | |
|             e.kill();
 | |
|             // Force depth to 0.
 | |
|             this._updateDepth(-this._depth);
 | |
|           }
 | |
|         }));
 | |
| 
 | |
|       // We track depth so that the _node may have children inside of it and
 | |
|       // not become unselected when they are dragged over.
 | |
|       JX.DOM.listen(
 | |
|         this._node,
 | |
|         'dragenter',
 | |
|         null,
 | |
|         JX.bind(this, function(e) {
 | |
|           if (contains(this._node, e.getTarget())) {
 | |
|             this._updateDepth(1);
 | |
|           }
 | |
|         }));
 | |
| 
 | |
|       JX.DOM.listen(
 | |
|         this._node,
 | |
|         'dragleave',
 | |
|         null,
 | |
|         JX.bind(this, function(e) {
 | |
|           if (contains(this._node, e.getTarget())) {
 | |
|             this._updateDepth(-1);
 | |
|           }
 | |
|         }));
 | |
| 
 | |
|       JX.DOM.listen(
 | |
|         this._node,
 | |
|         'dragover',
 | |
|         null,
 | |
|         function(e) {
 | |
|           // NOTE: We must set this, or Chrome refuses to drop files from the
 | |
|           // download shelf.
 | |
|           e.getRawEvent().dataTransfer.dropEffect = 'copy';
 | |
|           e.kill();
 | |
|         });
 | |
| 
 | |
|       JX.DOM.listen(
 | |
|         this._node,
 | |
|         'drop',
 | |
|         null,
 | |
|         JX.bind(this, function(e) {
 | |
|           e.kill();
 | |
| 
 | |
|           var files = e.getRawEvent().dataTransfer.files;
 | |
|           for (var ii = 0; ii < files.length; ii++) {
 | |
|             this._sendRequest(files[ii]);
 | |
|           }
 | |
| 
 | |
|           // Force depth to 0.
 | |
|           this._updateDepth(-this._depth);
 | |
|         }));
 | |
| 
 | |
|       if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported()) {
 | |
|         JX.DOM.listen(
 | |
|           this._node,
 | |
|           'paste',
 | |
|           null,
 | |
|           JX.bind(this, function(e) {
 | |
|             var clipboard = e.getRawEvent().clipboardData;
 | |
|             if (!clipboard) {
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             // If there's any text on the clipboard, just let the event fire
 | |
|             // normally, choosing the text over any images. See T5437 / D9647.
 | |
|             var text = clipboard.getData('text/plain').toString();
 | |
|             if (text.length) {
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             // Safari and Firefox have clipboardData, but no items. They
 | |
|             // don't seem to provide a way to get image data directly yet.
 | |
|             if (!clipboard.items) {
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             for (var ii = 0; ii < clipboard.items.length; ii++) {
 | |
|               var item = clipboard.items[ii];
 | |
|               if (!/^image\//.test(item.type)) {
 | |
|                 continue;
 | |
|               }
 | |
|               var spec = item.getAsFile();
 | |
|               // pasted files don't have a name; see
 | |
|               // https://code.google.com/p/chromium/issues/detail?id=361145
 | |
|               if (!spec.name) {
 | |
|                 spec.name = 'pasted_file';
 | |
|               }
 | |
|               this._sendRequest(spec);
 | |
|             }
 | |
|           }));
 | |
|       }
 | |
|     },
 | |
|     _sendRequest : function(spec) {
 | |
|       var file = new JX.PhabricatorFileUpload()
 | |
|         .setName(spec.name)
 | |
|         .setTotalBytes(spec.size)
 | |
|         .setStatus('uploading')
 | |
|         .update();
 | |
| 
 | |
|       this.invoke('willUpload', file);
 | |
| 
 | |
|       var up_uri = JX.$U(this.getURI())
 | |
|         .setQueryParam('name', file.getName())
 | |
|         .setQueryParam('__upload__', 1);
 | |
| 
 | |
|       if (this.getViewPolicy()) {
 | |
|         up_uri.setQueryParam('viewPolicy', this.getViewPolicy());
 | |
|       }
 | |
| 
 | |
|       up_uri = up_uri.toString();
 | |
| 
 | |
|       var onupload = JX.bind(this, function(r) {
 | |
|         if (r.error) {
 | |
|           file
 | |
|             .setStatus('error')
 | |
|             .setError(r.error)
 | |
|             .update();
 | |
| 
 | |
|           this.invoke('didError', file);
 | |
|         } else {
 | |
|           file
 | |
|             .setID(r.id)
 | |
|             .setPHID(r.phid)
 | |
|             .setURI(r.uri)
 | |
|             .setMarkup(r.html)
 | |
|             .setStatus('done')
 | |
|             .update();
 | |
| 
 | |
|           this.invoke('didUpload', file);
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       var req = new JX.Request(up_uri, onupload);
 | |
| 
 | |
|       var onerror = JX.bind(this, function(error) {
 | |
|         file.setStatus('error');
 | |
| 
 | |
|         if (error) {
 | |
|           file.setError(error.code + ': ' + error.info);
 | |
|         } else {
 | |
|           var xhr = req.getTransport();
 | |
|           if (xhr.responseText) {
 | |
|             file.setError('Server responded: ' + xhr.responseText);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         file.update();
 | |
|         this.invoke('didError', file);
 | |
|       });
 | |
| 
 | |
|       var onprogress = JX.bind(this, function(progress) {
 | |
|         file
 | |
|           .setTotalBytes(progress.total)
 | |
|           .setUploadedBytes(progress.loaded)
 | |
|           .update();
 | |
| 
 | |
|         this.invoke('progress', file);
 | |
|       });
 | |
| 
 | |
|       req.listen('error', onerror);
 | |
|       req.listen('uploadprogress', onprogress);
 | |
| 
 | |
|       req
 | |
|         .setRawData(spec)
 | |
|         .send();
 | |
|     }
 | |
|   },
 | |
|   properties: {
 | |
|     URI : null,
 | |
|     activatedClass : null,
 | |
|     viewPolicy : null
 | |
|   }
 | |
| });
 |