Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13210198
D9561.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
D9561.id.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -8,7 +8,7 @@
'names' =>
array(
'core.pkg.css' => 'a8d8a51c',
- 'core.pkg.js' => '07b01d4f',
+ 'core.pkg.js' => '920e18bd',
'darkconsole.pkg.js' => 'ca8671ce',
'differential.pkg.css' => '4a93db37',
'differential.pkg.js' => 'eca39a2c',
@@ -202,7 +202,7 @@
'rsrc/externals/javelin/lib/Router.js' => '29274e2b',
'rsrc/externals/javelin/lib/URI.js' => 'd9a9b862',
'rsrc/externals/javelin/lib/Vector.js' => 'bd0aedcd',
- 'rsrc/externals/javelin/lib/Workflow.js' => '09b15cf1',
+ 'rsrc/externals/javelin/lib/Workflow.js' => '9a24d9c4',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '2295d074',
@@ -468,7 +468,7 @@
'rsrc/js/core/behavior-object-selector.js' => 'e6f67523',
'rsrc/js/core/behavior-oncopy.js' => 'c3e218fe',
'rsrc/js/core/behavior-phabricator-nav.js' => 'b5842a5e',
- 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'ba22863c',
+ 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '8ddb4ee2',
'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593',
'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45',
'rsrc/js/core/behavior-reorder-applications.js' => 'a8e3795d',
@@ -612,7 +612,7 @@
'javelin-behavior-phabricator-notification-example' => 'c51a6616',
'javelin-behavior-phabricator-object-selector' => 'e6f67523',
'javelin-behavior-phabricator-oncopy' => 'c3e218fe',
- 'javelin-behavior-phabricator-remarkup-assist' => 'ba22863c',
+ 'javelin-behavior-phabricator-remarkup-assist' => '8ddb4ee2',
'javelin-behavior-phabricator-reveal-content' => '8f24abfc',
'javelin-behavior-phabricator-search-typeahead' => 'fbeabd1e',
'javelin-behavior-phabricator-show-all-transactions' => '7c273581',
@@ -682,7 +682,7 @@
'javelin-view-interpreter' => '0c33c1a0',
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
- 'javelin-workflow' => '09b15cf1',
+ 'javelin-workflow' => '9a24d9c4',
'lightbox-attachment-css' => '7acac05d',
'maniphest-batch-editor' => '8f380ebc',
'maniphest-report-css' => '6fc16517',
@@ -871,18 +871,6 @@
array(
0 => 'javelin-install',
),
- '09b15cf1' =>
- array(
- 0 => 'javelin-stratcom',
- 1 => 'javelin-request',
- 2 => 'javelin-dom',
- 3 => 'javelin-vector',
- 4 => 'javelin-install',
- 5 => 'javelin-util',
- 6 => 'javelin-mask',
- 7 => 'javelin-uri',
- 8 => 'javelin-routable',
- ),
'0a3f3021' =>
array(
0 => 'javelin-behavior',
@@ -1478,6 +1466,17 @@
2 => 'javelin-stratcom',
3 => 'javelin-uri',
),
+ '8ddb4ee2' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-stratcom',
+ 2 => 'javelin-dom',
+ 3 => 'phabricator-phtize',
+ 4 => 'phabricator-textareautils',
+ 5 => 'javelin-workflow',
+ 6 => 'javelin-vector',
+ 7 => 'javelin-util',
+ ),
'8ef9ab58' =>
array(
0 => 'javelin-behavior',
@@ -1537,6 +1536,18 @@
3 => 'javelin-dom',
4 => 'phabricator-draggable-list',
),
+ '9a24d9c4' =>
+ array(
+ 0 => 'javelin-stratcom',
+ 1 => 'javelin-request',
+ 2 => 'javelin-dom',
+ 3 => 'javelin-vector',
+ 4 => 'javelin-install',
+ 5 => 'javelin-util',
+ 6 => 'javelin-mask',
+ 7 => 'javelin-uri',
+ 8 => 'javelin-routable',
+ ),
'9b9197be' =>
array(
0 => 'javelin-behavior',
@@ -1712,16 +1723,6 @@
0 => 'javelin-install',
1 => 'javelin-dom',
),
- 'ba22863c' =>
- array(
- 0 => 'javelin-behavior',
- 1 => 'javelin-stratcom',
- 2 => 'javelin-dom',
- 3 => 'phabricator-phtize',
- 4 => 'phabricator-textareautils',
- 5 => 'javelin-workflow',
- 6 => 'javelin-vector',
- ),
'bd0aedcd' =>
array(
0 => 'javelin-install',
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
@@ -5,14 +5,62 @@
public function processRequest() {
$request = $this->getRequest();
- $user = $request->getUser();
+ $viewer = $request->getUser();
- $dialog = id(new AphrontDialogView())
- ->setUser($user)
+ $severity = AphrontErrorView::SEVERITY_NOTICE;
+ $errors = array(
+ pht(
+ 'To upload files more easily, drag and drop them directly into '.
+ 'the text of your comment.'));
+
+ $e_file = true;
+ if ($request->isFormPost()) {
+ $errors = array();
+ $severity = AphrontErrorView::SEVERITY_ERROR;
+
+ if (!$request->getFileExists('file')) {
+ $e_file = pht('Required');
+ $errors[] = pht('You must select a file to upload.');
+ } else {
+ try {
+ $file = PhabricatorFile::newFromPHPUpload(
+ $_FILES['file'],
+ array(
+ 'authorPHID' => $viewer->getPHID(),
+ 'isExplicitUpload' => true,
+ ));
+ } catch (Exception $ex) {
+ $e_file = pht('Error');
+ $errors[] = $ex->getMessage();
+ }
+ }
+
+ if (!$errors) {
+ return id(new AphrontAjaxResponse())->setContent(
+ array(
+ 'id' => $file->getID(),
+ 'phid' => $file->getPHID(),
+ 'uri' => $file->getBestURI(),
+ ));
+ }
+ }
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendChild(
+ id(new AphrontFormFileControl())
+ ->setName('file')
+ ->setError($e_file));
+
+ $dialog = $this->newDialog()
+ ->setFormEncoding('multipart/form-data')
->setTitle(pht('Upload File'))
- ->appendChild(pht(
- 'To add files, drag and drop them into the comment text area.'))
- ->addCancelButton('/', pht('Close'));
+ ->setWidth(AphrontDialogView::WIDTH_FORM)
+ ->setErrors($errors)
+ ->setErrorSeverity($severity)
+ ->appendChild($form->buildLayoutView())
+ ->addCancelButton('/', pht('Close'))
+ ->addSubmitButton(pht('Upload File'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php
--- a/src/view/AphrontDialogView.php
+++ b/src/view/AphrontDialogView.php
@@ -22,17 +22,28 @@
private $errors = array();
private $flush;
private $validationException;
-
+ private $errorSeverity = AphrontErrorView::SEVERITY_ERROR;
+ private $formEncoding;
const WIDTH_DEFAULT = 'default';
const WIDTH_FORM = 'form';
const WIDTH_FULL = 'full';
+ public function setFormEncoding($form_encoding) {
+ $this->formEncoding = $form_encoding;
+ return $this;
+ }
+
public function setMethod($method) {
$this->method = $method;
return $this;
}
+ public function setErrorSeverity($error_severity) {
+ $this->errorSeverity = $error_severity;
+ return $this;
+ }
+
public function setIsStandalone($is_standalone) {
$this->isStandalone = $is_standalone;
return $this;
@@ -240,9 +251,10 @@
);
$form_attributes = array(
- 'action' => $this->submitURI,
- 'method' => $this->method,
- 'id' => $this->formID,
+ 'action' => $this->submitURI,
+ 'method' => $this->method,
+ 'id' => $this->formID,
+ 'enctype' => $this->formEncoding,
);
$hidden_inputs = array();
@@ -287,7 +299,9 @@
if ($errors) {
$children = array(
- id(new AphrontErrorView())->setErrors($errors),
+ id(new AphrontErrorView())
+ ->setErrors($errors)
+ ->setSeverity($this->errorSeverity),
$children);
}
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
@@ -33,9 +33,19 @@
statics : {
_stack : [],
newFromForm : function(form, data) {
- var pairs = JX.DOM.convertFormToListOfPairs(form);
- for (var k in data) {
- pairs.push([k, data[k]]);
+
+ var workflow = new JX.Workflow(form.getAttribute('action'), {});
+ workflow.setMethod(form.getAttribute('method'));
+
+ // If the form is "multipart/form-data", use FormData to marshall
+ if (form.enctype == 'multipart/form-data') {
+ workflow.setRawData(new FormData(form));
+ } else {
+ var pairs = JX.DOM.convertFormToListOfPairs(form);
+ for (var k in data) {
+ pairs.push([k, data[k]]);
+ }
+ workflow.setDataWithListOfPairs(pairs);
}
// Disable form elements during the request
@@ -51,15 +61,13 @@
}
}
- var workflow = new JX.Workflow(form.getAttribute('action'), {});
- workflow.setDataWithListOfPairs(pairs);
- workflow.setMethod(form.getAttribute('method'));
workflow.listen('finally', function() {
// Re-enable form elements
for (var ii = 0; ii < inputs.length; ii++) {
inputs[ii] && (inputs[ii].disabled = false);
}
});
+
return workflow;
},
newFromLink : function(link) {
@@ -130,17 +138,35 @@
return;
}
- var data = JX.DOM.convertFormToListOfPairs(form);
- data.push([button.name, button.value || true]);
+ var data;
+ var raw_data;
+ if (form.enctype == 'multipart/form-data') {
+ raw_data = new FormData(form);
+ raw_data.append(button.name, button.value || 'true');
+ } else {
+ data = JX.DOM.convertFormToListOfPairs(form);
+ data.push([button.name, button.value || true]);
+ }
var active = JX.Workflow._getActiveWorkflow();
- var e = active.invoke('submit', {form: form, data: data});
+ var submit_data = {
+ form: form,
+ data: data,
+ rawData: raw_data
+ };
+
+ var e = active.invoke('submit', submit_data);
if (!e.getStopped()) {
active._destroy();
- active
- .setURI(form.getAttribute('action') || active.getURI())
- .setDataWithListOfPairs(data)
- .start();
+ active.setURI(form.getAttribute('action') || active.getURI());
+
+ if (raw_data) {
+ active.setRawData(raw_data);
+ } else {
+ active.setDataWithListOfPairs(data);
+ }
+
+ active.start();
}
},
_getActiveWorkflow : function() {
@@ -153,6 +179,7 @@
_root : null,
_pushed : false,
_data : null,
+ _rawData: null,
_onload : function(r) {
// 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.
@@ -240,13 +267,23 @@
var uri = this.getURI();
var method = this.getMethod();
var r = new JX.Request(uri, JX.bind(this, this._onload));
- var list_of_pairs = this._data;
- list_of_pairs.push(['__wflow__', true]);
- r.setDataWithListOfPairs(list_of_pairs);
- r.setDataSerializer(this.getDataSerializer());
+
+ // If we have raw data, use that. Normally, this means we're submitting
+ // a form which includes a file input.
+ if (this._rawData) {
+ this._rawData.append('__wflow__', 'true');
+ r.setRawData(this._rawData);
+ } else {
+ var list_of_pairs = this._data;
+ list_of_pairs.push(['__wflow__', true]);
+ r.setDataWithListOfPairs(list_of_pairs);
+ r.setDataSerializer(this.getDataSerializer());
+ }
+
if (method) {
r.setMethod(method);
}
+
r.listen('finally', JX.bind(this, this.invoke, 'finally'));
r.listen('error', JX.bind(this, function(error) {
var e = this.invoke('error', error);
@@ -257,6 +294,7 @@
// user to "/error/". We could emit a blanket 'workflow-failed' type
// event instead.
}));
+
r.send();
},
@@ -280,6 +318,11 @@
return this;
},
+ setRawData: function(form_data) {
+ this._rawData = form_data;
+ return this;
+ },
+
setDataWithListOfPairs : function(list_of_pairs) {
this._data = list_of_pairs;
return this;
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
@@ -7,6 +7,7 @@
* phabricator-textareautils
* javelin-workflow
* javelin-vector
+ * javelin-util
*/
JX.behavior('phabricator-remarkup-assist', function(config) {
@@ -93,6 +94,11 @@
range.start + l.length + m.length);
}
+ function onupload(area, response) {
+ var ref = '{F' + response.id + '}';
+ JX.TextAreaUtils.setSelectionText(area, ref);
+ }
+
function assist(area, action, root) {
// If the user has some text selected, we'll try to use that (for example,
// if they have a word selected and want to bold it). Otherwise we'll insert
@@ -150,7 +156,9 @@
.start();
break;
case 'fa-cloud-upload':
- new JX.Workflow('/file/uploaddialog/').start();
+ new JX.Workflow('/file/uploaddialog/')
+ .setHandler(JX.bind(null, onupload, area))
+ .start();
break;
case 'fa-arrows-alt':
if (edit_mode == 'fa-arrows-alt') {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, May 18, 4:33 AM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6275032
Default Alt Text
D9561.id.diff (13 KB)
Attached To
Mode
D9561: [Draft] Use FormData for Ajax file uploads
Attached
Detach File
Event Timeline
Log In to Comment