@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Support drag-and-drop to set cover images on workboard cards

Summary: This was slightly more complex than I believed, but not too terrible.

Test Plan:
{F1096126}

- Also used some normal file uploaders to make sure I didn't break that.

Reviewers: chad

Reviewed By: chad

Differential Revision: https://secure.phabricator.com/D15202

+352 -139
resources/builtin/image-526x526.png

This is a binary file and will not be displayed.

+33 -32
resources/celerity/map.php
··· 8 8 return array( 9 9 'names' => array( 10 10 'core.pkg.css' => 'a7d4cf8f', 11 - 'core.pkg.js' => 'ef5e33db', 11 + 'core.pkg.js' => '808ae845', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '2de124c9', 14 - 'differential.pkg.js' => '5c2ba922', 14 + 'differential.pkg.js' => '6b42b4bc', 15 15 'diffusion.pkg.css' => 'f45955ed', 16 16 'diffusion.pkg.js' => '3a9a8bfa', 17 17 'maniphest.pkg.css' => '4845691a', ··· 155 155 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', 156 156 'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b', 157 157 'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524', 158 - 'rsrc/css/phui/workboards/phui-workcard.css' => 'adf34f58', 158 + 'rsrc/css/phui/workboards/phui-workcard.css' => 'a869098a', 159 159 'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04', 160 160 'rsrc/css/sprite-login.css' => '60e8560e', 161 161 'rsrc/css/sprite-menu.css' => '9dd65b92', ··· 414 414 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 415 415 'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5', 416 416 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 417 - 'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95', 417 + 'rsrc/js/application/projects/behavior-project-boards.js' => '5191522f', 418 418 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 419 419 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 420 420 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', ··· 446 446 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 447 447 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 448 448 'rsrc/js/core/Busy.js' => '59a7976a', 449 - 'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac', 449 + 'rsrc/js/core/DragAndDropFileUpload.js' => 'da044194', 450 450 'rsrc/js/core/DraggableList.js' => '8905523d', 451 - 'rsrc/js/core/FileUpload.js' => '477359c8', 451 + 'rsrc/js/core/FileUpload.js' => '680ea2c8', 452 452 'rsrc/js/core/Hovercard.js' => '1bd28176', 453 453 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 454 454 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', ··· 654 654 'javelin-behavior-phui-profile-menu' => '12884df9', 655 655 'javelin-behavior-policy-control' => 'd0c516d5', 656 656 'javelin-behavior-policy-rule-editor' => '5e9f347c', 657 - 'javelin-behavior-project-boards' => '48470f95', 657 + 'javelin-behavior-project-boards' => '5191522f', 658 658 'javelin-behavior-project-create' => '065227cc', 659 659 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 660 660 'javelin-behavior-recurring-edit' => '5f1c4d5f', ··· 741 741 'phabricator-core-css' => '5b3563c8', 742 742 'phabricator-countdown-css' => 'e7544472', 743 743 'phabricator-dashboard-css' => 'eb458607', 744 - 'phabricator-drag-and-drop-file-upload' => 'ad10aeac', 744 + 'phabricator-drag-and-drop-file-upload' => 'da044194', 745 745 'phabricator-draggable-list' => '8905523d', 746 746 'phabricator-fatal-config-template-css' => '8e6c6fcd', 747 747 'phabricator-feed-css' => 'ecd4ec57', 748 - 'phabricator-file-upload' => '477359c8', 748 + 'phabricator-file-upload' => '680ea2c8', 749 749 'phabricator-filetree-view-css' => 'fccf9f82', 750 750 'phabricator-flag-css' => '5337623f', 751 751 'phabricator-keyboard-shortcut' => '1ae869f2', ··· 832 832 'phui-timeline-view-css' => '2efceff8', 833 833 'phui-two-column-view-css' => 'c75bfc5b', 834 834 'phui-workboard-view-css' => 'b07a5524', 835 - 'phui-workcard-view-css' => 'adf34f58', 835 + 'phui-workcard-view-css' => 'a869098a', 836 836 'phui-workpanel-view-css' => 'e1bd8d04', 837 837 'phuix-action-list-view' => 'b5c256b8', 838 838 'phuix-action-view' => '8cf6d262', ··· 1133 1133 'javelin-dom', 1134 1134 'javelin-workflow', 1135 1135 ), 1136 - '477359c8' => array( 1137 - 'javelin-install', 1138 - 'javelin-dom', 1139 - 'phabricator-notification', 1140 - ), 1141 1136 47830651 => array( 1142 1137 'javelin-behavior', 1143 1138 'javelin-dom', ··· 1154 1149 'javelin-dom', 1155 1150 'javelin-workflow', 1156 1151 ), 1157 - '48470f95' => array( 1158 - 'javelin-behavior', 1159 - 'javelin-dom', 1160 - 'javelin-util', 1161 - 'javelin-vector', 1162 - 'javelin-stratcom', 1163 - 'javelin-workflow', 1164 - 'phabricator-draggable-list', 1165 - ), 1166 1152 '49b73b36' => array( 1167 1153 'javelin-behavior', 1168 1154 'javelin-dom', ··· 1203 1189 'javelin-install', 1204 1190 'javelin-typeahead-source', 1205 1191 'javelin-util', 1192 + ), 1193 + '5191522f' => array( 1194 + 'javelin-behavior', 1195 + 'javelin-dom', 1196 + 'javelin-util', 1197 + 'javelin-vector', 1198 + 'javelin-stratcom', 1199 + 'javelin-workflow', 1200 + 'phabricator-draggable-list', 1201 + 'phabricator-drag-and-drop-file-upload', 1206 1202 ), 1207 1203 '519705ea' => array( 1208 1204 'javelin-install', ··· 1330 1326 'javelin-install', 1331 1327 'javelin-request', 1332 1328 'javelin-workflow', 1329 + ), 1330 + '680ea2c8' => array( 1331 + 'javelin-install', 1332 + 'javelin-dom', 1333 + 'phabricator-notification', 1333 1334 ), 1334 1335 '6882e80a' => array( 1335 1336 'javelin-dom', ··· 1674 1675 'javelin-util', 1675 1676 'phabricator-busy', 1676 1677 ), 1677 - 'ad10aeac' => array( 1678 - 'javelin-install', 1679 - 'javelin-util', 1680 - 'javelin-request', 1681 - 'javelin-dom', 1682 - 'javelin-uri', 1683 - 'phabricator-file-upload', 1684 - ), 1685 1678 'b064af76' => array( 1686 1679 'javelin-behavior', 1687 1680 'javelin-stratcom', ··· 1896 1889 'javelin-dom', 1897 1890 'javelin-util', 1898 1891 'phabricator-shaped-request', 1892 + ), 1893 + 'da044194' => array( 1894 + 'javelin-install', 1895 + 'javelin-util', 1896 + 'javelin-request', 1897 + 'javelin-dom', 1898 + 'javelin-uri', 1899 + 'phabricator-file-upload', 1899 1900 ), 1900 1901 'dbbf48b6' => array( 1901 1902 'javelin-behavior',
+2
src/__phutil_library_map__.php
··· 2880 2880 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 2881 2881 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', 2882 2882 'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php', 2883 + 'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php', 2883 2884 'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php', 2884 2885 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 2885 2886 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', ··· 7304 7305 ), 7305 7306 'PhabricatorProjectController' => 'PhabricatorController', 7306 7307 'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase', 7308 + 'PhabricatorProjectCoverController' => 'PhabricatorProjectController', 7307 7309 'PhabricatorProjectCustomField' => 'PhabricatorCustomField', 7308 7310 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 7309 7311 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
+79
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 28 28 $types[] = ManiphestTransaction::TYPE_UNBLOCK; 29 29 $types[] = ManiphestTransaction::TYPE_PARENT; 30 30 $types[] = ManiphestTransaction::TYPE_COLUMN; 31 + $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; 31 32 $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 32 33 $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 33 34 ··· 66 67 return $xaction->getOldValue(); 67 68 case ManiphestTransaction::TYPE_SUBPRIORITY: 68 69 return $object->getSubpriority(); 70 + case ManiphestTransaction::TYPE_COVER_IMAGE: 71 + return $object->getCoverImageFilePHID(); 69 72 case ManiphestTransaction::TYPE_MERGED_INTO: 70 73 case ManiphestTransaction::TYPE_MERGED_FROM: 71 74 return null; ··· 92 95 case ManiphestTransaction::TYPE_MERGED_INTO: 93 96 case ManiphestTransaction::TYPE_MERGED_FROM: 94 97 case ManiphestTransaction::TYPE_UNBLOCK: 98 + case ManiphestTransaction::TYPE_COVER_IMAGE: 95 99 return $xaction->getNewValue(); 96 100 case ManiphestTransaction::TYPE_PARENT: 97 101 case ManiphestTransaction::TYPE_COLUMN: ··· 160 164 return; 161 165 case ManiphestTransaction::TYPE_MERGED_INTO: 162 166 $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); 167 + return; 168 + case ManiphestTransaction::TYPE_COVER_IMAGE: 169 + $file_phid = $xaction->getNewValue(); 170 + 171 + if ($file_phid) { 172 + $file = id(new PhabricatorFileQuery()) 173 + ->setViewer($this->getActor()) 174 + ->withPHIDs(array($file_phid)) 175 + ->executeOne(); 176 + } else { 177 + $file = null; 178 + } 179 + 180 + if (!$file || !$file->isTransformableImage()) { 181 + $object->setProperty('cover.filePHID', null); 182 + $object->setProperty('cover.thumbnailPHID', null); 183 + return; 184 + } 185 + 186 + $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; 187 + 188 + $xform = PhabricatorFileTransform::getTransformByKey($xform_key) 189 + ->executeTransform($file); 190 + 191 + $object->setProperty('cover.filePHID', $file->getPHID()); 192 + $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); 163 193 return; 164 194 case ManiphestTransaction::TYPE_MERGED_FROM: 165 195 case ManiphestTransaction::TYPE_PARENT: ··· 819 849 } 820 850 } 821 851 break; 852 + case ManiphestTransaction::TYPE_COVER_IMAGE: 853 + foreach ($xactions as $xaction) { 854 + $old = $xaction->getOldValue(); 855 + $new = $xaction->getNewValue(); 856 + if (!$new) { 857 + continue; 858 + } 859 + 860 + if ($new === $old) { 861 + continue; 862 + } 863 + 864 + $file = id(new PhabricatorFileQuery()) 865 + ->setViewer($this->getActor()) 866 + ->withPHIDs(array($new)) 867 + ->executeOne(); 868 + if (!$file) { 869 + $errors[] = new PhabricatorApplicationTransactionValidationError( 870 + $type, 871 + pht('Invalid'), 872 + pht('File "%s" is not valid.', $new), 873 + $xaction); 874 + continue; 875 + } 876 + 877 + if (!$file->isTransformableImage()) { 878 + $errors[] = new PhabricatorApplicationTransactionValidationError( 879 + $type, 880 + pht('Invalid'), 881 + pht('File "%s" is not a valid image file.', $new), 882 + $xaction); 883 + continue; 884 + } 885 + } 886 + break; 822 887 } 823 888 824 889 return $errors; ··· 939 1004 ->setViewer($this->getActor()) 940 1005 ->withPHIDs(array($column_phid)) 941 1006 ->executeOne(); 1007 + } 1008 + 1009 + protected function extractFilePHIDsFromCustomTransaction( 1010 + PhabricatorLiskDAO $object, 1011 + PhabricatorApplicationTransaction $xaction) { 1012 + $phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction); 1013 + 1014 + switch ($xaction->getTransactionType()) { 1015 + case ManiphestTransaction::TYPE_COVER_IMAGE: 1016 + $phids[] = $xaction->getNewValue(); 1017 + break; 1018 + } 1019 + 1020 + return $phids; 942 1021 } 943 1022 944 1023
+4
src/applications/maniphest/storage/ManiphestTask.php
··· 226 226 return idx($this->properties, $key, $default); 227 227 } 228 228 229 + public function getCoverImageFilePHID() { 230 + return idx($this->properties, 'cover.filePHID'); 231 + } 232 + 229 233 public function getCoverImageThumbnailPHID() { 230 234 return idx($this->properties, 'cover.thumbnailPHID'); 231 235 }
+4
src/applications/maniphest/storage/ManiphestTransaction.php
··· 16 16 const TYPE_UNBLOCK = 'unblock'; 17 17 const TYPE_PARENT = 'parent'; 18 18 const TYPE_COLUMN = 'column'; 19 + const TYPE_COVER_IMAGE = 'cover-image'; 19 20 20 21 // NOTE: this type is deprecated. Keep it around for legacy installs 21 22 // so any transactions render correctly. ··· 162 163 sort($new_cols); 163 164 164 165 return ($old_cols === $new_cols); 166 + case self::TYPE_COVER_IMAGE: 167 + // At least for now, don't show these. 168 + return true; 165 169 } 166 170 167 171 return parent::shouldHide();
+1
src/applications/project/application/PhabricatorProjectApplication.php
··· 72 72 '(?:query/(?P<queryKey>[^/]+)/)?' 73 73 => 'PhabricatorProjectBoardViewController', 74 74 'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController', 75 + 'cover/' => 'PhabricatorProjectCoverController', 75 76 'board/(?P<projectID>[1-9]\d*)/' => array( 76 77 'edit/(?:(?P<id>\d+)/)?' 77 78 => 'PhabricatorProjectColumnEditController',
+3
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 219 219 'projectPHID' => $project->getPHID(), 220 220 'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'), 221 221 'createURI' => $this->getCreateURI(), 222 + 'uploadURI' => '/file/dropupload/', 223 + 'coverURI' => $this->getApplicationURI('cover/'), 224 + 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), 222 225 'order' => $this->sortKey, 223 226 ); 224 227 $this->initBehavior(
+49
src/applications/project/controller/PhabricatorProjectController.php
··· 147 147 return $this; 148 148 } 149 149 150 + protected function newCardResponse($board_phid, $object_phid) { 151 + $viewer = $this->getViewer(); 152 + 153 + $project = id(new PhabricatorProjectQuery()) 154 + ->setViewer($viewer) 155 + ->withPHIDs(array($board_phid)) 156 + ->executeOne(); 157 + if (!$project) { 158 + return new Aphront404Response(); 159 + } 160 + 161 + // Reload the object so it reflects edits which have been applied. 162 + $object = id(new ManiphestTaskQuery()) 163 + ->setViewer($viewer) 164 + ->withPHIDs(array($object_phid)) 165 + ->needProjectPHIDs(true) 166 + ->executeOne(); 167 + if (!$object) { 168 + return new Aphront404Response(); 169 + } 170 + 171 + $except_phids = array($board_phid); 172 + if ($project->getHasSubprojects() || $project->getHasMilestones()) { 173 + $descendants = id(new PhabricatorProjectQuery()) 174 + ->setViewer($viewer) 175 + ->withAncestorProjectPHIDs($except_phids) 176 + ->execute(); 177 + foreach ($descendants as $descendant) { 178 + $except_phids[] = $descendant->getPHID(); 179 + } 180 + } 181 + 182 + $rendering_engine = id(new PhabricatorBoardRenderingEngine()) 183 + ->setViewer($viewer) 184 + ->setObjects(array($object)) 185 + ->setExcludedProjectPHIDs($except_phids); 186 + 187 + $card = $rendering_engine->renderCard($object->getPHID()); 188 + 189 + $item = $card->getItem(); 190 + $item->addClass('phui-workcard'); 191 + 192 + return id(new AphrontAjaxResponse()) 193 + ->setContent( 194 + array( 195 + 'task' => $item, 196 + )); 197 + } 198 + 150 199 }
+53
src/applications/project/controller/PhabricatorProjectCoverController.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectCoverController 4 + extends PhabricatorProjectController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $request->validateCSRF(); 10 + 11 + $board_phid = $request->getStr('boardPHID'); 12 + $object_phid = $request->getStr('objectPHID'); 13 + $file_phid = $request->getStr('filePHID'); 14 + 15 + $object = id(new ManiphestTaskQuery()) 16 + ->setViewer($viewer) 17 + ->withPHIDs(array($object_phid)) 18 + ->requireCapabilities( 19 + array( 20 + PhabricatorPolicyCapability::CAN_VIEW, 21 + PhabricatorPolicyCapability::CAN_EDIT, 22 + )) 23 + ->executeOne(); 24 + if (!$object) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + $file = id(new PhabricatorFileQuery()) 29 + ->setViewer($viewer) 30 + ->withPHIDs(array($file_phid)) 31 + ->executeOne(); 32 + if (!$file) { 33 + return new Aphront404Response(); 34 + } 35 + 36 + $xactions = array(); 37 + 38 + $xactions[] = id(new ManiphestTransaction()) 39 + ->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE) 40 + ->setNewValue($file->getPHID()); 41 + 42 + $editor = id(new ManiphestTransactionEditor()) 43 + ->setActor($viewer) 44 + ->setContinueOnMissingFields(true) 45 + ->setContinueOnNoEffect(true) 46 + ->setContentSourceFromRequest($request); 47 + 48 + $editor->applyTransactions($object, $xactions); 49 + 50 + return $this->newCardResponse($board_phid, $object_phid); 51 + } 52 + 53 + }
+3 -34
src/applications/project/controller/PhabricatorProjectMoveController.php
··· 7 7 $viewer = $request->getViewer(); 8 8 $id = $request->getURIData('id'); 9 9 10 + $request->validateCSRF(); 11 + 10 12 $column_phid = $request->getStr('columnPHID'); 11 13 $object_phid = $request->getStr('objectPHID'); 12 14 $after_phid = $request->getStr('afterPHID'); 13 15 $before_phid = $request->getStr('beforePHID'); 14 16 $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); 15 - 16 17 17 18 $project = id(new PhabricatorProjectQuery()) 18 19 ->setViewer($viewer) ··· 175 176 176 177 $editor->applyTransactions($object, $xactions); 177 178 178 - // Reload the object so it reflects edits which have been applied. 179 - $object = id(new ManiphestTaskQuery()) 180 - ->setViewer($viewer) 181 - ->withPHIDs(array($object_phid)) 182 - ->needProjectPHIDs(true) 183 - ->executeOne(); 184 - 185 - $except_phids = array($board_phid); 186 - if ($project->getHasSubprojects() || $project->getHasMilestones()) { 187 - $descendants = id(new PhabricatorProjectQuery()) 188 - ->setViewer($viewer) 189 - ->withAncestorProjectPHIDs($except_phids) 190 - ->execute(); 191 - foreach ($descendants as $descendant) { 192 - $except_phids[] = $descendant->getPHID(); 193 - } 194 - } 195 - 196 - $rendering_engine = id(new PhabricatorBoardRenderingEngine()) 197 - ->setViewer($viewer) 198 - ->setObjects(array($object)) 199 - ->setExcludedProjectPHIDs($except_phids); 200 - 201 - $card = $rendering_engine->renderCard($object->getPHID()); 202 - 203 - $item = $card->getItem(); 204 - $item->addClass('phui-workcard'); 205 - 206 - return id(new AphrontAjaxResponse()) 207 - ->setContent( 208 - array( 209 - 'task' => $item, 210 - )); 179 + return $this->newCardResponse($board_phid, $object_phid); 211 180 } 212 181 213 182 }
+4
webroot/rsrc/css/phui/workboards/phui-workcard.css
··· 106 106 width: 263px; 107 107 } 108 108 109 + .phui-workcard.phui-object-item.phui-workcard-upload-target { 110 + background-color: {$sh-greenbackground}; 111 + } 112 + 109 113 110 114 /* - Draggable Colors --------------------------------------------------------*/ 111 115
+34
webroot/rsrc/js/application/projects/behavior-project-boards.js
··· 7 7 * javelin-stratcom 8 8 * javelin-workflow 9 9 * phabricator-draggable-list 10 + * phabricator-drag-and-drop-file-upload 10 11 */ 11 12 12 13 JX.behavior('project-boards', function(config, statics) { ··· 348 349 init_board(); 349 350 } 350 351 }); 352 + 353 + if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { 354 + var drop = new JX.PhabricatorDragAndDropFileUpload('project-card') 355 + .setURI(config.uploadURI) 356 + .setChunkThreshold(config.chunkThreshold); 357 + 358 + drop.listen('didBeginDrag', function(node) { 359 + JX.DOM.alterClass(node, 'phui-workcard-upload-target', true); 360 + }); 361 + 362 + drop.listen('didEndDrag', function(node) { 363 + JX.DOM.alterClass(node, 'phui-workcard-upload-target', false); 364 + }); 365 + 366 + drop.listen('didUpload', function(file) { 367 + var node = file.getTargetNode(); 368 + 369 + var data = { 370 + boardPHID: statics.projectPHID, 371 + objectPHID: JX.Stratcom.getData(node).objectPHID, 372 + filePHID: file.getPHID() 373 + }; 374 + 375 + new JX.Workflow(config.coverURI, data) 376 + .setHandler(function(r) { 377 + JX.DOM.replace(node, JX.$H(r.task)); 378 + }) 379 + .start(); 380 + }); 381 + 382 + drop.start(); 383 + } 384 + 351 385 return true; 352 386 } 353 387
+82 -73
webroot/rsrc/js/core/DragAndDropFileUpload.js
··· 11 11 12 12 JX.install('PhabricatorDragAndDropFileUpload', { 13 13 14 - construct : function(node) { 15 - this._node = node; 14 + construct : function(target) { 15 + if (JX.DOM.isNode(target)) { 16 + this._node = target; 17 + } else { 18 + this._sigil = target; 19 + } 16 20 }, 17 21 18 22 events : [ ··· 39 43 40 44 members : { 41 45 _node : null, 46 + _sigil: null, 42 47 _depth : 0, 43 48 _isEnabled: false, 44 49 ··· 53 58 54 59 _updateDepth : function(delta) { 55 60 if (this._depth === 0 && delta > 0) { 56 - this.invoke('didBeginDrag'); 61 + this.invoke('didBeginDrag', this._getTarget()); 57 62 } 58 63 59 64 this._depth += delta; 60 65 61 66 if (this._depth === 0 && delta < 0) { 62 - this.invoke('didEndDrag'); 67 + this.invoke('didEndDrag', this._getTarget()); 63 68 } 64 69 }, 65 70 66 - start : function() { 71 + _getTarget: function() { 72 + return this._target || this._node; 73 + }, 67 74 75 + start : function() { 68 76 69 77 // TODO: move this to JX.DOM.contains()? 70 78 function contains(container, child) { ··· 80 88 81 89 // Firefox has some issues sometimes; implement this click handler so 82 90 // the user can recover. See T5188. 83 - JX.DOM.listen( 84 - this._node, 85 - 'click', 86 - null, 87 - JX.bind(this, function (e) { 88 - if (!this.getIsEnabled()) { 89 - return; 90 - } 91 - if (this._depth) { 92 - e.kill(); 93 - // Force depth to 0. 94 - this._updateDepth(-this._depth); 95 - } 96 - })); 91 + var on_click = JX.bind(this, function (e) { 92 + if (!this.getIsEnabled()) { 93 + return; 94 + } 95 + 96 + if (this._depth) { 97 + e.kill(); 98 + // Force depth to 0. 99 + this._updateDepth(-this._depth); 100 + } 101 + }); 97 102 98 103 // We track depth so that the _node may have children inside of it and 99 104 // not become unselected when they are dragged over. 100 - JX.DOM.listen( 101 - this._node, 102 - 'dragenter', 103 - null, 104 - JX.bind(this, function(e) { 105 - if (!this.getIsEnabled()) { 106 - return; 107 - } 105 + var on_dragenter = JX.bind(this, function(e) { 106 + if (!this.getIsEnabled()) { 107 + return; 108 + } 108 109 109 - if (contains(this._node, e.getTarget())) { 110 - this._updateDepth(1); 111 - } 112 - })); 110 + if (!this._node && !this._depth) { 111 + this._target = e.getNode(this._sigil); 112 + } 113 113 114 - JX.DOM.listen( 115 - this._node, 116 - 'dragleave', 117 - null, 118 - JX.bind(this, function(e) { 119 - if (!this.getIsEnabled()) { 120 - return; 121 - } 114 + if (contains(this._getTarget(), e.getTarget())) { 115 + this._updateDepth(1); 116 + } 117 + }); 122 118 123 - if (contains(this._node, e.getTarget())) { 124 - this._updateDepth(-1); 125 - } 126 - })); 119 + var on_dragleave = JX.bind(this, function(e) { 120 + if (!this.getIsEnabled()) { 121 + return; 122 + } 123 + 124 + if (contains(this._getTarget(), e.getTarget())) { 125 + this._updateDepth(-1); 126 + } 127 + }); 127 128 128 - JX.DOM.listen( 129 - this._node, 130 - 'dragover', 131 - null, 132 - JX.bind(this, function(e) { 133 - if (!this.getIsEnabled()) { 134 - return; 135 - } 129 + var on_dragover = JX.bind(this, function(e) { 130 + if (!this.getIsEnabled()) { 131 + return; 132 + } 133 + 134 + // NOTE: We must set this, or Chrome refuses to drop files from the 135 + // download shelf. 136 + e.getRawEvent().dataTransfer.dropEffect = 'copy'; 137 + e.kill(); 138 + }); 136 139 137 - // NOTE: We must set this, or Chrome refuses to drop files from the 138 - // download shelf. 139 - e.getRawEvent().dataTransfer.dropEffect = 'copy'; 140 - e.kill(); 141 - })); 140 + var on_drop = JX.bind(this, function(e) { 141 + if (!this.getIsEnabled()) { 142 + return; 143 + } 142 144 143 - JX.DOM.listen( 144 - this._node, 145 - 'drop', 146 - null, 147 - JX.bind(this, function(e) { 148 - if (!this.getIsEnabled()) { 149 - return; 150 - } 145 + e.kill(); 151 146 152 - e.kill(); 147 + var files = e.getRawEvent().dataTransfer.files; 148 + for (var ii = 0; ii < files.length; ii++) { 149 + this._sendRequest(files[ii]); 150 + } 153 151 154 - var files = e.getRawEvent().dataTransfer.files; 155 - for (var ii = 0; ii < files.length; ii++) { 156 - this._sendRequest(files[ii]); 157 - } 152 + // Force depth to 0. 153 + this._updateDepth(-this._depth); 154 + }); 158 155 159 - // Force depth to 0. 160 - this._updateDepth(-this._depth); 161 - })); 156 + if (this._node) { 157 + JX.DOM.listen(this._node, 'click', null, on_click); 158 + JX.DOM.listen(this._node, 'dragenter', null, on_dragenter); 159 + JX.DOM.listen(this._node, 'dragleave', null, on_dragleave); 160 + JX.DOM.listen(this._node, 'dragover', null, on_dragover); 161 + JX.DOM.listen(this._node, 'drop', null, on_drop); 162 + } else { 163 + JX.Stratcom.listen('click', this._sigil, on_click); 164 + JX.Stratcom.listen('dragenter', this._sigil, on_dragenter); 165 + JX.Stratcom.listen('dragleave', this._sigil, on_dragleave); 166 + JX.Stratcom.listen('dragover', this._sigil, on_dragover); 167 + JX.Stratcom.listen('drop', this._sigil, on_drop); 168 + } 162 169 163 - if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported()) { 170 + if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported() && 171 + this._node) { 164 172 JX.DOM.listen( 165 173 this._node, 166 174 'paste', ··· 399 407 .setURI(r.uri) 400 408 .setMarkup(r.html) 401 409 .setStatus('done') 410 + .setTargetNode(this._getTarget()) 402 411 .update(); 403 412 404 413 this.invoke('didUpload', file);
+1
webroot/rsrc/js/core/FileUpload.js
··· 23 23 URI: null, 24 24 status: null, 25 25 markup: null, 26 + targetNode: null, 26 27 error: null 27 28 }, 28 29