diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,10 +8,10 @@ return array( 'names' => array( 'core.pkg.css' => '204cabae', - 'core.pkg.js' => '6972d365', + 'core.pkg.js' => '9f2969e9', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', - 'differential.pkg.js' => 'd0cd0df6', + 'differential.pkg.js' => '4b7d8f19', 'diffusion.pkg.css' => '91c5d3a6', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', @@ -245,7 +245,7 @@ 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', - 'rsrc/externals/javelin/lib/Workflow.js' => '28cfbdd0', + 'rsrc/externals/javelin/lib/Workflow.js' => '0eb34d1d', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68', @@ -457,7 +457,7 @@ 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', - 'rsrc/js/core/DragAndDropFileUpload.js' => '81f182b5', + 'rsrc/js/core/DragAndDropFileUpload.js' => '58dea2fa', 'rsrc/js/core/DraggableList.js' => '5a13c79f', 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', @@ -467,7 +467,7 @@ 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => 'e67df814', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', - 'rsrc/js/core/TextAreaUtils.js' => '5813016a', + 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => 'df5e11d2', 'rsrc/js/core/ToolTip.js' => '6323f942', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', @@ -478,7 +478,7 @@ 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'b5b36110', - 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4f6a4b4e', + 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => '568931f3', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', @@ -496,7 +496,7 @@ 'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '340c8eff', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', @@ -514,6 +514,7 @@ 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475', + 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', @@ -578,7 +579,7 @@ 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', - 'javelin-behavior-aphront-drag-and-drop-textarea' => '4f6a4b4e', + 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', 'javelin-behavior-audio-source' => '59b251eb', @@ -654,7 +655,7 @@ 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => '340c8eff', + 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -665,6 +666,7 @@ 'javelin-behavior-pholio-mock-edit' => '246dc085', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-phui-dropdown-menu' => '54733475', + 'javelin-behavior-phui-file-upload' => 'b003d4fb', 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-profile-menu' => '12884df9', @@ -743,7 +745,7 @@ 'javelin-workboard-card' => 'c587b80f', 'javelin-workboard-column' => 'bae58312', 'javelin-workboard-controller' => '55baf5ed', - 'javelin-workflow' => '28cfbdd0', + 'javelin-workflow' => '0eb34d1d', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => '9b9580b7', @@ -763,7 +765,7 @@ 'phabricator-core-css' => 'd0801452', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-dashboard-css' => 'bc6f2127', - 'phabricator-drag-and-drop-file-upload' => '81f182b5', + 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => '5a13c79f', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', @@ -787,7 +789,7 @@ 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => 'e709f6d0', - 'phabricator-textareautils' => '5813016a', + 'phabricator-textareautils' => '320810c8', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '6323f942', 'phabricator-ui-example-css' => '528b19de', @@ -975,10 +977,31 @@ 'javelin-dom', 'javelin-router', ), + '0eb34d1d' => array( + 'javelin-stratcom', + 'javelin-request', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + 'javelin-util', + 'javelin-mask', + 'javelin-uri', + 'javelin-routable', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', ), + '116cf19b' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + 'phuix-autocomplete', + ), '12884df9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1075,17 +1098,6 @@ 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), - '28cfbdd0' => array( - 'javelin-stratcom', - 'javelin-request', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - 'javelin-util', - 'javelin-mask', - 'javelin-uri', - 'javelin-routable', - ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1116,21 +1128,16 @@ '2ee659ce' => array( 'javelin-install', ), - '327a00d1' => array( - 'javelin-behavior', - 'javelin-stratcom', + '320810c8' => array( + 'javelin-install', 'javelin-dom', - 'javelin-workflow', + 'javelin-vector', ), - '340c8eff' => array( + '327a00d1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', 'javelin-workflow', - 'javelin-vector', - 'phuix-autocomplete', ), '3ab51e2c' => array( 'javelin-behavior', @@ -1199,6 +1206,12 @@ 'javelin-dom', 'javelin-workflow', ), + '484a6e22' => array( + 'javelin-behavior', + 'javelin-dom', + 'phabricator-drag-and-drop-file-upload', + 'phabricator-textareautils', + ), '49b73b36' => array( 'javelin-behavior', 'javelin-dom', @@ -1216,12 +1229,6 @@ 'javelin-stratcom', 'javelin-dom', ), - '4f6a4b4e' => array( - 'javelin-behavior', - 'javelin-dom', - 'phabricator-drag-and-drop-file-upload', - 'phabricator-textareautils', - ), '4fbbc3e9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1319,10 +1326,13 @@ 'javelin-request', 'javelin-util', ), - '5813016a' => array( + '58dea2fa' => array( 'javelin-install', + 'javelin-util', + 'javelin-request', 'javelin-dom', - 'javelin-vector', + 'javelin-uri', + 'phabricator-file-upload', ), '59a7976a' => array( 'javelin-install', @@ -1516,14 +1526,6 @@ 'javelin-vector', 'javelin-stratcom', ), - '81f182b5' => array( - 'javelin-install', - 'javelin-util', - 'javelin-request', - 'javelin-dom', - 'javelin-uri', - 'phabricator-file-upload', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1740,6 +1742,12 @@ 'javelin-util', 'phabricator-busy', ), + 'b003d4fb' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phuix-dropdown-menu', + ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1594,6 +1594,7 @@ 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', + 'PHUIFormFileControl' => 'view/form/control/PHUIFormFileControl.php', 'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php', 'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php', 'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php', @@ -5996,6 +5997,7 @@ 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 'PHUIFeedStoryView' => 'AphrontView', 'PHUIFormDividerControl' => 'AphrontFormControl', + 'PHUIFormFileControl' => 'AphrontFormControl', 'PHUIFormFreeformDateControl' => 'AphrontFormControl', 'PHUIFormIconSetControl' => 'AphrontFormControl', 'PHUIFormInsetView' => 'AphrontView', diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php --- a/src/applications/files/controller/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php @@ -56,7 +56,7 @@ $file_phid = $result['filePHID']; if ($file_phid) { $file = $this->loadFile($file_phid); - $result += $this->getFileDictionary($file); + $result += $file->getDragAndDropDictionary(); } return id(new AphrontAjaxResponse())->setContent($result); @@ -84,7 +84,7 @@ } else { $result = array( 'complete' => true, - ) + $this->getFileDictionary($file); + ) + $file->getDragAndDropDictionary(); } return id(new AphrontAjaxResponse())->setContent($result); @@ -99,18 +99,10 @@ 'isExplicitUpload' => true, )); - $result = $this->getFileDictionary($file); + $result = $file->getDragAndDropDictionary(); return id(new AphrontAjaxResponse())->setContent($result); } - private function getFileDictionary(PhabricatorFile $file) { - return array( - 'id' => $file->getID(), - 'phid' => $file->getPHID(), - 'uri' => $file->getBestURI(), - ); - } - private function loadFile($file_phid) { $viewer = $this->getViewer(); diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php --- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php +++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php @@ -6,12 +6,51 @@ public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - return $this->newDialog() - ->setTitle(pht('Upload File')) - ->appendChild(pht( - 'To add files, drag and drop them into the comment text area.')) - ->addCancelButton('/', pht('Close')); + $e_file = true; + $errors = array(); + if ($request->isDialogFormPost()) { + $file_phids = $request->getStrList('filePHIDs'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->setRaisePolicyExceptions(true) + ->execute(); + } else { + $files = array(); + } + + if ($files) { + $results = array(); + foreach ($files as $file) { + $results[] = $file->getDragAndDropDictionary(); + } + + $content = array( + 'files' => $results, + ); + return id(new AphrontAjaxResponse())->setContent($content); + } else { + $e_file = pht('Required'); + $errors[] = pht('You must choose a file to upload.'); + } + } + + $form = id(new AphrontFormView()) + ->appendChild( + id(new PHUIFormFileControl()) + ->setName('filePHIDs') + ->setLabel(pht('Upload File')) + ->setAllowMultiple(true) + ->setError($e_file)); + + return $this->newDialog() + ->setTitle(pht('File')) + ->setErrors($errors) + ->appendForm($form) + ->addSubmitButton(pht('Upload')) + ->addCancelButton('/'); } } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -851,6 +851,14 @@ return $supported; } + public function getDragAndDropDictionary() { + return array( + 'id' => $this->getID(), + 'phid' => $this->getPHID(), + 'uri' => $this->getBestURI(), + ); + } + public function instantiateStorageEngine() { return self::buildEngine($this->getStorageEngine()); } diff --git a/src/view/form/control/PHUIFormFileControl.php b/src/view/form/control/PHUIFormFileControl.php new file mode 100644 --- /dev/null +++ b/src/view/form/control/PHUIFormFileControl.php @@ -0,0 +1,44 @@ +allowMultiple = $allow_multiple; + return $this; + } + + public function getAllowMultiple() { + return $this->allowMultiple; + } + + protected function renderInput() { + $file_id = $this->getID(); + + Javelin::initBehavior( + 'phui-file-upload', + array( + 'fileInputID' => $file_id, + 'inputName' => $this->getName(), + 'uploadURI' => '/file/dropupload/', + 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), + )); + + return phutil_tag( + 'input', + array( + 'type' => 'file', + 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, + 'name' => $this->getName().'.raw', + 'id' => $file_id, + 'disabled' => $this->getDisabled() ? 'disabled' : null, + )); + } + +} diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js --- a/webroot/rsrc/externals/javelin/lib/Workflow.js +++ b/webroot/rsrc/externals/javelin/lib/Workflow.js @@ -25,7 +25,7 @@ this.setData(data || {}); }, - events : ['error', 'finally', 'submit'], + events : ['error', 'finally', 'submit', 'start'], statics : { _stack : [], @@ -54,6 +54,9 @@ } var workflow = new JX.Workflow(form.getAttribute('action'), {}); + + workflow._form = form; + workflow.setDataWithListOfPairs(pairs); workflow.setMethod(form.getAttribute('method')); workflow.listen('finally', function() { @@ -137,9 +140,14 @@ data.push([button.name, button.value || true]); var active = JX.Workflow._getActiveWorkflow(); + + active._form = form; + var e = active.invoke('submit', {form: form, data: data}); if (!e.getStopped()) { - active._destroy(); + // NOTE: Don't remove the current dialog yet because additional + // handlers may still want to access the nodes. + active .setURI(form.getAttribute('action') || active.getURI()) .setDataWithListOfPairs(data) @@ -156,7 +164,41 @@ _root : null, _pushed : false, _data : null, + + _form: null, + _paused: 0, + _nextCallback: null, + + getSourceForm: function() { + return this._form; + }, + + pause: function() { + this._paused++; + return this; + }, + + resume: function() { + if (!this._paused) { + JX.$E('Resuming a workflow which is not paused!'); + } + + this._paused--; + + if (!this._paused) { + var next = this._nextCallback; + this._nextCallback = null; + if (next) { + next(); + } + } + + return this; + }, + _onload : function(r) { + this._destroy(); + // It is permissible to send back a falsey redirect to force a page // reload, so we need to take this branch if the key is present. if (r && (typeof r.redirect != 'undefined')) { @@ -247,7 +289,19 @@ this._root = null; } }, + start : function() { + var next = JX.bind(this, this._send); + + this.pause(); + this._nextCallback = next; + + this.invoke('start', this); + + this.resume(); + }, + + _send: function() { var uri = this.getURI(); var method = this.getMethod(); var r = new JX.Request(uri, JX.bind(this, this._onload)); @@ -291,6 +345,11 @@ return this; }, + addData: function(key, value) { + this._data.push([key, value]); + return this; + }, + setDataWithListOfPairs : function(list_of_pairs) { this._data = list_of_pairs; return this; diff --git a/webroot/rsrc/js/core/DragAndDropFileUpload.js b/webroot/rsrc/js/core/DragAndDropFileUpload.js --- a/webroot/rsrc/js/core/DragAndDropFileUpload.js +++ b/webroot/rsrc/js/core/DragAndDropFileUpload.js @@ -155,7 +155,7 @@ var files = e.getRawEvent().dataTransfer.files; for (var ii = 0; ii < files.length; ii++) { - this._sendRequest(files[ii]); + this.sendRequest(files[ii]); } // Force depth to 0. @@ -216,7 +216,7 @@ if (!spec.name) { spec.name = 'pasted_file'; } - this._sendRequest(spec); + this.sendRequest(spec); } })); } @@ -224,7 +224,7 @@ this.setIsEnabled(true); }, - _sendRequest : function(spec) { + sendRequest : function(spec) { var file = new JX.PhabricatorFileUpload() .setRawFileObject(spec) .setName(spec.name) diff --git a/webroot/rsrc/js/core/TextAreaUtils.js b/webroot/rsrc/js/core/TextAreaUtils.js --- a/webroot/rsrc/js/core/TextAreaUtils.js +++ b/webroot/rsrc/js/core/TextAreaUtils.js @@ -62,6 +62,26 @@ JX.TextAreaUtils.setSelectionRange(area, start, end); }, + + /** + * Insert a reference to a given uploaded file into a textarea. + */ + insertFileReference: function(area, file) { + var ref = '{F' + file.getID() + '}'; + + // If we're inserting immediately after a "}" (usually, another file + // reference), put some newlines before our token so that multiple file + // uploads get laid out more nicely. + var range = JX.TextAreaUtils.getSelectionRange(area); + var before = area.value.substring(0, range.start); + if (before.match(/\}$/)) { + ref = '\n\n' + ref; + } + + JX.TextAreaUtils.setSelectionText(area, ref, false); + }, + + /** * Get the document pixel positions of the beginning and end of a character * range in a textarea. diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js --- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js +++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js @@ -10,32 +10,23 @@ var target = JX.$(config.target); - function onupload(f) { - var ref = '{F' + f.getID() + '}'; - - // If we're inserting immediately after a "}" (usually, another file - // reference), put some newlines before our token so that multiple file - // uploads get laid out more nicely. - var range = JX.TextAreaUtils.getSelectionRange(target); - var before = target.value.substring(0, range.start); - if (before.match(/\}$/)) { - ref = '\n\n' + ref; - } - - JX.TextAreaUtils.setSelectionText(target, ref, false); - } - if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { var drop = new JX.PhabricatorDragAndDropFileUpload(target) .setURI(config.uri) .setChunkThreshold(config.chunkThreshold); + drop.listen('didBeginDrag', function() { JX.DOM.alterClass(target, config.activatedClass, true); }); + drop.listen('didEndDrag', function() { JX.DOM.alterClass(target, config.activatedClass, false); }); - drop.listen('didUpload', onupload); + + drop.listen('didUpload', function(file) { + JX.TextAreaUtils.insertFileReference(target, file); + }); + drop.start(); } diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -194,7 +194,21 @@ .start(); break; case 'fa-cloud-upload': - new JX.Workflow('/file/uploaddialog/').start(); + new JX.Workflow('/file/uploaddialog/') + .setHandler(function(response) { + var files = response.files; + for (var ii = 0; ii < files.length; ii++) { + var file = files[ii]; + + var upload = new JX.PhabricatorFileUpload() + .setID(file.id) + .setPHID(file.phid) + .setURI(file.uri); + + JX.TextAreaUtils.insertFileReference(area, upload); + } + }) + .start(); break; case 'fa-arrows-alt': if (edit_mode == 'fa-arrows-alt') { diff --git a/webroot/rsrc/js/phui/behavior-phui-file-upload.js b/webroot/rsrc/js/phui/behavior-phui-file-upload.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/phui/behavior-phui-file-upload.js @@ -0,0 +1,80 @@ +/** + * @provides javelin-behavior-phui-file-upload + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * phuix-dropdown-menu + */ + +JX.behavior('phui-file-upload', function(config) { + + function startUpload(workflow, input) { + var files = input.files; + + if (!files || !files.length) { + return; + } + + var state = { + workflow: workflow, + input: input, + waiting: 0, + phids: [] + }; + + var callback = JX.bind(null, didUpload, state); + + var dummy = input; + var uploader = new JX.PhabricatorDragAndDropFileUpload(dummy) + .setURI(config.uploadURI) + .setChunkThreshold(config.chunkThreshold); + + uploader.listen('didUpload', callback); + uploader.start(); + + workflow.pause(); + for (var ii = 0; ii < files.length; ii++) { + state.waiting++; + uploader.sendRequest(files[ii]); + } + } + + function didUpload(state, file) { + state.phids.push(file.getPHID()); + state.waiting--; + + if (state.waiting) { + return; + } + + state.workflow + .addData(config.inputName, state.phids.join(', ')) + .resume(); + } + + JX.Workflow.listen('start', function(workflow) { + var form = workflow.getSourceForm(); + if (!form) { + return; + } + + var input; + try { + input = JX.$(config.fileInputID); + } catch (ex) { + return; + } + + var local_form = JX.DOM.findAbove(input, 'form'); + if (!local_form) { + return; + } + + if (local_form !== form) { + return; + } + + startUpload(workflow, input); + }); + +});