From d2baa88171fcc3f327b157277afb905430b5a918 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 27 Apr 2017 15:07:32 -0700 Subject: [PATCH 001/543] Allow Owners packages to have arbitrarily long names Summary: - Change column type from `sort128` to `sort`. - Remove `originalName`. This column is unused. Long ago, we used it to generate a `Thread-Topic` header for mail, but just use PHIDs now (the value just needs to be stable for a given object, users normally don't see it). Test Plan: - Created a package with a beautifully long name. Magnificent! - Grepped for `originalName` / `getOriginalName()`, found no Owners hits. - Verified that there isn't any name-length validation code to remove. {F4925637} Reviewers: chad, amckinley Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17798 --- resources/sql/autopatches/20170427.owners.01.long.sql | 2 ++ .../owners/storage/PhabricatorOwnersPackage.php | 7 +------ 2 files changed, 3 insertions(+), 6 deletions(-) create mode 100644 resources/sql/autopatches/20170427.owners.01.long.sql diff --git a/resources/sql/autopatches/20170427.owners.01.long.sql b/resources/sql/autopatches/20170427.owners.01.long.sql new file mode 100644 index 0000000000..01a463f52a --- /dev/null +++ b/resources/sql/autopatches/20170427.owners.01.long.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + DROP originalName; diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 1b12365e4f..2b22a7a145 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -12,7 +12,6 @@ final class PhabricatorOwnersPackage PhabricatorNgramsInterface { protected $name; - protected $originalName; protected $auditingEnabled; protected $autoReview; protected $description; @@ -105,8 +104,7 @@ final class PhabricatorOwnersPackage self::CONFIG_TIMESTAMPS => false, self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'sort128', - 'originalName' => 'text255', + 'name' => 'sort', 'description' => 'text', 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', @@ -137,9 +135,6 @@ final class PhabricatorOwnersPackage public function setName($name) { $this->name = $name; - if (!$this->getID()) { - $this->originalName = $name; - } return $this; } From d6bce34a5db1a838a988440f09f7728747c9e067 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 26 Apr 2017 10:48:50 -0700 Subject: [PATCH 002/543] Update Conpherence to use EditEngine Summary: Moves Conpherence to use EditEngine. This removes the "First Message" field, but I think that's ok until we have direct messaging of some sort, then maybe have built-ins cover that case. Test Plan: - Visit /new/ and /edit/ for creating new rooms. - Edit a room in full conpherence - Edit a room in durable column - grep for METADATA calls Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11729 Differential Revision: https://secure.phabricator.com/D16677 --- resources/celerity/map.php | 26 ++-- src/__phutil_library_map__.php | 6 +- .../PhabricatorConpherenceApplication.php | 6 +- .../constants/ConpherenceUpdateActions.php | 1 - .../controller/ConpherenceController.php | 3 +- .../ConpherenceNewRoomController.php | 117 ----------------- .../ConpherenceRoomEditController.php | 11 ++ .../ConpherenceUpdateController.php | 102 +-------------- .../editor/ConpherenceEditEngine.php | 118 ++++++++++++++++++ .../view/ConpherenceDurableColumnView.php | 4 +- .../view/ConpherenceThreadListView.php | 2 +- .../conpherence/behavior-durable-column.js | 2 +- 12 files changed, 157 insertions(+), 241 deletions(-) delete mode 100644 src/applications/conpherence/controller/ConpherenceNewRoomController.php create mode 100644 src/applications/conpherence/controller/ConpherenceRoomEditController.php create mode 100644 src/applications/conpherence/editor/ConpherenceEditEngine.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8c426f8b7f..3e09edfc4e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '84ce260a', - 'core.pkg.js' => 'fffe0122', + 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -378,7 +378,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => '2ae077e1', 'rsrc/js/application/conpherence/behavior-menu.js' => 'c9b99b77', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', @@ -639,7 +639,7 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'aa3bd034', + 'javelin-behavior-durable-column' => '2ae077e1', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', @@ -1095,6 +1095,16 @@ return array( 'javelin-install', 'javelin-util', ), + '2ae077e1' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1793,16 +1803,6 @@ return array( 'javelin-util', 'phabricator-prefab', ), - 'aa3bd034' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'ab2f381b' => array( 'javelin-request', 'javelin-behavior', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4d6ef65368..ca24aedf75 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -292,13 +292,13 @@ phutil_register_library_map(array( 'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php', 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', + 'ConpherenceEditEngine' => 'applications/conpherence/editor/ConpherenceEditEngine.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php', 'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php', 'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php', 'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php', - 'ConpherenceNewRoomController' => 'applications/conpherence/controller/ConpherenceNewRoomController.php', 'ConpherenceNotificationPanelController' => 'applications/conpherence/controller/ConpherenceNotificationPanelController.php', 'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php', 'ConpherenceParticipantController' => 'applications/conpherence/controller/ConpherenceParticipantController.php', @@ -308,6 +308,7 @@ phutil_register_library_map(array( 'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php', 'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', + 'ConpherenceRoomEditController' => 'applications/conpherence/controller/ConpherenceRoomEditController.php', 'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php', 'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php', 'ConpherenceRoomPreferencesController' => 'applications/conpherence/controller/ConpherenceRoomPreferencesController.php', @@ -5074,13 +5075,13 @@ phutil_register_library_map(array( 'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', + 'ConpherenceEditEngine' => 'PhabricatorEditEngine', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceIndex' => 'ConpherenceDAO', 'ConpherenceLayoutView' => 'AphrontTagView', 'ConpherenceListController' => 'ConpherenceController', 'ConpherenceMenuItemView' => 'AphrontTagView', - 'ConpherenceNewRoomController' => 'ConpherenceController', 'ConpherenceNotificationPanelController' => 'ConpherenceController', 'ConpherenceParticipant' => 'ConpherenceDAO', 'ConpherenceParticipantController' => 'ConpherenceController', @@ -5090,6 +5091,7 @@ phutil_register_library_map(array( 'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', + 'ConpherenceRoomEditController' => 'ConpherenceController', 'ConpherenceRoomListController' => 'ConpherenceController', 'ConpherenceRoomPictureController' => 'ConpherenceController', 'ConpherenceRoomPreferencesController' => 'ConpherenceController', diff --git a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php index e969b48447..a0c21bd33a 100644 --- a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php +++ b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php @@ -45,8 +45,10 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication { => 'ConpherenceViewController', 'columnview/' => 'ConpherenceColumnViewController', - 'new/' - => 'ConpherenceNewRoomController', + $this->getEditRoutePattern('new/') + => 'ConpherenceRoomEditController', + $this->getEditRoutePattern('edit/') + => 'ConpherenceRoomEditController', 'picture/(?P[1-9]\d*)/' => 'ConpherenceRoomPictureController', 'search/(?:query/(?P[^/]+)/)?' diff --git a/src/applications/conpherence/constants/ConpherenceUpdateActions.php b/src/applications/conpherence/constants/ConpherenceUpdateActions.php index a2b97f9c6a..afec800de3 100644 --- a/src/applications/conpherence/constants/ConpherenceUpdateActions.php +++ b/src/applications/conpherence/constants/ConpherenceUpdateActions.php @@ -2,7 +2,6 @@ final class ConpherenceUpdateActions extends ConpherenceConstants { - const METADATA = 'metadata'; const MESSAGE = 'message'; const DRAFT = 'draft'; const JOIN_ROOM = 'join_room'; diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index f7fd7ef1b4..90328cca04 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -91,7 +91,8 @@ abstract class ConpherenceController extends PhabricatorController { $header->addActionItem( id(new PHUIIconCircleView()) - ->setHref($this->getApplicationURI("update/{$id}/")) + ->setHref( + $this->getApplicationURI('edit/'.$conpherence->getID()).'/') ->setIcon('fa-pencil') ->addClass('hide-on-device') ->setColor('violet') diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php deleted file mode 100644 index 6cbe86aaa2..0000000000 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ /dev/null @@ -1,117 +0,0 @@ -getUser(); - - $title = pht('New Room'); - $e_title = true; - $validation_exception = null; - - $conpherence = ConpherenceThread::initializeNewRoom($user); - $participants = array(); - if ($request->isFormPost()) { - $editor = new ConpherenceEditor(); - $xactions = array(); - - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) - ->setNewValue($request->getStr('title')); - - $participants = $request->getArr('participants'); - $participants[] = $user->getPHID(); - $participants = array_unique($participants); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) - ->setNewValue(array('+' => $participants)); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) - ->setNewValue($request->getStr('topic')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - - try { - $editor - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setActor($user) - ->applyTransactions($conpherence, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/'.$conpherence->getMonogram()); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_title = $ex->getShortMessage( - ConpherenceThreadTitleTransaction::TRANSACTIONTYPE); - - $conpherence->setViewPolicy($request->getStr('viewPolicy')); - $conpherence->setEditPolicy($request->getStr('editPolicy')); - } - } else { - if ($request->getStr('participant')) { - $participants[] = $request->getStr('participant'); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - - $submit_uri = $this->getApplicationURI('new/'); - $cancel_uri = $this->getApplicationURI('search/'); - - $dialog = $this->newDialog() - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setValidationException($validation_exception) - ->setUser($user) - ->setTitle($title) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Create Room')); - - $form = id(new PHUIFormLayoutView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormTextControl()) - ->setError($e_title) - ->setLabel(pht('Name')) - ->setName('title') - ->setValue($request->getStr('title'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Topic')) - ->setName('topic') - ->setValue($request->getStr('topic'))) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setName('participants') - ->setUser($user) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setValue($participants) - ->setLabel(pht('Other Participants'))) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - - $dialog->appendChild($form); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/conpherence/controller/ConpherenceRoomEditController.php b/src/applications/conpherence/controller/ConpherenceRoomEditController.php new file mode 100644 index 0000000000..56f808056c --- /dev/null +++ b/src/applications/conpherence/controller/ConpherenceRoomEditController.php @@ -0,0 +1,11 @@ +setController($this) + ->buildResponse(); + } +} diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 728d917589..a814c64452 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -12,7 +12,7 @@ final class ConpherenceUpdateController $need_participants = false; $needed_capabilities = array(PhabricatorPolicyCapability::CAN_VIEW); - $action = $request->getStr('action', ConpherenceUpdateActions::METADATA); + $action = $request->getStr('action'); switch ($action) { case ConpherenceUpdateActions::REMOVE_PERSON: $person_phid = $request->getStr('remove_person'); @@ -21,7 +21,6 @@ final class ConpherenceUpdateController } break; case ConpherenceUpdateActions::ADD_PERSON: - case ConpherenceUpdateActions::METADATA: $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; break; case ConpherenceUpdateActions::LOAD: @@ -110,35 +109,6 @@ final class ConpherenceUpdateController $response_mode = 'go-home'; } break; - case ConpherenceUpdateActions::METADATA: - $title = $request->getStr('title'); - $topic = $request->getStr('topic'); - - // all other metadata updates are continue requests - if (!$request->isContinueRequest()) { - break; - } - - $title = $request->getStr('title'); - $topic = $request->getStr('topic'); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) - ->setNewValue($title); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) - ->setNewValue($topic); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - if (!$request->getExists('force_ajax')) { - $response_mode = 'redirect'; - } - break; case ConpherenceUpdateActions::LOAD: $updated = false; $response_mode = 'ajax'; @@ -208,10 +178,6 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::REMOVE_PERSON: $dialog = $this->renderRemovePersonDialog($conpherence); break; - case ConpherenceUpdateActions::METADATA: - default: - $dialog = $this->renderMetadataDialog($conpherence, $error_view); - break; } return @@ -332,62 +298,6 @@ final class ConpherenceUpdateController return $dialog; } - private function renderMetadataDialog( - ConpherenceThread $conpherence, - $error_view) { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $title = pht('Update Room'); - $form = id(new PHUIFormLayoutView()) - ->appendChild($error_view) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Title')) - ->setName('title') - ->setValue($conpherence->getTitle())) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Topic')) - ->setName('topic') - ->setValue($conpherence->getTopic())); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - - $view = id(new AphrontDialogView()) - ->setTitle($title) - ->addHiddenInput('action', 'metadata') - ->addHiddenInput( - 'latest_transaction_id', - $request->getInt('latest_transaction_id')) - ->addHiddenInput('__continue__', true) - ->appendChild($form); - - if ($request->getExists('force_ajax')) { - $view->addHiddenInput('force_ajax', true); - } - - return $view; - } - private function loadAndRenderUpdates( $action, $conpherence_id, @@ -395,7 +305,6 @@ final class ConpherenceUpdateController $need_transactions = false; switch ($action) { - case ConpherenceUpdateActions::METADATA: case ConpherenceUpdateActions::LOAD: $need_transactions = true; break; @@ -444,15 +353,6 @@ final class ConpherenceUpdateController $header = null; $people_widget = null; switch ($action) { - case ConpherenceUpdateActions::METADATA: - $header = $this->buildHeaderPaneContent($conpherence); - $header = hsprintf('%s', $header); - $nav_item = id(new ConpherenceThreadListView()) - ->setUser($user) - ->setBaseURI($this->getApplicationURI()) - ->renderThreadItem($conpherence); - $nav_item = hsprintf('%s', $nav_item); - break; case ConpherenceUpdateActions::ADD_PERSON: $people_widget = id(new ConpherenceParticipantView()) ->setUser($user) diff --git a/src/applications/conpherence/editor/ConpherenceEditEngine.php b/src/applications/conpherence/editor/ConpherenceEditEngine.php new file mode 100644 index 0000000000..91cd8fd081 --- /dev/null +++ b/src/applications/conpherence/editor/ConpherenceEditEngine.php @@ -0,0 +1,118 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new ConpherenceThreadQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Room'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Room: %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('Create Room'); + } + + protected function getObjectName() { + return pht('Room'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + public function isEngineConfigurable() { + return false; + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + $participant_phids = array($viewer->getPHID()); + $initial_phids = array(); + } else { + $participant_phids = $object->getParticipantPHIDs(); + $initial_phids = $participant_phids; + } + + // Only show participants on create or conduit, not edit + $conduit_only = !$this->getIsCreate(); + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Room name.')) + ->setConduitTypeDescription(pht('New Room name.')) + ->setIsRequired(true) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) + ->setValue($object->getTitle()), + + id(new PhabricatorTextEditField()) + ->setKey('topic') + ->setLabel(pht('Topic')) + ->setDescription(pht('Room topic.')) + ->setConduitTypeDescription(pht('New Room topic.')) + ->setTransactionType( + ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) + ->setValue($object->getTopic()), + + id(new PhabricatorUsersEditField()) + ->setKey('participants') + ->setValue($participant_phids) + ->setInitialValue($initial_phids) + ->setIsConduitOnly($conduit_only) + ->setAliases(array('users', 'members', 'participants', 'userPHID')) + ->setDescription(pht('Room participants.')) + ->setUseEdgeTransactions(true) + ->setConduitTypeDescription(pht('New Room participants.')) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) + ->setLabel(pht('Initial Participants')), + + ); + } + +} diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index e3240bc2e4..5d6a5eaaec 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -363,9 +363,9 @@ final class ConpherenceDurableColumnView extends AphrontTagView { $actions[] = array( 'name' => pht('Edit Room'), 'disabled' => !$can_edit, - 'href' => '/conpherence/update/'.$conpherence->getID().'/?nopic', + 'href' => '/conpherence/edit/'.$conpherence->getID().'/', 'icon' => 'fa-pencil', - 'key' => ConpherenceUpdateActions::METADATA, + 'key' => 'go_edit', ); $actions[] = array( 'name' => pht('View in Conpherence'), diff --git a/src/applications/conpherence/view/ConpherenceThreadListView.php b/src/applications/conpherence/view/ConpherenceThreadListView.php index af15f82a6e..42f1b605c1 100644 --- a/src/applications/conpherence/view/ConpherenceThreadListView.php +++ b/src/applications/conpherence/view/ConpherenceThreadListView.php @@ -117,7 +117,7 @@ final class ConpherenceThreadListView extends AphrontView { $new_icon = id(new PHUIIconView()) ->setIcon('fa-plus-square') ->addSigil('has-tooltip') - ->setHref('/conpherence/new/') + ->setHref('/conpherence/edit/') ->setWorkflow(true) ->setMetaData(array( 'tip' => pht('New Room'), diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index c6c82bc3be..a521046d8e 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -177,7 +177,7 @@ JX.behavior('durable-column', function(config, statics) { var params = null; switch (action) { - case 'metadata': + case 'go_edit': threadManager.runUpdateWorkflowFromLink( link, { From 608dd061510defdd5f2ab065e3034287d304acbd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 29 Apr 2017 19:53:14 -0700 Subject: [PATCH 003/543] Add a lipsum generator for Conpherence Rooms Summary: Builds a basic room generator for Conpherence, picks a random name, adds 10 random users to it, sets view and edit policy to all users. Test Plan: `bin/lipsum generate conpherence` {F4928815} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17699 --- src/__phutil_library_map__.php | 6 ++ .../ConpherenceEditConduitAPIMethod.php | 19 ++++ ...catorConpherenceRoomContextFreeGrammar.php | 97 +++++++++++++++++++ ...icatorConpherenceRoomTestDataGenerator.php | 76 +++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php create mode 100644 src/applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php create mode 100644 src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ca24aedf75..33618ca547 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -292,6 +292,7 @@ phutil_register_library_map(array( 'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php', 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', + 'ConpherenceEditConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php', 'ConpherenceEditEngine' => 'applications/conpherence/editor/ConpherenceEditEngine.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', @@ -2390,6 +2391,8 @@ phutil_register_library_map(array( 'PhabricatorConpherenceNotificationsSetting' => 'applications/settings/setting/PhabricatorConpherenceNotificationsSetting.php', 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', 'PhabricatorConpherenceProfileMenuItem' => 'applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php', + 'PhabricatorConpherenceRoomContextFreeGrammar' => 'applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php', + 'PhabricatorConpherenceRoomTestDataGenerator' => 'applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php', 'PhabricatorConpherenceSoundSetting' => 'applications/settings/setting/PhabricatorConpherenceSoundSetting.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php', @@ -5075,6 +5078,7 @@ phutil_register_library_map(array( 'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', + 'ConpherenceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ConpherenceEditEngine' => 'PhabricatorEditEngine', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', @@ -7489,6 +7493,8 @@ phutil_register_library_map(array( 'PhabricatorConpherenceNotificationsSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', 'PhabricatorConpherenceProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorConpherenceRoomContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhabricatorConpherenceRoomTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorConpherenceSoundSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', diff --git a/src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php new file mode 100644 index 0000000000..9fc799c194 --- /dev/null +++ b/src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php @@ -0,0 +1,19 @@ + array( + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept] ([city])', + '[dept] ([city])', + '[dept] - [city]', + '[dept] - [room]', + '[dept] / [room]', + '[dept] [room]', + '[city] ([dept]) - [room]', + '[dept] ([city]) - [room]', + '[dept] ([city]) [room]', + ), + 'dept' => array( + 'Eng', + 'Engineering', + 'User Interface', + 'Design', + 'Data Science', + 'Database', + 'Marketing', + 'Content', + 'Ads', + 'Operations', + 'Network Ops', + 'Ops', + 'Server Ops', + 'IT', + 'Information Technology', + 'i18n', + 'Internationalization', + 'Human Resources', + 'HR', + 'Research & Development', + 'R&D', + 'Management', + 'Directors', + 'Managers', + 'Support', + 'Customer Support', + 'Finance', + 'Sales', + 'Purchasing', + 'Education', + 'Hardware Engineering', + 'Software', + 'Supply Management', + 'Logistics', + 'Growth', + 'Content Strategy', + 'Developer Relations', + 'Accounting', + 'Production', + ), + 'city' => array( + 'Palo Alto', + 'Mtn View', + 'Cupertino', + 'Los Altos', + 'Menlo Park', + 'Santa Cruz', + 'S.F.', + 'San Francisco', + 'Seattle', + 'London', + 'New York', + 'Dublin', + 'Tokyo', + ), + 'room' => array( + 'General', + 'Announcements', + 'Staff', + 'Interns', + 'Managers', + 'Book Club', + 'Parking', + 'Sports', + 'Social', + 'Commuting', + 'For Sale', + 'Parents@', + ), + ); + } +} diff --git a/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php b/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php new file mode 100644 index 0000000000..8346df3313 --- /dev/null +++ b/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php @@ -0,0 +1,76 @@ +loadRandomUser(); + + $name = $this->newRoomName(); + + $participants = array(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + + $rando_phids = array(); + $rando_phids[] = $author->getPHID(); + foreach ($participants as $actor) { + $rando_phids[] = $actor->getPHID(); + } + + $xactions = array(); + + $xactions[] = array( + 'type' => 'name', + 'value' => $name, + ); + + $xactions[] = array( + 'type' => 'participants.set', + 'value' => $rando_phids, + ); + + $xactions[] = array( + 'type' => 'view', + 'value' => 'users', + ); + + $xactions[] = array( + 'type' => 'edit', + 'value' => 'users', + ); + + $params = array( + 'transactions' => $xactions, + ); + + $result = id(new ConduitCall('conpherence.edit', $params)) + ->setUser($author) + ->execute(); + + return $result['object']['phid']; + } + + protected function newRoomName() { + $generator = new PhabricatorConpherenceRoomContextFreeGrammar(); + $name = $generator->generate(); + return $name; + } + + + +} From 89d0c8e388fbe0b27b6b73d9a5f201a883239d1c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 30 Apr 2017 12:57:24 -0700 Subject: [PATCH 004/543] Use grey dots for disabled users, even if a user is also unverified Summary: Fixes T12559. Test Plan: {F4929988} {F4929989} {F4929990} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12559 Differential Revision: https://secure.phabricator.com/D17806 --- .../people/markup/PhabricatorMentionRemarkupRule.php | 4 +++- .../people/phid/PhabricatorPeopleUserPHIDType.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index 1f82d78423..aa5b7f908e 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -150,7 +150,9 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { $tag->addClass('phabricator-remarkup-mention-nopermission'); } - if (!$user->isResponsive()) { + if ($user->getIsDisabled()) { + $tag->setDotColor(PHUITagView::COLOR_GREY); + } else if (!$user->isResponsive()) { $tag->setDotColor(PHUITagView::COLOR_VIOLET); } else { if ($user->getAwayUntil()) { diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php index b2a456cd51..f0512e91f1 100644 --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -61,7 +61,9 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType { } $availability = null; - if (!$user->isResponsive()) { + if ($user->getIsDisabled()) { + $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED; + } else if (!$user->isResponsive()) { $availability = PhabricatorObjectHandle::AVAILABILITY_NOEMAIL; } else { $until = $user->getAwayUntil(); From b7c4f60e235d233d74a6341debf5172b5f4370d0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 30 Apr 2017 12:44:59 -0700 Subject: [PATCH 005/543] Only recognize "Fixes ..." in main revision content like the Summary or Test Plan Summary: Fixes T12642. Currently, writing "Fixes T..." in a comment gets picked up as a formal "fixes". This is a bit confusing, and can also give you a "no effect" error if you "fixes ..." a task which is already "fixes"'d. We could make the duplicate action a non-error, but just prevent the text from having an effect instead, which seems cleaner. Test Plan: - Wrote "Fixes ..." in a summary, saw a "fixes" relationship established. - Wrote "Fixes ..." in a comment, got a "mention" instead. - `var_dump()`'d some stuff as a sanity check, looked reasonable. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12642 Differential Revision: https://secure.phabricator.com/D17805 --- .../editor/DifferentialTransactionEditor.php | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 0086c101e2..9204a1aadb 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1191,12 +1191,23 @@ final class DifferentialTransactionEditor array $changes, PhutilMarkupEngine $engine) { - $flat_blocks = mpull($changes, 'getNewValue'); - $huge_block = implode("\n\n", $flat_blocks); - + // For "Fixes ..." and "Depends on ...", we're only going to look at + // content blocks which are part of the revision itself (like "Summary" + // and "Test Plan"), not comments. + $content_parts = array(); + foreach ($changes as $change) { + if ($change->getTransaction()->isCommentTransaction()) { + continue; + } + $content_parts[] = $change->getNewValue(); + } + if (!$content_parts) { + return array(); + } + $content_block = implode("\n\n", $content_parts); $task_map = array(); $task_refs = id(new ManiphestCustomFieldStatusParser()) - ->parseCorpus($huge_block); + ->parseCorpus($content_block); foreach ($task_refs as $match) { foreach ($match['monograms'] as $monogram) { $task_id = (int)trim($monogram, 'tT'); @@ -1206,7 +1217,7 @@ final class DifferentialTransactionEditor $rev_map = array(); $rev_refs = id(new DifferentialCustomFieldDependsOnParser()) - ->parseCorpus($huge_block); + ->parseCorpus($content_block); foreach ($rev_refs as $match) { foreach ($match['monograms'] as $monogram) { $rev_id = (int)trim($monogram, 'dD'); From f2ca348b3a7a91a1f8bf8a061b592f9d60ec7dc9 Mon Sep 17 00:00:00 2001 From: Josh Cox Date: Wed, 5 Apr 2017 06:08:14 -0400 Subject: [PATCH 006/543] Set project's ObjectName to its PHID when it doesn't have a hashtag Summary: Fixes T12659. Previously this would lead to an error when trying to run `arc diff` on a revision that had a milestone as a reviewer (or any non-octothorpe'd Object Name) Test Plan: Followed repro steps in T12659 and didn't get the error described. Also clicked around and didn't notice any obvious regressions in projects or differential Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, yelirekim Maniphest Tasks: T12659 Differential Revision: https://secure.phabricator.com/D17807 --- .../project/phid/PhabricatorProjectProjectPHIDType.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php index 7a04103a7c..3aa6088780 100644 --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -47,6 +47,10 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType { $handle->setObjectName('#'.$slug); $handle->setURI("/tag/{$slug}/"); } else { + // We set the name to the project's PHID to avoid a parse error when a + // project has no hashtag (as is the case with milestones by default). + // See T12659 for more details + $handle->setCommandLineObjectName($project->getPHID()); $handle->setURI("/project/view/{$id}/"); } From da6d624fe8782ceabea1098749d96af14218cb7c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 1 May 2017 09:17:34 -0700 Subject: [PATCH 007/543] Mark ConpherenceCreate and Update conduit calls as frozen Summary: T12656, mark these methods as frozen and use conpherence.edit instead. Test Plan: Visit conduit, check status is displayed. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17808 --- .../ConpherenceCreateThreadConduitAPIMethod.php | 10 ++++++++++ .../ConpherenceUpdateThreadConduitAPIMethod.php | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php index 9cae87e10a..e751318c16 100644 --- a/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php @@ -11,6 +11,16 @@ final class ConpherenceCreateThreadConduitAPIMethod return pht('Create a new conpherence thread.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "conpherence.edit" instead.'); + } + protected function defineParamTypes() { return array( 'title' => 'required string', diff --git a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php index e7d927905d..71000000c1 100644 --- a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php @@ -11,6 +11,16 @@ final class ConpherenceUpdateThreadConduitAPIMethod return pht('Update an existing conpherence room.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "conpherence.edit" instead.'); + } + protected function defineParamTypes() { return array( 'id' => 'optional int', From 0ebe56be40863a4c6d68a212553cd82d960eeae0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 1 May 2017 10:37:12 -0700 Subject: [PATCH 008/543] Fix two Calendar availability cache issues Summary: Fixes T12661. When changing the start date of an event from some time in the past to some time significantly in the future (more than 24 hours), we'd invalidate only future caches and leave users in an "away" state. Instead, just invalidate all past and future caches (this is simpler than trying to figure out a narrower window, and should not make us do too much extra work). When uninviting users from events, their caches also didn't get cleared correctly. Instead, clear them. Test Plan: - Changed an event from "Apr 1 - June 1" to "May 15 - June 1", saw availablity clear properly. - Uninvited user `@dog` from an ongoing event, saw availability clear properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12661 Differential Revision: https://secure.phabricator.com/D17809 --- .../editor/PhabricatorCalendarEventEditor.php | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index eee6b46751..4ab13fd360 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -127,6 +127,9 @@ final class PhabricatorCalendarEventEditor // Clear the availability caches for users whose availability is affected // by this edit. + $phids = mpull($object->getInvitees(), 'getInviteePHID'); + $phids = array_fuse($phids); + $invalidate_all = false; $invalidate_phids = array(); foreach ($xactions as $xaction) { @@ -146,16 +149,21 @@ final class PhabricatorCalendarEventEditor $invalidate_phids[$acting_phid] = $acting_phid; break; case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE: - foreach ($xaction->getNewValue() as $phid => $ignored) { + foreach ($xaction->getOldValue() as $phid) { + // Add the possibly un-invited user to the list of potentially + // affected users if they are't already present. + $phids[$phid] = $phid; + + $invalidate_phids[$phid] = $phid; + } + + foreach ($xaction->getNewValue() as $phid) { $invalidate_phids[$phid] = $phid; } break; } } - $phids = mpull($object->getInvitees(), 'getInviteePHID'); - $phids = array_fuse($phids); - if (!$invalidate_all) { $phids = array_select_keys($phids, $invalidate_phids); } @@ -168,10 +176,9 @@ final class PhabricatorCalendarEventEditor queryfx( $conn_w, 'UPDATE %T SET availabilityCacheTTL = NULL - WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d', + WHERE phid IN (%Ls)', $user->getTableName(), - $phids, - $object->getStartDateTimeEpochForCache()); + $phids); } return $xactions; From dff028c4907dd1959859733ea0d947f244559e7f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 1 May 2017 11:16:38 -0700 Subject: [PATCH 009/543] Update PonderQuestion for Modular Transactions Summary: Ref T12624, this moves PonderQuestion over to modular transactions. Test Plan: - Create a question - Edit a question - Comment on question - Answer question - Edit Answer - Close Question - Verify answer count in list views - Check Question History - Check Answer History Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12624 Differential Revision: https://secure.phabricator.com/D17810 --- src/__phutil_library_map__.php | 14 +- .../controller/PonderAnswerSaveController.php | 2 +- .../PonderQuestionEditController.php | 11 +- .../PonderQuestionStatusController.php | 2 +- .../ponder/editor/PonderQuestionEditor.php | 120 +------- .../storage/PonderQuestionTransaction.php | 279 +----------------- .../PonderQuestionAnswerTransaction.php | 28 ++ .../PonderQuestionAnswerWikiTransaction.php | 56 ++++ .../PonderQuestionContentTransaction.php | 56 ++++ .../PonderQuestionStatusTransaction.php | 74 +++++ .../PonderQuestionTitleTransaction.php | 55 ++++ .../xaction/PonderQuestionTransactionType.php | 4 + 12 files changed, 309 insertions(+), 392 deletions(-) create mode 100644 src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php create mode 100644 src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php create mode 100644 src/applications/ponder/xaction/PonderQuestionContentTransaction.php create mode 100644 src/applications/ponder/xaction/PonderQuestionStatusTransaction.php create mode 100644 src/applications/ponder/xaction/PonderQuestionTitleTransaction.php create mode 100644 src/applications/ponder/xaction/PonderQuestionTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 33618ca547..2ddff09728 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4572,7 +4572,10 @@ phutil_register_library_map(array( 'PonderFooterView' => 'applications/ponder/view/PonderFooterView.php', 'PonderModerateCapability' => 'applications/ponder/capability/PonderModerateCapability.php', 'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php', + 'PonderQuestionAnswerTransaction' => 'applications/ponder/xaction/PonderQuestionAnswerTransaction.php', + 'PonderQuestionAnswerWikiTransaction' => 'applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php', 'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php', + 'PonderQuestionContentTransaction' => 'applications/ponder/xaction/PonderQuestionContentTransaction.php', 'PonderQuestionCreateMailReceiver' => 'applications/ponder/mail/PonderQuestionCreateMailReceiver.php', 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', @@ -4586,9 +4589,12 @@ phutil_register_library_map(array( 'PonderQuestionSearchEngine' => 'applications/ponder/query/PonderQuestionSearchEngine.php', 'PonderQuestionStatus' => 'applications/ponder/constants/PonderQuestionStatus.php', 'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php', + 'PonderQuestionStatusTransaction' => 'applications/ponder/xaction/PonderQuestionStatusTransaction.php', + 'PonderQuestionTitleTransaction' => 'applications/ponder/xaction/PonderQuestionTitleTransaction.php', 'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php', 'PonderQuestionTransactionComment' => 'applications/ponder/storage/PonderQuestionTransactionComment.php', 'PonderQuestionTransactionQuery' => 'applications/ponder/query/PonderQuestionTransactionQuery.php', + 'PonderQuestionTransactionType' => 'applications/ponder/xaction/PonderQuestionTransactionType.php', 'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php', 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php', @@ -10126,7 +10132,10 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', ), + 'PonderQuestionAnswerTransaction' => 'PonderQuestionTransactionType', + 'PonderQuestionAnswerWikiTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionCommentController' => 'PonderController', + 'PonderQuestionContentTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionCreateMailReceiver' => 'PhabricatorMailReceiver', 'PonderQuestionEditController' => 'PonderController', 'PonderQuestionEditor' => 'PonderEditor', @@ -10140,9 +10149,12 @@ phutil_register_library_map(array( 'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PonderQuestionStatus' => 'PonderConstants', 'PonderQuestionStatusController' => 'PonderController', - 'PonderQuestionTransaction' => 'PhabricatorApplicationTransaction', + 'PonderQuestionStatusTransaction' => 'PonderQuestionTransactionType', + 'PonderQuestionTitleTransaction' => 'PonderQuestionTransactionType', + 'PonderQuestionTransaction' => 'PhabricatorModularTransaction', 'PonderQuestionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PonderQuestionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PonderQuestionTransactionType' => 'PhabricatorModularTransactionType', 'PonderQuestionViewController' => 'PonderController', 'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec', diff --git a/src/applications/ponder/controller/PonderAnswerSaveController.php b/src/applications/ponder/controller/PonderAnswerSaveController.php index cac17acbed..72e0e5ee04 100644 --- a/src/applications/ponder/controller/PonderAnswerSaveController.php +++ b/src/applications/ponder/controller/PonderAnswerSaveController.php @@ -38,7 +38,7 @@ final class PonderAnswerSaveController extends PonderController { $xactions = array(); $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERS) + ->setTransactionType(PonderQuestionAnswerTransaction::TRANSACTIONTYPE) ->setNewValue( array( '+' => array( diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php index 49ca5fe846..c872933ca2 100644 --- a/src/applications/ponder/controller/PonderQuestionEditController.php +++ b/src/applications/ponder/controller/PonderQuestionEditController.php @@ -63,20 +63,23 @@ final class PonderQuestionEditController extends PonderController { $xactions = array(); $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_TITLE) + ->setTransactionType(PonderQuestionTitleTransaction::TRANSACTIONTYPE) ->setNewValue($v_title); $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) + ->setTransactionType( + PonderQuestionContentTransaction::TRANSACTIONTYPE) ->setNewValue($v_content); $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERWIKI) + ->setTransactionType( + PonderQuestionAnswerWikiTransaction::TRANSACTIONTYPE) ->setNewValue($v_wiki); if (!$is_new) { $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_STATUS) + ->setTransactionType( + PonderQuestionStatusTransaction::TRANSACTIONTYPE) ->setNewValue($v_status); } diff --git a/src/applications/ponder/controller/PonderQuestionStatusController.php b/src/applications/ponder/controller/PonderQuestionStatusController.php index a4671d5915..ab791b740f 100644 --- a/src/applications/ponder/controller/PonderQuestionStatusController.php +++ b/src/applications/ponder/controller/PonderQuestionStatusController.php @@ -28,7 +28,7 @@ final class PonderQuestionStatusController $xactions = array(); $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PonderQuestionTransaction::TYPE_STATUS) + ->setTransactionType(PonderQuestionStatusTransaction::TRANSACTIONTYPE) ->setNewValue($v_status); $editor = id(new PonderQuestionEditor()) diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 73d9ed0ce0..9b34031f13 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -32,7 +32,7 @@ final class PonderQuestionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return true; } } @@ -46,7 +46,7 @@ final class PonderQuestionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: $new_value = $xaction->getNewValue(); $new = idx($new_value, '+', array()); foreach ($new as $new_answer) { @@ -70,117 +70,9 @@ final class PonderQuestionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_SPACE; - $types[] = PonderQuestionTransaction::TYPE_TITLE; - $types[] = PonderQuestionTransaction::TYPE_CONTENT; - $types[] = PonderQuestionTransaction::TYPE_ANSWERS; - $types[] = PonderQuestionTransaction::TYPE_STATUS; - $types[] = PonderQuestionTransaction::TYPE_ANSWERWIKI; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_TITLE: - return $object->getTitle(); - case PonderQuestionTransaction::TYPE_CONTENT: - return $object->getContent(); - case PonderQuestionTransaction::TYPE_ANSWERS: - return mpull($object->getAnswers(), 'getPHID'); - case PonderQuestionTransaction::TYPE_STATUS: - return $object->getStatus(); - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - return $object->getAnswerWiki(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_TITLE: - case PonderQuestionTransaction::TYPE_CONTENT: - case PonderQuestionTransaction::TYPE_STATUS: - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - return $xaction->getNewValue(); - case PonderQuestionTransaction::TYPE_ANSWERS: - $raw_new_value = $xaction->getNewValue(); - $new_value = array(); - foreach ($raw_new_value as $key => $answers) { - $phids = array(); - foreach ($answers as $answer) { - $obj = idx($answer, 'answer'); - if (!$answer) { - continue; - } - $phids[] = $obj->getPHID(); - } - $new_value[$key] = $phids; - } - $xaction->setNewValue($new_value); - return $this->getPHIDTransactionNewValue($xaction); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_CONTENT: - $object->setContent($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - $object->setAnswerWiki($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_ANSWERS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $add = array_diff_key($new, $old); - $rem = array_diff_key($old, $new); - - $count = $object->getAnswerCount(); - $count += count($add); - $count -= count($rem); - - $object->setAnswerCount($count); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PonderQuestionTransaction::TYPE_TITLE: - case PonderQuestionTransaction::TYPE_CONTENT: - case PonderQuestionTransaction::TYPE_STATUS: - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - return $v; - } - - return parent::mergeTransactions($u, $v); - } - protected function supportsSearch() { return true; } @@ -190,7 +82,7 @@ final class PonderQuestionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return false; } @@ -202,7 +94,7 @@ final class PonderQuestionEditor array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return false; } } @@ -221,7 +113,7 @@ final class PonderQuestionEditor array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return false; } } @@ -269,7 +161,7 @@ final class PonderQuestionEditor $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); // If the user just asked the question, add the question text. - if ($type == PonderQuestionTransaction::TYPE_CONTENT) { + if ($type == PonderQuestionContentTransaction::TRANSACTIONTYPE) { if ($old === null) { $body->addRawSection($new); } diff --git a/src/applications/ponder/storage/PonderQuestionTransaction.php b/src/applications/ponder/storage/PonderQuestionTransaction.php index 7abfd247f5..73a0899ca8 100644 --- a/src/applications/ponder/storage/PonderQuestionTransaction.php +++ b/src/applications/ponder/storage/PonderQuestionTransaction.php @@ -1,13 +1,7 @@ getTransactionType()) { - case self::TYPE_ANSWERS: - $phids[] = $this->getNewAnswerPHID(); - $phids[] = $this->getObjectPHID(); - break; - } - - return $phids; - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $blocks[] = $this->getNewValue(); - break; - } - return $blocks; - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s asked this question.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s edited the question title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_CONTENT: - return pht( - '%s edited the question description.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ANSWERWIKI: - return pht( - '%s edited the question answer wiki.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ANSWERS: - $answer_handle = $this->getHandle($this->getNewAnswerPHID()); - $question_handle = $this->getHandle($object_phid); - - return pht( - '%s answered %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_STATUS: - switch ($new) { - case PonderQuestionStatus::STATUS_OPEN: - return pht( - '%s reopened this question.', - $this->renderHandleLink($author_phid)); - case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: - return pht( - '%s closed this question as resolved.', - $this->renderHandleLink($author_phid)); - case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: - return pht( - '%s closed this question as obsolete.', - $this->renderHandleLink($author_phid)); - case PonderQuestionStatus::STATUS_CLOSED_INVALID: - return pht( - '%s closed this question as invalid.', - $this->renderHandleLink($author_phid)); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PonderQuestionTransactionType'; } public function getMailTags() { @@ -120,13 +35,13 @@ final class PonderQuestionTransaction case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - case self::TYPE_STATUS: - case self::TYPE_ANSWERWIKI: + case PonderQuestionTitleTransaction::TRANSACTIONTYPE: + case PonderQuestionContentTransaction::TRANSACTIONTYPE: + case PonderQuestionStatusTransaction::TRANSACTIONTYPE: + case PonderQuestionAnswerWikiTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; - case self::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_ANSWERS; break; default: @@ -136,182 +51,4 @@ final class PonderQuestionTransaction return $tags; } - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - case self::TYPE_ANSWERWIKI: - return 'fa-pencil'; - case self::TYPE_STATUS: - return PonderQuestionStatus::getQuestionStatusIcon($new); - case self::TYPE_ANSWERS: - return 'fa-plus'; - } - - return parent::getIcon(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - case self::TYPE_ANSWERWIKI: - return PhabricatorTransactions::COLOR_BLUE; - case self::TYPE_ANSWERS: - return PhabricatorTransactions::COLOR_GREEN; - case self::TYPE_STATUS: - return PonderQuestionStatus::getQuestionStatusTagColor($new); - } - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - case self::TYPE_ANSWERWIKI: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - - public function getActionStrength() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return 3; - } - break; - case self::TYPE_ANSWERS: - return 2; - } - - return parent::getActionStrength(); - } - - public function getActionName() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht('Asked'); - } - break; - case self::TYPE_ANSWERS: - return pht('Answered'); - } - - return parent::getActionName(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s asked a question: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s edited the title of %s (was "%s")', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old); - } - case self::TYPE_CONTENT: - return pht( - '%s edited the description of %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_ANSWERWIKI: - return pht( - '%s edited the answer wiki for %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_ANSWERS: - $answer_handle = $this->getHandle($this->getNewAnswerPHID()); - $question_handle = $this->getHandle($object_phid); - return pht( - '%s answered %s', - $this->renderHandleLink($author_phid), - $answer_handle->renderLink($question_handle->getFullName())); - case self::TYPE_STATUS: - switch ($new) { - case PonderQuestionStatus::STATUS_OPEN: - return pht( - '%s reopened %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: - return pht( - '%s closed %s as resolved.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PonderQuestionStatus::STATUS_CLOSED_INVALID: - return pht( - '%s closed %s as invalid.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: - return pht( - '%s closed %s as obsolete.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $text = null; - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $text = $this->getNewValue(); - break; - } - return $text; - } - - /** - * Currently the application only supports adding answers one at a time. - * This data is stored as a list of phids. Use this function to get the - * new phid. - */ - private function getNewAnswerPHID() { - $new = $this->getNewValue(); - $old = $this->getOldValue(); - $add = array_diff($new, $old); - - if (count($add) != 1) { - throw new Exception( - pht('There should be only one answer added at a time.')); - } - - return reset($add); - } - } diff --git a/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php b/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php new file mode 100644 index 0000000000..a51eada7b4 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php @@ -0,0 +1,28 @@ +getAnswers(); + } + + public function applyInternalEffects($object, $value) { + $count = $object->getAnswerCount(); + $count++; + $object->setAnswerCount($count); + } + + public function getTitle() { + return pht( + '%s added an answer.', + $this->renderAuthor()); + } + + public function getIcon() { + return 'fa-plus'; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php b/src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php new file mode 100644 index 0000000000..b6b5b150fc --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php @@ -0,0 +1,56 @@ +getAnswerWiki(); + } + + public function applyInternalEffects($object, $value) { + $object->setAnswerWiki($value); + } + + public function getTitle() { + return pht( + '%s updated the answer wiki.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the answer wiki for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO ANSWER WIKI'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionContentTransaction.php b/src/applications/ponder/xaction/PonderQuestionContentTransaction.php new file mode 100644 index 0000000000..d958170475 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionContentTransaction.php @@ -0,0 +1,56 @@ +getContent(); + } + + public function applyInternalEffects($object, $value) { + $object->setContent($value); + } + + public function getTitle() { + return pht( + '%s updated the question details.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the question details for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO QUESTION DETAILS'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionStatusTransaction.php b/src/applications/ponder/xaction/PonderQuestionStatusTransaction.php new file mode 100644 index 0000000000..85080b12b7 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionStatusTransaction.php @@ -0,0 +1,74 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + switch ($new) { + case PonderQuestionStatus::STATUS_OPEN: + return pht( + '%s reopened this question.', + $this->renderAuthor()); + case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: + return pht( + '%s closed this question as resolved.', + $this->renderAuthor()); + case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: + return pht( + '%s closed this question as obsolete.', + $this->renderAuthor()); + case PonderQuestionStatus::STATUS_CLOSED_INVALID: + return pht( + '%s closed this question as invalid.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + switch ($new) { + case PonderQuestionStatus::STATUS_OPEN: + return pht( + '%s reopened %s.', + $this->renderAuthor(), + $this->renderObject()); + case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: + return pht( + '%s closed %s as resolved.', + $this->renderAuthor(), + $this->renderObject()); + case PonderQuestionStatus::STATUS_CLOSED_INVALID: + return pht( + '%s closed %s as invalid.', + $this->renderAuthor(), + $this->renderObject()); + case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: + return pht( + '%s closed %s as obsolete.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + return PonderQuestionStatus::getQuestionStatusIcon($new); + } + + public function getColor() { + $new = $this->getNewValue(); + return PonderQuestionStatus::getQuestionStatusTagColor($new); + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionTitleTransaction.php b/src/applications/ponder/xaction/PonderQuestionTitleTransaction.php new file mode 100644 index 0000000000..e96af9d78f --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionTitleTransaction.php @@ -0,0 +1,55 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + return pht( + '%s updated the question from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Questions must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionTransactionType.php b/src/applications/ponder/xaction/PonderQuestionTransactionType.php new file mode 100644 index 0000000000..6b68122464 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionTransactionType.php @@ -0,0 +1,4 @@ + Date: Mon, 1 May 2017 12:34:43 -0700 Subject: [PATCH 010/543] Convert PonderAnswer to modular transactions Summary: Fixes T12624. Converts PonderAnswer over to modular transactions. Test Plan: - Add an answer - Edit an answer - Hide an answer - Comment on an answer Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12624 Differential Revision: https://secure.phabricator.com/D17811 --- src/__phutil_library_map__.php | 10 +- .../controller/PonderAnswerEditController.php | 4 +- .../controller/PonderAnswerSaveController.php | 4 +- .../ponder/editor/PonderAnswerEditor.php | 67 -------- .../storage/PonderAnswerTransaction.php | 143 +----------------- .../PonderAnswerContentTransaction.php | 56 +++++++ .../PonderAnswerQuestionIDTransaction.php | 16 ++ .../xaction/PonderAnswerStatusTransaction.php | 62 ++++++++ .../xaction/PonderAnswerTransactionType.php | 4 + 9 files changed, 154 insertions(+), 212 deletions(-) create mode 100644 src/applications/ponder/xaction/PonderAnswerContentTransaction.php create mode 100644 src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php create mode 100644 src/applications/ponder/xaction/PonderAnswerStatusTransaction.php create mode 100644 src/applications/ponder/xaction/PonderAnswerTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2ddff09728..1dc20b5019 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4551,18 +4551,22 @@ phutil_register_library_map(array( 'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php', 'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php', 'PonderAnswerCommentController' => 'applications/ponder/controller/PonderAnswerCommentController.php', + 'PonderAnswerContentTransaction' => 'applications/ponder/xaction/PonderAnswerContentTransaction.php', 'PonderAnswerEditController' => 'applications/ponder/controller/PonderAnswerEditController.php', 'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php', 'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php', 'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php', 'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php', 'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php', + 'PonderAnswerQuestionIDTransaction' => 'applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php', 'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php', 'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php', 'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php', + 'PonderAnswerStatusTransaction' => 'applications/ponder/xaction/PonderAnswerStatusTransaction.php', 'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php', 'PonderAnswerTransactionComment' => 'applications/ponder/storage/PonderAnswerTransactionComment.php', 'PonderAnswerTransactionQuery' => 'applications/ponder/query/PonderAnswerTransactionQuery.php', + 'PonderAnswerTransactionType' => 'applications/ponder/xaction/PonderAnswerTransactionType.php', 'PonderAnswerView' => 'applications/ponder/view/PonderAnswerView.php', 'PonderConstants' => 'applications/ponder/constants/PonderConstants.php', 'PonderController' => 'applications/ponder/controller/PonderController.php', @@ -10099,18 +10103,22 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'PonderAnswerCommentController' => 'PonderController', + 'PonderAnswerContentTransaction' => 'PonderAnswerTransactionType', 'PonderAnswerEditController' => 'PonderController', 'PonderAnswerEditor' => 'PonderEditor', 'PonderAnswerHistoryController' => 'PonderController', 'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver', 'PonderAnswerPHIDType' => 'PhabricatorPHIDType', 'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PonderAnswerQuestionIDTransaction' => 'PonderAnswerTransactionType', 'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PonderAnswerSaveController' => 'PonderController', 'PonderAnswerStatus' => 'PonderConstants', - 'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction', + 'PonderAnswerStatusTransaction' => 'PonderAnswerTransactionType', + 'PonderAnswerTransaction' => 'PhabricatorModularTransaction', 'PonderAnswerTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PonderAnswerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PonderAnswerTransactionType' => 'PhabricatorModularTransactionType', 'PonderAnswerView' => 'AphrontTagView', 'PonderConstants' => 'Phobject', 'PonderController' => 'PhabricatorController', diff --git a/src/applications/ponder/controller/PonderAnswerEditController.php b/src/applications/ponder/controller/PonderAnswerEditController.php index 3362e15834..9c48c7c669 100644 --- a/src/applications/ponder/controller/PonderAnswerEditController.php +++ b/src/applications/ponder/controller/PonderAnswerEditController.php @@ -42,11 +42,11 @@ final class PonderAnswerEditController extends PonderController { if (!$errors) { $xactions = array(); $xactions[] = id(new PonderAnswerTransaction()) - ->setTransactionType(PonderAnswerTransaction::TYPE_CONTENT) + ->setTransactionType(PonderAnswerContentTransaction::TRANSACTIONTYPE) ->setNewValue($v_content); $xactions[] = id(new PonderAnswerTransaction()) - ->setTransactionType(PonderAnswerTransaction::TYPE_STATUS) + ->setTransactionType(PonderAnswerStatusTransaction::TRANSACTIONTYPE) ->setNewValue($v_status); $editor = id(new PonderAnswerEditor()) diff --git a/src/applications/ponder/controller/PonderAnswerSaveController.php b/src/applications/ponder/controller/PonderAnswerSaveController.php index 72e0e5ee04..53e0f06ea6 100644 --- a/src/applications/ponder/controller/PonderAnswerSaveController.php +++ b/src/applications/ponder/controller/PonderAnswerSaveController.php @@ -58,11 +58,11 @@ final class PonderAnswerSaveController extends PonderController { $xactions = array(); $xactions[] = id(clone $template) - ->setTransactionType(PonderAnswerTransaction::TYPE_QUESTION_ID) + ->setTransactionType(PonderAnswerQuestionIDTransaction::TRANSACTIONTYPE) ->setNewValue($question->getID()); $xactions[] = id(clone $template) - ->setTransactionType(PonderAnswerTransaction::TYPE_CONTENT) + ->setTransactionType(PonderAnswerContentTransaction::TRANSACTIONTYPE) ->setNewValue($content); $editor = id(new PonderAnswerEditor()) diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index 10a90a1945..5bde437e3e 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -8,78 +8,11 @@ final class PonderAnswerEditor extends PonderEditor { public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = PhabricatorTransactions::TYPE_EDGE; - - $types[] = PonderAnswerTransaction::TYPE_CONTENT; - $types[] = PonderAnswerTransaction::TYPE_STATUS; - $types[] = PonderAnswerTransaction::TYPE_QUESTION_ID; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderAnswerTransaction::TYPE_CONTENT: - case PonderAnswerTransaction::TYPE_STATUS: - return $object->getContent(); - case PonderAnswerTransaction::TYPE_QUESTION_ID: - return $object->getQuestionID(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderAnswerTransaction::TYPE_CONTENT: - case PonderAnswerTransaction::TYPE_STATUS: - case PonderAnswerTransaction::TYPE_QUESTION_ID: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderAnswerTransaction::TYPE_CONTENT: - $object->setContent($xaction->getNewValue()); - break; - case PonderAnswerTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - break; - case PonderAnswerTransaction::TYPE_QUESTION_ID: - $object->setQuestionID($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PonderAnswerTransaction::TYPE_CONTENT: - return $v; - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/ponder/storage/PonderAnswerTransaction.php b/src/applications/ponder/storage/PonderAnswerTransaction.php index 924817b34e..784b8a36e7 100644 --- a/src/applications/ponder/storage/PonderAnswerTransaction.php +++ b/src/applications/ponder/storage/PonderAnswerTransaction.php @@ -1,11 +1,7 @@ getTransactionType()) { - case self::TYPE_CONTENT: - case self::TYPE_STATUS: - $phids[] = $this->getObjectPHID(); - break; - } - - return $phids; - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $blocks[] = $this->getNewValue(); - break; - } - return $blocks; - } - - public function shouldHide() { - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION_ID: - return true; - } - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - if ($old === '') { - return pht( - '%s added %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s edited %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_STATUS: - if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { - return pht( - '%s marked %s as visible.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { - return pht( - '%s marked %s as hidden.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - if ($old === '') { - return pht( - '%s added %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_STATUS: - if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { - return pht( - '%s marked %s as visible.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { - return pht( - '%s marked %s as hidden.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $text = null; - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $text = $this->getNewValue(); - break; - } - return $text; - } - - - public function hasChangeDetails() { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return $old !== null; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'PonderAnswerTransactionType'; } } diff --git a/src/applications/ponder/xaction/PonderAnswerContentTransaction.php b/src/applications/ponder/xaction/PonderAnswerContentTransaction.php new file mode 100644 index 0000000000..5d5c6ad157 --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerContentTransaction.php @@ -0,0 +1,56 @@ +getContent(); + } + + public function applyInternalEffects($object, $value) { + $object->setContent($value); + } + + public function getTitle() { + return pht( + '%s updated the answer details.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the answer details for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO ANSWER DETAILS'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php b/src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php new file mode 100644 index 0000000000..a66608479c --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php @@ -0,0 +1,16 @@ +getQuestionID(); + } + + public function applyInternalEffects($object, $value) { + $object->setQuestionID($value); + } + +} diff --git a/src/applications/ponder/xaction/PonderAnswerStatusTransaction.php b/src/applications/ponder/xaction/PonderAnswerStatusTransaction.php new file mode 100644 index 0000000000..46a359aa5e --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerStatusTransaction.php @@ -0,0 +1,62 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return pht( + '%s marked this answer as visible.', + $this->renderAuthor()); + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return pht( + '%s marked this answer as hidden.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return pht( + '%s marked %s as visible.', + $this->renderAuthor(), + $this->renderObject()); + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return pht( + '%s marked %s as hidden.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return 'fa-ban'; + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return 'fa-check'; + } + } + + public function getColor() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return 'green'; + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return 'indigo'; + } + } + +} diff --git a/src/applications/ponder/xaction/PonderAnswerTransactionType.php b/src/applications/ponder/xaction/PonderAnswerTransactionType.php new file mode 100644 index 0000000000..84268f33b2 --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerTransactionType.php @@ -0,0 +1,4 @@ + Date: Mon, 1 May 2017 15:19:00 -0700 Subject: [PATCH 011/543] Update Macro for Modular Transactions Summary: Overall, seems to work ok. Test Plan: - Add a Macro - Edit Macro - Use Macro - Disable Macro - Re-enable Macro - Attach Audio - Set Audio to loop - Annoy cats {F4932069} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17813 --- src/__phutil_library_map__.php | 14 +- .../PhabricatorMacroAudioController.php | 5 +- .../PhabricatorMacroDisableController.php | 3 +- .../PhabricatorMacroEditController.php | 6 +- .../PhabricatorMacroViewController.php | 2 +- .../macro/editor/PhabricatorMacroEditor.php | 90 +----- .../storage/PhabricatorMacroTransaction.php | 289 +----------------- ...abricatorMacroAudioBehaviorTransaction.php | 69 +++++ .../PhabricatorMacroAudioTransaction.php | 56 ++++ .../PhabricatorMacroDisabledTransaction.php | 50 +++ .../PhabricatorMacroFileTransaction.php | 33 ++ .../PhabricatorMacroNameTransaction.php | 55 ++++ .../PhabricatorMacroTransactionType.php | 4 + 13 files changed, 295 insertions(+), 381 deletions(-) create mode 100644 src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php create mode 100644 src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php create mode 100644 src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php create mode 100644 src/applications/macro/xaction/PhabricatorMacroFileTransaction.php create mode 100644 src/applications/macro/xaction/PhabricatorMacroNameTransaction.php create mode 100644 src/applications/macro/xaction/PhabricatorMacroTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1dc20b5019..86dd5a00d8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2972,26 +2972,32 @@ phutil_register_library_map(array( 'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php', 'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php', 'PhabricatorMacroApplication' => 'applications/macro/application/PhabricatorMacroApplication.php', + 'PhabricatorMacroAudioBehaviorTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php', 'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php', + 'PhabricatorMacroAudioTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioTransaction.php', 'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php', 'PhabricatorMacroConfigOptions' => 'applications/macro/config/PhabricatorMacroConfigOptions.php', 'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php', 'PhabricatorMacroDatasource' => 'applications/macro/typeahead/PhabricatorMacroDatasource.php', 'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php', + 'PhabricatorMacroDisabledTransaction' => 'applications/macro/xaction/PhabricatorMacroDisabledTransaction.php', 'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php', 'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php', + 'PhabricatorMacroFileTransaction' => 'applications/macro/xaction/PhabricatorMacroFileTransaction.php', 'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php', 'PhabricatorMacroMacroPHIDType' => 'applications/macro/phid/PhabricatorMacroMacroPHIDType.php', 'PhabricatorMacroMailReceiver' => 'applications/macro/mail/PhabricatorMacroMailReceiver.php', 'PhabricatorMacroManageCapability' => 'applications/macro/capability/PhabricatorMacroManageCapability.php', 'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php', 'PhabricatorMacroMemeDialogController' => 'applications/macro/controller/PhabricatorMacroMemeDialogController.php', + 'PhabricatorMacroNameTransaction' => 'applications/macro/xaction/PhabricatorMacroNameTransaction.php', 'PhabricatorMacroQuery' => 'applications/macro/query/PhabricatorMacroQuery.php', 'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php', 'PhabricatorMacroSearchEngine' => 'applications/macro/query/PhabricatorMacroSearchEngine.php', 'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php', 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', + 'PhabricatorMacroTransactionType' => 'applications/macro/xaction/PhabricatorMacroTransactionType.php', 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', 'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php', 'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php', @@ -8162,26 +8168,32 @@ phutil_register_library_map(array( 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorMacroApplication' => 'PhabricatorApplication', + 'PhabricatorMacroAudioBehaviorTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroAudioController' => 'PhabricatorMacroController', + 'PhabricatorMacroAudioTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroCommentController' => 'PhabricatorMacroController', 'PhabricatorMacroConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMacroController' => 'PhabricatorController', 'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorMacroDisableController' => 'PhabricatorMacroController', + 'PhabricatorMacroDisabledTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroEditController' => 'PhabricatorMacroController', 'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorMacroFileTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroListController' => 'PhabricatorMacroController', 'PhabricatorMacroMacroPHIDType' => 'PhabricatorPHIDType', 'PhabricatorMacroMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorMacroManageCapability' => 'PhabricatorPolicyCapability', 'PhabricatorMacroMemeController' => 'PhabricatorMacroController', 'PhabricatorMacroMemeDialogController' => 'PhabricatorMacroController', + 'PhabricatorMacroNameTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorMacroReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorMacroSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorMacroTransaction' => 'PhabricatorModularTransaction', 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorMacroTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorMacroViewController' => 'PhabricatorMacroController', 'PhabricatorMailEmailHeraldField' => 'HeraldField', 'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup', diff --git a/src/applications/macro/controller/PhabricatorMacroAudioController.php b/src/applications/macro/controller/PhabricatorMacroAudioController.php index 7e2b924637..382c82ae3a 100644 --- a/src/applications/macro/controller/PhabricatorMacroAudioController.php +++ b/src/applications/macro/controller/PhabricatorMacroAudioController.php @@ -34,7 +34,7 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { if ($request->getBool('behaviorForm')) { $xactions[] = id(new PhabricatorMacroTransaction()) ->setTransactionType( - PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR) + PhabricatorMacroAudioBehaviorTransaction::TRANSACTIONTYPE) ->setNewValue($request->getStr('audioBehavior')); } else { $file = null; @@ -54,7 +54,8 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { $e_file = pht('Invalid'); } else { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_AUDIO) + ->setTransactionType( + PhabricatorMacroAudioTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); } } else { diff --git a/src/applications/macro/controller/PhabricatorMacroDisableController.php b/src/applications/macro/controller/PhabricatorMacroDisableController.php index a9868647f0..6c74e44442 100644 --- a/src/applications/macro/controller/PhabricatorMacroDisableController.php +++ b/src/applications/macro/controller/PhabricatorMacroDisableController.php @@ -22,7 +22,8 @@ final class PhabricatorMacroDisableController if ($request->isDialogFormPost() || $macro->getIsDisabled()) { $xaction = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_DISABLED) + ->setTransactionType( + PhabricatorMacroDisabledTransaction::TRANSACTIONTYPE) ->setNewValue($macro->getIsDisabled() ? 0 : 1); $editor = id(new PhabricatorMacroEditor()) diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index 110296c172..fc84e057ae 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -135,13 +135,15 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { if ($new_name !== null) { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_NAME) + ->setTransactionType( + PhabricatorMacroNameTransaction::TRANSACTIONTYPE) ->setNewValue($new_name); } if ($file) { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_FILE) + ->setTransactionType( + PhabricatorMacroFileTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); } diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 386029c7ce..032d2228a9 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -45,7 +45,7 @@ final class PhabricatorMacroViewController if (!$macro->getIsDisabled()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { - $header->setStatus('fa-ban', 'red', pht('Archived')); + $header->setStatus('fa-ban', 'indigo', pht('Archived')); } $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index 1bb139a4de..797fa702a9 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -13,79 +13,18 @@ final class PhabricatorMacroEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = PhabricatorMacroTransaction::TYPE_NAME; - $types[] = PhabricatorMacroTransaction::TYPE_DISABLED; - $types[] = PhabricatorMacroTransaction::TYPE_FILE; - $types[] = PhabricatorMacroTransaction::TYPE_AUDIO; - $types[] = PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorMacroTransaction::TYPE_DISABLED: - return $object->getIsDisabled(); - case PhabricatorMacroTransaction::TYPE_FILE: - return $object->getFilePHID(); - case PhabricatorMacroTransaction::TYPE_AUDIO: - return $object->getAudioPHID(); - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - return $object->getAudioBehavior(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME: - case PhabricatorMacroTransaction::TYPE_DISABLED: - case PhabricatorMacroTransaction::TYPE_FILE: - case PhabricatorMacroTransaction::TYPE_AUDIO: - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_DISABLED: - $object->setIsDisabled($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_FILE: - $object->setFilePHID($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_AUDIO: - $object->setAudioPHID($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - $object->setAudioBehavior($xaction->getNewValue()); - break; - } - } - protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_FILE: - case PhabricatorMacroTransaction::TYPE_AUDIO: + case PhabricatorMacroFileTransaction::TRANSACTIONTYPE: + case PhabricatorMacroAudioTransaction::TRANSACTIONTYPE: // When changing a macro's image or audio, attach the underlying files // to the macro (and detach the old files). $old = $xaction->getOldValue(); @@ -117,34 +56,9 @@ final class PhabricatorMacroEditor } } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PhabricatorMacroTransaction::TYPE_NAME: - case PhabricatorMacroTransaction::TYPE_DISABLED: - case PhabricatorMacroTransaction::TYPE_FILE: - case PhabricatorMacroTransaction::TYPE_AUDIO: - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - return $v; - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME; - return ($xaction->getOldValue() !== null); - default: - break; - } - } return true; } diff --git a/src/applications/macro/storage/PhabricatorMacroTransaction.php b/src/applications/macro/storage/PhabricatorMacroTransaction.php index 46eb932476..3a15b4c15d 100644 --- a/src/applications/macro/storage/PhabricatorMacroTransaction.php +++ b/src/applications/macro/storage/PhabricatorMacroTransaction.php @@ -1,14 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_FILE: - case self::TYPE_AUDIO: - if ($old !== null) { - $phids[] = $old; - } - $phids[] = $new; - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return pht( - '%s renamed this macro from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - break; - case self::TYPE_DISABLED: - if ($new) { - return pht( - '%s disabled this macro.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s restored this macro.', - $this->renderHandleLink($author_phid)); - } - break; - - case self::TYPE_AUDIO: - if (!$old) { - return pht( - '%s attached audio: %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s changed the audio for this macro from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_AUDIO_BEHAVIOR: - switch ($new) { - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: - return pht( - '%s set the audio to play once.', - $this->renderHandleLink($author_phid)); - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: - return pht( - '%s set the audio to loop.', - $this->renderHandleLink($author_phid)); - default: - return pht( - '%s disabled the audio for this macro.', - $this->renderHandleLink($author_phid)); - } - - case self::TYPE_FILE: - if ($old === null) { - return pht( - '%s created this macro.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the image for this macro from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - case self::TYPE_DISABLED: - if ($new) { - return pht( - '%s disabled %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s restored %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - case self::TYPE_FILE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - case self::TYPE_AUDIO: - if (!$old) { - return pht( - '%s attached audio to %s: %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s changed the audio for %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_AUDIO_BEHAVIOR: - switch ($new) { - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: - return pht( - '%s set the audio for %s to play once.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: - return pht( - '%s set the audio for %s to loop.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - default: - return pht( - '%s disabled the audio for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - } - - return parent::getTitleForFeed(); - } - - public function getActionName() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht('Created'); - } else { - return pht('Renamed'); - } - case self::TYPE_DISABLED: - if ($new) { - return pht('Disabled'); - } else { - return pht('Restored'); - } - case self::TYPE_FILE: - if ($old === null) { - return pht('Created'); - } else { - return pht('Edited Image'); - } - - case self::TYPE_AUDIO: - return pht('Audio'); - - case self::TYPE_AUDIO_BEHAVIOR: - return pht('Audio Behavior'); - - } - - return parent::getActionName(); - } - - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_DISABLED: - return 2.0; - case self::TYPE_FILE: - return 1.5; - } - return parent::getActionStrength(); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return 'fa-pencil'; - case self::TYPE_FILE: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - case self::TYPE_DISABLED: - if ($new) { - return 'fa-times'; - } else { - return 'fa-undo'; - } - case self::TYPE_AUDIO: - return 'fa-headphones'; - } - - return parent::getIcon(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return PhabricatorTransactions::COLOR_BLUE; - case self::TYPE_FILE: - if ($old === null) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_BLUE; - } - case self::TYPE_DISABLED: - if ($new) { - return PhabricatorTransactions::COLOR_RED; - } else { - return PhabricatorTransactions::COLOR_SKY; - } - } - - return parent::getColor(); + public function getBaseTransactionClass() { + return 'PhabricatorMacroTransactionType'; } diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php new file mode 100644 index 0000000000..e27e9081ed --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php @@ -0,0 +1,69 @@ +getAudioBehavior(); + } + + public function applyInternalEffects($object, $value) { + $object->setAudioBehavior($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + switch ($new) { + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: + return pht( + '%s set the audio to play once.', + $this->renderAuthor()); + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: + return pht( + '%s set the audio to loop.', + $this->renderAuthor()); + default: + return pht( + '%s disabled the audio for this macro.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + switch ($new) { + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: + return pht( + '%s set the audio for %s to play once.', + $this->renderAuthor(), + $this->renderObject()); + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: + return pht( + '%s set the audio for %s to loop.', + $this->renderAuthor(), + $this->renderObject()); + default: + return pht( + '%s disabled the audio for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + switch ($new) { + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: + return 'fa-play-circle'; + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: + return 'fa-repeat'; + default: + return 'fa-pause-circle'; + } + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php new file mode 100644 index 0000000000..aacf9f4016 --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php @@ -0,0 +1,56 @@ +getAudioPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setAudioPHID($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + if (!$old) { + return pht( + '%s attached audio: %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } else { + return pht( + '%s changed the audio for this macro from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + if (!$old) { + return pht( + '%s attached audio to %s: %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } else { + return pht( + '%s changed the audio for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getIcon() { + return 'fa-music'; + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php b/src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php new file mode 100644 index 0000000000..334dc9ef6f --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php @@ -0,0 +1,50 @@ +getIsDisabled(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s disabled this macro.', + $this->renderAuthor()); + } else { + return pht( + '%s restored this macro.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue()) { + return pht( + '%s disabled %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s restored %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + if ($this->getNewValue()) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php new file mode 100644 index 0000000000..40e51fe1df --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php @@ -0,0 +1,33 @@ +getFilePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setFilePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the image for this macro.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the image for macro %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-file-image-o'; + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php new file mode 100644 index 0000000000..ebd81530f5 --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php @@ -0,0 +1,55 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this macro from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s macro %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Macros must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroTransactionType.php b/src/applications/macro/xaction/PhabricatorMacroTransactionType.php new file mode 100644 index 0000000000..6a8e98c05b --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroTransactionType.php @@ -0,0 +1,4 @@ + Date: Tue, 2 May 2017 15:11:54 -0700 Subject: [PATCH 012/543] Modernize PhameBlog with modular transactions Summary: Moves PhameBlog over to the wonderful world of modular transactions and the riches that lay beyond... Test Plan: - Create Blog - Edit Blog - Set Header - Delete Header - Add picture - Archive blog - Set incorrect domain values - Be irresponsible with subtitle length - Activate blog - Change description Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17815 --- src/__phutil_library_map__.php | 22 +- .../blog/PhameBlogArchiveController.php | 2 +- .../blog/PhameBlogHeaderPictureController.php | 2 +- .../PhameBlogProfilePictureController.php | 3 +- .../phame/editor/PhameBlogEditEngine.php | 14 +- .../phame/editor/PhameBlogEditor.php | 245 +------------ src/applications/phame/storage/PhameBlog.php | 44 +-- .../phame/storage/PhameBlogTransaction.php | 327 +----------------- .../PhameBlogDescriptionTransaction.php | 60 ++++ .../PhameBlogFullDomainTransaction.php | 95 +++++ .../PhameBlogHeaderImageTransaction.php | 34 ++ .../xaction/PhameBlogNameTransaction.php | 59 ++++ .../PhameBlogParentDomainTransaction.php | 82 +++++ .../PhameBlogParentSiteTransaction.php | 66 ++++ .../PhameBlogProfileImageTransaction.php | 34 ++ .../xaction/PhameBlogStatusTransaction.php | 55 +++ .../xaction/PhameBlogSubtitleTransaction.php | 63 ++++ .../xaction/PhameBlogTransactionType.php | 4 + 18 files changed, 611 insertions(+), 600 deletions(-) create mode 100644 src/applications/phame/xaction/PhameBlogDescriptionTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogFullDomainTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogNameTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogParentDomainTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogParentSiteTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogProfileImageTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogStatusTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogSubtitleTransaction.php create mode 100644 src/applications/phame/xaction/PhameBlogTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 86dd5a00d8..0c71c8eab1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4232,24 +4232,34 @@ phutil_register_library_map(array( 'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php', 'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php', 'PhameBlogDatasource' => 'applications/phame/typeahead/PhameBlogDatasource.php', + 'PhameBlogDescriptionTransaction' => 'applications/phame/xaction/PhameBlogDescriptionTransaction.php', 'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php', 'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php', 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', 'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php', + 'PhameBlogFullDomainTransaction' => 'applications/phame/xaction/PhameBlogFullDomainTransaction.php', 'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php', + 'PhameBlogHeaderImageTransaction' => 'applications/phame/xaction/PhameBlogHeaderImageTransaction.php', 'PhameBlogHeaderPictureController' => 'applications/phame/controller/blog/PhameBlogHeaderPictureController.php', 'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php', 'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php', 'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php', + 'PhameBlogNameTransaction' => 'applications/phame/xaction/PhameBlogNameTransaction.php', + 'PhameBlogParentDomainTransaction' => 'applications/phame/xaction/PhameBlogParentDomainTransaction.php', + 'PhameBlogParentSiteTransaction' => 'applications/phame/xaction/PhameBlogParentSiteTransaction.php', + 'PhameBlogProfileImageTransaction' => 'applications/phame/xaction/PhameBlogProfileImageTransaction.php', 'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php', 'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php', 'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php', 'PhameBlogSearchConduitAPIMethod' => 'applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php', 'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php', 'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php', + 'PhameBlogStatusTransaction' => 'applications/phame/xaction/PhameBlogStatusTransaction.php', + 'PhameBlogSubtitleTransaction' => 'applications/phame/xaction/PhameBlogSubtitleTransaction.php', 'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php', 'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php', + 'PhameBlogTransactionType' => 'applications/phame/xaction/PhameBlogTransactionType.php', 'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php', 'PhameConstants' => 'applications/phame/constants/PhameConstants.php', 'PhameController' => 'applications/phame/controller/PhameController.php', @@ -9690,24 +9700,34 @@ phutil_register_library_map(array( 'PhameBlogController' => 'PhameController', 'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability', 'PhameBlogDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhameBlogDescriptionTransaction' => 'PhameBlogTransactionType', 'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhameBlogEditController' => 'PhameBlogController', 'PhameBlogEditEngine' => 'PhabricatorEditEngine', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', 'PhameBlogFeedController' => 'PhameBlogController', + 'PhameBlogFullDomainTransaction' => 'PhameBlogTransactionType', 'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhameBlogHeaderImageTransaction' => 'PhameBlogTransactionType', 'PhameBlogHeaderPictureController' => 'PhameBlogController', 'PhameBlogListController' => 'PhameBlogController', 'PhameBlogListView' => 'AphrontTagView', 'PhameBlogManageController' => 'PhameBlogController', + 'PhameBlogNameTransaction' => 'PhameBlogTransactionType', + 'PhameBlogParentDomainTransaction' => 'PhameBlogTransactionType', + 'PhameBlogParentSiteTransaction' => 'PhameBlogTransactionType', + 'PhameBlogProfileImageTransaction' => 'PhameBlogTransactionType', 'PhameBlogProfilePictureController' => 'PhameBlogController', 'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhameBlogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhameBlogSite' => 'PhameSite', - 'PhameBlogTransaction' => 'PhabricatorApplicationTransaction', + 'PhameBlogStatusTransaction' => 'PhameBlogTransactionType', + 'PhameBlogSubtitleTransaction' => 'PhameBlogTransactionType', + 'PhameBlogTransaction' => 'PhabricatorModularTransaction', 'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhameBlogTransactionType' => 'PhabricatorModularTransactionType', 'PhameBlogViewController' => 'PhameLiveController', 'PhameConstants' => 'Phobject', 'PhameController' => 'PhabricatorController', diff --git a/src/applications/phame/controller/blog/PhameBlogArchiveController.php b/src/applications/phame/controller/blog/PhameBlogArchiveController.php index 4c8d4a4a63..8b63bad9ff 100644 --- a/src/applications/phame/controller/blog/PhameBlogArchiveController.php +++ b/src/applications/phame/controller/blog/PhameBlogArchiveController.php @@ -32,7 +32,7 @@ final class PhameBlogArchiveController $xactions = array(); $xactions[] = id(new PhameBlogTransaction()) - ->setTransactionType(PhameBlogTransaction::TYPE_STATUS) + ->setTransactionType(PhameBlogStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhameBlogEditor()) diff --git a/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php b/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php index ab026fecbf..0106b9571d 100644 --- a/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php @@ -61,7 +61,7 @@ final class PhameBlogHeaderPictureController $xactions = array(); $xactions[] = id(new PhameBlogTransaction()) - ->setTransactionType(PhameBlogTransaction::TYPE_HEADERIMAGE) + ->setTransactionType(PhameBlogHeaderImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhameBlogEditor()) diff --git a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php index 3368046414..04b41dbb79 100644 --- a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php @@ -76,7 +76,8 @@ final class PhameBlogProfilePictureController $xactions = array(); $xactions[] = id(new PhameBlogTransaction()) - ->setTransactionType(PhameBlogTransaction::TYPE_PROFILEIMAGE) + ->setTransactionType( + PhameBlogProfileImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhameBlogEditor()) diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 7d6511d55c..6d6c0a9f77 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -75,7 +75,7 @@ final class PhameBlogEditEngine ->setDescription(pht('Blog name.')) ->setConduitDescription(pht('Retitle the blog.')) ->setConduitTypeDescription(pht('New blog title.')) - ->setTransactionType(PhameBlogTransaction::TYPE_NAME) + ->setTransactionType(PhameBlogNameTransaction::TRANSACTIONTYPE) ->setValue($object->getName()), id(new PhabricatorTextEditField()) ->setKey('subtitle') @@ -83,7 +83,7 @@ final class PhameBlogEditEngine ->setDescription(pht('Blog subtitle.')) ->setConduitDescription(pht('Change the blog subtitle.')) ->setConduitTypeDescription(pht('New blog subtitle.')) - ->setTransactionType(PhameBlogTransaction::TYPE_SUBTITLE) + ->setTransactionType(PhameBlogSubtitleTransaction::TRANSACTIONTYPE) ->setValue($object->getSubtitle()), id(new PhabricatorRemarkupEditField()) ->setKey('description') @@ -91,7 +91,7 @@ final class PhameBlogEditEngine ->setDescription(pht('Blog description.')) ->setConduitDescription(pht('Change the blog description.')) ->setConduitTypeDescription(pht('New blog description.')) - ->setTransactionType(PhameBlogTransaction::TYPE_DESCRIPTION) + ->setTransactionType(PhameBlogDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), id(new PhabricatorTextEditField()) ->setKey('domainFullURI') @@ -104,7 +104,7 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Change the blog full domain URI.')) ->setConduitTypeDescription(pht('New blog full domain URI.')) ->setValue($object->getDomainFullURI()) - ->setTransactionType(PhameBlogTransaction::TYPE_FULLDOMAIN), + ->setTransactionType(PhameBlogFullDomainTransaction::TRANSACTIONTYPE), id(new PhabricatorTextEditField()) ->setKey('parentSite') ->setLabel(pht('Parent Site Name')) @@ -112,7 +112,7 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Change the blog parent site name.')) ->setConduitTypeDescription(pht('New blog parent site name.')) ->setValue($object->getParentSite()) - ->setTransactionType(PhameBlogTransaction::TYPE_PARENTSITE), + ->setTransactionType(PhameBlogParentSiteTransaction::TRANSACTIONTYPE), id(new PhabricatorTextEditField()) ->setKey('parentDomain') ->setLabel(pht('Parent Site URI')) @@ -120,11 +120,11 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Change the blog parent domain.')) ->setConduitTypeDescription(pht('New blog parent domain.')) ->setValue($object->getParentDomain()) - ->setTransactionType(PhameBlogTransaction::TYPE_PARENTDOMAIN), + ->setTransactionType(PhameBlogParentDomainTransaction::TRANSACTIONTYPE), id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) - ->setTransactionType(PhameBlogTransaction::TYPE_STATUS) + ->setTransactionType(PhameBlogStatusTransaction::TRANSACTIONTYPE) ->setIsConduitOnly(true) ->setOptions(PhameBlog::getStatusNameMap()) ->setDescription(pht('Active or archived status.')) diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index 14f63f51ac..f30a74065e 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -11,251 +11,22 @@ final class PhameBlogEditor return pht('Phame Blogs'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this blog.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = PhameBlogTransaction::TYPE_NAME; - $types[] = PhameBlogTransaction::TYPE_SUBTITLE; - $types[] = PhameBlogTransaction::TYPE_DESCRIPTION; - $types[] = PhameBlogTransaction::TYPE_FULLDOMAIN; - $types[] = PhameBlogTransaction::TYPE_PARENTSITE; - $types[] = PhameBlogTransaction::TYPE_PARENTDOMAIN; - $types[] = PhameBlogTransaction::TYPE_STATUS; - $types[] = PhameBlogTransaction::TYPE_HEADERIMAGE; - $types[] = PhameBlogTransaction::TYPE_PROFILEIMAGE; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - return $object->getName(); - case PhameBlogTransaction::TYPE_SUBTITLE: - return $object->getSubtitle(); - case PhameBlogTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhameBlogTransaction::TYPE_FULLDOMAIN: - return $object->getDomainFullURI(); - case PhameBlogTransaction::TYPE_PARENTSITE: - return $object->getParentSite(); - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - return $object->getParentDomain(); - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - return $object->getProfileImagePHID(); - case PhameBlogTransaction::TYPE_HEADERIMAGE: - return $object->getHeaderImagePHID(); - case PhameBlogTransaction::TYPE_STATUS: - return $object->getStatus(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - case PhameBlogTransaction::TYPE_SUBTITLE: - case PhameBlogTransaction::TYPE_DESCRIPTION: - case PhameBlogTransaction::TYPE_STATUS: - case PhameBlogTransaction::TYPE_PARENTSITE: - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - case PhameBlogTransaction::TYPE_HEADERIMAGE: - return $xaction->getNewValue(); - case PhameBlogTransaction::TYPE_FULLDOMAIN: - $domain = $xaction->getNewValue(); - if (!strlen($xaction->getNewValue())) { - return null; - } - return $domain; - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - return $object->setName($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_SUBTITLE: - return $object->setSubtitle($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_DESCRIPTION: - return $object->setDescription($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_FULLDOMAIN: - $new_value = $xaction->getNewValue(); - if (strlen($new_value)) { - $uri = new PhutilURI($new_value); - $domain = $uri->getDomain(); - $object->setDomain($domain); - } else { - $object->setDomain(null); - } - $object->setDomainFullURI($new_value); - return; - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - return $object->setProfileImagePHID($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_HEADERIMAGE: - return $object->setHeaderImagePHID($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_STATUS: - return $object->setStatus($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_PARENTSITE: - return $object->setParentSite($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - return $object->setParentDomain($xaction->getNewValue()); - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - case PhameBlogTransaction::TYPE_SUBTITLE: - case PhameBlogTransaction::TYPE_DESCRIPTION: - case PhameBlogTransaction::TYPE_FULLDOMAIN: - case PhameBlogTransaction::TYPE_PARENTSITE: - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - case PhameBlogTransaction::TYPE_HEADERIMAGE: - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - case PhameBlogTransaction::TYPE_STATUS: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - - switch ($type) { - case PhameBlogTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (phutil_utf8_strlen($new) > 64) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The selected blog title is too long. The maximum length '. - 'of a blog title is 64 characters.'), - $xaction); - } - } - break; - case PhameBlogTransaction::TYPE_SUBTITLE: - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (phutil_utf8_strlen($new) > 64) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The selected blog subtitle is too long. The maximum length '. - 'of a blog subtitle is 64 characters.'), - $xaction); - } - } - break; - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - if (!$xactions) { - continue; - } - $parent_domain = last($xactions)->getNewValue(); - if (empty($parent_domain)) { - continue; - } - try { - PhabricatorEnv::requireValidRemoteURIForLink($parent_domain); - } catch (Exception $ex) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid URI'), - pht('Parent Domain must be set to a valid Remote URI.'), - nonempty(last($xactions), null)); - $errors[] = $error; - } - break; - case PhameBlogTransaction::TYPE_FULLDOMAIN: - if (!$xactions) { - continue; - } - $custom_domain = last($xactions)->getNewValue(); - if (empty($custom_domain)) { - continue; - } - list($error_label, $error_text) = - $object->validateCustomDomain($custom_domain); - if ($error_label) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - $error_label, - $error_text, - nonempty(last($xactions), null)); - $errors[] = $error; - } - if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) { - $error_text = pht( - 'For custom domains to work, the blog must have a view policy of '. - 'public.'); - $error = new PhabricatorApplicationTransactionValidationError( - PhabricatorTransactions::TYPE_VIEW_POLICY, - pht('Invalid Policy'), - $error_text, - nonempty(last($xactions), null)); - $errors[] = $error; - } - $domain = new PhutilURI($custom_domain); - $domain = $domain->getDomain(); - $duplicate_blog = id(new PhameBlogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withDomain($domain) - ->executeOne(); - if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Not Unique'), - pht('Domain must be unique; another blog already has this domain.'), - nonempty(last($xactions), null)); - $errors[] = $error; - } - - break; - } - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index bd4954d0ab..b72fc4bcfe 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -127,53 +127,41 @@ final class PhameBlog extends PhameDAO $supported_protocols = array('http', 'https'); if (!in_array($protocol, $supported_protocols)) { - return array( - $label, - pht( + return pht( 'The custom domain should include a valid protocol in the URI '. '(for example, "%s"). Valid protocols are "http" or "https".', - $example_domain), - ); + $example_domain); } if (strlen($path) && $path != '/') { - return array( - $label, - pht( + return pht( 'The custom domain should not specify a path (hosting a Phame '. 'blog at a path is currently not supported). Instead, just provide '. 'the bare domain name (for example, "%s").', - $example_domain), - ); + $example_domain); } if (strpos($domain, '.') === false) { - return array( - $label, - pht( + return pht( 'The custom domain should contain at least one dot (.) because '. 'some browsers fail to set cookies on domains without a dot. '. 'Instead, use a normal looking domain name like "%s".', - $example_domain), - ); + $example_domain); } if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { $href = PhabricatorEnv::getProductionURI( '/config/edit/policy.allow-public/'); - return array( - pht('Fix Configuration'), - pht( - 'For custom domains to work, this Phabricator instance must be '. - 'configured to allow the public access policy. Configure this '. - 'setting %s, or ask an administrator to configure this setting. '. - 'The domain can be specified later once this setting has been '. - 'changed.', - phutil_tag( - 'a', - array('href' => $href), - pht('here'))), - ); + return pht( + 'For custom domains to work, this Phabricator instance must be '. + 'configured to allow the public access policy. Configure this '. + 'setting %s, or ask an administrator to configure this setting. '. + 'The domain can be specified later once this setting has been '. + 'changed.', + phutil_tag( + 'a', + array('href' => $href), + pht('here'))); } return null; diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index f08c7be688..c5702a2b18 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -1,17 +1,7 @@ getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - if ($old === null) { - return true; - } - } - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $req_phids = array(); - switch ($this->getTransactionType()) { - case self::TYPE_PROFILEIMAGE: - case self::TYPE_HEADERIMAGE: - $req_phids[] = $old; - $req_phids[] = $new; - break; - } - - return array_merge($req_phids, parent::getRequiredHandlePHIDs()); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - break; - case self::TYPE_DESCRIPTION: - case self::TYPE_FULLDOMAIN: - return 'fa-pencil'; - case self::TYPE_HEADERIMAGE: - return 'fa-image'; - case self::TYPE_PROFILEIMAGE: - return 'fa-star'; - case self::TYPE_STATUS: - if ($new == PhameBlog::STATUS_ARCHIVED) { - return 'fa-ban'; - } else { - return 'fa-check'; - } - break; - } - return parent::getIcon(); - } - - public function getColor() { - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($new == PhameBlog::STATUS_ARCHIVED) { - return 'violet'; - } else { - return 'green'; - } - } - return parent::getColor(); + public function getBaseTransactionClass() { + return 'PhameBlogTransactionType'; } public function getMailTags() { @@ -121,247 +43,4 @@ final class PhameBlogTransaction return $tags; } - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this blog.', - $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this blog.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s updated the blog\'s name to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_SUBTITLE: - if ($old === null) { - return pht( - '%s set this blog\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the blog\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the blog\'s description.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_FULLDOMAIN: - return pht( - '%s updated the blog\'s full domain to "%s".', - $this->renderHandleLink($author_phid), - $new); - break; - case self::TYPE_PARENTSITE: - if ($old === null) { - return pht( - '%s set this blog\'s parent site to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the blog\'s parent site to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_PARENTDOMAIN: - if ($old === null) { - return pht( - '%s set this blog\'s parent domain to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the blog\'s parent domain to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_HEADERIMAGE: - if (!$old) { - return pht( - "%s set this blog's header image to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this blog's header image.", - $this->renderHandleLink($author_phid)); - } else { - return pht( - "%s updated this blog's header image from %s to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - case self::TYPE_PROFILEIMAGE: - if (!$old) { - return pht( - "%s set this blog's profile image to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this blog's profile image.", - $this->renderHandleLink($author_phid)); - } else { - return pht( - "%s updated this blog's profile image from %s to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - case self::TYPE_STATUS: - switch ($new) { - case PhameBlog::STATUS_ACTIVE: - return pht( - '%s published this blog.', - $this->renderHandleLink($author_phid)); - case PhameBlog::STATUS_ARCHIVED: - return pht( - '%s archived this blog.', - $this->renderHandleLink($author_phid)); - } - - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the name for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_SUBTITLE: - if ($old === null) { - return pht( - '%s set the subtitle for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the subtitle for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_FULLDOMAIN: - return pht( - '%s updated the full domain for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_PARENTSITE: - return pht( - '%s updated the parent site for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_PARENTDOMAIN: - return pht( - '%s updated the parent domain for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_HEADERIMAGE: - return pht( - '%s updated the header image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_PROFILEIMAGE: - return pht( - '%s updated the profile image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_STATUS: - switch ($new) { - case PhameBlog::STATUS_ACTIVE: - return pht( - '%s published the blog %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PhameBlog::STATUS_ARCHIVED: - return pht( - '%s archived the blog %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - - } - - return parent::getTitleForFeed(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - - return parent::renderChangeDetails($viewer); - } - } diff --git a/src/applications/phame/xaction/PhameBlogDescriptionTransaction.php b/src/applications/phame/xaction/PhameBlogDescriptionTransaction.php new file mode 100644 index 0000000000..2bc12f3998 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogDescriptionTransaction.php @@ -0,0 +1,60 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO BLOG DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function getIcon() { + return 'fa-file-text-o'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogFullDomainTransaction.php b/src/applications/phame/xaction/PhameBlogFullDomainTransaction.php new file mode 100644 index 0000000000..5287f9cc06 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogFullDomainTransaction.php @@ -0,0 +1,95 @@ +getDomainFullURI(); + } + + public function applyInternalEffects($object, $value) { + if (strlen($value)) { + $uri = new PhutilURI($value); + $domain = $uri->getDomain(); + $object->setDomain($domain); + } else { + $object->setDomain(null); + } + $object->setDomainFullURI($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set this blog\'s full domain to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s full domain from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set %s blog\'s full domain to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else { + return pht( + '%s updated %s blog\'s full domain from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $custom_domain = last($xactions)->getNewValue(); + if (empty($custom_domain)) { + return $errors; + } + + $error_text = $object->validateCustomDomain($custom_domain); + if ($error_text) { + $errors[] = $this->newInvalidError($error_text); + } + + if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) { + $errors[] = $this->newInvalidError( + pht('For custom domains to work, the blog must have a view policy of '. + 'public. This blog is currently set to "%s".', + $object->getViewPolicy())); + } + + $domain = new PhutilURI($custom_domain); + $domain = $domain->getDomain(); + $duplicate_blog = id(new PhameBlogQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withDomain($domain) + ->executeOne(); + if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) { + $errors[] = $this->newInvalidError( + pht('Domain must be unique; another blog already has this domain.')); + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php b/src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php new file mode 100644 index 0000000000..b328b9aca5 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php @@ -0,0 +1,34 @@ +getHeaderImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setHeaderImagePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the header image for this blog.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the header image for blog %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-camera'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogNameTransaction.php b/src/applications/phame/xaction/PhameBlogNameTransaction.php new file mode 100644 index 0000000000..dbca5304a8 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogNameTransaction.php @@ -0,0 +1,59 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this blog from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s blog froms %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Blogs must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-rss'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogParentDomainTransaction.php b/src/applications/phame/xaction/PhameBlogParentDomainTransaction.php new file mode 100644 index 0000000000..227c8ce8c3 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogParentDomainTransaction.php @@ -0,0 +1,82 @@ +getParentDomain(); + } + + public function applyInternalEffects($object, $value) { + $object->setParentDomain($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set this blog\'s parent domain to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s parent domain from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set %s blog\'s parent domain to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else { + return pht( + '%s updated %s blog\'s parent domain from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $parent_domain = last($xactions)->getNewValue(); + if (empty($parent_domain)) { + return $errors; + } + + try { + PhabricatorEnv::requireValidRemoteURIForLink($parent_domain); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + pht('Parent Domain must be set to a valid Remote URI.')); + } + + $max_length = $object->getColumnMaximumByteLength('parentDomain'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The parent domain can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } +} diff --git a/src/applications/phame/xaction/PhameBlogParentSiteTransaction.php b/src/applications/phame/xaction/PhameBlogParentSiteTransaction.php new file mode 100644 index 0000000000..1b62b98488 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogParentSiteTransaction.php @@ -0,0 +1,66 @@ +getParentSite(); + } + + public function applyInternalEffects($object, $value) { + $object->setParentSite($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set this blog\'s parent site to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s parent site from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set %s blog\'s parent site to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else { + return pht( + '%s updated %s blog\'s parent site from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('parentSite'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The parent site can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } +} diff --git a/src/applications/phame/xaction/PhameBlogProfileImageTransaction.php b/src/applications/phame/xaction/PhameBlogProfileImageTransaction.php new file mode 100644 index 0000000000..77acf680fd --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogProfileImageTransaction.php @@ -0,0 +1,34 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the profile image for this blog.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the profile image for blog %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-file-image-o'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogStatusTransaction.php b/src/applications/phame/xaction/PhameBlogStatusTransaction.php new file mode 100644 index 0000000000..21345ea249 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogStatusTransaction.php @@ -0,0 +1,55 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + switch ($new) { + case PhameBlog::STATUS_ACTIVE: + return pht( + '%s published this blog.', + $this->renderAuthor()); + case PhameBlog::STATUS_ARCHIVED: + return pht( + '%s archived this blog.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + switch ($new) { + case PhameBlog::STATUS_ACTIVE: + return pht( + '%s published the blog %s.', + $this->renderAuthor(), + $this->renderObject()); + case PhameBlog::STATUS_ARCHIVED: + return pht( + '%s archived the blog %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new == PhameBlog::STATUS_ARCHIVED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} diff --git a/src/applications/phame/xaction/PhameBlogSubtitleTransaction.php b/src/applications/phame/xaction/PhameBlogSubtitleTransaction.php new file mode 100644 index 0000000000..87788a31f7 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogSubtitleTransaction.php @@ -0,0 +1,63 @@ +getSubtitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubtitle($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set this blog\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('subtitle'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The subtitle can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogTransactionType.php b/src/applications/phame/xaction/PhameBlogTransactionType.php new file mode 100644 index 0000000000..cf83633b9f --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogTransactionType.php @@ -0,0 +1,4 @@ + Date: Wed, 3 May 2017 11:11:14 -0700 Subject: [PATCH 013/543] Update Macro for EditEngine Summary: Updates Macro to use EditEngine. Also removes "URL" field for adding a Macro, which I think it's worth pursuing. Test Plan: - Create a Macro - Forget to name it - Try a PDF - Use a Macro - Edit a macro (not working) Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17821 --- src/__phutil_library_map__.php | 4 +- .../PhabricatorMacroApplication.php | 3 +- .../PhabricatorMacroEditController.php | 302 +----------------- .../editor/PhabricatorMacroEditEngine.php | 88 +++++ .../macro/editor/PhabricatorMacroEditor.php | 9 +- .../storage/PhabricatorFileImageMacro.php | 20 +- .../PhabricatorMacroFileTransaction.php | 35 ++ .../PhabricatorMacroNameTransaction.php | 25 ++ 8 files changed, 181 insertions(+), 305 deletions(-) create mode 100644 src/applications/macro/editor/PhabricatorMacroEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0c71c8eab1..7716a29ce7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2982,6 +2982,7 @@ phutil_register_library_map(array( 'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php', 'PhabricatorMacroDisabledTransaction' => 'applications/macro/xaction/PhabricatorMacroDisabledTransaction.php', 'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php', + 'PhabricatorMacroEditEngine' => 'applications/macro/editor/PhabricatorMacroEditEngine.php', 'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php', 'PhabricatorMacroFileTransaction' => 'applications/macro/xaction/PhabricatorMacroFileTransaction.php', 'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php', @@ -8187,7 +8188,8 @@ phutil_register_library_map(array( 'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorMacroDisableController' => 'PhabricatorMacroController', 'PhabricatorMacroDisabledTransaction' => 'PhabricatorMacroTransactionType', - 'PhabricatorMacroEditController' => 'PhabricatorMacroController', + 'PhabricatorMacroEditController' => 'PhameBlogController', + 'PhabricatorMacroEditEngine' => 'PhabricatorEditEngine', 'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorMacroFileTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroListController' => 'PhabricatorMacroController', diff --git a/src/applications/macro/application/PhabricatorMacroApplication.php b/src/applications/macro/application/PhabricatorMacroApplication.php index 3519a3f520..d0e6bbab04 100644 --- a/src/applications/macro/application/PhabricatorMacroApplication.php +++ b/src/applications/macro/application/PhabricatorMacroApplication.php @@ -33,7 +33,8 @@ final class PhabricatorMacroApplication extends PhabricatorApplication { 'create/' => 'PhabricatorMacroEditController', 'view/(?P[1-9]\d*)/' => 'PhabricatorMacroViewController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorMacroCommentController', - 'edit/(?P[1-9]\d*)/' => 'PhabricatorMacroEditController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorMacroEditController', 'audio/(?P[1-9]\d*)/' => 'PhabricatorMacroAudioController', 'disable/(?P[1-9]\d*)/' => 'PhabricatorMacroDisableController', 'meme/' => 'PhabricatorMacroMemeController', diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index fc84e057ae..d4b7d5aeec 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -1,304 +1,10 @@ getViewer(); - $id = $request->getURIData('id'); - - $this->requireApplicationCapability( - PhabricatorMacroManageCapability::CAPABILITY); - - if ($id) { - $macro = id(new PhabricatorMacroQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needFiles(true) - ->executeOne(); - if (!$macro) { - return new Aphront404Response(); - } - } else { - $macro = new PhabricatorFileImageMacro(); - $macro->setAuthorPHID($viewer->getPHID()); - } - - $errors = array(); - $e_name = true; - $e_file = null; - $file = null; - - if ($request->isFormPost()) { - $original = clone $macro; - - $new_name = null; - if ($request->getBool('name_form') || !$macro->getID()) { - $new_name = $request->getStr('name'); - - $macro->setName($new_name); - - if (!strlen($macro->getName())) { - $errors[] = pht('Macro name is required.'); - $e_name = pht('Required'); - } else if (!preg_match('/^[a-z0-9:_-]{3,}\z/', $macro->getName())) { - $errors[] = pht( - 'Macro must be at least three characters long and contain only '. - 'lowercase letters, digits, hyphens, colons and underscores.'); - $e_name = pht('Invalid'); - } else { - $e_name = null; - } - } - - $uri = $request->getStr('url'); - - $engine = new PhabricatorDestructionEngine(); - - $file = null; - if ($request->getFileExists('file')) { - $file = PhabricatorFile::newFromPHPUpload( - $_FILES['file'], - array( - 'name' => $request->getStr('name'), - 'authorPHID' => $viewer->getPHID(), - 'isExplicitUpload' => true, - 'canCDN' => true, - )); - } else if ($uri) { - try { - // Rate limit outbound fetches to make this mechanism less useful for - // scanning networks and ports. - PhabricatorSystemActionEngine::willTakeAction( - array($viewer->getPHID()), - new PhabricatorFilesOutboundRequestAction(), - 1); - - $file = PhabricatorFile::newFromFileDownload( - $uri, - array( - 'name' => $request->getStr('name'), - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - 'isExplicitUpload' => true, - 'canCDN' => true, - )); - - if (!$file->isViewableInBrowser()) { - $mime_type = $file->getMimeType(); - $engine->destroyObject($file); - $file = null; - throw new Exception( - pht( - 'The URI "%s" does not correspond to a valid image file, got '. - 'a file with MIME type "%s". You must specify the URI of a '. - 'valid image file.', - $uri, - $mime_type)); - } else { - $file - ->setAuthorPHID($viewer->getPHID()) - ->save(); - } - } catch (HTTPFutureHTTPResponseStatus $status) { - $errors[] = pht( - 'The URI "%s" could not be loaded, got %s error.', - $uri, - $status->getStatusCode()); - } catch (Exception $ex) { - $errors[] = $ex->getMessage(); - } - } else if ($request->getStr('phid')) { - $file = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($request->getStr('phid'))) - ->executeOne(); - } - - if ($file) { - if (!$file->isViewableInBrowser()) { - $errors[] = pht('You must upload an image.'); - $e_file = pht('Invalid'); - } else { - $macro->setFilePHID($file->getPHID()); - $macro->attachFile($file); - $e_file = null; - } - } - - if (!$macro->getID() && !$file) { - $errors[] = pht('You must upload an image to create a macro.'); - $e_file = pht('Required'); - } - - if (!$errors) { - try { - $xactions = array(); - - if ($new_name !== null) { - $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType( - PhabricatorMacroNameTransaction::TRANSACTIONTYPE) - ->setNewValue($new_name); - } - - if ($file) { - $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType( - PhabricatorMacroFileTransaction::TRANSACTIONTYPE) - ->setNewValue($file->getPHID()); - } - - $editor = id(new PhabricatorMacroEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request); - - $xactions = $editor->applyTransactions($original, $xactions); - - $view_uri = $this->getApplicationURI('/view/'.$original->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($view_uri); - } catch (AphrontDuplicateKeyQueryException $ex) { - throw $ex; - $errors[] = pht('Macro name is not unique!'); - $e_name = pht('Duplicate'); - } - } - } - - $current_file = null; - if ($macro->getFilePHID()) { - $current_file = $macro->getFile(); - } - - $form = new AphrontFormView(); - $form->addHiddenInput('name_form', 1); - $form->setUser($request->getUser()); - - $form - ->setEncType('multipart/form-data') - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($macro->getName()) - ->setCaption( - pht('This word or phrase will be replaced with the image.')) - ->setError($e_name)); - - if (!$macro->getID()) { - if ($current_file) { - $current_file_view = id(new PhabricatorFileLinkView()) - ->setViewer($viewer) - ->setFilePHID($current_file->getPHID()) - ->setFileName($current_file->getName()) - ->setFileViewable(true) - ->setFileViewURI($current_file->getBestURI()) - ->render(); - $form->addHiddenInput('phid', $current_file->getPHID()); - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Selected File')) - ->setValue($current_file_view)); - - $other_label = pht('Change File'); - } else { - $other_label = pht('File'); - } - - $form->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('URL')) - ->setName('url') - ->setValue($request->getStr('url')) - ->setError($request->getFileExists('file') ? false : $e_file)); - - $form->appendChild( - id(new AphrontFormFileControl()) - ->setLabel($other_label) - ->setName('file') - ->setError($request->getStr('url') ? false : $e_file)); - } - - - $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); - - if ($macro->getID()) { - $cancel_uri = $view_uri; - } else { - $cancel_uri = $this->getApplicationURI(); - } - - $form - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Image Macro')) - ->addCancelButton($cancel_uri)); - - $crumbs = $this->buildApplicationCrumbs(); - - if ($macro->getID()) { - $title = pht('Edit Macro: %s', $macro->getName()); - $crumb = pht('Edit Macro'); - $header_icon = 'fa-pencil'; - - $crumbs->addTextCrumb(pht('Macro: %s', $macro->getName()), $view_uri); - } else { - $title = pht('Create Image Macro'); - $crumb = pht('Create Macro'); - $header_icon = 'fa-plus-square'; - } - - $crumbs->addTextCrumb($crumb, $request->getRequestURI()); - $crumbs->setBorder(true); - - $upload = null; - if ($macro->getID()) { - $upload_form = id(new AphrontFormView()) - ->setEncType('multipart/form-data') - ->setUser($request->getUser()); - - $upload_form->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('URL')) - ->setName('url') - ->setValue($request->getStr('url'))); - - $upload_form - ->appendChild( - id(new AphrontFormFileControl()) - ->setLabel(pht('File')) - ->setName('file')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Upload File'))); - - $upload = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Upload New File')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($upload_form); - } - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Macro')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - $upload, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return id(new PhabricatorMacroEditEngine()) + ->setController($this) + ->buildResponse(); } - } diff --git a/src/applications/macro/editor/PhabricatorMacroEditEngine.php b/src/applications/macro/editor/PhabricatorMacroEditEngine.php new file mode 100644 index 0000000000..3f472ff0ce --- /dev/null +++ b/src/applications/macro/editor/PhabricatorMacroEditEngine.php @@ -0,0 +1,88 @@ +getViewer(); + return PhabricatorFileImageMacro::initializeNewFileImageMacro($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorMacroQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Macro'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Macro'); + } + + protected function getObjectName() { + return pht('Macro'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhabricatorMacroManageCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Macro name.')) + ->setConduitDescription(pht('Rename the macro.')) + ->setConduitTypeDescription(pht('New macro name.')) + ->setTransactionType(PhabricatorMacroNameTransaction::TRANSACTIONTYPE) + ->setValue($object->getName()), + id(new PhabricatorFileEditField()) + ->setKey('filePHID') + ->setLabel(pht('Image File')) + ->setDescription(pht('Image file to import.')) + ->setTransactionType(PhabricatorMacroFileTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('File PHID to import.')) + ->setConduitTypeDescription(pht('File PHID.')), + ); + + } + +} diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index 797fa702a9..d9067c5367 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -11,11 +11,12 @@ final class PhabricatorMacroEditor return pht('Macros'); } - public function getTransactionTypes() { - $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; + public function getCreateObjectTitle($author, $object) { + return pht('%s created this macro.', $author); + } - return $types; + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); } protected function applyCustomExternalTransaction( diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php index bc23e639d7..0cd6726f5f 100644 --- a/src/applications/macro/storage/PhabricatorFileImageMacro.php +++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php @@ -41,6 +41,12 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO return $this->assertAttached($this->audio); } + public static function initializeNewFileImageMacro(PhabricatorUser $actor) { + $macro = id(new self()) + ->setAuthorPHID($actor->getPHID()); + return $macro; + } + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -80,6 +86,10 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO return parent::save(); } + public function getViewURI() { + return '/macro/view/'.$this->getID().'/'; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -128,11 +138,19 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { - return PhabricatorPolicies::getMostOpenPolicy(); + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + $app = PhabricatorApplication::getByClass( + 'PhabricatorMacroApplication'); + return $app->getPolicy(PhabricatorMacroManageCapability::CAPABILITY); + } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php index 40e51fe1df..77c9f24dd9 100644 --- a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php @@ -26,6 +26,41 @@ final class PhabricatorMacroFileTransaction $this->renderObject()); } + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + if ($this->isEmptyTextTransaction($file_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Image macros must have a file.')); + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableInBrowser()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + + } + + return $errors; + } + public function getIcon() { return 'fa-file-image-o'; } diff --git a/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php index ebd81530f5..5b7b6f4417 100644 --- a/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php @@ -32,6 +32,7 @@ final class PhabricatorMacroNameTransaction public function validateTransactions($object, array $xactions) { $errors = array(); + $viewer = $this->getActor(); if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { $errors[] = $this->newRequiredError( @@ -40,13 +41,37 @@ final class PhabricatorMacroNameTransaction $max_length = $object->getColumnMaximumByteLength('name'); foreach ($xactions as $xaction) { + $old_value = $this->generateOldValue($object); $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); if ($new_length > $max_length) { $errors[] = $this->newInvalidError( pht('The name can be no longer than %s characters.', new PhutilNumber($max_length))); } + + if (!preg_match('/^[a-z0-9:_-]{3,}\z/', $new_value)) { + $errors[] = $this->newInvalidError( + pht('Macro name "%s" be at least three characters long and contain '. + 'only lowercase letters, digits, hyphens, colons and '. + 'underscores.', + $new_value)); + } + + // Check name is unique when updating / creating + if ($old_value != $new_value) { + $macro = id(new PhabricatorMacroQuery()) + ->setViewer($viewer) + ->withNames(array($new_value)) + ->executeOne(); + + if ($macro) { + $errors[] = $this->newInvalidError( + pht('Macro "%s" already exists.', $new_value)); + } + } + } return $errors; From 4aec809b69e2b314cf5b620f098a694895b326fa Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 3 May 2017 09:26:20 -0700 Subject: [PATCH 014/543] Update PhamePost for modular transactions Summary: Updates PhamePost for modular transactions. Test Plan: - Create a post - Edit a post - Add a header image - Delete header image - Award Token - Leave comment - Unpublish post - Check History page - Move post - Archive post {F4936456} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17818 --- src/__phutil_library_map__.php | 16 +- .../post/PhamePostArchiveController.php | 2 +- .../post/PhamePostHeaderPictureController.php | 2 +- .../post/PhamePostMoveController.php | 2 +- .../post/PhamePostPublishController.php | 2 +- .../phame/editor/PhamePostEditEngine.php | 10 +- .../phame/editor/PhamePostEditor.php | 172 +---------- .../phame/storage/PhamePostTransaction.php | 270 +----------------- .../xaction/PhameBlogNameTransaction.php | 2 +- .../xaction/PhameBlogStatusTransaction.php | 10 +- .../xaction/PhamePostBlogTransaction.php | 64 +++++ .../xaction/PhamePostBodyTransaction.php | 60 ++++ .../PhamePostHeaderImageTransaction.php | 33 +++ .../xaction/PhamePostSubtitleTransaction.php | 63 ++++ .../xaction/PhamePostTitleTransaction.php | 55 ++++ .../xaction/PhamePostTransactionType.php | 4 + .../PhamePostVisibilityTransaction.php | 70 +++++ 17 files changed, 392 insertions(+), 445 deletions(-) create mode 100644 src/applications/phame/xaction/PhamePostBlogTransaction.php create mode 100644 src/applications/phame/xaction/PhamePostBodyTransaction.php create mode 100644 src/applications/phame/xaction/PhamePostHeaderImageTransaction.php create mode 100644 src/applications/phame/xaction/PhamePostSubtitleTransaction.php create mode 100644 src/applications/phame/xaction/PhamePostTitleTransaction.php create mode 100644 src/applications/phame/xaction/PhamePostTransactionType.php create mode 100644 src/applications/phame/xaction/PhamePostVisibilityTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7716a29ce7..70c5ea2cf1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4272,12 +4272,15 @@ phutil_register_library_map(array( 'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php', + 'PhamePostBlogTransaction' => 'applications/phame/xaction/PhamePostBlogTransaction.php', + 'PhamePostBodyTransaction' => 'applications/phame/xaction/PhamePostBodyTransaction.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', 'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', 'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php', + 'PhamePostHeaderImageTransaction' => 'applications/phame/xaction/PhamePostHeaderImageTransaction.php', 'PhamePostHeaderPictureController' => 'applications/phame/controller/post/PhamePostHeaderPictureController.php', 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', @@ -4290,10 +4293,14 @@ phutil_register_library_map(array( 'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php', 'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php', 'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php', + 'PhamePostSubtitleTransaction' => 'applications/phame/xaction/PhamePostSubtitleTransaction.php', + 'PhamePostTitleTransaction' => 'applications/phame/xaction/PhamePostTitleTransaction.php', 'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php', 'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php', 'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php', + 'PhamePostTransactionType' => 'applications/phame/xaction/PhamePostTransactionType.php', 'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php', + 'PhamePostVisibilityTransaction' => 'applications/phame/xaction/PhamePostVisibilityTransaction.php', 'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php', 'PhameSite' => 'applications/phame/site/PhameSite.php', 'PhluxController' => 'applications/phlux/controller/PhluxController.php', @@ -9753,12 +9760,15 @@ phutil_register_library_map(array( 'PhabricatorFulltextInterface', ), 'PhamePostArchiveController' => 'PhamePostController', + 'PhamePostBlogTransaction' => 'PhamePostTransactionType', + 'PhamePostBodyTransaction' => 'PhamePostTransactionType', 'PhamePostController' => 'PhameController', 'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhamePostEditController' => 'PhamePostController', 'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', 'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhamePostHeaderImageTransaction' => 'PhamePostTransactionType', 'PhamePostHeaderPictureController' => 'PhamePostController', 'PhamePostHistoryController' => 'PhamePostController', 'PhamePostListController' => 'PhamePostController', @@ -9771,10 +9781,14 @@ phutil_register_library_map(array( 'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhamePostTransaction' => 'PhabricatorApplicationTransaction', + 'PhamePostSubtitleTransaction' => 'PhamePostTransactionType', + 'PhamePostTitleTransaction' => 'PhamePostTransactionType', + 'PhamePostTransaction' => 'PhabricatorModularTransaction', 'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhamePostTransactionType' => 'PhabricatorModularTransactionType', 'PhamePostViewController' => 'PhameLiveController', + 'PhamePostVisibilityTransaction' => 'PhamePostTransactionType', 'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhameSite' => 'PhabricatorSite', 'PhluxController' => 'PhabricatorController', diff --git a/src/applications/phame/controller/post/PhamePostArchiveController.php b/src/applications/phame/controller/post/PhamePostArchiveController.php index 5a3b32944a..b8647121ef 100644 --- a/src/applications/phame/controller/post/PhamePostArchiveController.php +++ b/src/applications/phame/controller/post/PhamePostArchiveController.php @@ -26,7 +26,7 @@ final class PhamePostArchiveController extends PhamePostController { $new_value = PhameConstants::VISIBILITY_ARCHIVED; $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); id(new PhamePostEditor()) diff --git a/src/applications/phame/controller/post/PhamePostHeaderPictureController.php b/src/applications/phame/controller/post/PhamePostHeaderPictureController.php index 2e60c9ba71..60075cc077 100644 --- a/src/applications/phame/controller/post/PhamePostHeaderPictureController.php +++ b/src/applications/phame/controller/post/PhamePostHeaderPictureController.php @@ -61,7 +61,7 @@ final class PhamePostHeaderPictureController $xactions = array(); $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_HEADERIMAGE) + ->setTransactionType(PhamePostHeaderImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhamePostEditor()) diff --git a/src/applications/phame/controller/post/PhamePostMoveController.php b/src/applications/phame/controller/post/PhamePostMoveController.php index 3e09a9ba2d..d088b260b5 100644 --- a/src/applications/phame/controller/post/PhamePostMoveController.php +++ b/src/applications/phame/controller/post/PhamePostMoveController.php @@ -28,7 +28,7 @@ final class PhamePostMoveController extends PhamePostController { $xactions = array(); $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setTransactionType(PhamePostBlogTransaction::TRANSACTIONTYPE) ->setNewValue($v_blog); $editor = id(new PhamePostEditor()) diff --git a/src/applications/phame/controller/post/PhamePostPublishController.php b/src/applications/phame/controller/post/PhamePostPublishController.php index 567099f0d4..70989082bd 100644 --- a/src/applications/phame/controller/post/PhamePostPublishController.php +++ b/src/applications/phame/controller/post/PhamePostPublishController.php @@ -34,7 +34,7 @@ final class PhamePostPublishController extends PhamePostController { } $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); id(new PhamePostEditor()) diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index e80c1f6d0d..af1b1091d1 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -84,7 +84,7 @@ final class PhamePostEditEngine pht('Choose a blog to create a post on (or move a post to).')) ->setConduitTypeDescription(pht('PHID of the blog.')) ->setAliases(array('blogPHID')) - ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setTransactionType(PhamePostBlogTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDListHTTPParameterType()) ->setSingleValue($blog_phid) ->setIsReorderable(false) @@ -97,7 +97,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post title.')) ->setConduitDescription(pht('Retitle the post.')) ->setConduitTypeDescription(pht('New post title.')) - ->setTransactionType(PhamePostTransaction::TYPE_TITLE) + ->setTransactionType(PhamePostTitleTransaction::TRANSACTIONTYPE) ->setValue($object->getTitle()), id(new PhabricatorTextEditField()) ->setKey('subtitle') @@ -105,7 +105,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post subtitle.')) ->setConduitDescription(pht('Change the post subtitle.')) ->setConduitTypeDescription(pht('New post subtitle.')) - ->setTransactionType(PhamePostTransaction::TYPE_SUBTITLE) + ->setTransactionType(PhamePostSubtitleTransaction::TRANSACTIONTYPE) ->setValue($object->getSubtitle()), id(new PhabricatorSelectEditField()) ->setKey('visibility') @@ -113,7 +113,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post visibility.')) ->setConduitDescription(pht('Change post visibility.')) ->setConduitTypeDescription(pht('New post visibility constant.')) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE) ->setValue($object->getVisibility()) ->setOptions(PhameConstants::getPhamePostStatusMap()), id(new PhabricatorRemarkupEditField()) @@ -122,7 +122,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post body.')) ->setConduitDescription(pht('Change post body.')) ->setConduitTypeDescription(pht('New post body.')) - ->setTransactionType(PhamePostTransaction::TYPE_BODY) + ->setTransactionType(PhamePostBodyTransaction::TRANSACTIONTYPE) ->setValue($object->getBody()) ->setPreviewPanel( id(new PHUIRemarkupPreviewPanel()) diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 929613fe80..8f0e2b5099 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -11,177 +11,21 @@ final class PhamePostEditor return pht('Phame Posts'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this post.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = PhamePostTransaction::TYPE_BLOG; - $types[] = PhamePostTransaction::TYPE_TITLE; - $types[] = PhamePostTransaction::TYPE_SUBTITLE; - $types[] = PhamePostTransaction::TYPE_BODY; - $types[] = PhamePostTransaction::TYPE_VISIBILITY; - $types[] = PhamePostTransaction::TYPE_HEADERIMAGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_BLOG: - return $object->getBlogPHID(); - case PhamePostTransaction::TYPE_TITLE: - return $object->getTitle(); - case PhamePostTransaction::TYPE_SUBTITLE: - return $object->getSubtitle(); - case PhamePostTransaction::TYPE_BODY: - return $object->getBody(); - case PhamePostTransaction::TYPE_VISIBILITY: - return $object->getVisibility(); - case PhamePostTransaction::TYPE_HEADERIMAGE: - return $object->getHeaderImagePHID(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_TITLE: - case PhamePostTransaction::TYPE_SUBTITLE: - case PhamePostTransaction::TYPE_BODY: - case PhamePostTransaction::TYPE_VISIBILITY: - case PhamePostTransaction::TYPE_HEADERIMAGE: - case PhamePostTransaction::TYPE_BLOG: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_TITLE: - return $object->setTitle($xaction->getNewValue()); - case PhamePostTransaction::TYPE_SUBTITLE: - return $object->setSubtitle($xaction->getNewValue()); - case PhamePostTransaction::TYPE_BODY: - return $object->setBody($xaction->getNewValue()); - case PhamePostTransaction::TYPE_BLOG: - return $object->setBlogPHID($xaction->getNewValue()); - case PhamePostTransaction::TYPE_HEADERIMAGE: - return $object->setHeaderImagePHID($xaction->getNewValue()); - case PhamePostTransaction::TYPE_VISIBILITY: - if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { - $object->setDatePublished(0); - } else if ($xaction->getNewValue() == - PhameConstants::VISIBILITY_ARCHIVED) { - $object->setDatePublished(0); - } else { - $object->setDatePublished(PhabricatorTime::getNow()); - } - return $object->setVisibility($xaction->getNewValue()); - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_TITLE: - case PhamePostTransaction::TYPE_SUBTITLE: - case PhamePostTransaction::TYPE_BODY: - case PhamePostTransaction::TYPE_VISIBILITY: - case PhamePostTransaction::TYPE_HEADERIMAGE: - case PhamePostTransaction::TYPE_BLOG: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhamePostTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhamePostTransaction::TYPE_BLOG: - if ($this->getIsNewObject()) { - if (!$xactions) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht( - 'When creating a post, you must specify which blog it '. - 'should belong to.'), - null); - - $error->setIsMissingFieldError(true); - - $errors[] = $error; - break; - } - } - - foreach ($xactions as $xaction) { - $new_phid = $xaction->getNewValue(); - - $blog = id(new PhameBlogQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - - if ($blog) { - continue; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The specified blog PHID ("%s") is not valid. You can only '. - 'create a post on (or move a post into) a blog which you '. - 'have permission to see and edit.', - $new_phid), - $xaction); - } - - break; - } - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index 6e54aeda6d..1c259911f3 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -1,14 +1,7 @@ getTransactionType()) { - case self::TYPE_BODY: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function shouldHide() { - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $phids = parent::getRequiredHandlePHIDs(); - - switch ($this->getTransactionType()) { - case self::TYPE_BLOG: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - if ($old) { - $phids[] = $old; - } - - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return 'fa-plus'; - break; - case self::TYPE_HEADERIMAGE: - return 'fa-camera-retro'; - break; - case self::TYPE_VISIBILITY: - if ($new == PhameConstants::VISIBILITY_PUBLISHED) { - return 'fa-globe'; - } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { - return 'fa-ban'; - } else { - return 'fa-eye-slash'; - } - break; - } - return parent::getIcon(); - } - public function getMailTags() { $tags = parent::getMailTags(); @@ -110,200 +46,4 @@ final class PhamePostTransaction return $tags; } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s authored this post.', - $this->renderHandleLink($author_phid)); - case self::TYPE_BLOG: - return pht( - '%s moved this post from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s authored this post.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s updated the post\'s name to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_SUBTITLE: - if ($old === null) { - return pht( - '%s set the post\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the post\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_BODY: - return pht( - '%s updated the blog post.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_HEADERIMAGE: - return pht( - '%s updated the header image.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_VISIBILITY: - if ($new == PhameConstants::VISIBILITY_DRAFT) { - return pht( - '%s marked this post as a draft.', - $this->renderHandleLink($author_phid)); - } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { - return pht( - '%s archived this post.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s published this post.', - $this->renderHandleLink($author_phid)); - } - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s authored %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_BLOG: - return pht( - '%s moved post "%s" from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s authored %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the name for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_SUBTITLE: - return pht( - '%s updated the subtitle for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_BODY: - return pht( - '%s updated the blog post %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_HEADERIMAGE: - return pht( - '%s updated the header image for post %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_VISIBILITY: - if ($new == PhameConstants::VISIBILITY_DRAFT) { - return pht( - '%s marked %s as a draft.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { - return pht( - '%s marked %s as archived.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s published %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_BODY: - if ($old === null) { - return $this->getNewValue(); - } - break; - } - - return null; - } - - public function getColor() { - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return PhabricatorTransactions::COLOR_GREEN; - } - return parent::getColor(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_BODY: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - switch ($this->getTransactionType()) { - case self::TYPE_BODY: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - - return parent::renderChangeDetails($viewer); - } - } diff --git a/src/applications/phame/xaction/PhameBlogNameTransaction.php b/src/applications/phame/xaction/PhameBlogNameTransaction.php index dbca5304a8..c219ba494b 100644 --- a/src/applications/phame/xaction/PhameBlogNameTransaction.php +++ b/src/applications/phame/xaction/PhameBlogNameTransaction.php @@ -23,7 +23,7 @@ final class PhameBlogNameTransaction public function getTitleForFeed() { return pht( - '%s renamed %s blog froms %s to %s.', + '%s renamed %s blog from %s to %s.', $this->renderAuthor(), $this->renderObject(), $this->renderOldValue(), diff --git a/src/applications/phame/xaction/PhameBlogStatusTransaction.php b/src/applications/phame/xaction/PhameBlogStatusTransaction.php index 21345ea249..b3366bece7 100644 --- a/src/applications/phame/xaction/PhameBlogStatusTransaction.php +++ b/src/applications/phame/xaction/PhameBlogStatusTransaction.php @@ -45,11 +45,11 @@ final class PhameBlogStatusTransaction public function getIcon() { $new = $this->getNewValue(); - if ($new == PhameBlog::STATUS_ARCHIVED) { - return 'fa-ban'; - } else { - return 'fa-check'; - } + if ($new == PhameBlog::STATUS_ARCHIVED) { + return 'fa-ban'; + } else { + return 'fa-check'; } + } } diff --git a/src/applications/phame/xaction/PhamePostBlogTransaction.php b/src/applications/phame/xaction/PhamePostBlogTransaction.php new file mode 100644 index 0000000000..cfe335a6ce --- /dev/null +++ b/src/applications/phame/xaction/PhamePostBlogTransaction.php @@ -0,0 +1,64 @@ +getBlogPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setBlogPHID($value); + } + + public function getTitle() { + return pht( + '%s changed the blog for this post.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the blog for post %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getBlogPHID(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Posts must be attached to a blog.')); + } + + foreach ($xactions as $xaction) { + $new_phid = $xaction->getNewValue(); + + $blog = id(new PhameBlogQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + if ($blog) { + continue; + } + + $errors[] = $this->newInvalidError( + pht('The specified blog PHID ("%s") is not valid. You can only '. + 'create a post on (or move a post into) a blog which you '. + 'have permission to see and edit.', + $new_phid)); + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhamePostBodyTransaction.php b/src/applications/phame/xaction/PhamePostBodyTransaction.php new file mode 100644 index 0000000000..bf34cf73ff --- /dev/null +++ b/src/applications/phame/xaction/PhamePostBodyTransaction.php @@ -0,0 +1,60 @@ +getBody(); + } + + public function applyInternalEffects($object, $value) { + $object->setBody($value); + } + + public function getTitle() { + return pht( + '%s updated the post content.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the post content for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO POST CONTENT'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function getIcon() { + return 'fa-file-text-o'; + } + +} diff --git a/src/applications/phame/xaction/PhamePostHeaderImageTransaction.php b/src/applications/phame/xaction/PhamePostHeaderImageTransaction.php new file mode 100644 index 0000000000..2b9631cdc4 --- /dev/null +++ b/src/applications/phame/xaction/PhamePostHeaderImageTransaction.php @@ -0,0 +1,33 @@ +getHeaderImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setHeaderImagePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the header image for this post.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the header image for post %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-camera'; + } + +} diff --git a/src/applications/phame/xaction/PhamePostSubtitleTransaction.php b/src/applications/phame/xaction/PhamePostSubtitleTransaction.php new file mode 100644 index 0000000000..05b0f34790 --- /dev/null +++ b/src/applications/phame/xaction/PhamePostSubtitleTransaction.php @@ -0,0 +1,63 @@ +getSubtitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubtitle($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set this post\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the post\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('subtitle'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The subtitle can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhamePostTitleTransaction.php b/src/applications/phame/xaction/PhamePostTitleTransaction.php new file mode 100644 index 0000000000..99c556cc3a --- /dev/null +++ b/src/applications/phame/xaction/PhamePostTitleTransaction.php @@ -0,0 +1,55 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + return pht( + '%s renamed this blog post from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s blog post from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Posts must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhamePostTransactionType.php b/src/applications/phame/xaction/PhamePostTransactionType.php new file mode 100644 index 0000000000..b8ce182bc7 --- /dev/null +++ b/src/applications/phame/xaction/PhamePostTransactionType.php @@ -0,0 +1,4 @@ +getVisibility(); + } + + public function applyInternalEffects($object, $value) { + if ($value == PhameConstants::VISIBILITY_DRAFT) { + $object->setDatePublished(0); + } else if ($value == PhameConstants::VISIBILITY_ARCHIVED) { + $object->setDatePublished(0); + } else { + $object->setDatePublished(PhabricatorTime::getNow()); + } + $object->setVisibility($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new == PhameConstants::VISIBILITY_DRAFT) { + return pht( + '%s marked this post as a draft.', + $this->renderAuthor()); + } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { + return pht( + '%s archived this post.', + $this->renderAuthor()); + } else { + return pht( + '%s published this post.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new == PhameConstants::VISIBILITY_DRAFT) { + return pht( + '%s marked %s as a draft.', + $this->renderAuthor(), + $this->renderObject()); + } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { + return pht( + '%s marked %s as archived.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s published %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new == PhameConstants::VISIBILITY_PUBLISHED) { + return 'fa-rss'; + } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { + return 'fa-ban'; + } else { + return 'fa-eye-slash'; + } + } +} From d467a9e3378c3cdcdece8f54e44e9ee1f391b919 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 2 May 2017 08:52:09 -0700 Subject: [PATCH 015/543] Modernize PonderQuestion with EditEngine Summary: Just a small touch up to move this to edit engine. Test Plan: - Create a question - Edit a question - Close question - Test NUX state Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17812 --- src/__phutil_library_map__.php | 2 + .../PhabricatorPonderApplication.php | 36 +-- .../ponder/controller/PonderController.php | 10 +- .../PonderQuestionEditController.php | 221 +----------------- .../ponder/editor/PonderAnswerEditor.php | 8 + .../editor/PonderQuestionEditEngine.php | 109 +++++++++ .../ponder/editor/PonderQuestionEditor.php | 11 +- .../ponder/storage/PonderQuestion.php | 8 + .../PonderQuestionAnswerTransaction.php | 7 + 9 files changed, 170 insertions(+), 242 deletions(-) create mode 100644 src/applications/ponder/editor/PonderQuestionEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 70c5ea2cf1..9a3f4fb538 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4606,6 +4606,7 @@ phutil_register_library_map(array( 'PonderQuestionContentTransaction' => 'applications/ponder/xaction/PonderQuestionContentTransaction.php', 'PonderQuestionCreateMailReceiver' => 'applications/ponder/mail/PonderQuestionCreateMailReceiver.php', 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', + 'PonderQuestionEditEngine' => 'applications/ponder/editor/PonderQuestionEditEngine.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', 'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php', 'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php', @@ -10194,6 +10195,7 @@ phutil_register_library_map(array( 'PonderQuestionContentTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionCreateMailReceiver' => 'PhabricatorMailReceiver', 'PonderQuestionEditController' => 'PonderController', + 'PonderQuestionEditEngine' => 'PhabricatorEditEngine', 'PonderQuestionEditor' => 'PonderEditor', 'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine', 'PonderQuestionHistoryController' => 'PonderController', diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index ced8606e99..ed37c7ef6e 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -60,22 +60,26 @@ final class PhabricatorPonderApplication extends PhabricatorApplication { '/ponder/' => array( '(?:query/(?P[^/]+)/)?' => 'PonderQuestionListController', - 'answer/add/' - => 'PonderAnswerSaveController', - 'answer/edit/(?P\d+)/' - => 'PonderAnswerEditController', - 'answer/comment/(?P\d+)/' - => 'PonderAnswerCommentController', - 'answer/history/(?P\d+)/' - => 'PonderAnswerHistoryController', - 'question/edit/(?:(?P\d+)/)?' - => 'PonderQuestionEditController', - 'question/create/' - => 'PonderQuestionEditController', - 'question/comment/(?P\d+)/' - => 'PonderQuestionCommentController', - 'question/history/(?P\d+)/' - => 'PonderQuestionHistoryController', + 'answer/' => array( + 'add/' + => 'PonderAnswerSaveController', + 'edit/(?P\d+)/' + => 'PonderAnswerEditController', + 'comment/(?P\d+)/' + => 'PonderAnswerCommentController', + 'history/(?P\d+)/' + => 'PonderAnswerHistoryController', + ), + 'question/' => array( + $this->getEditRoutePattern('edit/') + => 'PonderQuestionEditController', + 'create/' + => 'PonderQuestionEditController', + 'comment/(?P\d+)/' + => 'PonderQuestionCommentController', + 'history/(?P\d+)/' + => 'PonderQuestionHistoryController', + ), 'preview/' => 'PhabricatorMarkupPreviewController', 'question/status/(?P[1-9]\d*)/' diff --git a/src/applications/ponder/controller/PonderController.php b/src/applications/ponder/controller/PonderController.php index a14d70c773..99edcc1af1 100644 --- a/src/applications/ponder/controller/PonderController.php +++ b/src/applications/ponder/controller/PonderController.php @@ -27,13 +27,9 @@ abstract class PonderController extends PhabricatorController { protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $href = $this->getApplicationURI('question/create/'); - $crumbs - ->addAction( - id(new PHUIListItemView()) - ->setName(pht('Ask Question')) - ->setHref($href) - ->setIcon('fa-plus-square')); + id(new PonderQuestionEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php index c872933ca2..c881d60f92 100644 --- a/src/applications/ponder/controller/PonderQuestionEditController.php +++ b/src/applications/ponder/controller/PonderQuestionEditController.php @@ -1,222 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $question = id(new PonderQuestionQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$question) { - return new Aphront404Response(); - } - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $question->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $is_new = false; - } else { - $is_new = true; - $question = PonderQuestion::initializeNewQuestion($viewer); - $v_projects = array(); - } - - $v_title = $question->getTitle(); - $v_content = $question->getContent(); - $v_wiki = $question->getAnswerWiki(); - $v_view = $question->getViewPolicy(); - $v_space = $question->getSpacePHID(); - $v_status = $question->getStatus(); - - - $errors = array(); - $e_title = true; - if ($request->isFormPost()) { - $v_title = $request->getStr('title'); - $v_content = $request->getStr('content'); - $v_wiki = $request->getStr('answerWiki'); - $v_projects = $request->getArr('projects'); - $v_view = $request->getStr('viewPolicy'); - $v_space = $request->getStr('spacePHID'); - $v_status = $request->getStr('status'); - - $len = phutil_utf8_strlen($v_title); - if ($len < 1) { - $errors[] = pht('Title must not be empty.'); - $e_title = pht('Required'); - } else if ($len > 255) { - $errors[] = pht('Title is too long.'); - $e_title = pht('Too Long'); - } - - if (!$errors) { - $template = id(new PonderQuestionTransaction()); - $xactions = array(); - - $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTitleTransaction::TRANSACTIONTYPE) - ->setNewValue($v_title); - - $xactions[] = id(clone $template) - ->setTransactionType( - PonderQuestionContentTransaction::TRANSACTIONTYPE) - ->setNewValue($v_content); - - $xactions[] = id(clone $template) - ->setTransactionType( - PonderQuestionAnswerWikiTransaction::TRANSACTIONTYPE) - ->setNewValue($v_wiki); - - if (!$is_new) { - $xactions[] = id(clone $template) - ->setTransactionType( - PonderQuestionStatusTransaction::TRANSACTIONTYPE) - ->setNewValue($v_status); - } - - $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view); - - $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) - ->setNewValue($v_space); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new PonderQuestionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - $editor->applyTransactions($question, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/Q'.$question->getID()); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($question) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Question')) - ->setName('title') - ->setValue($v_title) - ->setError($e_title)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('content') - ->setID('content') - ->setValue($v_content) - ->setLabel(pht('Question Details')) - ->setUser($viewer)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('answerWiki') - ->setID('answerWiki') - ->setValue($v_wiki) - ->setLabel(pht('Answer Summary')) - ->setUser($viewer)) - ->appendControl( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($question) - ->setSpacePHID($v_space) - ->setPolicies($policies) - ->setValue($v_view) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)); - - - if (!$is_new) { - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setValue($v_status) - ->setOptions(PonderQuestionStatus::getQuestionStatusMap())); - } - - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())); - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($this->getApplicationURI()) - ->setValue(pht('Submit'))); - - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader(pht('Question Preview')) - ->setControlID('content') - ->setPreviewURI($this->getApplicationURI('preview/')); - - $answer_preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader(pht('Answer Summary Preview')) - ->setControlID('answerWiki') - ->setPreviewURI($this->getApplicationURI('preview/')); - - $crumbs = $this->buildApplicationCrumbs(); - - $id = $question->getID(); - if ($id) { - $crumbs->addTextCrumb("Q{$id}", "/Q{$id}"); - $crumbs->addTextCrumb(pht('Edit')); - $title = pht('Edit Question'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - } else { - $crumbs->addTextCrumb(pht('Ask Question')); - $title = pht('Ask New Question'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - } - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Question')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - $preview, - $answer_preview, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return id(new PonderQuestionEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index 5bde437e3e..8d66fffc17 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -6,6 +6,14 @@ final class PonderAnswerEditor extends PonderEditor { return pht('Ponder Answers'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s added this answer.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s added %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; diff --git a/src/applications/ponder/editor/PonderQuestionEditEngine.php b/src/applications/ponder/editor/PonderQuestionEditEngine.php new file mode 100644 index 0000000000..640f657ad1 --- /dev/null +++ b/src/applications/ponder/editor/PonderQuestionEditEngine.php @@ -0,0 +1,109 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PonderQuestionQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Question'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Question: %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('New Question'); + } + + protected function getObjectName() { + return pht('Question'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('question/edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + + return array( + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Question')) + ->setDescription(pht('Question title.')) + ->setConduitTypeDescription(pht('New question title.')) + ->setTransactionType( + PonderQuestionTitleTransaction::TRANSACTIONTYPE) + ->setValue($object->getTitle()) + ->setIsRequired(true), + id(new PhabricatorRemarkupEditField()) + ->setKey('content') + ->setLabel(pht('Details')) + ->setDescription(pht('Long details of the question.')) + ->setConduitTypeDescription(pht('New question details.')) + ->setValue($object->getContent()) + ->setTransactionType( + PonderQuestionContentTransaction::TRANSACTIONTYPE), + id(new PhabricatorRemarkupEditField()) + ->setKey('answerWiki') + ->setLabel(pht('Answer Summary')) + ->setDescription(pht('Answer summary of the question.')) + ->setConduitTypeDescription(pht('New question answer summary.')) + ->setValue($object->getAnswerWiki()) + ->setTransactionType( + PonderQuestionAnswerWikiTransaction::TRANSACTIONTYPE), + id(new PhabricatorSelectEditField()) + ->setKey('status') + ->setLabel(pht('Status')) + ->setDescription(pht('Status of the question.')) + ->setConduitTypeDescription(pht('New question status.')) + ->setValue($object->getStatus()) + ->setTransactionType( + PonderQuestionStatusTransaction::TRANSACTIONTYPE) + ->setOptions(PonderQuestionStatus::getQuestionStatusMap()), + + ); + } + +} diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 9b34031f13..a17aebecf8 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -9,6 +9,14 @@ final class PonderQuestionEditor return pht('Ponder Questions'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this question.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + /** * This is used internally on @{method:applyInitialEffects} if a transaction * of type PonderQuestionTransaction::TYPE_ANSWERS is in the mix. The value @@ -64,11 +72,8 @@ final class PonderQuestionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; - $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PhabricatorTransactions::TYPE_SPACE; return $types; } diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 6594219f5a..c505deeed9 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -105,6 +105,14 @@ final class PonderQuestion extends PonderDAO return $this->comments; } + public function getMonogram() { + return 'Q'.$this->getID(); + } + + public function getViewURI() { + return '/'.$this->getMonogram(); + } + public function attachAnswers(array $answers) { assert_instances_of($answers, 'PonderAnswer'); $this->answers = $answers; diff --git a/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php b/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php index a51eada7b4..ca78b61d3f 100644 --- a/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php +++ b/src/applications/ponder/xaction/PonderQuestionAnswerTransaction.php @@ -21,6 +21,13 @@ final class PonderQuestionAnswerTransaction $this->renderAuthor()); } + public function getTitleForFeed() { + return pht( + '%s added an answer to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + public function getIcon() { return 'fa-plus'; } From 60a19e36463b201b5c0e1b52e814b038bb4514c5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 3 May 2017 10:39:37 -0700 Subject: [PATCH 016/543] Allow ApplicationTransactionEditor to figure out whether TYPE_COMMENT is supported or not Summary: See D17812, etc. We can figure this out by looking at the object carefully. We don't need to go delete all the old TYPE_COMMENT (it doesn't hurt anything) but can nuke it when we see it. Test Plan: - Made a comment in Slowvote (supports commenting). - Viewed an Almanac device (does not support commenting). Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17822 --- .../slowvote/editor/PhabricatorSlowvoteEditor.php | 1 - .../PhabricatorApplicationTransactionEditor.php | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php index 94e31e8b92..fa320428e5 100644 --- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php +++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php @@ -14,7 +14,6 @@ final class PhabricatorSlowvoteEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorSlowvoteTransaction::TYPE_QUESTION; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 8362d1328d..d58c8d5c76 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -298,6 +298,18 @@ abstract class PhabricatorApplicationTransactionEditor } } + if ($template) { + try { + $comment = $template->getApplicationTransactionCommentObject(); + } catch (PhutilMethodNotImplementedException $ex) { + $comment = null; + } + + if ($comment) { + $types[] = PhabricatorTransactions::TYPE_COMMENT; + } + } + return $types; } From 5307f511703c74ed81991c9f4e7383f8b25682a5 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 3 May 2017 11:26:14 -0700 Subject: [PATCH 017/543] Update Macro comment form Summary: Moves over to transaction commenting. Test Plan: Leave a comment (tested with TYPE_COMMENT still present). Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17823 --- src/__phutil_library_map__.php | 2 - .../PhabricatorMacroApplication.php | 1 - .../PhabricatorMacroCommentController.php | 63 ------------------- .../PhabricatorMacroViewController.php | 30 ++++----- 4 files changed, 13 insertions(+), 83 deletions(-) delete mode 100644 src/applications/macro/controller/PhabricatorMacroCommentController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9a3f4fb538..0198b253dc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2975,7 +2975,6 @@ phutil_register_library_map(array( 'PhabricatorMacroAudioBehaviorTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php', 'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php', 'PhabricatorMacroAudioTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioTransaction.php', - 'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php', 'PhabricatorMacroConfigOptions' => 'applications/macro/config/PhabricatorMacroConfigOptions.php', 'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php', 'PhabricatorMacroDatasource' => 'applications/macro/typeahead/PhabricatorMacroDatasource.php', @@ -8190,7 +8189,6 @@ phutil_register_library_map(array( 'PhabricatorMacroAudioBehaviorTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroAudioController' => 'PhabricatorMacroController', 'PhabricatorMacroAudioTransaction' => 'PhabricatorMacroTransactionType', - 'PhabricatorMacroCommentController' => 'PhabricatorMacroController', 'PhabricatorMacroConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMacroController' => 'PhabricatorController', 'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource', diff --git a/src/applications/macro/application/PhabricatorMacroApplication.php b/src/applications/macro/application/PhabricatorMacroApplication.php index d0e6bbab04..3db2ad49c8 100644 --- a/src/applications/macro/application/PhabricatorMacroApplication.php +++ b/src/applications/macro/application/PhabricatorMacroApplication.php @@ -32,7 +32,6 @@ final class PhabricatorMacroApplication extends PhabricatorApplication { '(query/(?P[^/]+)/)?' => 'PhabricatorMacroListController', 'create/' => 'PhabricatorMacroEditController', 'view/(?P[1-9]\d*)/' => 'PhabricatorMacroViewController', - 'comment/(?P[1-9]\d*)/' => 'PhabricatorMacroCommentController', $this->getEditRoutePattern('edit/') => 'PhabricatorMacroEditController', 'audio/(?P[1-9]\d*)/' => 'PhabricatorMacroAudioController', diff --git a/src/applications/macro/controller/PhabricatorMacroCommentController.php b/src/applications/macro/controller/PhabricatorMacroCommentController.php deleted file mode 100644 index f038140be1..0000000000 --- a/src/applications/macro/controller/PhabricatorMacroCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $macro = id(new PhabricatorMacroQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$macro) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); - - $xactions = array(); - $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhabricatorMacroTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhabricatorMacroEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($macro, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 032d2228a9..1bcf34240a 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -36,6 +36,8 @@ final class PhabricatorMacroViewController $macro, new PhabricatorMacroTransactionQuery()); + $comment_form = $this->buildCommentForm($macro, $timeline); + $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($macro) @@ -48,29 +50,13 @@ final class PhabricatorMacroViewController $header->setStatus('fa-ban', 'indigo', pht('Archived')); } - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $comment_header = $is_serious - ? pht('Add Comment') - : pht('Grovel in Awe'); - - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $macro->getPHID()); - - $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($macro->getPHID()) - ->setDraft($draft) - ->setHeaderText($comment_header) - ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); - $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn(array( $timeline, - $add_comment_form, + $comment_form, )) ->addPropertySection(pht('Macro'), $file) ->addPropertySection(pht('Details'), $details); @@ -82,6 +68,16 @@ final class PhabricatorMacroViewController ->appendChild($view); } + private function buildCommentForm( + PhabricatorFileImageMacro $macro, $timeline) { + $viewer = $this->getViewer(); + + return id(new PhabricatorMacroEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($macro) + ->setTransactionTimeline($timeline); + } + private function buildCurtain( PhabricatorFileImageMacro $macro) { $can_manage = $this->hasApplicationCapability( From d34b338f3f85b08523a4ed663a090faa496030c8 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 3 May 2017 17:45:14 -0700 Subject: [PATCH 018/543] Implement modular transactions for application policy changes Summary: Still needs some cleanup, but ready for review in broad outline form. Test Plan: Made lots of policy changes to the Badges application and confirmed expected rows in `application_xactions`, confirmed expected changes to `phabricator.application-settings`. See example output (not quite working for custom policy objects) here: {F4922240} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, chad, epriestley Maniphest Tasks: T11476 Differential Revision: https://secure.phabricator.com/D17757 --- src/__phutil_library_map__.php | 6 + .../base/PhabricatorApplication.php | 1 - ...ricatorApplicationDetailViewController.php | 6 + .../PhabricatorApplicationEditController.php | 82 +++----- .../PhabricatorApplicationEditEngine.php | 64 ++++++ .../editor/PhabricatorApplicationEditor.php | 46 ++++ ...catorApplicationApplicationTransaction.php | 4 - ...atorApplicationPolicyChangeTransaction.php | 197 ++++++++++++++++++ .../policy/storage/PhabricatorPolicy.php | 8 +- ...habricatorApplicationTransactionEditor.php | 14 +- .../PhabricatorModularTransactionType.php | 4 + 11 files changed, 371 insertions(+), 61 deletions(-) create mode 100644 src/applications/meta/editor/PhabricatorApplicationEditEngine.php create mode 100644 src/applications/meta/editor/PhabricatorApplicationEditor.php create mode 100644 src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0198b253dc..0e1294957d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1849,9 +1849,12 @@ phutil_register_library_map(array( 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', + 'PhabricatorApplicationEditEngine' => 'applications/meta/editor/PhabricatorApplicationEditEngine.php', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', + 'PhabricatorApplicationEditor' => 'applications/meta/editor/PhabricatorApplicationEditor.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', + 'PhabricatorApplicationPolicyChangeTransaction' => 'applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php', 'PhabricatorApplicationProfileMenuItem' => 'applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php', 'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php', 'PhabricatorApplicationSchemaSpec' => 'applications/meta/storage/PhabricatorApplicationSchemaSpec.php', @@ -6889,9 +6892,12 @@ phutil_register_library_map(array( 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', + 'PhabricatorApplicationEditEngine' => 'PhabricatorEditEngine', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', + 'PhabricatorApplicationEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', + 'PhabricatorApplicationPolicyChangeTransaction' => 'PhabricatorApplicationTransactionType', 'PhabricatorApplicationProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationSchemaSpec' => 'PhabricatorConfigSchemaSpec', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 29f5ed35be..af683e0592 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -284,7 +284,6 @@ abstract class PhabricatorApplication throw new PhutilMethodNotImplementedException(); } - /* -( Fact Integration )--------------------------------------------------- */ diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index 709558eaa2..8be6d73a1d 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -38,6 +38,11 @@ final class PhabricatorApplicationDetailViewController $header->setStatus('fa-ban', 'dark', pht('Uninstalled')); } + $timeline = $this->buildTransactionTimeline( + $selected, + new PhabricatorApplicationApplicationTransactionQuery()); + $timeline->setShouldTerminate(true); + $curtain = $this->buildCurtain($selected); $details = $this->buildPropertySectionView($selected); $policies = $this->buildPolicyView($selected); @@ -61,6 +66,7 @@ final class PhabricatorApplicationDetailViewController ->setMainColumn(array( $policies, $panels, + $timeline, )) ->addPropertySection(pht('Details'), $details); diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php index ed51405db9..c9f6a2cafb 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEditController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -30,8 +30,15 @@ final class PhabricatorApplicationEditController ->execute(); if ($request->isFormPost()) { + $xactions = array(); + $result = array(); + $template = $application->getApplicationTransactionTemplate(); foreach ($application->getCapabilities() as $capability) { + if (!$application->isCapabilityEditable($capability)) { + continue; + } + $old = $application->getPolicy($capability); $new = $request->getStr('policy:'.$capability); @@ -40,67 +47,36 @@ final class PhabricatorApplicationEditController continue; } - if (empty($policies[$new])) { - // Not a standard policy, check for a custom policy. - $policy = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->withPHIDs(array($new)) - ->executeOne(); - if (!$policy) { - // Not a custom policy either. Can't set the policy to something - // invalid, so skip this. - continue; - } - } - - if ($new == PhabricatorPolicies::POLICY_PUBLIC) { - $capobj = PhabricatorPolicyCapability::getCapabilityByKey( - $capability); - if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { - // Can't set non-public policies to public. - continue; - } - } - $result[$capability] = $new; + + $xactions[] = id(clone $template) + ->setTransactionType( + PhabricatorApplicationPolicyChangeTransaction::TRANSACTIONTYPE) + ->setMetadataValue( + PhabricatorApplicationPolicyChangeTransaction::METADATA_ATTRIBUTE, + $capability) + ->setNewValue($new); } if ($result) { - $key = 'phabricator.application-settings'; - $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); - $value = $config_entry->getValue(); + $editor = id(new PhabricatorApplicationEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); - $phid = $application->getPHID(); - if (empty($value[$phid])) { - $value[$application->getPHID()] = array(); - } - if (empty($value[$phid]['policy'])) { - $value[$phid]['policy'] = array(); + try { + $editor->applyTransactions($application, $xactions); + return id(new AphrontRedirectResponse())->setURI($view_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; } - $value[$phid]['policy'] = $result + $value[$phid]['policy']; - - // Don't allow users to make policy edits which would lock them out of - // applications, since they would be unable to undo those actions. - PhabricatorEnv::overrideConfig($key, $value); - PhabricatorPolicyFilter::mustRetainCapability( - $user, - $application, - PhabricatorPolicyCapability::CAN_VIEW); - - PhabricatorPolicyFilter::mustRetainCapability( - $user, - $application, - PhabricatorPolicyCapability::CAN_EDIT); - - PhabricatorConfigEditor::storeNewValue( - $user, - $config_entry, - $value, - PhabricatorContentSource::newFromRequest($request)); + return $this->newDialog() + ->setTitle('Validation Failed') + ->setValidationException($validation_exception) + ->addCancelButton($view_uri); } - - return id(new AphrontRedirectResponse())->setURI($view_uri); } $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( diff --git a/src/applications/meta/editor/PhabricatorApplicationEditEngine.php b/src/applications/meta/editor/PhabricatorApplicationEditEngine.php new file mode 100644 index 0000000000..7cad5d9242 --- /dev/null +++ b/src/applications/meta/editor/PhabricatorApplicationEditEngine.php @@ -0,0 +1,64 @@ +getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Application'); + } + + protected function getObjectName() { + return pht('Application'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + return array(); + } + +} diff --git a/src/applications/meta/editor/PhabricatorApplicationEditor.php b/src/applications/meta/editor/PhabricatorApplicationEditor.php new file mode 100644 index 0000000000..83003b4c27 --- /dev/null +++ b/src/applications/meta/editor/PhabricatorApplicationEditor.php @@ -0,0 +1,46 @@ +getCapabilityName(); + return $application->getPolicy($capability); + } + + public function applyInternalEffects($object, $value) { + $application = $object; + $user = $this->getActor(); + + $key = 'phabricator.application-settings'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $current_value = $config_entry->getValue(); + + $phid = $application->getPHID(); + if (empty($current_value[$phid])) { + $current_value[$application->getPHID()] = array(); + } + if (empty($current_value[$phid]['policy'])) { + $current_value[$phid]['policy'] = array(); + } + + $new = array($this->getCapabilityName() => $value); + $current_value[$phid]['policy'] = $new + $current_value[$phid]['policy']; + + $editor = $this->getEditor(); + $content_source = $editor->getContentSource(); + PhabricatorConfigEditor::storeNewValue( + $user, + $config_entry, + $current_value, + $content_source); + } + + public function getTitle() { + $old = $this->renderPolicy($this->getOldValue()); + $new = $this->renderPolicy($this->getNewValue()); + + return pht( + '%s changed the "%s" policy from "%s" to "%s".', + $this->renderAuthor(), + $this->renderCapability(), + $old, + $new); + } + + public function getTitleForFeed() { + $old = $this->renderPolicy($this->getOldValue()); + $new = $this->renderPolicy($this->getNewValue()); + + return pht( + '%s changed the "%s" policy for application %s from "%s" to "%s".', + $this->renderAuthor(), + $this->renderCapability(), + $this->renderObject(), + $old, + $new); + } + + public function validateTransactions($object, array $xactions) { + $user = $this->getActor(); + $application = $object; + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->setObject($application) + ->execute(); + + $errors = array(); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE); + + if (empty($policies[$new])) { + // Not a standard policy, check for a custom policy. + $policy = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->withPHIDs(array($new)) + ->executeOne(); + if (!$policy) { + $errors[] = $this->newInvalidError( + pht('Policy does not exist.')); + continue; + } + } else { + $policy = idx($policies, $new); + } + + if (!$policy->isValidPolicyForEdit()) { + $errors[] = $this->newInvalidError( + pht('Can\'t set the policy to a policy you can\'t view!')); + continue; + } + + if ($new == PhabricatorPolicies::POLICY_PUBLIC) { + $capobj = PhabricatorPolicyCapability::getCapabilityByKey( + $capability); + if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { + $errors[] = $this->newInvalidError( + pht('Can\'t set non-public policies to public.')); + continue; + } + } + + if (!$application->isCapabilityEditable($capability)) { + $errors[] = $this->newInvalidError( + pht('Capability "%s" is not editable for this application.', + $capability)); + continue; + } + } + + // If we're changing these policies, the viewer needs to still be able to + // view or edit the application under the new policy. + $validate_map = array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + $validate_map = array_fill_keys($validate_map, array()); + + foreach ($xactions as $xaction) { + $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE); + if (!isset($validate_map[$capability])) { + continue; + } + + $validate_map[$capability][] = $xaction; + } + + foreach ($validate_map as $capability => $cap_xactions) { + if (!$cap_xactions) { + continue; + } + + $editor = $this->getEditor(); + $policy_errors = $editor->validatePolicyTransaction( + $object, + $cap_xactions, + self::TRANSACTIONTYPE, + $capability); + + foreach ($policy_errors as $error) { + $errors[] = $error; + } + } + + return $errors; + } + + private function renderPolicy($name) { + $policies = $this->getAllPolicies(); + if (empty($policies[$name])) { + // Not a standard policy, check for a custom policy. + $policy = id(new PhabricatorPolicyQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($name)) + ->executeOne(); + $policies[$name] = $policy; + } + + $policy = idx($policies, $name); + return $this->renderValue($policy->getFullName()); + } + + private function getAllPolicies() { + if (!$this->policies) { + $viewer = $this->getViewer(); + $application = $this->getObject(); + $this->policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($application) + ->execute(); + } + + return $this->policies; + } + + private function renderCapability() { + $application = $this->getObject(); + $capability = $this->getCapabilityName(); + return $application->getCapabilityLabel($capability); + } + + private function getCapabilityName() { + return $this->getMetadataValue(self::METADATA_ATTRIBUTE); + } + +} diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php index 68462cbc19..4141ef9c36 100644 --- a/src/applications/policy/storage/PhabricatorPolicy.php +++ b/src/applications/policy/storage/PhabricatorPolicy.php @@ -264,9 +264,11 @@ final class PhabricatorPolicy public function getFullName() { switch ($this->getType()) { case PhabricatorPolicyType::TYPE_PROJECT: - return pht('Project: %s', $this->getName()); + return pht('Members of Project: %s', $this->getName()); case PhabricatorPolicyType::TYPE_MASKED: return pht('Other: %s', $this->getName()); + case PhabricatorPolicyType::TYPE_USER: + return pht('Only User: %s', $this->getName()); default: return $this->getName(); } @@ -422,6 +424,10 @@ final class PhabricatorPolicy return ($this_strength > $other_strength); } + public function isValidPolicyForEdit() { + return $this->getType() !== PhabricatorPolicyType::TYPE_MASKED; + } + public static function getSpecialRules( PhabricatorPolicyInterface $object, PhabricatorUser $viewer, diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index d58c8d5c76..b5aa399521 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -334,6 +334,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->generateOldValue($object); } @@ -414,6 +416,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->generateNewValue($object, $xaction->getNewValue()); } @@ -553,6 +557,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->applyInternalEffects($object, $xaction->getNewValue()); } @@ -2163,7 +2169,7 @@ abstract class PhabricatorApplicationTransactionEditor return array_mergev($errors); } - private function validatePolicyTransaction( + public function validatePolicyTransaction( PhabricatorLiskDAO $object, array $xactions, $transaction_type, @@ -2772,7 +2778,11 @@ abstract class PhabricatorApplicationTransactionEditor } if (!$has_support) { - throw new Exception(pht('Capability not supported.')); + throw new Exception( + pht('The object being edited does not implement any standard '. + 'interfaces (like PhabricatorSubscribableInterface) which allow '. + 'CCs to be generated automatically. Override the "getMailCC()" '. + 'method and generate CCs explicitly.')); } return array_mergev($phids); diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 9d8510cdc9..8a56e8e8ce 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -315,4 +315,8 @@ abstract class PhabricatorModularTransactionType return $editor->getPHIDList($old, $new); } + public function getMetadataValue($key, $default = null) { + return $this->getStorage()->getMetadataValue($key, $default); + } + } From 06eae5578bff389c8a1f941a230c0616584579d5 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 4 May 2017 11:17:47 -0700 Subject: [PATCH 019/543] Update Passphrase for modular transactions Summary: Updates Passphrase for modular transactions. Test Plan: Create, edit, lock, view, lots of different types of Passphrases. Enable Conduit, Lock Passphrases, Destroy Secrets from the interface and verify from the DB it was eradicated. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17824 --- src/__phutil_library_map__.php | 20 +- .../PassphraseCredentialConduitController.php | 3 +- .../PassphraseCredentialDestroyController.php | 3 +- .../PassphraseCredentialEditController.php | 19 +- .../PassphraseCredentialLockController.php | 6 +- .../PassphraseCredentialRevealController.php | 2 +- .../PassphraseCredentialTransactionEditor.php | 178 +----------------- .../PassphraseCredentialTransaction.php | 123 +----------- ...PassphraseCredentialConduitTransaction.php | 53 ++++++ ...phraseCredentialDescriptionTransaction.php | 64 +++++++ ...PassphraseCredentialDestroyTransaction.php | 52 +++++ .../PassphraseCredentialLockTransaction.php | 41 ++++ ...assphraseCredentialLookedAtTransaction.php | 33 ++++ .../PassphraseCredentialNameTransaction.php | 71 +++++++ ...assphraseCredentialSecretIDTransaction.php | 56 ++++++ .../PassphraseCredentialTransactionType.php | 15 ++ ...assphraseCredentialUsernameTransaction.php | 56 ++++++ 17 files changed, 489 insertions(+), 306 deletions(-) create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php create mode 100644 src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0e1294957d..35ea8488d0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1762,23 +1762,32 @@ phutil_register_library_map(array( 'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php', 'PassphraseCredentialAuthorPolicyRule' => 'applications/passphrase/policyrule/PassphraseCredentialAuthorPolicyRule.php', 'PassphraseCredentialConduitController' => 'applications/passphrase/controller/PassphraseCredentialConduitController.php', + 'PassphraseCredentialConduitTransaction' => 'applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php', 'PassphraseCredentialControl' => 'applications/passphrase/view/PassphraseCredentialControl.php', 'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php', + 'PassphraseCredentialDescriptionTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php', 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', + 'PassphraseCredentialDestroyTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', 'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php', + 'PassphraseCredentialLockTransaction' => 'applications/passphrase/xaction/PassphraseCredentialLockTransaction.php', + 'PassphraseCredentialLookedAtTransaction' => 'applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php', + 'PassphraseCredentialNameTransaction' => 'applications/passphrase/xaction/PassphraseCredentialNameTransaction.php', 'PassphraseCredentialPHIDType' => 'applications/passphrase/phid/PassphraseCredentialPHIDType.php', 'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php', 'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php', 'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php', 'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php', + 'PassphraseCredentialSecretIDTransaction' => 'applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php', 'PassphraseCredentialTransaction' => 'applications/passphrase/storage/PassphraseCredentialTransaction.php', 'PassphraseCredentialTransactionEditor' => 'applications/passphrase/editor/PassphraseCredentialTransactionEditor.php', 'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php', + 'PassphraseCredentialTransactionType' => 'applications/passphrase/xaction/PassphraseCredentialTransactionType.php', 'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php', 'PassphraseCredentialTypeTestCase' => 'applications/passphrase/credentialtype/__tests__/PassphraseCredentialTypeTestCase.php', + 'PassphraseCredentialUsernameTransaction' => 'applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php', 'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php', 'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php', 'PassphraseDefaultEditCapability' => 'applications/passphrase/capability/PassphraseDefaultEditCapability.php', @@ -6801,23 +6810,32 @@ phutil_register_library_map(array( ), 'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule', 'PassphraseCredentialConduitController' => 'PassphraseController', + 'PassphraseCredentialConduitTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialControl' => 'AphrontFormControl', 'PassphraseCredentialCreateController' => 'PassphraseController', + 'PassphraseCredentialDescriptionTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialDestroyController' => 'PassphraseController', + 'PassphraseCredentialDestroyTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialEditController' => 'PassphraseController', 'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine', 'PassphraseCredentialListController' => 'PassphraseController', 'PassphraseCredentialLockController' => 'PassphraseController', + 'PassphraseCredentialLockTransaction' => 'PassphraseCredentialTransactionType', + 'PassphraseCredentialLookedAtTransaction' => 'PassphraseCredentialTransactionType', + 'PassphraseCredentialNameTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialPHIDType' => 'PhabricatorPHIDType', 'PassphraseCredentialPublicController' => 'PassphraseController', 'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PassphraseCredentialRevealController' => 'PassphraseController', 'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PassphraseCredentialTransaction' => 'PhabricatorApplicationTransaction', + 'PassphraseCredentialSecretIDTransaction' => 'PassphraseCredentialTransactionType', + 'PassphraseCredentialTransaction' => 'PhabricatorModularTransaction', 'PassphraseCredentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PassphraseCredentialTransactionType' => 'PhabricatorModularTransactionType', 'PassphraseCredentialType' => 'Phobject', 'PassphraseCredentialTypeTestCase' => 'PhabricatorTestCase', + 'PassphraseCredentialUsernameTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialViewController' => 'PassphraseController', 'PassphraseDAO' => 'PhabricatorLiskDAO', 'PassphraseDefaultEditCapability' => 'PhabricatorPolicyCapability', diff --git a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php index ce8f21f62d..3c95fd8459 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php @@ -50,7 +50,8 @@ final class PassphraseCredentialConduitController $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT) + ->setTransactionType( + PassphraseCredentialConduitTransaction::TRANSACTIONTYPE) ->setNewValue(!$credential->getAllowConduit()); $editor = id(new PassphraseCredentialTransactionEditor()) diff --git a/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php b/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php index 8858d0ef6b..e1f0532f8a 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php @@ -32,7 +32,8 @@ final class PassphraseCredentialDestroyController $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_DESTROY) + ->setTransactionType( + PassphraseCredentialDestroyTransaction::TRANSACTIONTYPE) ->setNewValue(1); $editor = id(new PassphraseCredentialTransactionEditor()) diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index bdb1802880..9c86a7f5c9 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -117,12 +117,19 @@ final class PassphraseCredentialEditController extends PassphraseController { } if (!$errors) { - $type_name = PassphraseCredentialTransaction::TYPE_NAME; - $type_desc = PassphraseCredentialTransaction::TYPE_DESCRIPTION; - $type_username = PassphraseCredentialTransaction::TYPE_USERNAME; - $type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY; - $type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID; - $type_is_locked = PassphraseCredentialTransaction::TYPE_LOCK; + $type_name = + PassphraseCredentialNameTransaction::TRANSACTIONTYPE; + $type_desc = + PassphraseCredentialDescriptionTransaction::TRANSACTIONTYPE; + $type_username = + PassphraseCredentialUsernameTransaction::TRANSACTIONTYPE; + $type_destroy = + PassphraseCredentialDestroyTransaction::TRANSACTIONTYPE; + $type_secret_id = + PassphraseCredentialSecretIDTransaction::TRANSACTIONTYPE; + $type_is_locked = + PassphraseCredentialLockTransaction::TRANSACTIONTYPE; + $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_space = PhabricatorTransactions::TYPE_SPACE; diff --git a/src/applications/passphrase/controller/PassphraseCredentialLockController.php b/src/applications/passphrase/controller/PassphraseCredentialLockController.php index 9832705427..b489851baa 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialLockController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialLockController.php @@ -40,11 +40,13 @@ final class PassphraseCredentialLockController $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT) + ->setTransactionType( + PassphraseCredentialConduitTransaction::TRANSACTIONTYPE) ->setNewValue(0); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK) + ->setTransactionType( + PassphraseCredentialLockTransaction::TRANSACTIONTYPE) ->setNewValue(1); $editor = id(new PassphraseCredentialTransactionEditor()) diff --git a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php index 4fb299b85e..3a40d253c9 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php @@ -67,7 +67,7 @@ final class PassphraseCredentialRevealController ->setDisableWorkflowOnCancel(true) ->addCancelButton($view_uri, pht('Done')); - $type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET; + $type_secret = PassphraseCredentialLookedAtTransaction::TRANSACTIONTYPE; $xactions = array( id(new PassphraseCredentialTransaction()) ->setTransactionType($type_secret) diff --git a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php index fd743f4aac..1e23c874cf 100644 --- a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php +++ b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php @@ -17,185 +17,15 @@ final class PassphraseCredentialTransactionEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PassphraseCredentialTransaction::TYPE_NAME; - $types[] = PassphraseCredentialTransaction::TYPE_DESCRIPTION; - $types[] = PassphraseCredentialTransaction::TYPE_USERNAME; - $types[] = PassphraseCredentialTransaction::TYPE_SECRET_ID; - $types[] = PassphraseCredentialTransaction::TYPE_DESTROY; - $types[] = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET; - $types[] = PassphraseCredentialTransaction::TYPE_LOCK; - $types[] = PassphraseCredentialTransaction::TYPE_CONDUIT; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - if ($this->getIsNewObject()) { - return null; - } - return $object->getName(); - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PassphraseCredentialTransaction::TYPE_USERNAME: - return $object->getUsername(); - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - return $object->getSecretID(); - case PassphraseCredentialTransaction::TYPE_DESTROY: - return (int)$object->getIsDestroyed(); - case PassphraseCredentialTransaction::TYPE_LOCK: - return (int)$object->getIsLocked(); - case PassphraseCredentialTransaction::TYPE_CONDUIT: - return (int)$object->getAllowConduit(); - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - return null; - } - - return parent::getCustomTransactionOldValue($object, $xaction); + public function getCreateObjectTitle($author, $object) { + return pht('%s created this credential.', $author); } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - case PassphraseCredentialTransaction::TYPE_USERNAME: - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - return $xaction->getNewValue(); - case PassphraseCredentialTransaction::TYPE_DESTROY: - case PassphraseCredentialTransaction::TYPE_LOCK: - return (int)$xaction->getNewValue(); - case PassphraseCredentialTransaction::TYPE_CONDUIT: - return (int)$xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_USERNAME: - $object->setUsername($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - $old_id = $object->getSecretID(); - if ($old_id) { - $this->destroySecret($old_id); - } - $object->setSecretID($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_DESTROY: - // When destroying a credential, wipe out its secret. - $is_destroyed = $xaction->getNewValue(); - $object->setIsDestroyed($is_destroyed); - if ($is_destroyed) { - $secret_id = $object->getSecretID(); - if ($secret_id) { - $this->destroySecret($secret_id); - $object->setSecretID(null); - } - } - return; - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - return; - case PassphraseCredentialTransaction::TYPE_LOCK: - $object->setIsLocked((int)$xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_CONDUIT: - $object->setAllowConduit((int)$xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - case PassphraseCredentialTransaction::TYPE_USERNAME: - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - case PassphraseCredentialTransaction::TYPE_DESTROY: - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - case PassphraseCredentialTransaction::TYPE_LOCK: - case PassphraseCredentialTransaction::TYPE_CONDUIT: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - private function destroySecret($secret_id) { - $table = new PassphraseSecret(); - queryfx( - $table->establishConnection('w'), - 'DELETE FROM %T WHERE id = %d', - $table->getTableName(), - $secret_id); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PassphraseCredentialTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Credential name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PassphraseCredentialTransaction::TYPE_USERNAME: - $credential_type = $object->getImplementation(); - if (!$credential_type->shouldRequireUsername()) { - break; - } - $missing = $this->validateIsEmptyTextField( - $object->getUsername(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Username is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); } protected function supportsSearch() { diff --git a/src/applications/passphrase/storage/PassphraseCredentialTransaction.php b/src/applications/passphrase/storage/PassphraseCredentialTransaction.php index e0c90fcdb9..b7e4f904ef 100644 --- a/src/applications/passphrase/storage/PassphraseCredentialTransaction.php +++ b/src/applications/passphrase/storage/PassphraseCredentialTransaction.php @@ -1,16 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); - case self::TYPE_LOCK: - return ($old === null); - case self::TYPE_USERNAME: - return !strlen($old); - case self::TYPE_LOOKEDATSECRET: - return false; - case self::TYPE_DESTROY: - // Don't show "undestroy" transactions because they're a bit confusing - // and redundant with restoring a secret. - if (!$new) { - return true; - } - } - return parent::shouldHide(); - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $author_phid = $this->getAuthorPHID(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this credential.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this credential from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_USERNAME: - if (strlen($old)) { - return pht( - '%s changed the username for this credential from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } else { - return pht( - '%s set the username for this credential to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_SECRET_ID: - if ($old === null) { - return pht( - '%s attached a new secret to this credential.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s updated the secret for this credential.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_DESTROY: - return pht( - '%s destroyed the secret for this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_LOOKEDATSECRET: - return pht( - '%s examined the secret plaintext for this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_LOCK: - return pht( - '%s locked this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_CONDUIT: - if ($old) { - return pht( - '%s disallowed Conduit API access to this credential.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s allowed Conduit API access to this credential.', - $this->renderHandleLink($author_phid)); - } - break; - } - - return parent::getTitle(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - json_encode($this->getOldValue()), - json_encode($this->getNewValue())); + public function getBaseTransactionClass() { + return 'PassphraseCredentialTransactionType'; } } diff --git a/src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php new file mode 100644 index 0000000000..57d9828560 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php @@ -0,0 +1,53 @@ +getAllowConduit(); + } + + public function applyInternalEffects($object, $value) { + $object->setAllowConduit((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s allowed Conduit API access to this credential.', + $this->renderAuthor()); + } else { + return pht( + '%s disallowed Conduit API access to this credential.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s allowed Conduit API access to credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s disallowed Conduit API access to credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new) { + return 'fa-tty'; + } else { + return 'fa-ban'; + } + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php new file mode 100644 index 0000000000..ec989cf9c9 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php @@ -0,0 +1,64 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function shouldHide() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return true; + } + return false; + } + + public function getTitle() { + return pht( + '%s updated the description for this credential.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO CREDENTIAL DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php new file mode 100644 index 0000000000..3983fd0bbc --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php @@ -0,0 +1,52 @@ +getIsDestroyed(); + } + + public function applyInternalEffects($object, $value) { + $is_destroyed = $value; + $object->setIsDestroyed($is_destroyed); + if ($is_destroyed) { + $secret_id = $object->getSecretID(); + if ($secret_id) { + $this->destroySecret($secret_id); + $object->setSecretID(null); + } + } + } + + public function shouldHide() { + $new = $this->getNewValue(); + if (!$new) { + return true; + } + } + + public function getTitle() { + return pht( + '%s destroyed the secret for this credential.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s destroyed the secret for credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-ban'; + } + + public function getColor() { + return 'red'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php new file mode 100644 index 0000000000..64eba1da71 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php @@ -0,0 +1,41 @@ +getIsLocked(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsLocked((int)$value); + } + + public function shouldHide() { + $new = $this->getNewValue(); + if ($new === null) { + return true; + } + return false; + } + + public function getTitle() { + return pht( + '%s locked this credential.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s locked credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-lock'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php new file mode 100644 index 0000000000..3d8cb36f31 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php @@ -0,0 +1,33 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s examined the secret plaintext for credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-eye'; + } + + public function getColor() { + return 'blue'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php new file mode 100644 index 0000000000..1afac71395 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php @@ -0,0 +1,71 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created this credential.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this credential from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s credential %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Credentials must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php new file mode 100644 index 0000000000..2c8e53553a --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php @@ -0,0 +1,56 @@ +getSecretID(); + } + + public function applyInternalEffects($object, $value) { + $old_id = $object->getSecretID(); + if ($old_id) { + $this->destroySecret($old_id); + } + $object->setSecretID($value); + } + + public function shouldHide() { + if (!$this->getOldValue()) { + return true; + } + + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s attached a new secret to this credential.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the secret for this credential.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s attached a new secret to %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the secret for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php b/src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php new file mode 100644 index 0000000000..4e6aadb697 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php @@ -0,0 +1,15 @@ +establishConnection('w'), + 'DELETE FROM %T WHERE id = %d', + $table->getTableName(), + $secret_id); + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php new file mode 100644 index 0000000000..fe07bd2990 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php @@ -0,0 +1,56 @@ +getUsername(); + } + + public function applyInternalEffects($object, $value) { + $object->setUsername($value); + } + + public function getTitle() { + return pht( + '%s set the username for this credential to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s set the username for credential %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $credential_type = $object->getImplementation(); + if ($credential_type->shouldRequireUsername()) { + if ($this->isEmptyTextTransaction($object->getUsername(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('This credential must have a username.')); + } + } + + $max_length = $object->getColumnMaximumByteLength('username'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The username can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} From 705fd11ba1735b736a6ab10ff2abeca97f0b7a19 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 4 May 2017 11:53:52 -0700 Subject: [PATCH 020/543] Update Legalpad to use modular transactions Summary: Update Legalpad for modular transactions Test Plan: - New Document (no sign) - New Document (individual) - New Document (corp) - Require Signature - get prompted to sign before I can do anything. - Edit Documents - Sign Documents - Comment on Documents Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17826 --- src/__phutil_library_map__.php | 14 +- .../LegalpadDocumentEditController.php | 15 +- .../editor/LegalpadDocumentEditor.php | 133 +++--------------- .../legalpad/storage/LegalpadTransaction.php | 77 +--------- .../LegalpadDocumentPreambleTransaction.php | 57 ++++++++ ...padDocumentRequireSignatureTransaction.php | 58 ++++++++ ...galpadDocumentSignatureTypeTransaction.php | 29 ++++ .../LegalpadDocumentTextTransaction.php | 60 ++++++++ .../LegalpadDocumentTitleTransaction.php | 58 ++++++++ .../LegalpadDocumentTransactionType.php | 4 + 10 files changed, 308 insertions(+), 197 deletions(-) create mode 100644 src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php create mode 100644 src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php create mode 100644 src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php create mode 100644 src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php create mode 100644 src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php create mode 100644 src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 35ea8488d0..bed1a60426 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1400,8 +1400,10 @@ phutil_register_library_map(array( 'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php', 'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php', 'LegalpadDocumentManageController' => 'applications/legalpad/controller/LegalpadDocumentManageController.php', + 'LegalpadDocumentPreambleTransaction' => 'applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php', 'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php', 'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php', + 'LegalpadDocumentRequireSignatureTransaction' => 'applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php', 'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php', 'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php', 'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php', @@ -1409,8 +1411,12 @@ phutil_register_library_map(array( 'LegalpadDocumentSignatureListController' => 'applications/legalpad/controller/LegalpadDocumentSignatureListController.php', 'LegalpadDocumentSignatureQuery' => 'applications/legalpad/query/LegalpadDocumentSignatureQuery.php', 'LegalpadDocumentSignatureSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php', + 'LegalpadDocumentSignatureTypeTransaction' => 'applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php', 'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php', 'LegalpadDocumentSignatureViewController' => 'applications/legalpad/controller/LegalpadDocumentSignatureViewController.php', + 'LegalpadDocumentTextTransaction' => 'applications/legalpad/xaction/LegalpadDocumentTextTransaction.php', + 'LegalpadDocumentTitleTransaction' => 'applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php', + 'LegalpadDocumentTransactionType' => 'applications/legalpad/xaction/LegalpadDocumentTransactionType.php', 'LegalpadMailReceiver' => 'applications/legalpad/mail/LegalpadMailReceiver.php', 'LegalpadObjectNeedsSignatureEdgeType' => 'applications/legalpad/edge/LegalpadObjectNeedsSignatureEdgeType.php', 'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php', @@ -6395,8 +6401,10 @@ phutil_register_library_map(array( 'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor', 'LegalpadDocumentListController' => 'LegalpadController', 'LegalpadDocumentManageController' => 'LegalpadController', + 'LegalpadDocumentPreambleTransaction' => 'LegalpadDocumentTransactionType', 'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'LegalpadDocumentRequireSignatureTransaction' => 'LegalpadDocumentTransactionType', 'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'LegalpadDocumentSignController' => 'LegalpadController', 'LegalpadDocumentSignature' => array( @@ -6407,15 +6415,19 @@ phutil_register_library_map(array( 'LegalpadDocumentSignatureListController' => 'LegalpadController', 'LegalpadDocumentSignatureQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentSignatureSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'LegalpadDocumentSignatureTypeTransaction' => 'LegalpadDocumentTransactionType', 'LegalpadDocumentSignatureVerificationController' => 'LegalpadController', 'LegalpadDocumentSignatureViewController' => 'LegalpadController', + 'LegalpadDocumentTextTransaction' => 'LegalpadDocumentTransactionType', + 'LegalpadDocumentTitleTransaction' => 'LegalpadDocumentTransactionType', + 'LegalpadDocumentTransactionType' => 'PhabricatorModularTransactionType', 'LegalpadMailReceiver' => 'PhabricatorObjectMailReceiver', 'LegalpadObjectNeedsSignatureEdgeType' => 'PhabricatorEdgeType', 'LegalpadReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'LegalpadRequireSignatureHeraldAction' => 'HeraldAction', 'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType', - 'LegalpadTransaction' => 'PhabricatorApplicationTransaction', + 'LegalpadTransaction' => 'PhabricatorModularTransaction', 'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment', 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView', diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php index 8f84f60339..9b1d2ead22 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -57,7 +57,8 @@ final class LegalpadDocumentEditController extends LegalpadController { $errors[] = pht('The document title may not be blank.'); } else { $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_TITLE) + ->setTransactionType( + LegalpadDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); } @@ -67,7 +68,8 @@ final class LegalpadDocumentEditController extends LegalpadController { $errors[] = pht('The document may not be blank.'); } else { $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_TEXT) + ->setTransactionType( + LegalpadDocumentTextTransaction::TRANSACTIONTYPE) ->setNewValue($text); } @@ -83,13 +85,15 @@ final class LegalpadDocumentEditController extends LegalpadController { if ($is_create) { $v_signature_type = $request->getStr('signatureType'); $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_SIGNATURE_TYPE) + ->setTransactionType( + LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE) ->setNewValue($v_signature_type); } $v_preamble = $request->getStr('preamble'); $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_PREAMBLE) + ->setTransactionType( + LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE) ->setNewValue($v_preamble); $v_require_signature = $request->getBool('requireSignature', 0); @@ -106,7 +110,8 @@ final class LegalpadDocumentEditController extends LegalpadController { } if ($viewer->getIsAdmin()) { $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_REQUIRE_SIGNATURE) + ->setTransactionType( + LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE) ->setNewValue($v_require_signature); } diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 5e319b5905..14430b2c33 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -3,8 +3,6 @@ final class LegalpadDocumentEditor extends PhabricatorApplicationTransactionEditor { - private $isContribution = false; - public function getEditorApplicationClass() { return 'PhabricatorLegalpadApplication'; } @@ -13,15 +11,6 @@ final class LegalpadDocumentEditor return pht('Legalpad Documents'); } - private function setIsContribution($is_contribution) { - $this->isContribution = $is_contribution; - return $this; - } - - private function isContribution() { - return $this->isContribution; - } - public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -29,99 +18,25 @@ final class LegalpadDocumentEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = LegalpadTransaction::TYPE_TITLE; - $types[] = LegalpadTransaction::TYPE_TEXT; - $types[] = LegalpadTransaction::TYPE_SIGNATURE_TYPE; - $types[] = LegalpadTransaction::TYPE_PREAMBLE; - $types[] = LegalpadTransaction::TYPE_REQUIRE_SIGNATURE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TITLE: - return $object->getDocumentBody()->getTitle(); - case LegalpadTransaction::TYPE_TEXT: - return $object->getDocumentBody()->getText(); - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - return $object->getSignatureType(); - case LegalpadTransaction::TYPE_PREAMBLE: - return $object->getPreamble(); - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - return (bool)$object->getRequireSignature(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TITLE: - case LegalpadTransaction::TYPE_TEXT: - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - case LegalpadTransaction::TYPE_PREAMBLE: - return $xaction->getNewValue(); - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - return (bool)$xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - $body = $object->getDocumentBody(); - $body->setTitle($xaction->getNewValue()); - $this->setIsContribution(true); - break; - case LegalpadTransaction::TYPE_TEXT: - $body = $object->getDocumentBody(); - $body->setText($xaction->getNewValue()); - $this->setIsContribution(true); - break; - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - $object->setSignatureType($xaction->getNewValue()); - break; - case LegalpadTransaction::TYPE_PREAMBLE: - $object->setPreamble($xaction->getNewValue()); - break; - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - $object->setRequireSignature((int)$xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - if ($xaction->getNewValue()) { - $session = new PhabricatorAuthSession(); - queryfx( - $session->establishConnection('w'), - 'UPDATE %T SET signedLegalpadDocuments = 0', - $session->getTableName()); - } - break; - } - return; - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { - if ($this->isContribution()) { + $is_contribution = false; + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case LegalpadDocumentTitleTransaction::TRANSACTIONTYPE: + case LegalpadDocumentTextTransaction::TRANSACTIONTYPE: + $is_contribution = true; + break; + } + } + + if ($is_contribution) { $object->setVersions($object->getVersions() + 1); $body = $object->getDocumentBody(); $body->setVersion($object->getVersions()); @@ -149,22 +64,6 @@ final class LegalpadDocumentEditor return $xactions; } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case LegalpadTransaction::TYPE_TITLE: - case LegalpadTransaction::TYPE_TEXT: - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - case LegalpadTransaction::TYPE_PREAMBLE: - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - return $v; - } - - return parent::mergeTransactions($u, $v); - } /* -( Sending Mail )------------------------------------------------------- */ @@ -201,10 +100,10 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TEXT: - case LegalpadTransaction::TYPE_TITLE: - case LegalpadTransaction::TYPE_PREAMBLE: - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: + case LegalpadDocumentTextTransaction::TRANSACTIONTYPE: + case LegalpadDocumentTitleTransaction::TRANSACTIONTYPE: + case LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE: + case LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE: return true; } diff --git a/src/applications/legalpad/storage/LegalpadTransaction.php b/src/applications/legalpad/storage/LegalpadTransaction.php index f85c569279..c43c86c5fc 100644 --- a/src/applications/legalpad/storage/LegalpadTransaction.php +++ b/src/applications/legalpad/storage/LegalpadTransaction.php @@ -1,12 +1,6 @@ getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_TEXT: - return ($old === null); - case self::TYPE_SIGNATURE_TYPE: - return true; - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - return pht( - '%s renamed this document from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_TEXT: - return pht( - "%s updated the document's text.", - $this->renderHandleLink($author_phid)); - case self::TYPE_PREAMBLE: - return pht( - '%s updated the preamble.', - $this->renderHandleLink($author_phid)); - case self::TYPE_REQUIRE_SIGNATURE: - if ($new) { - $text = pht( - '%s set the document to require signatures.', - $this->renderHandleLink($author_phid)); - } else { - $text = pht( - '%s set the document to not require signatures.', - $this->renderHandleLink($author_phid)); - } - return $text; - } - - return parent::getTitle(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_TEXT: - case self::TYPE_PREAMBLE: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'LegalpadDocumentTransactionType'; } } diff --git a/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php new file mode 100644 index 0000000000..a2b5e1f5cd --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php @@ -0,0 +1,57 @@ +getPreamble(); + } + + public function applyInternalEffects($object, $value) { + $object->setPreamble($value); + } + + public function getTitle() { + return pht( + '%s updated the document preamble.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the document preamble for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT PREAMBLE'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php new file mode 100644 index 0000000000..ffef42f0ce --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php @@ -0,0 +1,58 @@ +getRequireSignature(); + } + + public function applyInternalEffects($object, $value) { + $object->setRequireSignature($value); + } + + public function applyExternalEffects($object, $value) { + if (strlen($value)) { + $session = new PhabricatorAuthSession(); + queryfx( + $session->establishConnection('w'), + 'UPDATE %T SET signedLegalpadDocuments = 0', + $session->getTableName()); + } + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s set the document to require signatures.', + $this->renderAuthor()); + } else { + return pht( + '%s set the document to not require signatures.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s set the document %s to require signatures.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s set the document %s to not require signatures.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + return 'fa-pencil-square'; + } + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php new file mode 100644 index 0000000000..df9cf60457 --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php @@ -0,0 +1,29 @@ +getSignatureType(); + } + + public function applyInternalEffects($object, $value) { + $object->setSignatureType($value); + } + + public function getTitle() { + return pht( + '%s set the document signature type.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s set the document signature type for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php new file mode 100644 index 0000000000..c17ebc890d --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php @@ -0,0 +1,60 @@ +getDocumentBody(); + return $body->getText(); + } + + public function applyInternalEffects($object, $value) { + $body = $object->getDocumentBody(); + $body->setText($value); + $object->attachDocumentBody($body); + } + + public function getTitle() { + return pht( + '%s updated the document text.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the document text for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT TEXT'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php new file mode 100644 index 0000000000..c6a4b5b509 --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php @@ -0,0 +1,58 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + $body = $object->getDocumentBody(); + $body->setTitle($value); + $object->attachDocumentBody($body); + } + + public function getTitle() { + return pht( + '%s renamed this document from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed document %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php b/src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php new file mode 100644 index 0000000000..58763914ee --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php @@ -0,0 +1,4 @@ + Date: Thu, 4 May 2017 12:06:51 -0700 Subject: [PATCH 021/543] Use a lighter color for completed list items in remarkup Summary: In remarkup lists, it can be hard to clearly see which items still need to be completed. This makes completed items a little lighter for clarity. Test Plan: Review a long list with checked and unchecked items in a task. {F4938611} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17828 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3e09edfc4e..930beaa0eb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '84ce260a', + 'core.pkg.css' => '24ffbe93', 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', @@ -114,7 +114,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '9f4cb463', - 'rsrc/css/core/remarkup.css' => '17c0fb37', + 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '0233d039', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -804,7 +804,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => '17c0fb37', + 'phabricator-remarkup-css' => 'd1a5e11e', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index a986409227..0c07c145a8 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -126,12 +126,13 @@ } .phabricator-remarkup .remarkup-list-with-checkmarks input { - margin-right: 2px; + margin-right: 4px; opacity: 1; } .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item { text-decoration: line-through; + color: {$lightgreytext}; } .phabricator-remarkup ul.remarkup-list ol.remarkup-list, From d77a3f8760b4c73cfb98af526b597c155a9892c6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 4 May 2017 21:48:13 +0000 Subject: [PATCH 022/543] Update Spaces for modular transactions Summary: Updates the Spaces application for modular transactions, seemed easy to bang out. Test Plan: Create a space, edit a space, archive a space. Verify default space works as intended. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17829 --- src/__phutil_library_map__.php | 12 +- .../__tests__/PhabricatorSpacesTestCase.php | 6 +- .../PhabricatorSpacesArchiveController.php | 3 +- .../PhabricatorSpacesEditController.php | 9 +- .../PhabricatorSpacesViewController.php | 2 +- .../PhabricatorSpacesNamespaceEditor.php | 143 +----------------- .../PhabricatorSpacesNamespaceTransaction.php | 81 +--------- ...catorSpacesNamespaceArchiveTransaction.php | 60 ++++++++ ...catorSpacesNamespaceDefaultTransaction.php | 44 ++++++ ...rSpacesNamespaceDescriptionTransaction.php | 57 +++++++ ...bricatorSpacesNamespaceNameTransaction.php | 62 ++++++++ ...bricatorSpacesNamespaceTransactionType.php | 4 + 12 files changed, 258 insertions(+), 225 deletions(-) create mode 100644 src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php create mode 100644 src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php create mode 100644 src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php create mode 100644 src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php create mode 100644 src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bed1a60426..14305c04da 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3937,13 +3937,18 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php', 'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php', 'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php', + 'PhabricatorSpacesNamespaceArchiveTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php', 'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php', + 'PhabricatorSpacesNamespaceDefaultTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php', + 'PhabricatorSpacesNamespaceDescriptionTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php', 'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php', + 'PhabricatorSpacesNamespaceNameTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php', 'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php', 'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php', 'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php', 'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php', 'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php', + 'PhabricatorSpacesNamespaceTransactionType' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php', 'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php', 'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php', 'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php', @@ -9373,13 +9378,18 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorSpacesNamespaceArchiveTransaction' => 'PhabricatorSpacesNamespaceTransactionType', 'PhabricatorSpacesNamespaceDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorSpacesNamespaceDefaultTransaction' => 'PhabricatorSpacesNamespaceTransactionType', + 'PhabricatorSpacesNamespaceDescriptionTransaction' => 'PhabricatorSpacesNamespaceTransactionType', 'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorSpacesNamespaceNameTransaction' => 'PhabricatorSpacesNamespaceTransactionType', 'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType', 'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorModularTransaction', 'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorSpacesNamespaceTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController', 'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec', diff --git a/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php b/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php index 4215ec96fe..b2a6b0b87f 100644 --- a/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php +++ b/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php @@ -190,8 +190,10 @@ final class PhabricatorSpacesTestCase extends PhabricatorTestCase { $space = PhabricatorSpacesNamespace::initializeNewNamespace($actor); - $type_name = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; - $type_default = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; + $type_name = + PhabricatorSpacesNamespaceNameTransaction::TRANSACTIONTYPE; + $type_default = + PhabricatorSpacesNamespaceDefaultTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; diff --git a/src/applications/spaces/controller/PhabricatorSpacesArchiveController.php b/src/applications/spaces/controller/PhabricatorSpacesArchiveController.php index 82cf19e9e8..b0ab8cd45b 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesArchiveController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesArchiveController.php @@ -23,7 +23,8 @@ final class PhabricatorSpacesArchiveController $cancel_uri = '/'.$space->getMonogram(); if ($request->isFormPost()) { - $type_archive = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE; + $type_archive = + PhabricatorSpacesNamespaceArchiveTransaction::TRANSACTIONTYPE; $xactions = array(); $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) diff --git a/src/applications/spaces/controller/PhabricatorSpacesEditController.php b/src/applications/spaces/controller/PhabricatorSpacesEditController.php index 3f7dae9e82..faca39d634 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesEditController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesEditController.php @@ -67,9 +67,12 @@ final class PhabricatorSpacesEditController $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); - $type_name = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; - $type_desc = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION; - $type_default = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; + $type_name = + PhabricatorSpacesNamespaceNameTransaction::TRANSACTIONTYPE; + $type_desc = + PhabricatorSpacesNamespaceDescriptionTransaction::TRANSACTIONTYPE; + $type_default = + PhabricatorSpacesNamespaceDefaultTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php index 495a0c8dee..5fa1c01143 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesViewController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php @@ -39,7 +39,7 @@ final class PhabricatorSpacesViewController ->setHeaderIcon('fa-th-large'); if ($space->getIsArchived()) { - $header->setStatus('fa-ban', 'red', pht('Archived')); + $header->setStatus('fa-ban', 'indigo', pht('Archived')); } else { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } diff --git a/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php index caa45f28c2..e4dc9c5b69 100644 --- a/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php +++ b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php @@ -14,153 +14,18 @@ final class PhabricatorSpacesNamespaceEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - $name = $object->getNamespaceName(); - if (!strlen($name)) { - return null; - } - return $name; - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - if ($this->getIsNewObject()) { - return null; - } - return $object->getDescription(); - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - return $object->getIsArchived(); - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - return $object->getIsDefaultNamespace() ? 1 : null; - case PhabricatorTransactions::TYPE_VIEW_POLICY: - return $object->getViewPolicy(); - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return $object->getEditPolicy(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + public function getCreateObjectTitle($author, $object) { + return pht('%s created this space.', $author); } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return $xaction->getNewValue(); - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - return $xaction->getNewValue() ? 1 : 0; - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - return $xaction->getNewValue() ? 1 : null; - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $new = $xaction->getNewValue(); - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - $object->setNamespaceName($new); - return; - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - $object->setDescription($new); - return; - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - $object->setIsDefaultNamespace($new ? 1 : null); - return; - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - $object->setIsArchived($new ? 1 : 0); - return; - case PhabricatorTransactions::TYPE_VIEW_POLICY: - $object->setViewPolicy($new); - return; - case PhabricatorTransactions::TYPE_EDIT_POLICY: - $object->setEditPolicy($new); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getNamespaceName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Spaces must have a name.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - if (!$this->getIsNewObject()) { - foreach ($xactions as $xaction) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Only the first space created can be the default space, and '. - 'it must remain the default space evermore.'), - $xaction); - } - } - break; - - } - - return $errors; + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created space %s.', $author, $object); } } diff --git a/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php index 4c438537f1..0f50a870f6 100644 --- a/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php +++ b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php @@ -1,12 +1,7 @@ getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - - return parent::hasChangeDetails(); - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_phid = $this->getAuthorPHID(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this space.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this space from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this space.', - $this->renderHandleLink($author_phid)); - case self::TYPE_DEFAULT: - return pht( - '%s made this the default space.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ARCHIVE: - if ($new) { - return pht( - '%s archived this space.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s activated this space.', - $this->renderHandleLink($author_phid)); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhabricatorSpacesNamespaceTransactionType'; } } diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php new file mode 100644 index 0000000000..a1dedbd85f --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php @@ -0,0 +1,60 @@ +getIsArchived(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsArchived((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s archived this space.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this space.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s archived space %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s activated space %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + public function getColor() { + $new = $this->getNewValue(); + if ($new) { + return 'indigo'; + } + } + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php new file mode 100644 index 0000000000..bc09b06d91 --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php @@ -0,0 +1,44 @@ +getIsDefaultNamespace(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDefaultNamespace($value); + } + + public function getTitle() { + return pht( + '%s made this the default space.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s made space %s the default space.', + $this->renderAuthor(), + $this->renderObject()); + + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Only the first space created can be the default space, and '. + 'it must remain the default space evermore.')); + } + } + + return $errors; + } + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php new file mode 100644 index 0000000000..22b0faa5d1 --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php @@ -0,0 +1,57 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the space description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the space description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO SPACE DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php new file mode 100644 index 0000000000..d7fcbc2c7a --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php @@ -0,0 +1,62 @@ +getNamespaceName(); + } + + public function applyInternalEffects($object, $value) { + $object->setNamespaceName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created this space.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this space from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + return pht( + '%s renamed space %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getNamespaceName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Spaces must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('namespaceName'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php new file mode 100644 index 0000000000..a6f206a16c --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php @@ -0,0 +1,4 @@ + Date: Thu, 4 May 2017 17:00:02 -0700 Subject: [PATCH 023/543] Explicitly quote "From" name part when submitting mail to the Mailgun API Summary: We are submitting `epriestley (Evan Priestley) `, but should be submitting `"epriestley (Evan Priestley)" `. Add the missing quotes. Test Plan: Locally, this makes the API calls work against the Mailgun sandbox domain. Reviewers: chad, amckinley Reviewed By: chad, amckinley Differential Revision: https://secure.phabricator.com/D17831 --- .../adapter/PhabricatorMailImplementationMailgunAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php index 0890ac3004..cfe6491fe0 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php @@ -86,7 +86,7 @@ final class PhabricatorMailImplementationMailgunAdapter $from = idx($this->params, 'from'); if (idx($this->params, 'from-name')) { - $params['from'] = "{$this->params['from-name']} <{$from}>"; + $params['from'] = "\"{$this->params['from-name']}\" <{$from}>"; } else { $params['from'] = $from; } From 0dc90b891a7c6d0fe6bef3b5d6a8b40147c17f74 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 4 May 2017 20:16:21 -0700 Subject: [PATCH 024/543] Reimplement Slowvote transactions using modular transactions Summary: Fixes T12623. Adds new modular transactions to Slowvote. Also converts the `shuffle` column to `bool` for consistency with other boolean-ish columns. Test Plan: Create a new vote, modified everything that could be modified from the web UI, observed expected timeline. Example timeline: {F4938843} Example transaction values in DB: {F4938850} Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T12623 Differential Revision: https://secure.phabricator.com/D17830 --- .../20170504.1.slowvote.shuffle.sql | 2 + src/__phutil_library_map__.php | 14 +- .../PhabricatorSlowvoteCloseController.php | 3 +- .../PhabricatorSlowvoteEditController.php | 19 +- .../editor/PhabricatorSlowvoteEditor.php | 94 +------ .../storage/PhabricatorSlowvotePoll.php | 6 +- .../PhabricatorSlowvoteTransaction.php | 238 +----------------- .../PhabricatorSlowvoteCloseTransaction.php | 60 +++++ ...bricatorSlowvoteDescriptionTransaction.php | 60 +++++ ...PhabricatorSlowvoteQuestionTransaction.php | 70 ++++++ ...habricatorSlowvoteResponsesTransaction.php | 31 +++ .../PhabricatorSlowvoteShuffleTransaction.php | 55 ++++ .../PhabricatorSlowvoteTransactionType.php | 4 + 13 files changed, 323 insertions(+), 333 deletions(-) create mode 100644 resources/sql/autopatches/20170504.1.slowvote.shuffle.sql create mode 100644 src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php create mode 100644 src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php create mode 100644 src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php create mode 100644 src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php create mode 100644 src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php create mode 100644 src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php diff --git a/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql b/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql new file mode 100644 index 0000000000..5797f3fd5c --- /dev/null +++ b/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll + MODIFY shuffle BOOL NOT NULL DEFAULT 0; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 14305c04da..70dae27882 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3901,10 +3901,12 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php', 'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.php', + 'PhabricatorSlowvoteCloseTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php', 'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php', 'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php', 'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/PhabricatorSlowvoteDAO.php', 'PhabricatorSlowvoteDefaultViewCapability' => 'applications/slowvote/capability/PhabricatorSlowvoteDefaultViewCapability.php', + 'PhabricatorSlowvoteDescriptionTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php', 'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php', 'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php', @@ -3914,12 +3916,16 @@ phutil_register_library_map(array( 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php', 'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php', 'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php', + 'PhabricatorSlowvoteQuestionTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php', 'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php', + 'PhabricatorSlowvoteResponsesTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php', 'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php', 'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php', + 'PhabricatorSlowvoteShuffleTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php', 'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php', 'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php', 'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php', + 'PhabricatorSlowvoteTransactionType' => 'applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php', 'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php', 'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php', 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', @@ -9327,10 +9333,12 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteApplication' => 'PhabricatorApplication', 'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteCloseController' => 'PhabricatorSlowvoteController', + 'PhabricatorSlowvoteCloseTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteController' => 'PhabricatorController', 'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO', 'PhabricatorSlowvoteDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorSlowvoteDescriptionTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController', @@ -9350,12 +9358,16 @@ phutil_register_library_map(array( 'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType', 'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSlowvoteQuestionTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'PhabricatorSlowvoteResponsesTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorSlowvoteTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorSlowvoteShuffleTransaction' => 'PhabricatorSlowvoteTransactionType', + 'PhabricatorSlowvoteTransaction' => 'PhabricatorModularTransaction', 'PhabricatorSlowvoteTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorSlowvoteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorSlowvoteTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController', 'PhabricatorSlug' => 'Phobject', 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php index 05c12aec27..a4da0f7f7d 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php @@ -32,7 +32,8 @@ final class PhabricatorSlowvoteCloseController $xactions = array(); $xactions[] = id(new PhabricatorSlowvoteTransaction()) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_CLOSE) + ->setTransactionType( + PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorSlowvoteEditor()) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index e9f3d48de4..d490a77c50 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -77,23 +77,32 @@ final class PhabricatorSlowvoteEditController } } - $xactions = array(); $template = id(new PhabricatorSlowvoteTransaction()); + $xactions = array(); + + if ($is_new) { + $xactions[] = id(new PhabricatorSlowvoteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE); + } $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_QUESTION) + ->setTransactionType( + PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE) ->setNewValue($v_question); $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($v_description); $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_RESPONSES) + ->setTransactionType( + PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE) ->setNewValue($v_responses); $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_SHUFFLE) + ->setTransactionType( + PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE) ->setNewValue($v_shuffle); $xactions[] = id(clone $template) diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php index fa320428e5..3f6baefd8d 100644 --- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php +++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php @@ -13,104 +13,12 @@ final class PhabricatorSlowvoteEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; - $types[] = PhabricatorSlowvoteTransaction::TYPE_QUESTION; - $types[] = PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorSlowvoteTransaction::TYPE_RESPONSES; - $types[] = PhabricatorSlowvoteTransaction::TYPE_SHUFFLE; - $types[] = PhabricatorSlowvoteTransaction::TYPE_CLOSE; - return $types; } - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - if ($old === null) { - return true; - } - return ((int)$old !== (int)$new); - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - if ($old === null) { - return true; - } - return ((bool)$old !== (bool)$new); - } - - return parent::transactionHasEffect($object, $xaction); - } - - - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - return $object->getQuestion(); - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - return $object->getResponseVisibility(); - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - return $object->getShuffle(); - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: - return $object->getIsClosed(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - $object->setQuestion($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - $object->setResponseVisibility($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - $object->setShuffle($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: - $object->setIsClosed((int)$xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function shouldSendMail( + protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 78c21f5d65..06c4f6f68f 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -21,8 +21,8 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO protected $question; protected $description; protected $authorPHID; - protected $responseVisibility; - protected $shuffle; + protected $responseVisibility = 0; + protected $shuffle = 0; protected $method; protected $mailKey; protected $viewPolicy; @@ -54,7 +54,7 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO self::CONFIG_COLUMN_SCHEMA => array( 'question' => 'text255', 'responseVisibility' => 'uint32', - 'shuffle' => 'uint32', + 'shuffle' => 'bool', 'method' => 'uint32', 'description' => 'text', 'isClosed' => 'bool', diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php index fb1b7fc2f3..1781733acf 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php @@ -1,13 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_RESPONSES: - case self::TYPE_SHUFFLE: - case self::TYPE_CLOSE: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - if ($old === null) { - return pht( - '%s created this poll.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the poll question from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this poll.', - $this->renderHandleLink($author_phid)); - case self::TYPE_RESPONSES: - // TODO: This could be more detailed - return pht( - '%s changed who can see the responses.', - $this->renderHandleLink($author_phid)); - case self::TYPE_SHUFFLE: - if ($new) { - return pht( - '%s made poll responses appear in a random order.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s made poll responses appear in a fixed order.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_CLOSE: - if ($new) { - return pht( - '%s closed this poll.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s reopened this poll.', - $this->renderHandleLink($author_phid)); - } - - break; - } - - return parent::getTitle(); - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_DESCRIPTION: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_QUESTION: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_DESCRIPTION: - if ($old === null) { - return pht( - '%s set the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_RESPONSES: - // TODO: This could be more detailed - return pht( - '%s changed who can see the responses of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - case self::TYPE_SHUFFLE: - if ($new) { - return pht( - '%s made %s responses appear in a random order.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s made %s responses appear in a fixed order.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - case self::TYPE_CLOSE: - if ($new) { - return pht( - '%s closed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s reopened %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - case self::TYPE_DESCRIPTION: - case self::TYPE_RESPONSES: - return 'fa-pencil'; - case self::TYPE_SHUFFLE: - return 'fa-refresh'; - case self::TYPE_CLOSE: - if ($new) { - return 'fa-ban'; - } else { - return 'fa-pencil'; - } - } - - return parent::getIcon(); - } - - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - case self::TYPE_DESCRIPTION: - case self::TYPE_RESPONSES: - case self::TYPE_SHUFFLE: - case self::TYPE_CLOSE: - return PhabricatorTransactions::COLOR_BLUE; - } - - return parent::getColor(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'PhabricatorSlowvoteTransactionType'; } public function getMailTags() { $tags = parent::getMailTags(); switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - case self::TYPE_DESCRIPTION: - case self::TYPE_SHUFFLE: - case self::TYPE_CLOSE: + case PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE: + case PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE: + case PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE: + case PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; - case self::TYPE_RESPONSES: + case PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_RESPONSES; break; default: diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php new file mode 100644 index 0000000000..ce3eaf5879 --- /dev/null +++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php @@ -0,0 +1,60 @@ +getIsClosed(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsClosed((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s closed this poll.', + $this->renderAuthor()); + } else { + return pht( + '%s reopened this poll.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s closed %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s reopened %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new) { + return 'fa-ban'; + } else { + return 'fa-pencil'; + } + } + +} diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php new file mode 100644 index 0000000000..8a46f7dc5e --- /dev/null +++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php @@ -0,0 +1,60 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the description for this poll.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old === null) { + return pht( + '%s set the description of %s.', + $this->renderAuthor(), + $this->renderObject()); + + } else { + return pht( + '%s edited the description of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function hasChangeDetails() { + return true; + } + + public function newChangeDetailView() { + return $this->renderTextCorpusChangeDetails( + $this->getViewer(), + $this->getOldValue(), + $this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php new file mode 100644 index 0000000000..09c5f93e66 --- /dev/null +++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php @@ -0,0 +1,70 @@ +getQuestion(); + } + + public function applyInternalEffects($object, $value) { + $object->setQuestion($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created this poll.', + $this->renderAuthor()); + } else { + return pht( + '%s changed the poll question from "%s" to "%s".', + $this->renderAuthor(), + $old, + $new); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + + } else { + return pht( + '%s renamed %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $old = $this->getOldValue(); + + if ($old === null) { + return 'fa-plus'; + } else { + return 'fa-pencil'; + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getQuestion(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Polls must have a question.')); + } + + return $errors; + } + +} diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php new file mode 100644 index 0000000000..93035cbd6a --- /dev/null +++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php @@ -0,0 +1,31 @@ +getResponseVisibility(); + } + + public function applyInternalEffects($object, $value) { + $object->setResponseVisibility($value); + } + + public function getTitle() { + // TODO: This could be more detailed + return pht( + '%s changed who can see the responses.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + // TODO: This could be more detailed + return pht( + '%s changed who can see the responses of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php new file mode 100644 index 0000000000..645e86b393 --- /dev/null +++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php @@ -0,0 +1,55 @@ +getShuffle(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setShuffle((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s made poll responses appear in a random order.', + $this->renderAuthor()); + } else { + return pht( + '%s made poll responses appear in a fixed order.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s made %s responses appear in a random order.', + $this->renderAuthor(), + $this->renderObject()); + + } else { + return pht( + '%s made %s responses appear in a fixed order.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + return 'fa-refresh'; + } + +} diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php new file mode 100644 index 0000000000..f380aa101c --- /dev/null +++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php @@ -0,0 +1,4 @@ + Date: Fri, 5 May 2017 11:02:54 -0700 Subject: [PATCH 025/543] Update Fund for modular transactions Summary: Fixes T12627. Updates FundInitiative and FundBacker with modular transactions. Test Plan: Create an Initiative, back it with fake monies, close initiative, reopen, edit various fields. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12627 Differential Revision: https://secure.phabricator.com/D17782 --- src/__phutil_library_map__.php | 26 ++- .../FundInitiativeBackController.php | 2 +- .../FundInitiativeCloseController.php | 2 +- .../FundInitiativeEditController.php | 8 +- .../FundInitiativeViewController.php | 4 +- .../fund/editor/FundBackerEditor.php | 63 ------ .../fund/editor/FundInitiativeEditor.php | 214 +----------------- .../fund/phortune/FundBackerProduct.php | 4 +- .../fund/storage/FundBackerTransaction.php | 9 +- .../storage/FundInitiativeTransaction.php | 207 +---------------- .../xaction/FundBackerRefundTransaction.php | 13 ++ .../xaction/FundBackerStatusTransaction.php | 17 ++ .../xaction/FundBackerTransactionType.php | 4 + .../FundInitiativeBackerTransaction.php | 74 ++++++ .../FundInitiativeDescriptionTransaction.php | 75 ++++++ .../FundInitiativeMerchantTransaction.php | 93 ++++++++ .../xaction/FundInitiativeNameTransaction.php | 71 ++++++ .../FundInitiativeRefundTransaction.php | 77 +++++++ .../FundInitiativeRisksTransaction.php | 80 +++++++ .../FundInitiativeStatusTransaction.php | 51 +++++ .../xaction/FundInitiativeTransactionType.php | 4 + 21 files changed, 609 insertions(+), 489 deletions(-) create mode 100644 src/applications/fund/xaction/FundBackerRefundTransaction.php create mode 100644 src/applications/fund/xaction/FundBackerStatusTransaction.php create mode 100644 src/applications/fund/xaction/FundBackerTransactionType.php create mode 100644 src/applications/fund/xaction/FundInitiativeBackerTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeMerchantTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeNameTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeRefundTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeRisksTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeStatusTransaction.php create mode 100644 src/applications/fund/xaction/FundInitiativeTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 70dae27882..8389ee567e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1124,29 +1124,40 @@ phutil_register_library_map(array( 'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php', 'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php', 'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php', + 'FundBackerRefundTransaction' => 'applications/fund/xaction/FundBackerRefundTransaction.php', 'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php', + 'FundBackerStatusTransaction' => 'applications/fund/xaction/FundBackerStatusTransaction.php', 'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php', 'FundBackerTransactionQuery' => 'applications/fund/query/FundBackerTransactionQuery.php', + 'FundBackerTransactionType' => 'applications/fund/xaction/FundBackerTransactionType.php', 'FundController' => 'applications/fund/controller/FundController.php', 'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php', 'FundDAO' => 'applications/fund/storage/FundDAO.php', 'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php', 'FundInitiative' => 'applications/fund/storage/FundInitiative.php', 'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php', + 'FundInitiativeBackerTransaction' => 'applications/fund/xaction/FundInitiativeBackerTransaction.php', 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', 'FundInitiativeCommentController' => 'applications/fund/controller/FundInitiativeCommentController.php', + 'FundInitiativeDescriptionTransaction' => 'applications/fund/xaction/FundInitiativeDescriptionTransaction.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', + 'FundInitiativeMerchantTransaction' => 'applications/fund/xaction/FundInitiativeMerchantTransaction.php', + 'FundInitiativeNameTransaction' => 'applications/fund/xaction/FundInitiativeNameTransaction.php', 'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', 'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', + 'FundInitiativeRefundTransaction' => 'applications/fund/xaction/FundInitiativeRefundTransaction.php', 'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php', 'FundInitiativeReplyHandler' => 'applications/fund/mail/FundInitiativeReplyHandler.php', + 'FundInitiativeRisksTransaction' => 'applications/fund/xaction/FundInitiativeRisksTransaction.php', 'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php', + 'FundInitiativeStatusTransaction' => 'applications/fund/xaction/FundInitiativeStatusTransaction.php', 'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php', 'FundInitiativeTransactionComment' => 'applications/fund/storage/FundInitiativeTransactionComment.php', 'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php', + 'FundInitiativeTransactionType' => 'applications/fund/xaction/FundInitiativeTransactionType.php', 'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', @@ -6066,9 +6077,12 @@ phutil_register_library_map(array( 'FundBackerPHIDType' => 'PhabricatorPHIDType', 'FundBackerProduct' => 'PhortuneProductImplementation', 'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'FundBackerRefundTransaction' => 'FundBackerTransactionType', 'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'FundBackerTransaction' => 'PhabricatorApplicationTransaction', + 'FundBackerStatusTransaction' => 'FundBackerTransactionType', + 'FundBackerTransaction' => 'PhabricatorModularTransaction', 'FundBackerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'FundBackerTransactionType' => 'PhabricatorModularTransactionType', 'FundController' => 'PhabricatorController', 'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability', 'FundDAO' => 'PhabricatorLiskDAO', @@ -6086,20 +6100,28 @@ phutil_register_library_map(array( 'PhabricatorFulltextInterface', ), 'FundInitiativeBackController' => 'FundController', + 'FundInitiativeBackerTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeCloseController' => 'FundController', 'FundInitiativeCommentController' => 'FundController', + 'FundInitiativeDescriptionTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', 'FundInitiativeListController' => 'FundController', + 'FundInitiativeMerchantTransaction' => 'FundInitiativeTransactionType', + 'FundInitiativeNameTransaction' => 'FundInitiativeTransactionType', 'FundInitiativePHIDType' => 'PhabricatorPHIDType', 'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'FundInitiativeRefundTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'FundInitiativeReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'FundInitiativeRisksTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction', + 'FundInitiativeStatusTransaction' => 'FundInitiativeTransactionType', + 'FundInitiativeTransaction' => 'PhabricatorModularTransaction', 'FundInitiativeTransactionComment' => 'PhabricatorApplicationTransactionComment', 'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'FundInitiativeTransactionType' => 'PhabricatorModularTransactionType', 'FundInitiativeViewController' => 'FundController', 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', diff --git a/src/applications/fund/controller/FundInitiativeBackController.php b/src/applications/fund/controller/FundInitiativeBackController.php index 414a5ebd64..17ead82ebf 100644 --- a/src/applications/fund/controller/FundInitiativeBackController.php +++ b/src/applications/fund/controller/FundInitiativeBackController.php @@ -96,7 +96,7 @@ final class FundInitiativeBackController $xactions = array(); $xactions[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) + ->setTransactionType(FundBackerStatusTransaction::TRANSACTIONTYPE) ->setNewValue(FundBacker::STATUS_IN_CART); $editor = id(new FundBackerEditor()) diff --git a/src/applications/fund/controller/FundInitiativeCloseController.php b/src/applications/fund/controller/FundInitiativeCloseController.php index 6adddb0d80..97c6de3d97 100644 --- a/src/applications/fund/controller/FundInitiativeCloseController.php +++ b/src/applications/fund/controller/FundInitiativeCloseController.php @@ -25,7 +25,7 @@ final class FundInitiativeCloseController $is_close = !$initiative->isClosed(); if ($request->isFormPost()) { - $type_status = FundInitiativeTransaction::TYPE_STATUS; + $type_status = FundInitiativeStatusTransaction::TRANSACTIONTYPE; if ($is_close) { $new_status = FundInitiative::STATUS_CLOSED; diff --git a/src/applications/fund/controller/FundInitiativeEditController.php b/src/applications/fund/controller/FundInitiativeEditController.php index 6e092d1e05..0576e47330 100644 --- a/src/applications/fund/controller/FundInitiativeEditController.php +++ b/src/applications/fund/controller/FundInitiativeEditController.php @@ -68,10 +68,10 @@ final class FundInitiativeEditController $v_merchant = $request->getStr('merchantPHID'); $v_projects = $request->getArr('projects'); - $type_name = FundInitiativeTransaction::TYPE_NAME; - $type_desc = FundInitiativeTransaction::TYPE_DESCRIPTION; - $type_risk = FundInitiativeTransaction::TYPE_RISKS; - $type_merchant = FundInitiativeTransaction::TYPE_MERCHANT; + $type_name = FundInitiativeNameTransaction::TRANSACTIONTYPE; + $type_desc = FundInitiativeDescriptionTransaction::TRANSACTIONTYPE; + $type_risk = FundInitiativeRisksTransaction::TRANSACTIONTYPE; + $type_merchant = FundInitiativeMerchantTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 4960e7aa35..eee79e4df0 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -29,8 +29,8 @@ final class FundInitiativeViewController $initiative->getName()); if ($initiative->isClosed()) { - $status_icon = 'fa-times'; - $status_color = 'bluegrey'; + $status_icon = 'fa-ban'; + $status_color = 'indigo'; } else { $status_icon = 'fa-check'; $status_color = 'bluegrey'; diff --git a/src/applications/fund/editor/FundBackerEditor.php b/src/applications/fund/editor/FundBackerEditor.php index aabd1d8e60..403853863b 100644 --- a/src/applications/fund/editor/FundBackerEditor.php +++ b/src/applications/fund/editor/FundBackerEditor.php @@ -11,67 +11,4 @@ final class FundBackerEditor return pht('Fund Backing'); } - public function getTransactionTypes() { - $types = parent::getTransactionTypes(); - - $types[] = FundBackerTransaction::TYPE_STATUS; - $types[] = FundBackerTransaction::TYPE_REFUND; - - return $types; - } - - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - return $object->getStatus(); - case FundBackerTransaction::TYPE_REFUND: - return null; - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - case FundBackerTransaction::TYPE_REFUND: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case FundBackerTransaction::TYPE_REFUND: - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - case FundBackerTransaction::TYPE_REFUND: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - } diff --git a/src/applications/fund/editor/FundInitiativeEditor.php b/src/applications/fund/editor/FundInitiativeEditor.php index 40fc6f0174..e5c372fd12 100644 --- a/src/applications/fund/editor/FundInitiativeEditor.php +++ b/src/applications/fund/editor/FundInitiativeEditor.php @@ -11,16 +11,16 @@ final class FundInitiativeEditor return pht('Fund Initiatives'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this initiative.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = FundInitiativeTransaction::TYPE_NAME; - $types[] = FundInitiativeTransaction::TYPE_DESCRIPTION; - $types[] = FundInitiativeTransaction::TYPE_RISKS; - $types[] = FundInitiativeTransaction::TYPE_STATUS; - $types[] = FundInitiativeTransaction::TYPE_BACKER; - $types[] = FundInitiativeTransaction::TYPE_REFUND; - $types[] = FundInitiativeTransaction::TYPE_MERCHANT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_COMMENT; @@ -28,204 +28,6 @@ final class FundInitiativeEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case FundInitiativeTransaction::TYPE_NAME: - return $object->getName(); - case FundInitiativeTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case FundInitiativeTransaction::TYPE_RISKS: - return $object->getRisks(); - case FundInitiativeTransaction::TYPE_STATUS: - return $object->getStatus(); - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - return null; - case FundInitiativeTransaction::TYPE_MERCHANT: - return $object->getMerchantPHID(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundInitiativeTransaction::TYPE_NAME: - case FundInitiativeTransaction::TYPE_DESCRIPTION: - case FundInitiativeTransaction::TYPE_RISKS: - case FundInitiativeTransaction::TYPE_STATUS: - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - case FundInitiativeTransaction::TYPE_MERCHANT: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_RISKS: - $object->setRisks($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_MERCHANT: - $object->setMerchantPHID($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - $amount = $xaction->getMetadataValue( - FundInitiativeTransaction::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - - if ($type == FundInitiativeTransaction::TYPE_REFUND) { - $total = $object->getTotalAsCurrency()->subtract($amount); - } else { - $total = $object->getTotalAsCurrency()->add($amount); - } - - $object->setTotalAsCurrency($total); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: - case FundInitiativeTransaction::TYPE_DESCRIPTION: - case FundInitiativeTransaction::TYPE_RISKS: - case FundInitiativeTransaction::TYPE_STATUS: - case FundInitiativeTransaction::TYPE_MERCHANT: - return; - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - $backer = id(new FundBackerQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($xaction->getNewValue())) - ->executeOne(); - if (!$backer) { - throw new Exception(pht('Unable to load %s!', 'FundBacker')); - } - - $subx = array(); - - if ($type == FundInitiativeTransaction::TYPE_BACKER) { - $subx[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) - ->setNewValue(FundBacker::STATUS_PURCHASED); - } else { - $amount = $xaction->getMetadataValue( - FundInitiativeTransaction::PROPERTY_AMOUNT); - $subx[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) - ->setNewValue($amount); - } - - $editor = id(new FundBackerEditor()) - ->setActor($this->requireActor()) - ->setContentSource($this->getContentSource()) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true); - - $editor->applyTransactions($backer, $subx); - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Initiative name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case FundInitiativeTransaction::TYPE_MERCHANT: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Payable merchant is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($xactions) { - $merchant_phid = last($xactions)->getNewValue(); - - // Make sure the actor has permission to edit the merchant they're - // selecting. You aren't allowed to send payments to an account you - // do not control. - $merchants = id(new PhortuneMerchantQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($merchant_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - if (!$merchants) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You must specify a merchant account you control as the '. - 'recipient of funds from this initiative.'), - last($xactions)); - $errors[] = $error; - } - } - break; - } - - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/fund/phortune/FundBackerProduct.php b/src/applications/fund/phortune/FundBackerProduct.php index 679a1a41d2..be0dbd9171 100644 --- a/src/applications/fund/phortune/FundBackerProduct.php +++ b/src/applications/fund/phortune/FundBackerProduct.php @@ -105,7 +105,7 @@ final class FundBackerProduct extends PhortuneProductImplementation { $xactions = array(); $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(FundInitiativeTransaction::TYPE_BACKER) + ->setTransactionType(FundInitiativeBackerTransaction::TRANSACTIONTYPE) ->setMetadataValue( FundInitiativeTransaction::PROPERTY_AMOUNT, $backer->getAmountAsCurrency()->serializeForStorage()) @@ -134,7 +134,7 @@ final class FundBackerProduct extends PhortuneProductImplementation { $xactions = array(); $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(FundInitiativeTransaction::TYPE_REFUND) + ->setTransactionType(FundInitiativeRefundTransaction::TRANSACTIONTYPE) ->setMetadataValue( FundInitiativeTransaction::PROPERTY_AMOUNT, $amount->serializeForStorage()) diff --git a/src/applications/fund/storage/FundBackerTransaction.php b/src/applications/fund/storage/FundBackerTransaction.php index 555c7d2966..c24e769eb6 100644 --- a/src/applications/fund/storage/FundBackerTransaction.php +++ b/src/applications/fund/storage/FundBackerTransaction.php @@ -1,10 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_MERCHANT: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - case self::TYPE_REFUND: - $phids[] = $this->getMetadataValue(self::PROPERTY_BACKER); - break; - } - - return $phids; - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this initiative.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this initiative from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_RISKS: - return pht( - '%s edited the risks for this initiative.', - $this->renderHandleLink($author_phid)); - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of this initiative.', - $this->renderHandleLink($author_phid)); - case self::TYPE_STATUS: - switch ($new) { - case FundInitiative::STATUS_OPEN: - return pht( - '%s reopened this initiative.', - $this->renderHandleLink($author_phid)); - case FundInitiative::STATUS_CLOSED: - return pht( - '%s closed this initiative.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_BACKER: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - return pht( - '%s backed this initiative with %s.', - $this->renderHandleLink($author_phid), - $amount->formatForDisplay()); - case self::TYPE_REFUND: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - - $backer_phid = $this->getMetadataValue(self::PROPERTY_BACKER); - - return pht( - '%s refunded %s to %s.', - $this->renderHandleLink($author_phid), - $amount->formatForDisplay(), - $this->renderHandleLink($backer_phid)); - case self::TYPE_MERCHANT: - if ($old === null) { - return pht( - '%s set this initiative to pay to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s changed the merchant receiving funds from this '. - 'initiative from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_STATUS: - switch ($new) { - case FundInitiative::STATUS_OPEN: - return pht( - '%s reopened %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case FundInitiative::STATUS_CLOSED: - return pht( - '%s closed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_BACKER: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - return pht( - '%s backed %s with %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $amount->formatForDisplay()); - case self::TYPE_REFUND: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - - $backer_phid = $this->getMetadataValue(self::PROPERTY_BACKER); - - return pht( - '%s refunded %s to %s for %s.', - $this->renderHandleLink($author_phid), - $amount->formatForDisplay(), - $this->renderHandleLink($backer_phid), - $this->renderHandleLink($object_phid)); - } - - return parent::getTitleForFeed(); + public function getBaseTransactionClass() { + return 'FundInitiativeTransactionType'; } public function getMailTags() { @@ -219,31 +45,4 @@ final class FundInitiativeTransaction return $tags; } - - public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_RISKS: - return ($old === null); - } - return parent::shouldHide(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_RISKS: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } } diff --git a/src/applications/fund/xaction/FundBackerRefundTransaction.php b/src/applications/fund/xaction/FundBackerRefundTransaction.php new file mode 100644 index 0000000000..0aad952482 --- /dev/null +++ b/src/applications/fund/xaction/FundBackerRefundTransaction.php @@ -0,0 +1,13 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + +} diff --git a/src/applications/fund/xaction/FundBackerTransactionType.php b/src/applications/fund/xaction/FundBackerTransactionType.php new file mode 100644 index 0000000000..86ca6703d7 --- /dev/null +++ b/src/applications/fund/xaction/FundBackerTransactionType.php @@ -0,0 +1,4 @@ +getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $total = $object->getTotalAsCurrency()->add($amount); + $object->setTotalAsCurrency($total); + } + + public function applyExternalEffects($object, $value) { + $backer = id(new FundBackerQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + if (!$backer) { + throw new Exception(pht('Unable to load %s!', 'FundBacker')); + } + + $subx = array(); + $subx[] = id(new FundBackerTransaction()) + ->setTransactionType(FundBackerStatusTransaction::TRANSACTIONTYPE) + ->setNewValue(FundBacker::STATUS_PURCHASED); + + $content_source = $this->getEditor()->getContentSource(); + + $editor = id(new FundBackerEditor()) + ->setActor($this->getActor()) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($backer, $subx); + } + + public function getTitle() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + return pht( + '%s backed this initiative with %s.', + $this->renderAuthor(), + $amount->formatForDisplay()); + } + + public function getTitleForFeed() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + return pht( + '%s backed %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-heart'; + } + + public function getColor() { + return 'red'; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php b/src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php new file mode 100644 index 0000000000..f32b8499cf --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php @@ -0,0 +1,75 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + if (!strlen($old) && !strlen($new)) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the initiative description.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the initiative description.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + return pht( + '%s updated the initiative description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO INITIATIVE DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php b/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php new file mode 100644 index 0000000000..e2a2597c50 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php @@ -0,0 +1,93 @@ +getMerchantPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setMerchantPHID($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + $new_merchant = $this->renderHandleList(array($new)); + + $old = $this->getOldValue(); + $old_merchant = $this->renderHandleList(array($old)); + + if ($old) { + return pht( + '%s changed the merchant receiving funds from this '. + 'initiative from %s to %s.', + $this->renderAuthor(), + $old_merchant, + $new_merchant); + } else { + return pht( + '%s set the merchant receiving funds from this '. + 'initiative to %s.', + $this->renderAuthor(), + $new_merchant); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + $new_merchant = $this->renderHandleList(array($new)); + + $old = $this->getOldValue(); + $old_merchant = $this->renderHandleList(array($old)); + + return pht( + '%s changed the merchant receiving funds from %s '. + 'initiative from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $old_merchant, + $new_merchant); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getMerchantPHID(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Initiatives must have a payable merchant.')); + } + + foreach ($xactions as $xaction) { + $merchant_phid = $xaction->getNewValue(); + + // Make sure the actor has permission to edit the merchant they're + // selecting. You aren't allowed to send payments to an account you + // do not control. + $merchants = id(new PhortuneMerchantQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($merchant_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + if (!$merchants) { + $errors[] = $this->newInvalidError( + pht('You must specify a merchant account you control as the '. + 'recipient of funds from this initiative.')); + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-bank'; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeNameTransaction.php b/src/applications/fund/xaction/FundInitiativeNameTransaction.php new file mode 100644 index 0000000000..5883c32ce2 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeNameTransaction.php @@ -0,0 +1,71 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created this initiative.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this initiative from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created initiative %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s initiative from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Initiatives must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeRefundTransaction.php b/src/applications/fund/xaction/FundInitiativeRefundTransaction.php new file mode 100644 index 0000000000..60bb610a80 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeRefundTransaction.php @@ -0,0 +1,77 @@ +getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $total = $object->getTotalAsCurrency()->subtract($amount); + $object->setTotalAsCurrency($total); + } + + public function applyExternalEffects($object, $value) { + $backer = id(new FundBackerQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + if (!$backer) { + throw new Exception(pht('Unable to load %s!', 'FundBacker')); + } + + $subx = array(); + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $subx[] = id(new FundBackerTransaction()) + ->setTransactionType(FundBackerStatusTransaction::TRANSACTIONTYPE) + ->setNewValue($amount); + + $content_source = $this->getEditor()->getContentSource(); + + $editor = id(new FundBackerEditor()) + ->setActor($this->getActor()) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($backer, $subx); + } + + public function getTitle() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $backer_phid = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_BACKER); + + return pht( + '%s refunded %s to %s.', + $this->renderAuthor(), + $amount->formatForDisplay(), + $this->renderHandleLink($backer_phid)); + } + + public function getTitleForFeed() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $backer_phid = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_BACKER); + + return pht( + '%s refunded %s to %s for %s.', + $this->renderAuthor(), + $amount->formatForDisplay(), + $this->renderHandleLink($backer_phid), + $this->renderObject()); + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeRisksTransaction.php b/src/applications/fund/xaction/FundInitiativeRisksTransaction.php new file mode 100644 index 0000000000..183f9caa67 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeRisksTransaction.php @@ -0,0 +1,80 @@ +getRisks(); + } + + public function applyInternalEffects($object, $value) { + $object->setRisks($value); + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + if (!strlen($old) && !strlen($new)) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the initiative risks/challenges.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the initiative risks/challenges.', + $this->renderAuthor()); + } + + } + + public function getTitleForFeed() { + return pht( + '%s updated the initiative risks/challenges for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO INITIATIVE RISKS/CHALLENGES'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function getIcon() { + return 'fa-ambulance'; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeStatusTransaction.php b/src/applications/fund/xaction/FundInitiativeStatusTransaction.php new file mode 100644 index 0000000000..688c8b52c4 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeStatusTransaction.php @@ -0,0 +1,51 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { + return pht( + '%s closed this initiative.', + $this->renderAuthor()); + } else { + return pht( + '%s reopened this initiative.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { + return pht( + '%s closed the initiative %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s reopened the initiative %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeTransactionType.php b/src/applications/fund/xaction/FundInitiativeTransactionType.php new file mode 100644 index 0000000000..6bcd17f1c7 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeTransactionType.php @@ -0,0 +1,4 @@ + Date: Fri, 5 May 2017 11:12:26 -0700 Subject: [PATCH 026/543] Enable Feed stories for Fund Summary: Not sure when these stopped, also fixed mailtag contants. Test Plan: Close an initiative, see story, fund initiative, see story. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17835 --- .../fund/storage/FundInitiativeTransaction.php | 12 +++++++++--- .../fund/xaction/FundInitiativeBackerTransaction.php | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/applications/fund/storage/FundInitiativeTransaction.php b/src/applications/fund/storage/FundInitiativeTransaction.php index 72e335f0cd..f6071878c5 100644 --- a/src/applications/fund/storage/FundInitiativeTransaction.php +++ b/src/applications/fund/storage/FundInitiativeTransaction.php @@ -26,15 +26,21 @@ final class FundInitiativeTransaction return 'FundInitiativeTransactionType'; } + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + public function getMailTags() { $tags = parent::getMailTags(); switch ($this->getTransactionType()) { - case self::TYPE_STATUS: + case FundInitiativeStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_BACKER: - case self::TYPE_REFUND: + case FundInitiativeBackerTransaction::TRANSACTIONTYPE: + case FundInitiativeRefundTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_BACKER; break; default: diff --git a/src/applications/fund/xaction/FundInitiativeBackerTransaction.php b/src/applications/fund/xaction/FundInitiativeBackerTransaction.php index 94f4230975..72b4b1bd85 100644 --- a/src/applications/fund/xaction/FundInitiativeBackerTransaction.php +++ b/src/applications/fund/xaction/FundInitiativeBackerTransaction.php @@ -57,9 +57,10 @@ final class FundInitiativeBackerTransaction FundInitiativeTransaction::PROPERTY_AMOUNT); $amount = PhortuneCurrency::newFromString($amount); return pht( - '%s backed %s.', + '%s backed %s with %s.', $this->renderAuthor(), - $this->renderObject()); + $this->renderObject(), + $amount->formatForDisplay()); } public function getIcon() { From 0a87881e9813ac5d3662ab13577f04ae2152cf3e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 5 May 2017 19:50:05 -0700 Subject: [PATCH 027/543] Fix file attach bug in Macro Summary: This was mis-tested by only using one account, which could always see the image. External transaction moved file attachment to the modular transaction for file and audio instead. Test Plan: Test adding audio and a macro on a pleb account, visit with normal account and see macro fine. Reviewers: epriestley, amckinley Reviewed By: amckinley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17836 --- .../editor/PhabricatorMacroEditEngine.php | 2 +- .../macro/editor/PhabricatorMacroEditor.php | 38 ------------------- .../PhabricatorMacroAudioTransaction.php | 28 ++++++++++++++ .../PhabricatorMacroFileTransaction.php | 28 ++++++++++++++ 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/applications/macro/editor/PhabricatorMacroEditEngine.php b/src/applications/macro/editor/PhabricatorMacroEditEngine.php index 3f472ff0ce..2cafea937f 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditEngine.php +++ b/src/applications/macro/editor/PhabricatorMacroEditEngine.php @@ -6,7 +6,7 @@ final class PhabricatorMacroEditEngine const ENGINECONST = 'macro.image'; public function getEngineName() { - return pht('Macro Imagea'); + return pht('Macro Image'); } public function getSummaryHeader() { diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index d9067c5367..5d28b78f5f 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -19,44 +19,6 @@ final class PhabricatorMacroEditor return pht('%s created %s.', $author, $object); } - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroFileTransaction::TRANSACTIONTYPE: - case PhabricatorMacroAudioTransaction::TRANSACTIONTYPE: - // When changing a macro's image or audio, attach the underlying files - // to the macro (and detach the old files). - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - $all = array(); - if ($old) { - $all[] = $old; - } - if ($new) { - $all[] = $new; - } - - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($all) - ->execute(); - $files = mpull($files, null, 'getPHID'); - - $old_file = idx($files, $old); - if ($old_file) { - $old_file->detachFromObject($object->getPHID()); - } - - $new_file = idx($files, $new); - if ($new_file) { - $new_file->attachToObject($object->getPHID()); - } - break; - } - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php index aacf9f4016..26dc64c1f3 100644 --- a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php @@ -13,6 +13,34 @@ final class PhabricatorMacroAudioTransaction $object->setAudioPHID($value); } + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + public function getTitle() { $new = $this->getNewValue(); $old = $this->getOldValue(); diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php index 77c9f24dd9..35091cd585 100644 --- a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php @@ -13,6 +13,34 @@ final class PhabricatorMacroFileTransaction $object->setFilePHID($value); } + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + public function getTitle() { return pht( '%s changed the image for this macro.', From bcd87e0e3f3756970d26d5e9c4a60e4be73ef6a6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 5 May 2017 19:55:29 -0700 Subject: [PATCH 028/543] Don't apply patches or mark patches applied with `bin/storage upgrade --dryrun` Summary: Fixes T12682. Test Plan: Ran `bin/storage upgrade --dryrun` repeatedly with un-applied patches, saw it not apply them and not mark them applied. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12682 Differential Revision: https://secure.phabricator.com/D17837 --- .../workflow/PhabricatorStorageManagementWorkflow.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index e915cedb8d..48d753781d 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -1090,7 +1090,9 @@ abstract class PhabricatorStorageManagementWorkflow } $t_begin = microtime(true); - $api->applyPatch($patch); + if (!$is_dryrun) { + $api->applyPatch($patch); + } $t_end = microtime(true); $duration = ($t_end - $t_begin); @@ -1100,7 +1102,9 @@ abstract class PhabricatorStorageManagementWorkflow // If we're explicitly reapplying this patch, we don't need to // mark it as applied. if (!isset($state_map[$ref_key][$key])) { - $api->markPatchApplied($key, ($t_end - $t_begin)); + if (!$is_dryrun) { + $api->markPatchApplied($key, ($t_end - $t_begin)); + } $applied_map[$ref_key][$key] = true; } } From ade380530f947dd230f7dca95641e4870f1452e3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 14:52:18 -0700 Subject: [PATCH 029/543] Clean up some Phame transaction edit bugs Summary: Ref T12685. Sets required on required fields, cleans up mailtags on PhamePost Test Plan: Create a new blog, post. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17850 --- src/applications/phame/editor/PhameBlogEditEngine.php | 1 + src/applications/phame/editor/PhamePostEditEngine.php | 1 + src/applications/phame/storage/PhamePostTransaction.php | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 6d6c0a9f77..9b6be308c4 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -76,6 +76,7 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Retitle the blog.')) ->setConduitTypeDescription(pht('New blog title.')) ->setTransactionType(PhameBlogNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorTextEditField()) ->setKey('subtitle') diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index af1b1091d1..04738beb80 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -98,6 +98,7 @@ final class PhamePostEditEngine ->setConduitDescription(pht('Retitle the post.')) ->setConduitTypeDescription(pht('New post title.')) ->setTransactionType(PhamePostTitleTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) ->setValue($object->getTitle()), id(new PhabricatorTextEditField()) ->setKey('subtitle') diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index 1c259911f3..6adc640b67 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -34,9 +34,9 @@ final class PhamePostTransaction case PhabricatorTransactions::TYPE_SUBSCRIBERS: $tags[] = self::MAILTAG_SUBSCRIBERS; break; - case self::TYPE_TITLE: - case self::TYPE_SUBTITLE: - case self::TYPE_BODY: + case PhamePostTitleTransaction::TRANSACTIONTYPE: + case PhamePostSubtitleTransaction::TRANSACTIONTYPE: + case PhamePostBodyTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_CONTENT; break; default: From c505876d6162ed4d5fcabe52b2ede3b338addaaa Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 15:04:28 -0700 Subject: [PATCH 030/543] Clean up some Passphrase transaction bugs Summary: Ref T12685. Makes the description field full remarkup and fixes setting a credential secret after destruction. Test Plan: Change description a lot, set and destroy credentials. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17851 --- .../PassphraseCredentialEditController.php | 4 ++-- .../PassphraseCredentialSecretIDTransaction.php | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index 9c86a7f5c9..a35bd3479e 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -244,8 +244,8 @@ final class PassphraseCredentialEditController extends PassphraseController { ->setValue($v_name) ->setError($e_name)) ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + id(new PhabricatorRemarkupControl()) + ->setUser($viewer) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) diff --git a/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php index 2c8e53553a..8089943a96 100644 --- a/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php +++ b/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php @@ -17,17 +17,9 @@ final class PassphraseCredentialSecretIDTransaction $object->setSecretID($value); } - public function shouldHide() { - if (!$this->getOldValue()) { - return true; - } - - return false; - } - public function getTitle() { $old = $this->getOldValue(); - if ($old === null) { + if (!$old) { return pht( '%s attached a new secret to this credential.', $this->renderAuthor()); @@ -53,4 +45,8 @@ final class PassphraseCredentialSecretIDTransaction } } + public function getIcon() { + return 'fa-key'; + } + } From 32af1d9a9fb9698819162c961553bf7f91e4e4cf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 15:25:36 -0700 Subject: [PATCH 031/543] Restrict PhortuneMerchant creation at EditEngine level Summary: Ref T12685. Checks merchant capabilities at the edit engine level Test Plan: Test with and without admin level permissions. Get restricted access if I cheat. Still able to create with admin. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17852 --- .../phortune/editor/PhortuneMerchantEditEngine.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index 596f842574..04e07401c7 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -65,6 +65,11 @@ final class PhortuneMerchantEditEngine return false; } + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhortuneMerchantCapability::CAPABILITY); + } + protected function buildCustomEditFields($object) { $viewer = $this->getViewer(); From ff205964cffce518232249e54e567fbe5ec9af65 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 13:29:55 -0700 Subject: [PATCH 032/543] Make macro audio errors more clear Summary: Ref T12685. I bamboozled myself. Test Plan: {F4945786} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17847 --- .../macro/controller/PhabricatorMacroAudioController.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/applications/macro/controller/PhabricatorMacroAudioController.php b/src/applications/macro/controller/PhabricatorMacroAudioController.php index 382c82ae3a..fb6eba8401 100644 --- a/src/applications/macro/controller/PhabricatorMacroAudioController.php +++ b/src/applications/macro/controller/PhabricatorMacroAudioController.php @@ -50,7 +50,9 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { if ($file) { if (!$file->isAudio()) { - $errors[] = pht('You must upload audio.'); + $errors[] = pht( + 'The file you uploaded is invalid: it is not recognizable as '. + 'a valid audio file.'); $e_file = pht('Invalid'); } else { $xactions[] = id(new PhabricatorMacroTransaction()) @@ -59,7 +61,9 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { ->setNewValue($file->getPHID()); } } else { - $errors[] = pht('You must upload an audio file.'); + $errors[] = pht( + 'To change the audio for a macro, you must upload an audio '. + 'file.'); $e_file = pht('Required'); } } From bf30c072300c379f2604e5e576c2ac6f06169be8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 14:00:26 -0700 Subject: [PATCH 033/543] Clean up Slowvote transactions a little Summary: Ref T12685. Moves to `xaction` folder and sets description changes in transaction stories. Test Plan: Make a poll, edit the description. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17849 --- src/__phutil_library_map__.php | 12 ++++++------ .../PhabricatorSlowvoteCloseTransaction.php | 0 ...PhabricatorSlowvoteDescriptionTransaction.php | 16 +++++++++++----- .../PhabricatorSlowvoteQuestionTransaction.php | 0 .../PhabricatorSlowvoteResponsesTransaction.php | 0 .../PhabricatorSlowvoteShuffleTransaction.php | 0 .../PhabricatorSlowvoteTransactionType.php | 0 7 files changed, 17 insertions(+), 11 deletions(-) rename src/applications/slowvote/{xactions => xaction}/PhabricatorSlowvoteCloseTransaction.php (100%) rename src/applications/slowvote/{xactions => xaction}/PhabricatorSlowvoteDescriptionTransaction.php (76%) rename src/applications/slowvote/{xactions => xaction}/PhabricatorSlowvoteQuestionTransaction.php (100%) rename src/applications/slowvote/{xactions => xaction}/PhabricatorSlowvoteResponsesTransaction.php (100%) rename src/applications/slowvote/{xactions => xaction}/PhabricatorSlowvoteShuffleTransaction.php (100%) rename src/applications/slowvote/{xactions => xaction}/PhabricatorSlowvoteTransactionType.php (100%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8389ee567e..cb265bf3d3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3912,12 +3912,12 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php', 'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.php', - 'PhabricatorSlowvoteCloseTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php', + 'PhabricatorSlowvoteCloseTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php', 'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php', 'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php', 'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/PhabricatorSlowvoteDAO.php', 'PhabricatorSlowvoteDefaultViewCapability' => 'applications/slowvote/capability/PhabricatorSlowvoteDefaultViewCapability.php', - 'PhabricatorSlowvoteDescriptionTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php', + 'PhabricatorSlowvoteDescriptionTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php', 'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php', 'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php', @@ -3927,16 +3927,16 @@ phutil_register_library_map(array( 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php', 'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php', 'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php', - 'PhabricatorSlowvoteQuestionTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php', + 'PhabricatorSlowvoteQuestionTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php', 'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php', - 'PhabricatorSlowvoteResponsesTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php', + 'PhabricatorSlowvoteResponsesTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php', 'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php', 'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php', - 'PhabricatorSlowvoteShuffleTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php', + 'PhabricatorSlowvoteShuffleTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php', 'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php', 'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php', 'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php', - 'PhabricatorSlowvoteTransactionType' => 'applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php', + 'PhabricatorSlowvoteTransactionType' => 'applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php', 'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php', 'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php', 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php similarity index 100% rename from src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php rename to src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php similarity index 76% rename from src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php rename to src/applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php index 8a46f7dc5e..3a06dd4a88 100644 --- a/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php @@ -36,15 +36,21 @@ final class PhabricatorSlowvoteDescriptionTransaction } } - public function hasChangeDetails() { + public function hasChangeDetailView() { return true; } + public function getMailDiffSectionHeader() { + return pht('CHANGES TO POLL DESCRIPTION'); + } + public function newChangeDetailView() { - return $this->renderTextCorpusChangeDetails( - $this->getViewer(), - $this->getOldValue(), - $this->getNewValue()); + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); } public function newRemarkupChanges() { diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php similarity index 100% rename from src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php rename to src/applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php similarity index 100% rename from src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php rename to src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php similarity index 100% rename from src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php rename to src/applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php similarity index 100% rename from src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php rename to src/applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php From a1f5d37357d6fbf548062350603da033c7df271f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 16:12:27 -0700 Subject: [PATCH 034/543] Improve the behavior of PhabricatorFileEditField for Macros Summary: See D17848. This improves things a little bit in two cases: Case 1: - Create a macro. - Pick a valid file. - Pick an invalid name. - Submit form. - Before patch: your file is lost and you have to pick it again. - After patch: your file is "held" in the form, you just can't see it in the UI. If you submit again, it keeps the same file. If you pick a new file, it uses that one instead. Case 2: - Apply D17848. - Delete the `if ($value) {` thing that I'm weirded out about (see inline). - Edit a macro. - Don't pick a new file. - Before patch: error, can't null the image PHID. - Afer patch: not picking a new file means "keep the same file", but you can't tell from the UI. Basically, the behaviors are good now, they just aren't very clear from the UI since "the field has an existing/just-submitted value" and "the field is empty" look the same. I think this is still a net win and we can fix up the UI later. Test Plan: See workflows above. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17853 --- .../AphrontFileHTTPParameterType.php | 12 ++++- src/view/form/control/PHUIFormFileControl.php | 50 +++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php index ceff8c1ddb..c2e5f47f8f 100644 --- a/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php @@ -7,10 +7,17 @@ final class AphrontFileHTTPParameterType return $key.'_raw'; } + private function getDefaultKey($key) { + return $key.'_default'; + } + protected function getParameterExists(AphrontRequest $request, $key) { $file_key = $this->getFileKey($key); + $default_key = $this->getDefaultKey($key); + return $request->getExists($key) || - $request->getFileExists($file_key); + $request->getFileExists($file_key) || + $request->getExists($default_key); } protected function getParameterValue(AphrontRequest $request, $key) { @@ -27,8 +34,9 @@ final class AphrontFileHTTPParameterType // this code around as a fallback if the client-side JS goes awry. $file_key = $this->getFileKey($key); + $default_key = $this->getDefaultKey($key); if (!$request->getFileExists($file_key)) { - return null; + return $request->getStr($default_key); } $viewer = $this->getViewer(); diff --git a/src/view/form/control/PHUIFormFileControl.php b/src/view/form/control/PHUIFormFileControl.php index 57f5d30bf5..9dff7e21ca 100644 --- a/src/view/form/control/PHUIFormFileControl.php +++ b/src/view/form/control/PHUIFormFileControl.php @@ -30,15 +30,47 @@ final class PHUIFormFileControl '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, - )); + + // If the control has a value, add a hidden input which submits it as a + // default. This allows the file control to mean "don't change anything", + // instead of "remove the file", if the user submits the form without + // touching it. + + // This also allows the input to "hold" the value of an uploaded file if + // there is another error in the form: when you submit the form but are + // stopped because of an unrelated error, submitting it again will keep + // the value around (if you don't upload a new file) instead of requiring + // you to pick the file again. + + // TODO: This works alright, but is a bit of a hack, and the UI should + // provide the user better feedback about whether the state of the control + // is "keep the value the same" or "remove the value", and about whether + // or not the control is "holding" a value from a previous submission. + + $default_input = null; + $default_value = $this->getValue(); + if ($default_value !== null) { + $default_input = phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $this->getName().'_default', + 'value' => $default_value, + )); + } + + return array( + phutil_tag( + 'input', + array( + 'type' => 'file', + 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, + 'name' => $this->getName().'_raw', + 'id' => $file_id, + 'disabled' => $this->getDisabled() ? 'disabled' : null, + )), + $default_input, + ); } } From 3dae9701298fe4a85d5e9b039bc86e807555777c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 16:29:27 -0700 Subject: [PATCH 035/543] Clean up some rough Macro transaction edges Summary: Ref T12685, cleans up various macro issues, remove subscribers, fix feed stories, etc. Test Plan: Create a new macro, see no subscribers, edit various macros. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17848 --- .../editor/PhabricatorMacroEditEngine.php | 23 +++++++++- .../PhabricatorMacroFileTransaction.php | 42 +++++++++++-------- .../PhabricatorMacroNameTransaction.php | 3 +- ...icatorSubscriptionsEditEngineExtension.php | 3 +- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/applications/macro/editor/PhabricatorMacroEditEngine.php b/src/applications/macro/editor/PhabricatorMacroEditEngine.php index 2cafea937f..95627d2e11 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditEngine.php +++ b/src/applications/macro/editor/PhabricatorMacroEditEngine.php @@ -21,6 +21,10 @@ final class PhabricatorMacroEditEngine return 'PhabricatorMacroApplication'; } + public function isEngineConfigurable() { + return false; + } + protected function newEditableObject() { $viewer = $this->getViewer(); return PhabricatorFileImageMacro::initializeNewFileImageMacro($viewer); @@ -35,7 +39,7 @@ final class PhabricatorMacroEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit %s', $object->getName()); + return pht('Edit Macro %s', $object->getName()); } protected function getObjectEditShortText($object) { @@ -63,6 +67,19 @@ final class PhabricatorMacroEditEngine PhabricatorMacroManageCapability::CAPABILITY); } + protected function willConfigureFields($object, array $fields) { + if ($this->getIsCreate()) { + $subscribers_field = idx($fields, + PhabricatorSubscriptionsEditEngineExtension::FIELDKEY); + if ($subscribers_field) { + // By default, hide the subscribers field when creating a macro + // because it makes the workflow SO HARD and wastes SO MUCH TIME. + $subscribers_field->setIsHidden(true); + } + } + return $fields; + } + protected function buildCustomEditFields($object) { return array( @@ -73,6 +90,7 @@ final class PhabricatorMacroEditEngine ->setConduitDescription(pht('Rename the macro.')) ->setConduitTypeDescription(pht('New macro name.')) ->setTransactionType(PhabricatorMacroNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorFileEditField()) ->setKey('filePHID') @@ -80,7 +98,8 @@ final class PhabricatorMacroEditEngine ->setDescription(pht('Image file to import.')) ->setTransactionType(PhabricatorMacroFileTransaction::TRANSACTIONTYPE) ->setConduitDescription(pht('File PHID to import.')) - ->setConduitTypeDescription(pht('File PHID.')), + ->setConduitTypeDescription(pht('File PHID.')) + ->setValue($object->getFilePHID()), ); } diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php index 35091cd585..fb0c56f1c1 100644 --- a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php @@ -49,7 +49,7 @@ final class PhabricatorMacroFileTransaction public function getTitleForFeed() { return pht( - '%s changed the image for macro %s.', + '%s changed the image for %s.', $this->renderAuthor(), $this->renderObject()); } @@ -58,29 +58,37 @@ final class PhabricatorMacroFileTransaction $errors = array(); $viewer = $this->getActor(); + $old_phid = $object->getFilePHID(); + foreach ($xactions as $xaction) { $file_phid = $xaction->getNewValue(); - if ($this->isEmptyTextTransaction($file_phid, $xactions)) { - $errors[] = $this->newRequiredError( - pht('Image macros must have a file.')); + if (!$old_phid) { + if ($this->isEmptyTextTransaction($file_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Image macros must have a file.')); + return $errors; + } } - $file = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($file_phid)) - ->executeOne(); + // Only validate if file was uploaded + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); - if (!$file) { - $errors[] = $this->newInvalidError( - pht('"%s" is not a valid file PHID.', - $file_phid)); - } else { - if (!$file->isViewableInBrowser()) { - $mime_type = $file->getMimeType(); + if (!$file) { $errors[] = $this->newInvalidError( - pht('File mime type of "%s" is not a valid viewable image.', - $mime_type)); + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } } } diff --git a/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php index 5b7b6f4417..68710605aa 100644 --- a/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php @@ -23,7 +23,7 @@ final class PhabricatorMacroNameTransaction public function getTitleForFeed() { return pht( - '%s renamed %s macro %s to %s.', + '%s renamed %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), $this->renderOldValue(), @@ -37,6 +37,7 @@ final class PhabricatorMacroNameTransaction if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { $errors[] = $this->newRequiredError( pht('Macros must have a name.')); + return $errors; } $max_length = $object->getColumnMaximumByteLength('name'); diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php index ac0d3896cc..c37933a938 100644 --- a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php @@ -4,6 +4,7 @@ final class PhabricatorSubscriptionsEditEngineExtension extends PhabricatorEditEngineExtension { const EXTENSIONKEY = 'subscriptions.subscribers'; + const FIELDKEY = 'subscriberPHIDs'; const EDITKEY_ADD = 'subscribers.add'; const EDITKEY_SET = 'subscribers.set'; @@ -42,7 +43,7 @@ final class PhabricatorSubscriptionsEditEngineExtension } $subscribers_field = id(new PhabricatorSubscribersEditField()) - ->setKey('subscriberPHIDs') + ->setKey(self::FIELDKEY) ->setLabel(pht('Subscribers')) ->setEditTypeKey('subscribers') ->setAliases(array('subscriber', 'subscribers')) From f717e4b5634c178af8f763ff83846dd405e09404 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 19:47:35 -0700 Subject: [PATCH 036/543] Clean up some Fund language Summary: Ref T12685. Cleans up various Fund language nits. Test Plan: Read carefully. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17856 --- src/applications/fund/query/FundBackerSearchEngine.php | 1 + .../fund/xaction/FundInitiativeNameTransaction.php | 2 +- .../fund/xaction/FundInitiativeStatusTransaction.php | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/applications/fund/query/FundBackerSearchEngine.php b/src/applications/fund/query/FundBackerSearchEngine.php index c275e58a0c..ba4b3bc39e 100644 --- a/src/applications/fund/query/FundBackerSearchEngine.php +++ b/src/applications/fund/query/FundBackerSearchEngine.php @@ -126,6 +126,7 @@ final class FundBackerSearchEngine } $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No backers found.')) ->setHeaders( array( pht('Initiative'), diff --git a/src/applications/fund/xaction/FundInitiativeNameTransaction.php b/src/applications/fund/xaction/FundInitiativeNameTransaction.php index 5883c32ce2..4a47c20a17 100644 --- a/src/applications/fund/xaction/FundInitiativeNameTransaction.php +++ b/src/applications/fund/xaction/FundInitiativeNameTransaction.php @@ -37,7 +37,7 @@ final class FundInitiativeNameTransaction $this->renderObject()); } else { return pht( - '%s renamed %s initiative from %s to %s.', + '%s renamed %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), $this->renderOldValue(), diff --git a/src/applications/fund/xaction/FundInitiativeStatusTransaction.php b/src/applications/fund/xaction/FundInitiativeStatusTransaction.php index 688c8b52c4..5c0af0e705 100644 --- a/src/applications/fund/xaction/FundInitiativeStatusTransaction.php +++ b/src/applications/fund/xaction/FundInitiativeStatusTransaction.php @@ -28,12 +28,12 @@ final class FundInitiativeStatusTransaction public function getTitleForFeed() { if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { return pht( - '%s closed the initiative %s.', + '%s closed %s.', $this->renderAuthor(), $this->renderObject()); } else { return pht( - '%s reopened the initiative %s.', + '%s reopened %s.', $this->renderAuthor(), $this->renderObject()); } From 86f9318052c30e8a23267d774162b0517b4ff0c2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 8 May 2017 19:33:55 -0700 Subject: [PATCH 037/543] Update Fund for EditEngine Summary: Ref T12685, updates fund for edit engine. Test Plan: Create a Fund, Edit a Fund, wipe out Merchants, check errors for name and missing merchants, back Fund. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17855 --- src/__phutil_library_map__.php | 2 + .../PhabricatorFundApplication.php | 3 +- .../FundInitiativeEditController.php | 255 +----------------- .../fund/editor/FundInitiativeEditEngine.php | 152 +++++++++++ .../fund/storage/FundInitiative.php | 4 + 5 files changed, 165 insertions(+), 251 deletions(-) create mode 100644 src/applications/fund/editor/FundInitiativeEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cb265bf3d3..e520b2554b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1141,6 +1141,7 @@ phutil_register_library_map(array( 'FundInitiativeCommentController' => 'applications/fund/controller/FundInitiativeCommentController.php', 'FundInitiativeDescriptionTransaction' => 'applications/fund/xaction/FundInitiativeDescriptionTransaction.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', + 'FundInitiativeEditEngine' => 'applications/fund/editor/FundInitiativeEditEngine.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', @@ -6105,6 +6106,7 @@ phutil_register_library_map(array( 'FundInitiativeCommentController' => 'FundController', 'FundInitiativeDescriptionTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeEditController' => 'FundController', + 'FundInitiativeEditEngine' => 'PhabricatorEditEngine', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', 'FundInitiativeListController' => 'FundController', diff --git a/src/applications/fund/application/PhabricatorFundApplication.php b/src/applications/fund/application/PhabricatorFundApplication.php index 3a26e501e6..f255ea2682 100644 --- a/src/applications/fund/application/PhabricatorFundApplication.php +++ b/src/applications/fund/application/PhabricatorFundApplication.php @@ -43,7 +43,8 @@ final class PhabricatorFundApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'FundInitiativeListController', 'create/' => 'FundInitiativeEditController', 'comment/(?P[1-9]\d*)/' => 'FundInitiativeCommentController', - 'edit/(?:(?P\d+)/)?' => 'FundInitiativeEditController', + $this->getEditRoutePattern('edit/') + => 'FundInitiativeEditController', 'close/(?P\d+)/' => 'FundInitiativeCloseController', 'back/(?P\d+)/' => 'FundInitiativeBackController', 'backers/(?:(?P\d+)/)?(?:query/(?P[^/]+)/)?' diff --git a/src/applications/fund/controller/FundInitiativeEditController.php b/src/applications/fund/controller/FundInitiativeEditController.php index 0576e47330..10bf916913 100644 --- a/src/applications/fund/controller/FundInitiativeEditController.php +++ b/src/applications/fund/controller/FundInitiativeEditController.php @@ -1,256 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $initiative = id(new FundInitiativeQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$initiative) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $initiative = FundInitiative::initializeNewInitiative($viewer); - $is_new = true; - } - - if ($is_new) { - $title = pht('Create Initiative'); - $button_text = pht('Create Initiative'); - $cancel_uri = $this->getApplicationURI(); - $header_icon = 'fa-plus-square'; - } else { - $title = pht( - 'Edit Initiative: %s', - $initiative->getName()); - $button_text = pht('Save Changes'); - $cancel_uri = '/'.$initiative->getMonogram(); - $header_icon = 'fa-pencil'; - } - - $e_name = true; - $v_name = $initiative->getName(); - - $e_merchant = null; - $v_merchant = $initiative->getMerchantPHID(); - - $v_desc = $initiative->getDescription(); - $v_risk = $initiative->getRisks(); - - if ($is_new) { - $v_projects = array(); - } else { - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $initiative->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - } - - $validation_exception = null; - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_desc = $request->getStr('description'); - $v_risk = $request->getStr('risks'); - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - $v_merchant = $request->getStr('merchantPHID'); - $v_projects = $request->getArr('projects'); - - $type_name = FundInitiativeNameTransaction::TRANSACTIONTYPE; - $type_desc = FundInitiativeDescriptionTransaction::TRANSACTIONTYPE; - $type_risk = FundInitiativeRisksTransaction::TRANSACTIONTYPE; - $type_merchant = FundInitiativeMerchantTransaction::TRANSACTIONTYPE; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_desc) - ->setNewValue($v_desc); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_risk) - ->setNewValue($v_risk); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_merchant) - ->setNewValue($v_merchant); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new FundInitiativeEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($initiative, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/'.$initiative->getMonogram()); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - $e_merchant = $ex->getShortMessage($type_merchant); - - $initiative->setViewPolicy($v_view); - $initiative->setEditPolicy($v_edit); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($initiative) - ->execute(); - - $merchants = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - - $merchant_options = array(); - foreach ($merchants as $merchant) { - $merchant_options[$merchant->getPHID()] = pht( - 'Merchant %d %s', - $merchant->getID(), - $merchant->getName()); - } - - if ($v_merchant && empty($merchant_options[$v_merchant])) { - $merchant_options = array( - $v_merchant => pht('(Restricted Merchant)'), - ) + $merchant_options; - } - - if (!$merchant_options) { - return $this->newDialog() - ->setTitle(pht('No Valid Phortune Merchant Accounts')) - ->appendParagraph( - pht( - 'You do not control any merchant accounts which can receive '. - 'payments from this initiative. When you create an initiative, '. - 'you need to specify a merchant account where funds will be paid '. - 'to.')) - ->appendParagraph( - pht( - 'Create a merchant account in the Phortune application before '. - 'creating an initiative in Fund.')) - ->addCancelButton($this->getApplicationURI()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('merchantPHID') - ->setLabel(pht('Pay To Merchant')) - ->setValue($v_merchant) - ->setError($e_merchant) - ->setOptions($merchant_options)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('description') - ->setLabel(pht('Description')) - ->setValue($v_desc)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('risks') - ->setLabel(pht('Risks/Challenges')) - ->setValue($v_risk)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($initiative) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($initiative) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button_text) - ->addCancelButton($cancel_uri)); - - $crumbs = $this->buildApplicationCrumbs(); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Initiative')); - } else { - $crumbs->addTextCrumb( - $initiative->getMonogram(), - '/'.$initiative->getMonogram()); - $crumbs->addTextCrumb(pht('Edit')); - } - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText(pht('Initiative')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); + return id(new FundInitiativeEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/fund/editor/FundInitiativeEditEngine.php b/src/applications/fund/editor/FundInitiativeEditEngine.php new file mode 100644 index 0000000000..201a814a0c --- /dev/null +++ b/src/applications/fund/editor/FundInitiativeEditEngine.php @@ -0,0 +1,152 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new FundInitiativeQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Initiative'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Initiative: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Initiative'); + } + + protected function getObjectName() { + return pht('Initivative'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + FundCreateInitiativesCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + $v_merchant = $object->getMerchantPHID(); + + $merchants = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + $merchant_options = array(); + foreach ($merchants as $merchant) { + $merchant_options[$merchant->getPHID()] = pht( + 'Merchant %d %s', + $merchant->getID(), + $merchant->getName()); + } + + if ($v_merchant && empty($merchant_options[$v_merchant])) { + $merchant_options = array( + $v_merchant => pht('(Restricted Merchant)'), + ) + $merchant_options; + } + + $merchant_instructions = null; + if (!$merchant_options) { + $merchant_instructions = pht( + 'NOTE: You do not control any merchant accounts which can receive '. + 'payments from this initiative. When you create an initiative, '. + 'you need to specify a merchant account where funds will be paid '. + 'to. Create a merchant account in the Phortune application before '. + 'creating an initiative in Fund.'); + } + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Initiative name.')) + ->setConduitTypeDescription(pht('New initiative name.')) + ->setTransactionType( + FundInitiativeNameTransaction::TRANSACTIONTYPE) + ->setValue($object->getName()) + ->setIsRequired(true), + id(new PhabricatorSelectEditField()) + ->setKey('merchantPHID') + ->setLabel(pht('Merchant')) + ->setDescription(pht('Merchant operating the initiative.')) + ->setConduitTypeDescription(pht('New initiative merchant.')) + ->setControlInstructions($merchant_instructions) + ->setValue($object->getMerchantPHID()) + ->setTransactionType( + FundInitiativeMerchantTransaction::TRANSACTIONTYPE) + ->setOptions($merchant_options) + ->setIsRequired(true), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Initiative long description.')) + ->setConduitTypeDescription(pht('New initiative description.')) + ->setTransactionType( + FundInitiativeDescriptionTransaction::TRANSACTIONTYPE) + ->setValue($object->getDescription()), + id(new PhabricatorRemarkupEditField()) + ->setKey('risks') + ->setLabel(pht('Risks/Challenges')) + ->setDescription(pht('Initiative risks and challenges.')) + ->setConduitTypeDescription(pht('Initiative risks and challenges.')) + ->setTransactionType( + FundInitiativeRisksTransaction::TRANSACTIONTYPE) + ->setValue($object->getRisks()), + + ); + + } + +} diff --git a/src/applications/fund/storage/FundInitiative.php b/src/applications/fund/storage/FundInitiative.php index 517c377c44..a200a77b23 100644 --- a/src/applications/fund/storage/FundInitiative.php +++ b/src/applications/fund/storage/FundInitiative.php @@ -85,6 +85,10 @@ final class FundInitiative extends FundDAO return 'I'.$this->getID(); } + public function getViewURI() { + return '/'.$this->getMonogram(); + } + public function getProjectPHIDs() { return $this->assertAttached($this->projectPHIDs); } From b2cbb8f2ee5390458f0cf2d1ad99bb33dac47c8d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 9 May 2017 09:00:24 -0700 Subject: [PATCH 038/543] Update Fund for EditEngine commenting Summary: Ref T12685. Moves Fund to EditEngine commenting. Test Plan: View an old Fund, leave a comment, add a subscriber. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17857 --- src/__phutil_library_map__.php | 2 - .../PhabricatorFundApplication.php | 1 - .../FundInitiativeCommentController.php | 63 ------------------- .../FundInitiativeViewController.php | 29 +++------ .../FundInitiativeMerchantTransaction.php | 2 +- 5 files changed, 10 insertions(+), 87 deletions(-) delete mode 100644 src/applications/fund/controller/FundInitiativeCommentController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e520b2554b..21f5dc67c3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1138,7 +1138,6 @@ phutil_register_library_map(array( 'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php', 'FundInitiativeBackerTransaction' => 'applications/fund/xaction/FundInitiativeBackerTransaction.php', 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', - 'FundInitiativeCommentController' => 'applications/fund/controller/FundInitiativeCommentController.php', 'FundInitiativeDescriptionTransaction' => 'applications/fund/xaction/FundInitiativeDescriptionTransaction.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditEngine' => 'applications/fund/editor/FundInitiativeEditEngine.php', @@ -6103,7 +6102,6 @@ phutil_register_library_map(array( 'FundInitiativeBackController' => 'FundController', 'FundInitiativeBackerTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeCloseController' => 'FundController', - 'FundInitiativeCommentController' => 'FundController', 'FundInitiativeDescriptionTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditEngine' => 'PhabricatorEditEngine', diff --git a/src/applications/fund/application/PhabricatorFundApplication.php b/src/applications/fund/application/PhabricatorFundApplication.php index f255ea2682..9f29f30986 100644 --- a/src/applications/fund/application/PhabricatorFundApplication.php +++ b/src/applications/fund/application/PhabricatorFundApplication.php @@ -42,7 +42,6 @@ final class PhabricatorFundApplication extends PhabricatorApplication { '/fund/' => array( '(?:query/(?P[^/]+)/)?' => 'FundInitiativeListController', 'create/' => 'FundInitiativeEditController', - 'comment/(?P[1-9]\d*)/' => 'FundInitiativeCommentController', $this->getEditRoutePattern('edit/') => 'FundInitiativeEditController', 'close/(?P\d+)/' => 'FundInitiativeCloseController', diff --git a/src/applications/fund/controller/FundInitiativeCommentController.php b/src/applications/fund/controller/FundInitiativeCommentController.php deleted file mode 100644 index 2c4998a94c..0000000000 --- a/src/applications/fund/controller/FundInitiativeCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $initiative = id(new FundInitiativeQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$initiative) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = '/'.$initiative->getMonogram(); - - $xactions = array(); - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new FundInitiativeTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new FundInitiativeEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($initiative, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index eee79e4df0..6d6a19643d 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -52,15 +52,16 @@ final class FundInitiativeViewController $timeline = $this->buildTransactionTimeline( $initiative, new FundInitiativeTransactionQuery()); + $timeline->setQuoteRef($initiative->getMonogram()); - $add_comment = $this->buildCommentForm($initiative); + $comment_view = $this->buildCommentForm($initiative, $timeline); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $timeline, - $add_comment, + $comment_view, )) ->addPropertySection(pht('Details'), $details); @@ -164,26 +165,14 @@ final class FundInitiativeViewController return $curtain; } - private function buildCommentForm(FundInitiative $initiative) { + private function buildCommentForm(FundInitiative $initiative, $timeline) { $viewer = $this->getViewer(); + $box = id(new FundInitiativeEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($initiative) + ->setTransactionTimeline($timeline); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Add Liquidity'); - - $draft = PhabricatorDraft::newFromUserAndKey( - $viewer, $initiative->getPHID()); - - return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($initiative->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction( - $this->getApplicationURI('/comment/'.$initiative->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); + return $box; } diff --git a/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php b/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php index e2a2597c50..82bd6b5bf4 100644 --- a/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php +++ b/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php @@ -45,7 +45,7 @@ final class FundInitiativeMerchantTransaction return pht( '%s changed the merchant receiving funds from %s '. - 'initiative from %s to %s.', + 'from %s to %s.', $this->renderAuthor(), $this->renderObject(), $old_merchant, From c4aa0a9b9a4c3f5b3e6ea2eb18f7f7517b796c38 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 9 May 2017 09:54:49 -0700 Subject: [PATCH 039/543] Fix FundRefund transaction calls Summary: This function `renderHandleLink` doesn't exist. Update call. Test Plan: I'm still lost on how to refund a transaction? Existing? Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17858 --- .../fund/xaction/FundInitiativeRefundTransaction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/fund/xaction/FundInitiativeRefundTransaction.php b/src/applications/fund/xaction/FundInitiativeRefundTransaction.php index 60bb610a80..0741e29d10 100644 --- a/src/applications/fund/xaction/FundInitiativeRefundTransaction.php +++ b/src/applications/fund/xaction/FundInitiativeRefundTransaction.php @@ -55,7 +55,7 @@ final class FundInitiativeRefundTransaction '%s refunded %s to %s.', $this->renderAuthor(), $amount->formatForDisplay(), - $this->renderHandleLink($backer_phid)); + $this->renderHandle($backer_phid)); } public function getTitleForFeed() { @@ -69,7 +69,7 @@ final class FundInitiativeRefundTransaction '%s refunded %s to %s for %s.', $this->renderAuthor(), $amount->formatForDisplay(), - $this->renderHandleLink($backer_phid), + $this->renderHandle($backer_phid), $this->renderObject()); } From 437a843a8e3ac554730e8e0415e97da6cd801eca Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 9 May 2017 10:41:06 -0700 Subject: [PATCH 040/543] Clean up legalpad transaction copy Summary: Ref T12685, adds more informative transaction copy, fixes bugs. Test Plan: Create various documents and edit them. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17860 --- ...padDocumentRequireSignatureTransaction.php | 2 +- ...galpadDocumentSignatureTypeTransaction.php | 4 +- .../LegalpadDocumentTextTransaction.php | 14 +++++-- .../LegalpadDocumentTitleTransaction.php | 39 +++++++++++++------ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php index ffef42f0ce..2122ff10fa 100644 --- a/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php +++ b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php @@ -6,7 +6,7 @@ final class LegalpadDocumentRequireSignatureTransaction const TRANSACTIONTYPE = 'legalpad:require-signature'; public function generateOldValue($object) { - return $object->getRequireSignature(); + return (int)$object->getRequireSignature(); } public function applyInternalEffects($object, $value) { diff --git a/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php index df9cf60457..0fe42f0a6f 100644 --- a/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php +++ b/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php @@ -15,13 +15,13 @@ final class LegalpadDocumentSignatureTypeTransaction public function getTitle() { return pht( - '%s set the document signature type.', + '%s updated the document signature type.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( - '%s set the document signature type for %s.', + '%s updated the document signature type for %s.', $this->renderAuthor(), $this->renderObject()); } diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php index c17ebc890d..7c28002da2 100644 --- a/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php +++ b/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php @@ -17,9 +17,17 @@ final class LegalpadDocumentTextTransaction } public function getTitle() { - return pht( - '%s updated the document text.', - $this->renderAuthor()); + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s set the document text.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the document text.', + $this->renderAuthor()); + } } public function getTitleForFeed() { diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php index c6a4b5b509..725f4f4d4c 100644 --- a/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php +++ b/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php @@ -17,20 +17,37 @@ final class LegalpadDocumentTitleTransaction } public function getTitle() { - return pht( - '%s renamed this document from %s to %s.', - $this->renderAuthor(), - $this->renderOldValue(), - $this->renderNewValue()); + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s created this document.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this document from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } } public function getTitleForFeed() { - return pht( - '%s renamed document %s from %s to %s.', - $this->renderAuthor(), - $this->renderObject(), - $this->renderOldValue(), - $this->renderNewValue()); + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } } public function validateTransactions($object, array $xactions) { From d39758ca7cacf1dd68cb1f5ae35a75b200cee338 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 10 May 2017 12:35:18 -0700 Subject: [PATCH 041/543] Migrate Pholio to Frankenstein transactions Summary: Begins the process of migrating Pholio to Modular Transactions by starting with the mock's description and changing the base class of PholioTransaction. Expect several more of these diffs to quickly follow. Also changes the icon for description changes to `fa-pencil`; previously it was check or ban, depending on the open/closed state. Looks like an accidental switch fallthrough. Test Plan: made new mocks, edited their descriptions, observed same UI as previously Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D17864 --- src/__phutil_library_map__.php | 6 ++- .../controller/PholioMockEditController.php | 2 +- .../pholio/editor/PholioMockEditor.php | 8 ---- ...PhabricatorPholioMockTestDataGenerator.php | 2 +- .../pholio/storage/PholioTransaction.php | 47 +++---------------- .../xaction/PholioDescriptionTransaction.php | 47 +++++++++++++++++++ .../pholio/xaction/PholioTransactionType.php | 4 ++ 7 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 src/applications/pholio/xaction/PholioDescriptionTransaction.php create mode 100644 src/applications/pholio/xaction/PholioTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 21f5dc67c3..debee6de59 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4357,6 +4357,7 @@ phutil_register_library_map(array( 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php', 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', + 'PholioDescriptionTransaction' => 'applications/pholio/xaction/PholioDescriptionTransaction.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', @@ -4393,6 +4394,7 @@ phutil_register_library_map(array( 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', 'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php', 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', + 'PholioTransactionType' => 'applications/pholio/xaction/PholioTransactionType.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', @@ -9890,6 +9892,7 @@ phutil_register_library_map(array( 'PholioDAO' => 'PhabricatorLiskDAO', 'PholioDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PholioDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'PholioDescriptionTransaction' => 'PholioTransactionType', 'PholioImage' => array( 'PholioDAO', 'PhabricatorMarkupInterface', @@ -9940,9 +9943,10 @@ phutil_register_library_map(array( 'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PholioSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PholioTransaction' => 'PhabricatorApplicationTransaction', + 'PholioTransaction' => 'PhabricatorModularTransaction', 'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PholioTransactionType' => 'PhabricatorModularTransactionType', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 'PholioUploadedImageView' => 'AphrontView', 'PhortuneAccount' => array( diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 06d7e9c029..7a2a3ffbe6 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -66,7 +66,7 @@ final class PholioMockEditController extends PholioController { $xactions = array(); $type_name = PholioTransaction::TYPE_NAME; - $type_desc = PholioTransaction::TYPE_DESCRIPTION; + $type_desc = PholioDescriptionTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_cc = PhabricatorTransactions::TYPE_SUBSCRIBERS; diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 31d1602202..c80d8439ef 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -30,7 +30,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PholioTransaction::TYPE_NAME; - $types[] = PholioTransaction::TYPE_DESCRIPTION; $types[] = PholioTransaction::TYPE_STATUS; $types[] = PholioTransaction::TYPE_INLINE; @@ -50,8 +49,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { switch ($xaction->getTransactionType()) { case PholioTransaction::TYPE_NAME: return $object->getName(); - case PholioTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); case PholioTransaction::TYPE_STATUS: return $object->getStatus(); case PholioTransaction::TYPE_IMAGE_FILE: @@ -96,7 +93,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { switch ($xaction->getTransactionType()) { case PholioTransaction::TYPE_NAME: - case PholioTransaction::TYPE_DESCRIPTION: case PholioTransaction::TYPE_STATUS: case PholioTransaction::TYPE_IMAGE_NAME: case PholioTransaction::TYPE_IMAGE_DESCRIPTION: @@ -215,9 +211,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $object->setOriginalName($xaction->getNewValue()); } break; - case PholioTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - break; case PholioTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); break; @@ -311,7 +304,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { case PholioTransaction::TYPE_NAME: - case PholioTransaction::TYPE_DESCRIPTION: case PholioTransaction::TYPE_STATUS: return $v; case PholioTransaction::TYPE_IMAGE_REPLACE: diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index f81eb3e1d7..00999deafc 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -24,7 +24,7 @@ final class PhabricatorPholioMockTestDataGenerator $changes = array(); $changes[PholioTransaction::TYPE_NAME] = $this->generateTitle(); - $changes[PholioTransaction::TYPE_DESCRIPTION] = + $changes[PholioDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = PhabricatorPolicies::POLICY_PUBLIC; diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index d98c6b451b..0d2a76e2bf 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -1,10 +1,9 @@ getOldValue(); switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: return ($old === array(null => null)); @@ -89,7 +90,6 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { case self::TYPE_INLINE: return 'fa-comment'; case self::TYPE_NAME: - case self::TYPE_DESCRIPTION: case self::TYPE_STATUS: if ($new == PholioMock::STATUS_CLOSED) { return 'fa-ban'; @@ -119,7 +119,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $tags[] = self::MAILTAG_STATUS; break; case self::TYPE_NAME: - case self::TYPE_DESCRIPTION: + case PholioDescriptionTransaction::TRANSACTIONTYPE: case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: @@ -156,11 +156,6 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $new); } break; - case self::TYPE_DESCRIPTION: - return pht( - "%s updated the mock's description.", - $this->renderHandleLink($author_phid)); - break; case self::TYPE_STATUS: if ($new == PholioMock::STATUS_CLOSED) { return pht( @@ -268,12 +263,6 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $new); } break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; case self::TYPE_STATUS: if ($new == PholioMock::STATUS_CLOSED) { return pht( @@ -340,30 +329,6 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { return $text; } - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_IMAGE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - if ($this->getTransactionType() == - self::TYPE_IMAGE_DESCRIPTION) { - $old = reset($old); - $new = reset($new); - } - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); diff --git a/src/applications/pholio/xaction/PholioDescriptionTransaction.php b/src/applications/pholio/xaction/PholioDescriptionTransaction.php new file mode 100644 index 0000000000..077f26222a --- /dev/null +++ b/src/applications/pholio/xaction/PholioDescriptionTransaction.php @@ -0,0 +1,47 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + "%s updated the mock's description.", + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function shouldHide() { + $old = $this->getOldValue(); + return ($old === null); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + +} diff --git a/src/applications/pholio/xaction/PholioTransactionType.php b/src/applications/pholio/xaction/PholioTransactionType.php new file mode 100644 index 0000000000..293416b97c --- /dev/null +++ b/src/applications/pholio/xaction/PholioTransactionType.php @@ -0,0 +1,4 @@ + Date: Wed, 10 May 2017 13:37:26 -0700 Subject: [PATCH 042/543] Add Conduit edit endpoint for Macro Summary: Opens up the edit endpoint for Macros Test Plan: Review /conduit/method/macro.edit/ Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17866 --- src/__phutil_library_map__.php | 2 ++ .../conduit/MacroEditConduitAPIMethod.php | 19 +++++++++++++++++++ .../editor/PhabricatorMacroEditEngine.php | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/applications/macro/conduit/MacroEditConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index debee6de59..acfc7b2ff1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1451,6 +1451,7 @@ phutil_register_library_map(array( 'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php', 'MacroConduitAPIMethod' => 'applications/macro/conduit/MacroConduitAPIMethod.php', 'MacroCreateMemeConduitAPIMethod' => 'applications/macro/conduit/MacroCreateMemeConduitAPIMethod.php', + 'MacroEditConduitAPIMethod' => 'applications/macro/conduit/MacroEditConduitAPIMethod.php', 'MacroEmojiExample' => 'applications/uiexample/examples/MacroEmojiExample.php', 'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php', 'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php', @@ -6479,6 +6480,7 @@ phutil_register_library_map(array( 'LiskRawMigrationIterator' => 'PhutilBufferedIterator', 'MacroConduitAPIMethod' => 'ConduitAPIMethod', 'MacroCreateMemeConduitAPIMethod' => 'MacroConduitAPIMethod', + 'MacroEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'MacroEmojiExample' => 'PhabricatorUIExample', 'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod', 'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand', diff --git a/src/applications/macro/conduit/MacroEditConduitAPIMethod.php b/src/applications/macro/conduit/MacroEditConduitAPIMethod.php new file mode 100644 index 0000000000..de5fd38655 --- /dev/null +++ b/src/applications/macro/conduit/MacroEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Macro name.')) - ->setConduitDescription(pht('Rename the macro.')) + ->setConduitDescription(pht('Name of the macro.')) ->setConduitTypeDescription(pht('New macro name.')) ->setTransactionType(PhabricatorMacroNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) From b32a00dbace4f23d6d7a945c9a70d08be8e216e4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 10 May 2017 15:11:39 -0700 Subject: [PATCH 043/543] Update Legalpad for EditEngine Summary: Updates Legalpad to use EditEngine, paving the way for //transaction comments//. Spooky. Test Plan: - New Document - Require signing, Corp - see fail - Require signing, Noone - see fail - Require signing, Ind - get asked to sign - Edit Document Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17862 --- src/__phutil_library_map__.php | 2 + .../PhabricatorLegalpadApplication.php | 7 +- .../controller/LegalpadController.php | 2 +- .../LegalpadDocumentEditController.php | 272 +----------------- .../LegalpadDocumentListController.php | 2 +- .../editor/LegalpadDocumentEditEngine.php | 169 +++++++++++ .../editor/LegalpadDocumentEditor.php | 39 +++ .../legalpad/storage/LegalpadDocument.php | 4 + ...padDocumentRequireSignatureTransaction.php | 18 +- 9 files changed, 239 insertions(+), 276 deletions(-) create mode 100644 src/applications/legalpad/editor/LegalpadDocumentEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index acfc7b2ff1..78994a0ce4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1408,6 +1408,7 @@ phutil_register_library_map(array( 'LegalpadDocumentDatasource' => 'applications/legalpad/typeahead/LegalpadDocumentDatasource.php', 'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php', 'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php', + 'LegalpadDocumentEditEngine' => 'applications/legalpad/editor/LegalpadDocumentEditEngine.php', 'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php', 'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php', 'LegalpadDocumentManageController' => 'applications/legalpad/controller/LegalpadDocumentManageController.php', @@ -6434,6 +6435,7 @@ phutil_register_library_map(array( 'LegalpadDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'LegalpadDocumentDoneController' => 'LegalpadController', 'LegalpadDocumentEditController' => 'LegalpadController', + 'LegalpadDocumentEditEngine' => 'PhabricatorEditEngine', 'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor', 'LegalpadDocumentListController' => 'LegalpadController', 'LegalpadDocumentManageController' => 'LegalpadController', diff --git a/src/applications/legalpad/application/PhabricatorLegalpadApplication.php b/src/applications/legalpad/application/PhabricatorLegalpadApplication.php index 709fd7cc07..ecdb3f337b 100644 --- a/src/applications/legalpad/application/PhabricatorLegalpadApplication.php +++ b/src/applications/legalpad/application/PhabricatorLegalpadApplication.php @@ -53,9 +53,10 @@ final class PhabricatorLegalpadApplication extends PhabricatorApplication { '/L(?P\d+)' => 'LegalpadDocumentSignController', '/legalpad/' => array( '' => 'LegalpadDocumentListController', - '(?:query/(?P[^/]+)/)?' => 'LegalpadDocumentListController', - 'create/' => 'LegalpadDocumentEditController', - 'edit/(?P\d+)/' => 'LegalpadDocumentEditController', + '(?:query/(?P[^/]+)/)?' + => 'LegalpadDocumentListController', + $this->getEditRoutePattern('edit/') + => 'LegalpadDocumentEditController', 'comment/(?P\d+)/' => 'LegalpadDocumentCommentController', 'view/(?P\d+)/' => 'LegalpadDocumentManageController', 'done/' => 'LegalpadDocumentDoneController', diff --git a/src/applications/legalpad/controller/LegalpadController.php b/src/applications/legalpad/controller/LegalpadController.php index fbfb7038f4..006f1329d5 100644 --- a/src/applications/legalpad/controller/LegalpadController.php +++ b/src/applications/legalpad/controller/LegalpadController.php @@ -9,7 +9,7 @@ abstract class LegalpadController extends PhabricatorController { $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); if ($for_app) { - $nav->addFilter('create/', pht('Create Document')); + $nav->addFilter('edit/', pht('Create Document')); } id(new LegalpadDocumentSearchEngine()) diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php index 9b1d2ead22..be46a55dab 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -3,275 +3,9 @@ final class LegalpadDocumentEditController extends LegalpadController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - - if (!$id) { - $is_create = true; - - $this->requireApplicationCapability( - LegalpadCreateDocumentsCapability::CAPABILITY); - - $document = LegalpadDocument::initializeNewDocument($viewer); - $body = id(new LegalpadDocumentBody()) - ->setCreatorPHID($viewer->getPHID()); - $document->attachDocumentBody($body); - $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); - } else { - $is_create = false; - - $document = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->needDocumentBodies(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($id)) - ->executeOne(); - if (!$document) { - return new Aphront404Response(); - } - } - - $e_title = true; - $e_text = true; - - $title = $document->getDocumentBody()->getTitle(); - $text = $document->getDocumentBody()->getText(); - $v_signature_type = $document->getSignatureType(); - $v_preamble = $document->getPreamble(); - $v_require_signature = $document->getRequireSignature(); - - $errors = array(); - $can_view = null; - $can_edit = null; - if ($request->isFormPost()) { - - $xactions = array(); - - $title = $request->getStr('title'); - if (!strlen($title)) { - $e_title = pht('Required'); - $errors[] = pht('The document title may not be blank.'); - } else { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType( - LegalpadDocumentTitleTransaction::TRANSACTIONTYPE) - ->setNewValue($title); - } - - $text = $request->getStr('text'); - if (!strlen($text)) { - $e_text = pht('Required'); - $errors[] = pht('The document may not be blank.'); - } else { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType( - LegalpadDocumentTextTransaction::TRANSACTIONTYPE) - ->setNewValue($text); - } - - $can_view = $request->getStr('can_view'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($can_view); - $can_edit = $request->getStr('can_edit'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($can_edit); - - if ($is_create) { - $v_signature_type = $request->getStr('signatureType'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType( - LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE) - ->setNewValue($v_signature_type); - } - - $v_preamble = $request->getStr('preamble'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType( - LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE) - ->setNewValue($v_preamble); - - $v_require_signature = $request->getBool('requireSignature', 0); - if ($v_require_signature) { - if (!$viewer->getIsAdmin()) { - $errors[] = pht('Only admins may require signature.'); - } - $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; - if ($v_signature_type != $individual) { - $errors[] = pht( - 'Only documents with signature type "individual" may require '. - 'signing to use Phabricator.'); - } - } - if ($viewer->getIsAdmin()) { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType( - LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE) - ->setNewValue($v_require_signature); - } - - if (!$errors) { - $editor = id(new LegalpadDocumentEditor()) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setActor($viewer); - - $xactions = $editor->applyTransactions($document, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI($this->getApplicationURI('view/'.$document->getID())); - } - } - - if ($errors) { - // set these to what was specified in the form on post - $document->setViewPolicy($can_view); - $document->setEditPolicy($can_edit); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setID('document-title') - ->setLabel(pht('Title')) - ->setError($e_title) - ->setValue($title) - ->setName('title')); - - if ($is_create) { - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Who Should Sign?')) - ->setName(pht('signatureType')) - ->setValue($v_signature_type) - ->setOptions(LegalpadDocument::getSignatureTypeMap())); - $show_require = true; - $caption = pht('Applies only to documents individuals sign.'); - } else { - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Who Should Sign?')) - ->setValue($document->getSignatureTypeName())); - $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; - $show_require = $document->getSignatureType() == $individual; - $caption = null; - } - - if ($show_require) { - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setDisabled(!$viewer->getIsAdmin()) - ->setLabel(pht('Require Signature')) - ->addCheckbox( - 'requireSignature', - 'requireSignature', - pht('Should signing this document be required to use Phabricator?'), - $v_require_signature) - ->setCaption($caption)); - } - - $form - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setID('preamble') - ->setLabel(pht('Preamble')) - ->setValue($v_preamble) - ->setName('preamble') - ->setCaption( - pht('Optional help text for users signing this document.'))) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setID('document-text') - ->setLabel(pht('Document Body')) - ->setError($e_text) - ->setValue($text) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setName('text')); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($document) - ->execute(); - - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($document) - ->setPolicies($policies) - ->setName('can_view')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($document) - ->setPolicies($policies) - ->setName('can_edit')); - - $crumbs = $this->buildApplicationCrumbs(); - $submit = new AphrontFormSubmitControl(); - if ($is_create) { - $submit->setValue(pht('Create Document')); - $submit->addCancelButton($this->getApplicationURI()); - $title = pht('Create Document'); - $short = pht('Create'); - $header_icon = 'fa-plus-square'; - } else { - $submit->setValue(pht('Save Document')); - $submit->addCancelButton( - $this->getApplicationURI('view/'.$document->getID())); - $title = pht('Edit Document: %s', $document->getTitle()); - $short = pht('Edit'); - $header_icon = 'fa-pencil'; - - $crumbs->addTextCrumb( - $document->getMonogram(), - $this->getApplicationURI('view/'.$document->getID())); - } - - $form->appendChild($submit); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Document')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $crumbs->addTextCrumb($short); - $crumbs->setBorder(true); - - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader($document->getTitle()) - ->setPreviewURI($this->getApplicationURI('document/preview/')) - ->setControlID('document-text') - ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - $preview, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return id(new LegalpadDocumentEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentListController.php b/src/applications/legalpad/controller/LegalpadDocumentListController.php index cbd92f87a9..852f71970d 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentListController.php @@ -26,7 +26,7 @@ final class LegalpadDocumentListController extends LegalpadController { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Document')) - ->setHref($this->getApplicationURI('create/')) + ->setHref($this->getApplicationURI('edit/')) ->setIcon('fa-plus-square') ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php new file mode 100644 index 0000000000..66660c9ec2 --- /dev/null +++ b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php @@ -0,0 +1,169 @@ +getViewer(); + + $document = LegalpadDocument::initializeNewDocument($viewer); + $body = id(new LegalpadDocumentBody()) + ->setCreatorPHID($viewer->getPHID()); + $document->attachDocumentBody($body); + $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); + + return $document; + } + + protected function newObjectQuery() { + return id(new LegalpadDocumentQuery()) + ->needDocumentBodies(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Document'); + } + + protected function getObjectEditTitleText($object) { + $body = $object->getDocumentBody(); + $title = $body->getTitle(); + return pht('Edit Document: %s', $title); + } + + protected function getObjectEditShortText($object) { + $body = $object->getDocumentBody(); + return $body->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('Create Document'); + } + + protected function getObjectName() { + return pht('Document'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + $id = $object->getID(); + return $this->getApplication()->getApplicationURI('view/'.$id.'/'); + } + + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + LegalpadCreateDocumentsCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + $body = $object->getDocumentBody(); + $document_body = $body->getText(); + + $is_create = $this->getIsCreate(); + $is_admin = $viewer->getIsAdmin(); + + $fields = array(); + $fields[] = + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Title')) + ->setDescription(pht('Document Title.')) + ->setConduitTypeDescription(pht('New document title.')) + ->setValue($object->getTitle()) + ->setIsRequired(true) + ->setTransactionType( + LegalpadDocumentTitleTransaction::TRANSACTIONTYPE); + + if ($is_create) { + $fields[] = + id(new PhabricatorSelectEditField()) + ->setKey('signatureType') + ->setLabel(pht('Who Should Sign?')) + ->setDescription(pht('Type of signature required')) + ->setConduitTypeDescription(pht('New document signature type.')) + ->setValue($object->getSignatureType()) + ->setOptions(LegalpadDocument::getSignatureTypeMap()) + ->setTransactionType( + LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE); + $show_require = true; + } else { + $fields[] = id(new PhabricatorStaticEditField()) + ->setLabel(pht('Who Should Sign?')) + ->setValue($object->getSignatureTypeName()); + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + $show_require = $object->getSignatureType() == $individual; + } + + if ($show_require && $is_admin) { + $fields[] = + id(new PhabricatorBoolEditField()) + ->setKey('requireSignature') + ->setOptions( + pht('No Signature Required'), + pht('Signature Required to use Phabricator')) + ->setAsCheckbox(true) + ->setTransactionType( + LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Marks this document as required signing.')) + ->setConduitDescription( + pht('Marks this document as required signing.')) + ->setValue($object->getRequireSignature()); + } + + $fields[] = + id(new PhabricatorRemarkupEditField()) + ->setKey('preamble') + ->setLabel(pht('Preamble')) + ->setDescription(pht('The preamble of the document.')) + ->setConduitTypeDescription(pht('New document preamble.')) + ->setValue($object->getPreamble()) + ->setTransactionType( + LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE); + + $fields[] = + id(new PhabricatorRemarkupEditField()) + ->setKey('text') + ->setLabel(pht('Document Body')) + ->setDescription(pht('The body of text of the document.')) + ->setConduitTypeDescription(pht('New document body.')) + ->setValue($document_body) + ->setIsRequired(true) + ->setTransactionType( + LegalpadDocumentTextTransaction::TRANSACTIONTYPE); + + return $fields; + + } + +} diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 14430b2c33..7882635285 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -21,6 +21,14 @@ final class LegalpadDocumentEditor return $types; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this document.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -64,6 +72,37 @@ final class LegalpadDocumentEditor return $xactions; } + protected function validateAllTransactions(PhabricatorLiskDAO $object, + array $xactions) { + $errors = array(); + + $is_required = (bool)$object->getRequireSignature(); + $document_type = $object->getSignatureType(); + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE: + $is_required = (bool)$xaction->getNewValue(); + break; + case LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE: + $document_type = $xaction->getNewValue(); + break; + } + } + + if ($is_required && ($document_type != $individual)) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE, + pht('Invalid'), + pht('Only documents with signature type "individual" may '. + 'require signing to use Phabricator.'), + null); + } + + return $errors; + } + /* -( Sending Mail )------------------------------------------------------- */ diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index fcecac991f..55d1ef9fa9 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -120,6 +120,10 @@ final class LegalpadDocument extends LegalpadDAO return 'L'.$this->getID(); } + public function getViewURI() { + return '/'.$this->getMonogram(); + } + public function getUserSignature($phid) { return $this->assertAttachedKey($this->userSignatures, $phid); } diff --git a/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php index 2122ff10fa..3819f38a70 100644 --- a/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php +++ b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php @@ -10,11 +10,11 @@ final class LegalpadDocumentRequireSignatureTransaction } public function applyInternalEffects($object, $value) { - $object->setRequireSignature($value); + $object->setRequireSignature((int)$value); } public function applyExternalEffects($object, $value) { - if (strlen($value)) { + if ($value) { $session = new PhabricatorAuthSession(); queryfx( $session->establishConnection('w'), @@ -25,6 +25,7 @@ final class LegalpadDocumentRequireSignatureTransaction public function getTitle() { $new = $this->getNewValue(); + if ($new) { return pht( '%s set the document to require signatures.', @@ -51,6 +52,19 @@ final class LegalpadDocumentRequireSignatureTransaction } } + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $is_admin = $this->getActor()->getIsAdmin(); + + if (!$is_admin) { + $errors[] = $this->newInvalidError( + pht('Only admins may require signature.')); + } + + return $errors; + } + public function getIcon() { return 'fa-pencil-square'; } From fcf64953f093945f098b727ba9d9ce3e3e29502b Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 10 May 2017 15:11:28 -0700 Subject: [PATCH 044/543] Migate more of Pholio to Modular Transaction Summary: Another Franken-transaction. Adds transactions for ImageName and MockName. Adds transaction-level validation for presence of mock name; removed same from edit controller. Removes `PholioTransaction::getRemarkupBodyForFeed()`, which appears to be dead code since that method isn't defined on any other types. Test Plan: made a bunch of changes to pholio mocks and images and observed expected results Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, chad, epriestley Differential Revision: https://secure.phabricator.com/D17865 --- src/__phutil_library_map__.php | 12 ++- .../controller/PholioMockEditController.php | 11 +-- .../pholio/editor/PholioMockEditor.php | 29 ------ ...PhabricatorPholioMockTestDataGenerator.php | 4 +- .../pholio/storage/PholioTransaction.php | 78 +-------------- .../xaction/PholioImageNameTransaction.php | 95 +++++++++++++++++++ .../xaction/PholioImageTransactionType.php | 18 ++++ ...p => PholioMockDescriptionTransaction.php} | 4 +- .../xaction/PholioMockNameTransaction.php | 88 +++++++++++++++++ .../xaction/PholioMockTransactionType.php | 4 + 10 files changed, 226 insertions(+), 117 deletions(-) create mode 100644 src/applications/pholio/xaction/PholioImageNameTransaction.php create mode 100644 src/applications/pholio/xaction/PholioImageTransactionType.php rename src/applications/pholio/xaction/{PholioDescriptionTransaction.php => PholioMockDescriptionTransaction.php} (92%) create mode 100644 src/applications/pholio/xaction/PholioMockNameTransaction.php create mode 100644 src/applications/pholio/xaction/PholioMockTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 78994a0ce4..1c0bdca3db 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4359,10 +4359,11 @@ phutil_register_library_map(array( 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php', 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', - 'PholioDescriptionTransaction' => 'applications/pholio/xaction/PholioDescriptionTransaction.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', + 'PholioImageNameTransaction' => 'applications/pholio/xaction/PholioImageNameTransaction.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', + 'PholioImageTransactionType' => 'applications/pholio/xaction/PholioImageTransactionType.php', 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', 'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php', @@ -4371,6 +4372,7 @@ phutil_register_library_map(array( 'PholioMockAuthorHeraldField' => 'applications/pholio/herald/PholioMockAuthorHeraldField.php', 'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php', 'PholioMockDescriptionHeraldField' => 'applications/pholio/herald/PholioMockDescriptionHeraldField.php', + 'PholioMockDescriptionTransaction' => 'applications/pholio/xaction/PholioMockDescriptionTransaction.php', 'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php', 'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php', 'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php', @@ -4383,12 +4385,14 @@ phutil_register_library_map(array( 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php', + 'PholioMockNameTransaction' => 'applications/pholio/xaction/PholioMockNameTransaction.php', 'PholioMockPHIDType' => 'applications/pholio/phid/PholioMockPHIDType.php', 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 'PholioMockRelationship' => 'applications/pholio/relationships/PholioMockRelationship.php', 'PholioMockRelationshipSource' => 'applications/search/relationship/PholioMockRelationshipSource.php', 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', 'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php', + 'PholioMockTransactionType' => 'applications/pholio/xaction/PholioMockTransactionType.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', 'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php', 'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php', @@ -9896,14 +9900,15 @@ phutil_register_library_map(array( 'PholioDAO' => 'PhabricatorLiskDAO', 'PholioDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PholioDefaultViewCapability' => 'PhabricatorPolicyCapability', - 'PholioDescriptionTransaction' => 'PholioTransactionType', 'PholioImage' => array( 'PholioDAO', 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', ), + 'PholioImageNameTransaction' => 'PholioImageTransactionType', 'PholioImagePHIDType' => 'PhabricatorPHIDType', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PholioImageTransactionType' => 'PholioTransactionType', 'PholioImageUploadController' => 'PholioController', 'PholioInlineController' => 'PholioController', 'PholioInlineListController' => 'PholioController', @@ -9925,6 +9930,7 @@ phutil_register_library_map(array( 'PholioMockAuthorHeraldField' => 'PholioMockHeraldField', 'PholioMockCommentController' => 'PholioController', 'PholioMockDescriptionHeraldField' => 'PholioMockHeraldField', + 'PholioMockDescriptionTransaction' => 'PholioMockTransactionType', 'PholioMockEditController' => 'PholioController', 'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor', 'PholioMockEmbedView' => 'AphrontView', @@ -9937,12 +9943,14 @@ phutil_register_library_map(array( 'PholioMockListController' => 'PholioController', 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 'PholioMockNameHeraldField' => 'PholioMockHeraldField', + 'PholioMockNameTransaction' => 'PholioMockTransactionType', 'PholioMockPHIDType' => 'PhabricatorPHIDType', 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioMockRelationship' => 'PhabricatorObjectRelationship', 'PholioMockRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PholioMockThumbGridView' => 'AphrontView', + 'PholioMockTransactionType' => 'PholioTransactionType', 'PholioMockViewController' => 'PholioController', 'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 7a2a3ffbe6..42038cc2d3 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -65,8 +65,8 @@ final class PholioMockEditController extends PholioController { if ($request->isFormPost()) { $xactions = array(); - $type_name = PholioTransaction::TYPE_NAME; - $type_desc = PholioDescriptionTransaction::TRANSACTIONTYPE; + $type_name = PholioMockNameTransaction::TRANSACTIONTYPE; + $type_desc = PholioMockDescriptionTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_cc = PhabricatorTransactions::TYPE_SUBSCRIBERS; @@ -88,11 +88,6 @@ final class PholioMockEditController extends PholioController { $mock_xactions[$type_cc] = array('=' => $v_cc); $mock_xactions[$type_space] = $v_space; - if (!strlen($request->getStr('name'))) { - $e_name = pht('Required'); - $errors[] = pht('You must give the mock a name.'); - } - $file_phids = $request->getArr('file_phids'); if ($file_phids) { $files = id(new PhabricatorFileQuery()) @@ -173,7 +168,7 @@ final class PholioMockEditController extends PholioController { $posted_mock_images[] = $add_image; } else { $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_NAME) + ->setTransactionType(PholioImageNameTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $title)); $xactions[] = id(new PholioTransaction()) diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index c80d8439ef..7910830b92 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -29,12 +29,10 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PholioTransaction::TYPE_NAME; $types[] = PholioTransaction::TYPE_STATUS; $types[] = PholioTransaction::TYPE_INLINE; $types[] = PholioTransaction::TYPE_IMAGE_FILE; - $types[] = PholioTransaction::TYPE_IMAGE_NAME; $types[] = PholioTransaction::TYPE_IMAGE_DESCRIPTION; $types[] = PholioTransaction::TYPE_IMAGE_REPLACE; $types[] = PholioTransaction::TYPE_IMAGE_SEQUENCE; @@ -47,22 +45,11 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_NAME: - return $object->getName(); case PholioTransaction::TYPE_STATUS: return $object->getStatus(); case PholioTransaction::TYPE_IMAGE_FILE: $images = $object->getImages(); return mpull($images, 'getPHID'); - case PholioTransaction::TYPE_IMAGE_NAME: - $name = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $name = $image->getName(); - $phid = $image->getPHID(); - } - return array($phid => $name); case PholioTransaction::TYPE_IMAGE_DESCRIPTION: $description = null; $phid = null; @@ -92,9 +79,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_NAME: case PholioTransaction::TYPE_STATUS: - case PholioTransaction::TYPE_IMAGE_NAME: case PholioTransaction::TYPE_IMAGE_DESCRIPTION: case PholioTransaction::TYPE_IMAGE_SEQUENCE: return $xaction->getNewValue(); @@ -205,12 +190,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - if ($object->getOriginalName() === null) { - $object->setOriginalName($xaction->getNewValue()); - } - break; case PholioTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); break; @@ -263,12 +242,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $object->attachImages($images); break; - case PholioTransaction::TYPE_IMAGE_NAME: - $image = $this->getImageForXaction($object, $xaction); - $value = (string)head($xaction->getNewValue()); - $image->setName($value); - $image->save(); - break; case PholioTransaction::TYPE_IMAGE_DESCRIPTION: $image = $this->getImageForXaction($object, $xaction); $value = (string)head($xaction->getNewValue()); @@ -303,7 +276,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { - case PholioTransaction::TYPE_NAME: case PholioTransaction::TYPE_STATUS: return $v; case PholioTransaction::TYPE_IMAGE_REPLACE: @@ -315,7 +287,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { break; case PholioTransaction::TYPE_IMAGE_FILE: return $this->mergePHIDOrEdgeTransactions($u, $v); - case PholioTransaction::TYPE_IMAGE_NAME: case PholioTransaction::TYPE_IMAGE_DESCRIPTION: case PholioTransaction::TYPE_IMAGE_SEQUENCE: $raw_new_value_u = $u->getNewValue(); diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index 00999deafc..039b0ddeef 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -22,9 +22,9 @@ final class PhabricatorPholioMockTestDataGenerator // Accumulate Transactions $changes = array(); - $changes[PholioTransaction::TYPE_NAME] = + $changes[PholioMockNameTransaction::TRANSACTIONTYPE] = $this->generateTitle(); - $changes[PholioDescriptionTransaction::TRANSACTIONTYPE] = + $changes[PholioMockDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = PhabricatorPolicies::POLICY_PUBLIC; diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 0d2a76e2bf..85688b84d2 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -3,12 +3,10 @@ final class PholioTransaction extends PhabricatorModularTransaction { // Edits to the high level mock - const TYPE_NAME = 'name'; const TYPE_STATUS = 'status'; // Edits to images within the mock const TYPE_IMAGE_FILE = 'image-file'; - const TYPE_IMAGE_NAME= 'image-name'; const TYPE_IMAGE_DESCRIPTION = 'image-description'; const TYPE_IMAGE_REPLACE = 'image-replace'; const TYPE_IMAGE_SEQUENCE = 'image-sequence'; @@ -57,7 +55,7 @@ final class PholioTransaction extends PhabricatorModularTransaction { $phids[] = $old; break; case self::TYPE_IMAGE_DESCRIPTION: - case self::TYPE_IMAGE_NAME: + case PholioImageNameTransaction::TRANSACTIONTYPE: case self::TYPE_IMAGE_SEQUENCE: $phids[] = key($new); break; @@ -70,7 +68,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: return ($old === array(null => null)); // this is boring / silly to surface; changing sequence is NBD @@ -89,14 +86,12 @@ final class PholioTransaction extends PhabricatorModularTransaction { switch ($this->getTransactionType()) { case self::TYPE_INLINE: return 'fa-comment'; - case self::TYPE_NAME: case self::TYPE_STATUS: if ($new == PholioMock::STATUS_CLOSED) { return 'fa-ban'; } else { return 'fa-check'; } - case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: return 'fa-pencil'; @@ -118,9 +113,9 @@ final class PholioTransaction extends PhabricatorModularTransaction { case self::TYPE_STATUS: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_NAME: - case PholioDescriptionTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE_NAME: + case PholioMockNameTransaction::TRANSACTIONTYPE: + case PholioMockDescriptionTransaction::TRANSACTIONTYPE: + case PholioImageNameTransaction::TRANSACTIONTYPE: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: case self::TYPE_IMAGE_FILE: @@ -142,20 +137,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $type = $this->getTransactionType(); switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s renamed this mock from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; case self::TYPE_STATUS: if ($new == PholioMock::STATUS_CLOSED) { return pht( @@ -213,15 +194,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $this->renderHandleList($rem)); } break; - - case self::TYPE_IMAGE_NAME: - return pht( - '%s renamed an image (%s) from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new)), - reset($old), - reset($new)); - break; case self::TYPE_IMAGE_DESCRIPTION: return pht( '%s updated an image\'s (%s) description.', @@ -248,21 +220,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $type = $this->getTransactionType(); switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } - break; case self::TYPE_STATUS: if ($new == PholioMock::STATUS_CLOSED) { return pht( @@ -289,12 +246,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case self::TYPE_IMAGE_NAME: - return pht( - '%s updated the image names of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; case self::TYPE_IMAGE_DESCRIPTION: return pht( '%s updated image descriptions of %s.', @@ -312,23 +263,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { return parent::getTitleForFeed(); } - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $text = null; - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($this->getOldValue() === null) { - $mock = $story->getPrimaryObject(); - $text = $mock->getDescription(); - } - break; - case self::TYPE_INLINE: - $text = $this->getComment()->getContent(); - break; - } - - return $text; - } - public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); @@ -340,10 +274,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { } else { return PhabricatorTransactions::COLOR_GREEN; } - case self::TYPE_NAME: - if ($old === null) { - return PhabricatorTransactions::COLOR_GREEN; - } case self::TYPE_IMAGE_REPLACE: return PhabricatorTransactions::COLOR_YELLOW; case self::TYPE_IMAGE_FILE: diff --git a/src/applications/pholio/xaction/PholioImageNameTransaction.php b/src/applications/pholio/xaction/PholioImageNameTransaction.php new file mode 100644 index 0000000000..b888b1244b --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageNameTransaction.php @@ -0,0 +1,95 @@ +getImageForXaction($object); + if ($image) { + $name = $image->getName(); + $phid = $image->getPHID(); + } + return array($phid => $name); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (string)head($this->getNewValue()); + $image->setName($value); + $image->save(); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed an image (%s) from %s to %s.', + $this->renderAuthor(), + $this->renderHandle(key($new)), + $this->renderValue($old), + $this->renderValue($new)); + } + + public function getTitleForFeed() { + return pht( + '%s updated the image names of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = head_key($raw_new_value_u); + $phid_v = head_key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + + return null; + } + + public function shouldHide() { + $old = $this->getOldValue(); + return ($old === array(null => null)); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = head(array_values($xaction->getNewValue())); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Mock image names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + + +} diff --git a/src/applications/pholio/xaction/PholioImageTransactionType.php b/src/applications/pholio/xaction/PholioImageTransactionType.php new file mode 100644 index 0000000000..3e576776aa --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageTransactionType.php @@ -0,0 +1,18 @@ +getNewValue(); + $image_phid = head_key($raw_new_value); + $images = $mock->getImages(); + foreach ($images as $image) { + if ($image->getPHID() == $image_phid) { + return $image; + } + } + return null; + } + +} diff --git a/src/applications/pholio/xaction/PholioDescriptionTransaction.php b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php similarity index 92% rename from src/applications/pholio/xaction/PholioDescriptionTransaction.php rename to src/applications/pholio/xaction/PholioMockDescriptionTransaction.php index 077f26222a..ed14d43c99 100644 --- a/src/applications/pholio/xaction/PholioDescriptionTransaction.php +++ b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php @@ -1,7 +1,7 @@ getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + if ($object->getOriginalName() === null) { + $object->setOriginalName($this->getNewValue()); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderValue($new)); + } else { + return pht( + '%s renamed this mock from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old), + $this->renderValue($new)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old), + $this->renderValue($new)); + } + } + + public function getColor() { + $old = $this->getOldValue(); + + if ($old === null) { + return PhabricatorTransactions::COLOR_GREEN; + } + + return parent::getColor(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Mocks must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Mock names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/pholio/xaction/PholioMockTransactionType.php b/src/applications/pholio/xaction/PholioMockTransactionType.php new file mode 100644 index 0000000000..7993412304 --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockTransactionType.php @@ -0,0 +1,4 @@ + Date: Wed, 10 May 2017 20:00:15 -0700 Subject: [PATCH 045/543] Add a large profile picture to Projects Summary: The ports over a similar "profile image" menu item to Projects. It gives us some room to use the project icon in the sidenav along with a larger photo. It also will open up some room in the sub-page headers for us to focus on that page, and not the identity of the project at hand. Expect a few more project related touch up diffs. Test Plan: Review new projects menu on a few projects, update the image, see new image. Great for team photos. {F4951264} Reviewers: epriestley, amckinley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17869 --- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectManageController.php | 7 +- .../PhabricatorProjectProfileController.php | 2 +- .../PhabricatorProjectProfileMenuEngine.php | 4 ++ ...abricatorProjectDetailsProfileMenuItem.php | 4 +- ...abricatorProjectPictureProfileMenuItem.php | 69 +++++++++++++++++++ .../project/storage/PhabricatorProject.php | 1 + 7 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1c0bdca3db..a3e56a36af 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3635,6 +3635,7 @@ phutil_register_library_map(array( 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', + 'PhabricatorProjectPictureProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php', 'PhabricatorProjectPointsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProfileMenuEngine' => 'applications/project/engine/PhabricatorProjectProfileMenuEngine.php', @@ -9020,6 +9021,7 @@ phutil_register_library_map(array( 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', + 'PhabricatorProjectPictureProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectPointsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileMenuEngine' => 'PhabricatorProfileMenuEngine', diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 6ecdf6f62c..17b9030658 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -21,8 +21,7 @@ final class PhabricatorProjectManageController $header = id(new PHUIHeaderView()) ->setHeader(pht('Project History')) ->setUser($viewer) - ->setPolicyObject($project) - ->setImage($picture); + ->setPolicyObject($project); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -45,10 +44,14 @@ final class PhabricatorProjectManageController $crumbs->addTextCrumb(pht('Manage')); $crumbs->setBorder(true); + require_celerity_resource('project-view-css'); + $manage = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->addPropertySection(pht('Details'), $properties) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn( array( $timeline, diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index dae57e55f9..4c726f5884 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -29,7 +29,6 @@ final class PhabricatorProjectProfileController ->setHeader(array($project->getDisplayName(), $tag)) ->setUser($viewer) ->setPolicyObject($project) - ->setImage($picture) ->setProfileHeader(true); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { @@ -92,6 +91,7 @@ final class PhabricatorProjectProfileController $home = id(new PHUITwoColumnView()) ->setHeader($header) ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn( array( $properties, diff --git a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php index ef16b2eee8..77b69443da 100644 --- a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php +++ b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php @@ -20,6 +20,10 @@ final class PhabricatorProjectProfileMenuEngine protected function getBuiltinProfileItems($object) { $items = array(); + $items[] = $this->newItem() + ->setBuiltinKey(PhabricatorProject::ITEM_PICTURE) + ->setMenuItemKey(PhabricatorProjectPictureProfileMenuItem::MENUITEMKEY); + $items[] = $this->newItem() ->setBuiltinKey(PhabricatorProject::ITEM_PROFILE) ->setMenuItemKey(PhabricatorProjectDetailsProfileMenuItem::MENUITEMKEY); diff --git a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php index becdd36c16..96619b8ec5 100644 --- a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php @@ -46,15 +46,15 @@ final class PhabricatorProjectDetailsProfileMenuItem $project = $config->getProfileObject(); $id = $project->getID(); - $picture = $project->getProfileImageURI(); $name = $project->getName(); + $icon = $project->getDisplayIconIcon(); $href = "/project/profile/{$id}/"; $item = $this->newItem() ->setHref($href) ->setName($name) - ->setProfileImage($picture); + ->setIcon($icon); return array( $item, diff --git a/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php new file mode 100644 index 0000000000..2201687c81 --- /dev/null +++ b/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php @@ -0,0 +1,69 @@ +getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array(); + } + + protected function newNavigationMenuItems( + PhabricatorProfileMenuItemConfiguration $config) { + + $project = $config->getProfileObject(); + require_celerity_resource('people-picture-menu-item-css'); + + $picture = $project->getProfileImageURI(); + $href = $project->getProfileURI(); + + $classes = array(); + $classes[] = 'people-menu-image'; + if ($project->isArchived()) { + $classes[] = 'phui-image-disabled'; + } + + $photo = phutil_tag( + 'img', + array( + 'src' => $picture, + 'class' => implode(' ', $classes), + )); + + $view = phutil_tag_div('people-menu-image-container', $photo); + $view = phutil_tag( + 'a', + array( + 'href' => $href, + ), + $view); + + $item = $this->newItem() + ->appendChild($view); + + return array( + $item, + ); + } + +} diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index a46ebd3352..e1258829ac 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -49,6 +49,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken'; + const ITEM_PICTURE = 'project.picture'; const ITEM_PROFILE = 'project.profile'; const ITEM_POINTS = 'project.points'; const ITEM_WORKBOARD = 'project.workboard'; From 28a23e3822de3b75f5f581dd32ffb61934925297 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 10 May 2017 15:27:42 -0700 Subject: [PATCH 046/543] Update Legalpad to use comment transactions Summary: Updates Legalpad to use comment transactions Test Plan: Leave some comments. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17867 --- src/__phutil_library_map__.php | 2 - .../PhabricatorLegalpadApplication.php | 1 - .../LegalpadDocumentCommentController.php | 72 ------------------- .../LegalpadDocumentManageController.php | 36 +++------- 4 files changed, 9 insertions(+), 102 deletions(-) delete mode 100644 src/applications/legalpad/controller/LegalpadDocumentCommentController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a3e56a36af..7a7ca114d1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1404,7 +1404,6 @@ phutil_register_library_map(array( 'LegalpadDefaultViewCapability' => 'applications/legalpad/capability/LegalpadDefaultViewCapability.php', 'LegalpadDocument' => 'applications/legalpad/storage/LegalpadDocument.php', 'LegalpadDocumentBody' => 'applications/legalpad/storage/LegalpadDocumentBody.php', - 'LegalpadDocumentCommentController' => 'applications/legalpad/controller/LegalpadDocumentCommentController.php', 'LegalpadDocumentDatasource' => 'applications/legalpad/typeahead/LegalpadDocumentDatasource.php', 'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php', 'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php', @@ -6436,7 +6435,6 @@ phutil_register_library_map(array( 'LegalpadDAO', 'PhabricatorMarkupInterface', ), - 'LegalpadDocumentCommentController' => 'LegalpadController', 'LegalpadDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'LegalpadDocumentDoneController' => 'LegalpadController', 'LegalpadDocumentEditController' => 'LegalpadController', diff --git a/src/applications/legalpad/application/PhabricatorLegalpadApplication.php b/src/applications/legalpad/application/PhabricatorLegalpadApplication.php index ecdb3f337b..a3eaff5792 100644 --- a/src/applications/legalpad/application/PhabricatorLegalpadApplication.php +++ b/src/applications/legalpad/application/PhabricatorLegalpadApplication.php @@ -57,7 +57,6 @@ final class PhabricatorLegalpadApplication extends PhabricatorApplication { => 'LegalpadDocumentListController', $this->getEditRoutePattern('edit/') => 'LegalpadDocumentEditController', - 'comment/(?P\d+)/' => 'LegalpadDocumentCommentController', 'view/(?P\d+)/' => 'LegalpadDocumentManageController', 'done/' => 'LegalpadDocumentDoneController', 'verify/(?P[^/]+)/' diff --git a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php deleted file mode 100644 index 530696437b..0000000000 --- a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php +++ /dev/null @@ -1,72 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $document = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needDocumentBodies(true) - ->executeOne(); - - if (!$document) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - - $draft = PhabricatorDraft::buildFromRequest($request); - - $document_uri = $this->getApplicationURI('view/'.$document->getID()); - - $comment = $request->getStr('comment'); - - $xactions = array(); - - if (strlen($comment)) { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new LegalpadTransactionComment()) - ->setDocumentID($document->getID()) - ->setLineNumber(0) - ->setLineLength(0) - ->setContent($comment)); - } - - $editor = id(new LegalpadDocumentEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($document, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($document_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse())->setURI($document_uri); - } - } - -} diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php index 6d9360a731..b39bc89270 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php @@ -37,6 +37,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $document, new LegalpadTransactionQuery(), $engine); + $timeline->setQuoteRef($document->getMonogram()); $title = $document_body->getTitle(); @@ -50,9 +51,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $properties = $this->buildPropertyView($document, $engine); $document_view = $this->buildDocumentView($document, $engine); - $comment_form_id = celerity_generate_unique_node_id(); - - $add_comment = $this->buildAddCommentView($document, $comment_form_id); + $comment_form = $this->buildCommentView($document, $timeline); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -69,7 +68,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $properties, $document_view, $timeline, - $add_comment, + $comment_form, )); return $this->newPage() @@ -181,31 +180,14 @@ final class LegalpadDocumentManageController extends LegalpadController { ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); } - private function buildAddCommentView( - LegalpadDocument $document, - $comment_form_id) { + private function buildCommentView(LegalpadDocument $document, $timeline) { $viewer = $this->getViewer(); + $box = id(new LegalpadDocumentEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($document) + ->setTransactionTimeline($timeline); - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $document->getPHID()); - - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $title = $is_serious - ? pht('Add Comment') - : pht('Debate Legislation'); - - $form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($document->getPHID()) - ->setFormID($comment_form_id) - ->setHeaderText($title) - ->setDraft($draft) - ->setSubmitButtonName(pht('Add Comment')) - ->setAction($this->getApplicationURI('/comment/'.$document->getID().'/')) - ->setRequestURI($this->getRequest()->getRequestURI()); - - return $form; - + return $box; } } From 7aeb498565a903cb36f372823f8b9248c6e47a65 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 12 May 2017 19:04:16 +0000 Subject: [PATCH 047/543] Fix missing transaction constants in Phame Summary: Trailing logs for errors here, picked up some unset constants in Phame. Test Plan: New Blog, New Post, tail daemon log for errors. See feed stories. Reviewers: epriestley, amckinley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17871 --- .../phame/editor/PhamePostEditor.php | 2 +- .../phame/storage/PhameBlogTransaction.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 8f0e2b5099..488d7a4938 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -88,7 +88,7 @@ final class PhamePostEditor } else { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_VISIBILITY: + case PhamePostVisibilityTransaction::TRANSACTIONTYPE: if (!$object->isDraft() && !$object->isArchived()) { $body->addRemarkupSection(null, $object->getBody()); } diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index c5702a2b18..c605510d7d 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -26,14 +26,14 @@ final class PhameBlogTransaction case PhabricatorTransactions::TYPE_SUBSCRIBERS: $tags[] = self::MAILTAG_SUBSCRIBERS; break; - case self::TYPE_NAME: - case self::TYPE_SUBTITLE: - case self::TYPE_DESCRIPTION: - case self::TYPE_FULLDOMAIN: - case self::TYPE_PARENTSITE: - case self::TYPE_PARENTDOMAIN: - case self::TYPE_PROFILEIMAGE: - case self::TYPE_HEADERIMAGE: + case PhameBlogNameTransaction::TRANSACTIONTYPE: + case PhameBlogSubtitleTransaction::TRANSACTIONTYPE: + case PhameBlogDescriptionTransaction::TRANSACTIONTYPE: + case PhameBlogFullDomainTransaction::TRANSACTIONTYPE: + case PhameBlogParentSiteTransaction::TRANSACTIONTYPE: + case PhameBlogParentDomainTransaction::TRANSACTIONTYPE: + case PhameBlogProfileImageTransaction::TRANSACTIONTYPE: + case PhameBlogHeaderImageTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; default: From b3692cc12c9833fa99a8c8bb00692802a9301652 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 12 May 2017 19:04:48 +0000 Subject: [PATCH 048/543] Fix PonderAnswer transaction callsite Summary: Caught this in the logs, calling an old transaction, update it. Test Plan: Answer a question, tail log, see no error. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17870 --- src/applications/ponder/editor/PonderAnswerEditor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index 8d66fffc17..bab0e1f72d 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -71,7 +71,7 @@ final class PonderAnswerEditor extends PonderEditor { foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); $new = $xaction->getNewValue(); - if ($type == PonderAnswerTransaction::TYPE_CONTENT) { + if ($type == PonderAnswerContentTransaction::TRANSACTIONTYPE) { $body->addRawSection($new); } } From 74d86f09023ca23126160c3fe8b5115bd9108c1c Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Fri, 12 May 2017 16:00:55 -0700 Subject: [PATCH 049/543] Migrate Pholio image description and mock status to modular transactions Summary: Also removes more now-dead code from `PholioTransaction`. Test Plan: opened and closed a bunch of mocks, edited a bunch of image descriptions Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D17868 --- src/__phutil_library_map__.php | 4 + .../PholioMockArchiveController.php | 2 +- .../controller/PholioMockEditController.php | 2 +- .../pholio/editor/PholioMockEditor.php | 35 --------- .../pholio/storage/PholioTransaction.php | 73 +----------------- .../PholioImageDescriptionTransaction.php | 76 +++++++++++++++++++ .../xaction/PholioImageNameTransaction.php | 10 --- .../xaction/PholioMockStatusTransaction.php | 66 ++++++++++++++++ 8 files changed, 151 insertions(+), 117 deletions(-) create mode 100644 src/applications/pholio/xaction/PholioImageDescriptionTransaction.php create mode 100644 src/applications/pholio/xaction/PholioMockStatusTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7a7ca114d1..611b2c118c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4360,6 +4360,7 @@ phutil_register_library_map(array( 'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php', 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', + 'PholioImageDescriptionTransaction' => 'applications/pholio/xaction/PholioImageDescriptionTransaction.php', 'PholioImageNameTransaction' => 'applications/pholio/xaction/PholioImageNameTransaction.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', @@ -4391,6 +4392,7 @@ phutil_register_library_map(array( 'PholioMockRelationship' => 'applications/pholio/relationships/PholioMockRelationship.php', 'PholioMockRelationshipSource' => 'applications/search/relationship/PholioMockRelationshipSource.php', 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', + 'PholioMockStatusTransaction' => 'applications/pholio/xaction/PholioMockStatusTransaction.php', 'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php', 'PholioMockTransactionType' => 'applications/pholio/xaction/PholioMockTransactionType.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', @@ -9905,6 +9907,7 @@ phutil_register_library_map(array( 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', ), + 'PholioImageDescriptionTransaction' => 'PholioImageTransactionType', 'PholioImageNameTransaction' => 'PholioImageTransactionType', 'PholioImagePHIDType' => 'PhabricatorPHIDType', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -9949,6 +9952,7 @@ phutil_register_library_map(array( 'PholioMockRelationship' => 'PhabricatorObjectRelationship', 'PholioMockRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PholioMockStatusTransaction' => 'PholioMockTransactionType', 'PholioMockThumbGridView' => 'AphrontView', 'PholioMockTransactionType' => 'PholioTransactionType', 'PholioMockViewController' => 'PholioController', diff --git a/src/applications/pholio/controller/PholioMockArchiveController.php b/src/applications/pholio/controller/PholioMockArchiveController.php index be8066ea7b..feca94610b 100644 --- a/src/applications/pholio/controller/PholioMockArchiveController.php +++ b/src/applications/pholio/controller/PholioMockArchiveController.php @@ -32,7 +32,7 @@ final class PholioMockArchiveController $xactions = array(); $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_STATUS) + ->setTransactionType(PholioMockStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PholioMockEditor()) diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 42038cc2d3..e97af5da27 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -173,7 +173,7 @@ final class PholioMockEditController extends PholioController { array($existing_image->getPHID() => $title)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_DESCRIPTION) + PholioImageDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 7910830b92..6f5bf1df2b 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -29,11 +29,9 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PholioTransaction::TYPE_STATUS; $types[] = PholioTransaction::TYPE_INLINE; $types[] = PholioTransaction::TYPE_IMAGE_FILE; - $types[] = PholioTransaction::TYPE_IMAGE_DESCRIPTION; $types[] = PholioTransaction::TYPE_IMAGE_REPLACE; $types[] = PholioTransaction::TYPE_IMAGE_SEQUENCE; @@ -45,20 +43,9 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_STATUS: - return $object->getStatus(); case PholioTransaction::TYPE_IMAGE_FILE: $images = $object->getImages(); return mpull($images, 'getPHID'); - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: - $description = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $description = $image->getDescription(); - $phid = $image->getPHID(); - } - return array($phid => $description); case PholioTransaction::TYPE_IMAGE_REPLACE: $raw = $xaction->getNewValue(); return $raw->getReplacesImagePHID(); @@ -79,8 +66,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_STATUS: - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: case PholioTransaction::TYPE_IMAGE_SEQUENCE: return $xaction->getNewValue(); case PholioTransaction::TYPE_IMAGE_REPLACE: @@ -185,17 +170,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->setNewImages($new_images); } - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - break; - } - } - private function getImageForXaction( PholioMock $mock, PhabricatorApplicationTransaction $xaction) { @@ -242,12 +216,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $object->attachImages($images); break; - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: - $image = $this->getImageForXaction($object, $xaction); - $value = (string)head($xaction->getNewValue()); - $image->setDescription($value); - $image->save(); - break; case PholioTransaction::TYPE_IMAGE_SEQUENCE: $image = $this->getImageForXaction($object, $xaction); $value = (int)head($xaction->getNewValue()); @@ -276,8 +244,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { - case PholioTransaction::TYPE_STATUS: - return $v; case PholioTransaction::TYPE_IMAGE_REPLACE: $u_img = $u->getNewValue(); $v_img = $v->getNewValue(); @@ -287,7 +253,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { break; case PholioTransaction::TYPE_IMAGE_FILE: return $this->mergePHIDOrEdgeTransactions($u, $v); - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: case PholioTransaction::TYPE_IMAGE_SEQUENCE: $raw_new_value_u = $u->getNewValue(); $raw_new_value_v = $v->getNewValue(); diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 85688b84d2..84a833b29f 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -2,12 +2,8 @@ final class PholioTransaction extends PhabricatorModularTransaction { - // Edits to the high level mock - const TYPE_STATUS = 'status'; - // Edits to images within the mock const TYPE_IMAGE_FILE = 'image-file'; - const TYPE_IMAGE_DESCRIPTION = 'image-description'; const TYPE_IMAGE_REPLACE = 'image-replace'; const TYPE_IMAGE_SEQUENCE = 'image-sequence'; @@ -54,7 +50,7 @@ final class PholioTransaction extends PhabricatorModularTransaction { $phids[] = $new; $phids[] = $old; break; - case self::TYPE_IMAGE_DESCRIPTION: + case PholioImageDescriptionTransaction::TRANSACTIONTYPE: case PholioImageNameTransaction::TRANSACTIONTYPE: case self::TYPE_IMAGE_SEQUENCE: $phids[] = key($new); @@ -68,8 +64,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_DESCRIPTION: - return ($old === array(null => null)); // this is boring / silly to surface; changing sequence is NBD case self::TYPE_IMAGE_SEQUENCE: return true; @@ -86,13 +80,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { switch ($this->getTransactionType()) { case self::TYPE_INLINE: return 'fa-comment'; - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return 'fa-ban'; - } else { - return 'fa-check'; - } - case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: return 'fa-pencil'; case self::TYPE_IMAGE_FILE: @@ -110,13 +97,13 @@ final class PholioTransaction extends PhabricatorModularTransaction { case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; - case self::TYPE_STATUS: + case PholioMockStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; case PholioMockNameTransaction::TRANSACTIONTYPE: case PholioMockDescriptionTransaction::TRANSACTIONTYPE: case PholioImageNameTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE_DESCRIPTION: + case PholioImageDescriptionTransaction::TRANSACTIONTYPE: case self::TYPE_IMAGE_SEQUENCE: case self::TYPE_IMAGE_FILE: case self::TYPE_IMAGE_REPLACE: @@ -137,17 +124,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $type = $this->getTransactionType(); switch ($type) { - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return pht( - '%s closed this mock.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s opened this mock.', - $this->renderHandleLink($author_phid)); - } - break; case self::TYPE_INLINE: $count = 1; foreach ($this->getTransactionGroup() as $xaction) { @@ -194,12 +170,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $this->renderHandleList($rem)); } break; - case self::TYPE_IMAGE_DESCRIPTION: - return pht( - '%s updated an image\'s (%s) description.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new))); - break; case self::TYPE_IMAGE_SEQUENCE: return pht( '%s updated an image\'s (%s) sequence.', @@ -220,19 +190,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $type = $this->getTransactionType(); switch ($type) { - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return pht( - '%s closed a mock %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s opened a mock %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; case self::TYPE_INLINE: return pht( '%s added an inline comment to %s.', @@ -246,12 +203,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case self::TYPE_IMAGE_DESCRIPTION: - return pht( - '%s updated image descriptions of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; case self::TYPE_IMAGE_SEQUENCE: return pht( '%s updated image sequence of %s.', @@ -268,12 +219,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return PhabricatorTransactions::COLOR_INDIGO; - } else { - return PhabricatorTransactions::COLOR_GREEN; - } case self::TYPE_IMAGE_REPLACE: return PhabricatorTransactions::COLOR_YELLOW; case self::TYPE_IMAGE_FILE: @@ -291,16 +236,4 @@ final class PholioTransaction extends PhabricatorModularTransaction { return parent::getColor(); } - public function getNoEffectDescription() { - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_NAME: - return pht('The image title was not updated.'); - case self::TYPE_IMAGE_DESCRIPTION: - return pht('The image description was not updated.'); - case self::TYPE_IMAGE_SEQUENCE: - return pht('The image sequence was not updated.'); - } - - return parent::getNoEffectDescription(); - } } diff --git a/src/applications/pholio/xaction/PholioImageDescriptionTransaction.php b/src/applications/pholio/xaction/PholioImageDescriptionTransaction.php new file mode 100644 index 0000000000..32fd8da285 --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageDescriptionTransaction.php @@ -0,0 +1,76 @@ +getImageForXaction($object); + if ($image) { + $description = $image->getDescription(); + $phid = $image->getPHID(); + } + return array($phid => $description); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (string)head($this->getNewValue()); + $image->setDescription($value); + $image->save(); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s updated an image\'s (%s) description.', + $this->renderAuthor(), + $this->renderHandle(head_key($new))); + } + + public function getTitleForFeed() { + return pht( + '%s updated image descriptions of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = head_key($raw_new_value_u); + $phid_v = head_key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + + return null; + } + + public function shouldHide() { + $old = $this->getOldValue(); + return ($old === array(null => null)); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText(head($this->getOldValue())) + ->setNewText(head($this->getNewValue())); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageNameTransaction.php b/src/applications/pholio/xaction/PholioImageNameTransaction.php index b888b1244b..33b01903ee 100644 --- a/src/applications/pholio/xaction/PholioImageNameTransaction.php +++ b/src/applications/pholio/xaction/PholioImageNameTransaction.php @@ -42,16 +42,6 @@ final class PholioImageNameTransaction $this->renderObject()); } - public function getIcon() { - $new = $this->getNewValue(); - - if ($new == PholioMock::STATUS_CLOSED) { - return 'fa-ban'; - } else { - return 'fa-check'; - } - } - public function mergeTransactions( $object, PhabricatorApplicationTransaction $u, diff --git a/src/applications/pholio/xaction/PholioMockStatusTransaction.php b/src/applications/pholio/xaction/PholioMockStatusTransaction.php new file mode 100644 index 0000000000..ccacca2b5e --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockStatusTransaction.php @@ -0,0 +1,66 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return pht( + '%s closed this mock.', + $this->renderAuthor()); + } else { + return pht( + '%s opened this mock.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return pht( + '%s closed mock %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s opened mock %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + public function getColor() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return PhabricatorTransactions::COLOR_INDIGO; + } else { + return PhabricatorTransactions::COLOR_GREEN; + } + } + +} From db631b423f417b45386d21323067c3392a75116f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 12:13:59 -0400 Subject: [PATCH 050/543] Add basic Watching filter to /projects/ Summary: Ref T12707. Adds a simple filter for the viewer if logged in. Test Plan: Watch a project, click on watching list, see project I'm watching. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley Maniphest Tasks: T12707 Differential Revision: https://secure.phabricator.com/D17873 --- .../project/query/PhabricatorProjectSearchEngine.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 5c153be5a4..75c2045f50 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -128,6 +128,10 @@ final class PhabricatorProjectSearchEngine $names['joined'] = pht('Joined'); } + if ($this->requireViewer()->isLoggedIn()) { + $names['watching'] = pht('Watching'); + } + $names['active'] = pht('Active'); $names['all'] = pht('All'); @@ -153,6 +157,10 @@ final class PhabricatorProjectSearchEngine return $query ->setParameter('memberPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); + case 'watching': + return $query + ->setParameter('watcherPHIDs', array($viewer_phid)) + ->setParameter('status', 'active'); } return parent::buildSavedQueryFromBuiltin($query_key); From 11c5638832c2c084948a115a62c1b70ddee12ff9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 12 May 2017 11:05:02 -0700 Subject: [PATCH 051/543] Fix some error log issues with uninitialized commit/revision lists Summary: Fixes T12679. Reproduction steps appear to be: - As a logged-out user, view revision list or commit list. - Enable bucketing by action required. - Before patch: `foreach (null as ...)` causes error spew. - After patch: `foreach (array() as ...)` works great. Test Plan: - Reproduced issue by following steps above in Differential (revisions) and Diffusion (audits/commits). - After patches, no more errors in the log. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12679 Differential Revision: https://secure.phabricator.com/D17872 --- src/applications/audit/view/PhabricatorAuditListView.php | 2 +- .../differential/view/DifferentialRevisionListView.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/audit/view/PhabricatorAuditListView.php b/src/applications/audit/view/PhabricatorAuditListView.php index e249406724..f9399e66ab 100644 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ b/src/applications/audit/view/PhabricatorAuditListView.php @@ -2,7 +2,7 @@ final class PhabricatorAuditListView extends AphrontView { - private $commits; + private $commits = array(); private $header; private $showDrafts; private $noDataString; diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 5aacafbb69..5373192b31 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -5,7 +5,7 @@ */ final class DifferentialRevisionListView extends AphrontView { - private $revisions; + private $revisions = array(); private $handles; private $header; private $noDataString; From a25c79bbc01ee145224ca7269b5d9e4f01c51f05 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 15:27:29 -0400 Subject: [PATCH 052/543] Add Column Edit / History actions to workboard columns Summary: This brings up "Edit Column" as an action item under the main column dropdown as well as a "Column History" for completeness. Unsure column history is actually useful, but leaving it in anyways. It might be nice to have some sort of dialog version of a history page. Test Plan: Make a workboard, add a column, edit column name, stay on workboard. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17874 --- .../PhabricatorProjectBoardViewController.php | 16 ++++++++++++++++ .../PhabricatorProjectColumnDetailController.php | 2 +- .../PhabricatorProjectColumnEditController.php | 7 +------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 80ed701ff9..3b117c747f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -837,6 +837,16 @@ final class PhabricatorProjectBoardViewController ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); + // Column Related Actions Below + // + $edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/'; + $column_items[] = id(new PhabricatorActionView()) + ->setName(pht('Edit Column')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI($edit_uri)) + ->setDisabled(!$can_edit) + ->setWorkflow(true); + $can_hide = ($can_edit && !$column->isDefaultColumn()); $hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/'; $hide_uri = $this->getApplicationURI($hide_uri); @@ -858,6 +868,12 @@ final class PhabricatorProjectBoardViewController ->setWorkflow(true); } + $details_uri = 'board/'.$this->id.'/column/'.$column->getID().'/'; + $column_items[] = id(new PhabricatorActionView()) + ->setName(pht('Column History')) + ->setIcon('fa-columns') + ->setHref($this->getApplicationURI($details_uri)); + $column_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($column_items as $item) { diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index 84bdc2b89c..d922d24714 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -103,7 +103,7 @@ final class PhabricatorProjectColumnDetailController ->setIcon('fa-pencil') ->setHref($this->getApplicationURI($base_uri.'edit/'.$id.'/')) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); + ->setWorkflow(true)); return $actions; } diff --git a/src/applications/project/controller/PhabricatorProjectColumnEditController.php b/src/applications/project/controller/PhabricatorProjectColumnEditController.php index fb0c7b0910..94277c92e5 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnEditController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnEditController.php @@ -51,12 +51,7 @@ final class PhabricatorProjectColumnEditController $validation_exception = null; $base_uri = '/board/'.$project_id.'/'; - if ($is_new) { - // we want to go back to the board - $view_uri = $this->getApplicationURI($base_uri); - } else { - $view_uri = $this->getApplicationURI($base_uri.'column/'.$id.'/'); - } + $view_uri = $this->getApplicationURI($base_uri); if ($request->isFormPost()) { $v_name = $request->getStr('name'); From aa81979922cc7e82287223c9ce19082e5ab91a6c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 14 May 2017 13:33:53 -0700 Subject: [PATCH 053/543] Fix a runaway indentation level in LegalpadDocumentEditEngine Summary: Ref T12685. Test Plan: No behavioral changes. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17875 --- .../editor/LegalpadDocumentEditEngine.php | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php index 66660c9ec2..814647b82a 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php @@ -114,53 +114,53 @@ final class LegalpadDocumentEditEngine ->setConduitTypeDescription(pht('New document signature type.')) ->setValue($object->getSignatureType()) ->setOptions(LegalpadDocument::getSignatureTypeMap()) - ->setTransactionType( + ->setTransactionType( LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE); - $show_require = true; - } else { - $fields[] = id(new PhabricatorStaticEditField()) - ->setLabel(pht('Who Should Sign?')) - ->setValue($object->getSignatureTypeName()); - $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; - $show_require = $object->getSignatureType() == $individual; - } - - if ($show_require && $is_admin) { - $fields[] = - id(new PhabricatorBoolEditField()) - ->setKey('requireSignature') - ->setOptions( - pht('No Signature Required'), - pht('Signature Required to use Phabricator')) - ->setAsCheckbox(true) - ->setTransactionType( - LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE) - ->setDescription(pht('Marks this document as required signing.')) - ->setConduitDescription( - pht('Marks this document as required signing.')) - ->setValue($object->getRequireSignature()); - } + $show_require = true; + } else { + $fields[] = id(new PhabricatorStaticEditField()) + ->setLabel(pht('Who Should Sign?')) + ->setValue($object->getSignatureTypeName()); + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + $show_require = $object->getSignatureType() == $individual; + } + if ($show_require && $is_admin) { $fields[] = - id(new PhabricatorRemarkupEditField()) - ->setKey('preamble') - ->setLabel(pht('Preamble')) - ->setDescription(pht('The preamble of the document.')) - ->setConduitTypeDescription(pht('New document preamble.')) - ->setValue($object->getPreamble()) + id(new PhabricatorBoolEditField()) + ->setKey('requireSignature') + ->setOptions( + pht('No Signature Required'), + pht('Signature Required to use Phabricator')) + ->setAsCheckbox(true) ->setTransactionType( - LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE); + LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Marks this document as required signing.')) + ->setConduitDescription( + pht('Marks this document as required signing.')) + ->setValue($object->getRequireSignature()); + } - $fields[] = - id(new PhabricatorRemarkupEditField()) - ->setKey('text') - ->setLabel(pht('Document Body')) - ->setDescription(pht('The body of text of the document.')) - ->setConduitTypeDescription(pht('New document body.')) - ->setValue($document_body) - ->setIsRequired(true) - ->setTransactionType( - LegalpadDocumentTextTransaction::TRANSACTIONTYPE); + $fields[] = + id(new PhabricatorRemarkupEditField()) + ->setKey('preamble') + ->setLabel(pht('Preamble')) + ->setDescription(pht('The preamble of the document.')) + ->setConduitTypeDescription(pht('New document preamble.')) + ->setValue($object->getPreamble()) + ->setTransactionType( + LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE); + + $fields[] = + id(new PhabricatorRemarkupEditField()) + ->setKey('text') + ->setLabel(pht('Document Body')) + ->setDescription(pht('The body of text of the document.')) + ->setConduitTypeDescription(pht('New document body.')) + ->setValue($document_body) + ->setIsRequired(true) + ->setTransactionType( + LegalpadDocumentTextTransaction::TRANSACTIONTYPE); return $fields; From 95925ad58ffb66a77384fcc2d34ed88e7a2a1cbd Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 14 May 2017 13:35:54 -0700 Subject: [PATCH 054/543] Make getApplicationTransactionEditor() in PhabricatorApplication return an editor Summary: Ref T12685. I provided this incorrect (`return new` rather than `throw`) implementation earlier; it can now be replaced with a proper implementation. This caused application policy edits to spew this into the daemon log: ``` [2017-05-14 15:35:27] EXCEPTION: (Error) Call to undefined method PhutilMethodNotImplementedException::setActor() at [/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php:69] ``` Test Plan: - Used `bin/worker execute --id ` to execute a previously-failing task. - Saw a feed story publish. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17876 --- src/applications/base/PhabricatorApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index af683e0592..f525d0ec97 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -642,7 +642,7 @@ abstract class PhabricatorApplication public function getApplicationTransactionEditor() { - return new PhutilMethodNotImplementedException(pht('Coming Soon!')); + return new PhabricatorApplicationEditor(); } public function getApplicationTransactionObject() { From d2e1dd0ef092bf455666fd2894ae7e7ac3f2e9e7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 14 May 2017 13:39:54 -0700 Subject: [PATCH 055/543] In Applications, always redirect back to the detail page after edits Summary: Ref T12685. When you edit an application's policies but don't make any changes, you currently get stuck on the same page. This isn't how other edit screens work, and I think it's a holdover from eras long ago. Make it consistent with other applications. Also, fix a missing `pht()`. Test Plan: - Edited an application configuation. - Clicked "Save" without making changes. - After patch, was redirected back to detail page like in other applications. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17877 --- .../PhabricatorApplicationEditController.php | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php index c9f6a2cafb..f7c2eac81e 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEditController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -32,7 +32,6 @@ final class PhabricatorApplicationEditController if ($request->isFormPost()) { $xactions = array(); - $result = array(); $template = $application->getApplicationTransactionTemplate(); foreach ($application->getCapabilities() as $capability) { if (!$application->isCapabilityEditable($capability)) { @@ -47,8 +46,6 @@ final class PhabricatorApplicationEditController continue; } - $result[$capability] = $new; - $xactions[] = id(clone $template) ->setTransactionType( PhabricatorApplicationPolicyChangeTransaction::TRANSACTIONTYPE) @@ -58,25 +55,23 @@ final class PhabricatorApplicationEditController ->setNewValue($new); } - if ($result) { - $editor = id(new PhabricatorApplicationEditor()) - ->setActor($user) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true); + $editor = id(new PhabricatorApplicationEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); - try { - $editor->applyTransactions($application, $xactions); - return id(new AphrontRedirectResponse())->setURI($view_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - } - - return $this->newDialog() - ->setTitle('Validation Failed') - ->setValidationException($validation_exception) - ->addCancelButton($view_uri); + try { + $editor->applyTransactions($application, $xactions); + return id(new AphrontRedirectResponse())->setURI($view_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; } + + return $this->newDialog() + ->setTitle(pht('Validation Failed')) + ->setValidationException($validation_exception) + ->addCancelButton($view_uri); } $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( From c7a642255997ad539b06a21ed51f5942a0b584e2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 14 May 2017 13:49:23 -0700 Subject: [PATCH 056/543] Make a handful of minor Slowvote behaviors more consistent with other applications Summary: Ref T12685. - Better icon/color/label consistency. - Make "0" a valid response. - Fix a bug where creating a poll with Quicksand enabled led to bad times (form submitted back to the search screen). Test Plan: {F4956412} Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12685 Differential Revision: https://secure.phabricator.com/D17878 --- .../controller/PhabricatorSlowvoteEditController.php | 11 +++++++++-- .../controller/PhabricatorSlowvotePollController.php | 6 +++--- .../slowvote/editor/PhabricatorSlowvoteEditor.php | 8 ++++++++ .../query/PhabricatorSlowvoteSearchEngine.php | 1 + .../slowvote/storage/PhabricatorSlowvotePoll.php | 4 ++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index d490a77c50..ebf09ec929 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -68,7 +68,13 @@ final class PhabricatorSlowvoteEditController } if ($is_new) { - $responses = array_filter($responses); + // NOTE: Make sure common and useful response "0" is preserved. + foreach ($responses as $key => $response) { + if (!strlen($response)) { + unset($responses[$key]); + } + } + if (empty($responses)) { $errors[] = pht('You must offer at least one response.'); $e_response = pht('Required'); @@ -139,13 +145,14 @@ final class PhabricatorSlowvoteEditController } return id(new AphrontRedirectResponse()) - ->setURI('/V'.$poll->getID()); + ->setURI($poll->getURI()); } else { $poll->setViewPolicy($v_view_policy); } } $form = id(new AphrontFormView()) + ->setAction($request->getrequestURI()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index f788f5e94c..1ffab17791 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -35,9 +35,9 @@ final class PhabricatorSlowvotePollController )); } - $header_icon = $poll->getIsClosed() ? 'fa-ban' : 'fa-circle-o'; + $header_icon = $poll->getIsClosed() ? 'fa-ban' : 'fa-square-o'; $header_name = $poll->getIsClosed() ? pht('Closed') : pht('Open'); - $header_color = $poll->getIsClosed() ? 'dark' : 'bluegrey'; + $header_color = $poll->getIsClosed() ? 'indigo' : 'bluegrey'; $header = id(new PHUIHeaderView()) ->setHeader($poll->getQuestion()) @@ -89,7 +89,7 @@ final class PhabricatorSlowvotePollController $is_closed = $poll->getIsClosed(); $close_poll_text = $is_closed ? pht('Reopen Poll') : pht('Close Poll'); - $close_poll_icon = $is_closed ? 'fa-play-circle-o' : 'fa-ban'; + $close_poll_icon = $is_closed ? 'fa-check' : 'fa-ban'; $curtain->addAction( id(new PhabricatorActionView()) diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php index 3f6baefd8d..38dbfb12d6 100644 --- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php +++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php @@ -11,6 +11,14 @@ final class PhabricatorSlowvoteEditor return pht('Slowvote'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this poll.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php index fee52a908c..d1db26b8a4 100644 --- a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php +++ b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php @@ -55,6 +55,7 @@ final class PhabricatorSlowvoteSearchEngine id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') + ->setLabel(pht('Statuses')) ->setOptions(array( 'open' => pht('Open'), 'closed' => pht('Closed'), diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 06c4f6f68f..3b642256d2 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -112,6 +112,10 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO return 'V'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); From a5ad11d2d2f1d440b42681b8e65c71234c0b5e3c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 17:32:24 -0700 Subject: [PATCH 057/543] Add Member/Watcher info to search results Summary: Fixes T12707 Adds additional information to search results for if user is a member or a watcher of a project. Also removed the icon colors, which I'll find a better way to denote in future. Test Plan: Join a project, watch a project, view results list in /projects/ Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12707 Differential Revision: https://secure.phabricator.com/D17880 --- .../query/PhabricatorProjectSearchEngine.php | 4 +++- .../project/view/PhabricatorProjectListView.php | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 75c2045f50..70931a8b00 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -13,7 +13,9 @@ final class PhabricatorProjectSearchEngine public function newQuery() { return id(new PhabricatorProjectQuery()) - ->needImages(true); + ->needImages(true) + ->needMembers(true) + ->needWatchers(true); } protected function buildCustomSearchFields() { diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 3d6044f2b1..38c845167c 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -15,6 +15,7 @@ final class PhabricatorProjectListView extends AphrontView { public function renderList() { $viewer = $this->getUser(); + $viewer_phid = $viewer->getPHID(); $projects = $this->getProjects(); $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); @@ -26,10 +27,8 @@ final class PhabricatorProjectListView extends AphrontView { $id = $project->getID(); $icon = $project->getDisplayIconIcon(); - $color = $project->getColor(); - $icon_icon = id(new PHUIIconView()) - ->setIcon("{$icon} {$color}"); + ->setIcon($icon); $icon_name = $project->getDisplayIconName(); @@ -49,6 +48,17 @@ final class PhabricatorProjectListView extends AphrontView { $item->setDisabled(true); } + $is_member = $project->isUserMember($viewer_phid); + $is_watcher = $project->isUserWatcher($viewer_phid); + + if ($is_member) { + $item->addIcon('fa-user', pht('Member')); + } + + if ($is_watcher) { + $item->addIcon('fa-eye', pht('Watching')); + } + $list->addItem($item); } From 89e567ffd94594bd7ab8f2deb7fab3b2f632d1a9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 14:15:06 -0700 Subject: [PATCH 058/543] Move Board Manage actions up a level Summary: Moves "reorder columns" and "change background" up a level, redesigns "manage" page to be a little cleaner. Test Plan: Change colors, reorder columns, manage page, disable board, re-enable board. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17879 --- ...icatorProjectBoardBackgroundController.php | 3 +- ...habricatorProjectBoardManageController.php | 103 +++++------------- ...abricatorProjectBoardReorderController.php | 4 +- .../PhabricatorProjectBoardViewController.php | 29 +++-- 4 files changed, 51 insertions(+), 88 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index 3ba1f03a56..99260d1770 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -28,6 +28,7 @@ final class PhabricatorProjectBoardBackgroundController $this->setProject($board); $id = $board->getID(); + $view_uri = $this->getApplicationURI("board/{$id}/"); $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); if ($request->isFormPost()) { @@ -47,7 +48,7 @@ final class PhabricatorProjectBoardBackgroundController ->applyTransactions($board, $xactions); return id(new AphrontRedirectResponse()) - ->setURI($manage_uri); + ->setURI($view_uri); } $nav = $this->getProfileMenu(); diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index 75bff07106..aecb5aa42a 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -31,112 +31,58 @@ final class PhabricatorProjectBoardManageController $board_id = $board->getID(); $header = $this->buildHeaderView($board); - $actions = $this->buildActionView($board); - $properties = $this->buildPropertyView($board); - - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$board_id}/"); $crumbs->addTextCrumb(pht('Manage')); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); + $columns_list = $this->buildColumnsList($board, $columns); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($columns_list); $title = array( pht('Manage Workboard'), $board->getDisplayName(), ); - $columns_list = $this->buildColumnsList($board, $columns); - return $this->newPage() ->setTitle($title) ->setNavigation($nav) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $columns_list, - )); + ->appendChild($view); } private function buildHeaderView(PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setHeader(pht('Workboard: %s', $board->getDisplayName())); - - return $header; - } - - private function buildActionView(PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - $id = $board->getID(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $board, PhabricatorPolicyCapability::CAN_EDIT); - $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-exchange') - ->setName(pht('Reorder Columns')) - ->setHref($reorder_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - - $background_uri = $this->getApplicationURI("board/{$id}/background/"); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-paint-brush') - ->setName(pht('Change Background Color')) - ->setHref($background_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - + $id = $board->getID(); $disable_uri = $this->getApplicationURI("board/{$id}/disable/"); - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-ban') - ->setName(pht('Disable Board')) - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-ban') + ->setText(pht('Disable Board')) + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true); - return $actions; - } - - private function buildPropertyView( - PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - - $properties = id(new PHUIPropertyListView()) + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Workboard: %s', $board->getDisplayName())) ->setUser($viewer) - ->setObject($board); + ->setPolicyObject($board) + ->setProfileHeader(true) + ->addActionLink($button); - $background = $board->getDisplayWorkboardBackgroundColor(); - if ($background !== null) { - $map = PhabricatorProjectWorkboardBackgroundColor::getOptions(); - $map = ipull($map, 'name'); - - $name = idx($map, $background, $background); - $properties->addProperty(pht('Background Color'), $name); - } - - return $properties; + return $header; } private function buildColumnsList( @@ -165,6 +111,11 @@ final class PhabricatorProjectBoardManageController if ($column->isHidden()) { $item->setDisabled(true); + $item->addAttribute(pht('Hidden')); + $item->setImageIcon('fa-columns grey'); + } else { + $item->addAttribute(pht('Visible')); + $item->setImageIcon('fa-columns'); } $view->addItem($item); diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php index 05f1dd2d43..fc348bb02f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php @@ -23,13 +23,13 @@ final class PhabricatorProjectBoardReorderController $this->setProject($project); $project_id = $project->getID(); - $manage_uri = $this->getApplicationURI("board/{$project_id}/manage/"); + $view_uri = $this->getApplicationURI("board/{$project_id}/"); $reorder_uri = $this->getApplicationURI("board/{$project_id}/reorder/"); if ($request->isFormPost()) { // User clicked "Done", make sure the page reloads to show the new // column order. - return id(new AphrontRedirectResponse())->setURI($manage_uri); + return id(new AphrontRedirectResponse())->setURI($view_uri); } $columns = id(new PhabricatorProjectColumnQuery()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 3b117c747f..6a7083b10d 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -705,10 +705,21 @@ final class PhabricatorProjectBoardViewController ->setDisabled(!$can_edit) ->setWorkflow(true); + $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Manage Board')) - ->setHref($manage_uri); + ->setIcon('fa-exchange') + ->setName(pht('Reorder Columns')) + ->setHref($reorder_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true); + + $background_uri = $this->getApplicationURI("board/{$id}/background/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-paint-brush') + ->setName(pht('Change Background Color')) + ->setHref($background_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(false); if ($show_hidden) { $hidden_uri = $this->getURIWithState() @@ -727,6 +738,12 @@ final class PhabricatorProjectBoardViewController ->setName($hidden_text) ->setHref($hidden_uri); + $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-gear') + ->setName(pht('Manage Workboard')) + ->setHref($manage_uri); + $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( @@ -734,12 +751,6 @@ final class PhabricatorProjectBoardViewController PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); - $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-list-ul') - ->setName(pht('Batch Edit Visible Tasks...')) - ->setHref($batch_edit_uri) - ->setDisabled(!$can_batch_edit); - $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { From 7f54f79fd1c3a37d1c0942e3b3d27daae2e4efff Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 06:36:28 -0700 Subject: [PATCH 059/543] Add required needMembers/needWatchers calls to Project Profile/Subprojects tabs Summary: Fixes T12710. See that task for discussion. This is pretty ugly/redundant but not broken. (Feel free to reject this and pursue something else.) Test Plan: - For a project with active subprojects/milestones, viewed the project profile and subprojects tabs. - After patch: they're ugly, but no longer fatal. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12710 Differential Revision: https://secure.phabricator.com/D17882 --- .../controller/PhabricatorProjectProfileController.php | 2 ++ .../controller/PhabricatorProjectSubprojectsController.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 4c726f5884..60254ddcca 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -262,6 +262,8 @@ final class PhabricatorProjectProfileController ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) + ->needMembers(true) + ->needWatchers(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 4a89bb4cde..a25a41fad0 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -31,6 +31,8 @@ final class PhabricatorProjectSubprojectsController ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) + ->needMembers(true) + ->needWatchers(true) ->withIsMilestone(false) ->execute(); } else { @@ -42,6 +44,8 @@ final class PhabricatorProjectSubprojectsController ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) + ->needMembers(true) + ->needWatchers(true) ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); From 6fa91caf32aa80339e7ea5fef769f23f0220924a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 18:58:33 -0700 Subject: [PATCH 060/543] Clean up Project Board Manage / Column Manage pages Summary: Slightly nicer, more consistent UI. Also removed "Column History" from dropdowns as this is available on the general board manage page. Test Plan: Review Board and Column management pages. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17881 --- ...habricatorProjectBoardManageController.php | 5 +- .../PhabricatorProjectBoardViewController.php | 6 -- ...abricatorProjectColumnDetailController.php | 70 +++++++------------ .../PhabricatorProjectProfileController.php | 5 +- 4 files changed, 30 insertions(+), 56 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index aecb5aa42a..4607b50c2a 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -40,8 +40,12 @@ final class PhabricatorProjectBoardManageController $nav = $this->getProfileMenu(); $columns_list = $this->buildColumnsList($board, $columns); + require_celerity_resource('project-view-css'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setFooter($columns_list); $title = array( @@ -78,7 +82,6 @@ final class PhabricatorProjectBoardManageController $header = id(new PHUIHeaderView()) ->setHeader(pht('Workboard: %s', $board->getDisplayName())) ->setUser($viewer) - ->setPolicyObject($board) ->setProfileHeader(true) ->addActionLink($button); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 6a7083b10d..da1f0ccb95 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -879,12 +879,6 @@ final class PhabricatorProjectBoardViewController ->setWorkflow(true); } - $details_uri = 'board/'.$this->id.'/column/'.$column->getID().'/'; - $column_items[] = id(new PhabricatorActionView()) - ->setName(pht('Column History')) - ->setIcon('fa-columns') - ->setHref($this->getApplicationURI($details_uri)); - $column_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($column_items as $item) { diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index d922d24714..56eb16613f 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -44,36 +44,39 @@ final class PhabricatorProjectColumnDetailController $title = $column->getDisplayName(); $header = $this->buildHeaderView($column); - $actions = $this->buildActionView($column); - $properties = $this->buildPropertyView($column, $actions); + $properties = $this->buildPropertyView($column); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/"); $crumbs->addTextCrumb(pht('Column: %s', $title)); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); + require_celerity_resource('project-view-css'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setMainColumn(array( + $properties, + $timeline, + )); return $this->newPage() ->setTitle($title) ->setNavigation($nav) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $timeline, - )); + ->appendChild($view); } private function buildHeaderView(PhabricatorProjectColumn $column) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) + ->setHeader(pht('Column: %s', $column->getDisplayName())) ->setUser($viewer) - ->setHeader($column->getDisplayName()); + ->setProfileHeader(true); if ($column->isHidden()) { $header->setStatus('fa-ban', 'dark', pht('Hidden')); @@ -82,41 +85,13 @@ final class PhabricatorProjectColumnDetailController return $header; } - private function buildActionView(PhabricatorProjectColumn $column) { - $viewer = $this->getRequest()->getUser(); - - $id = $column->getID(); - $project_id = $this->getProject()->getID(); - $base_uri = '/board/'.$project_id.'/'; - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $column, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Column')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI($base_uri.'edit/'.$id.'/')) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - - return $actions; - } - private function buildPropertyView( - PhabricatorProjectColumn $column, - PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + PhabricatorProjectColumn $column) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($column) - ->setActionList($actions); + ->setObject($column); $limit = $column->getPointLimit(); if ($limit === null) { @@ -126,7 +101,12 @@ final class PhabricatorProjectColumnDetailController } $properties->addProperty(pht('Point Limit'), $limit_text); - return $properties; + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + + return $box; } } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 60254ddcca..e5ae78fd20 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -113,10 +113,7 @@ final class PhabricatorProjectProfileController ->setCrumbs($crumbs) ->setTitle($project->getDisplayName()) ->setPageObjectPHIDs(array($project->getPHID())) - ->appendChild( - array( - $home, - )); + ->appendChild($home); } private function buildPropertyListView( From ea874a28251bdb0b19dd231168087b435e2bd18d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 09:02:39 -0700 Subject: [PATCH 061/543] Restrict watching and member project display better Summary: Fixes T12713. We don't need to show watching and member info on other views other than ApplicationSearch (for now) so add a few methods to restrict the calls. Test Plan: Visit project search, profile, project home, project home with subprojects Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12713 Differential Revision: https://secure.phabricator.com/D17883 --- .../PhabricatorProjectProfileController.php | 2 -- ...habricatorProjectSubprojectsController.php | 4 --- .../query/PhabricatorProjectSearchEngine.php | 2 ++ .../view/PhabricatorProjectListView.php | 29 ++++++++++++++----- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index e5ae78fd20..32dbec3c0f 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -259,8 +259,6 @@ final class PhabricatorProjectProfileController ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index a25a41fad0..4a89bb4cde 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -31,8 +31,6 @@ final class PhabricatorProjectSubprojectsController ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withIsMilestone(false) ->execute(); } else { @@ -44,8 +42,6 @@ final class PhabricatorProjectSubprojectsController ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 70931a8b00..df13278812 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -229,6 +229,8 @@ final class PhabricatorProjectSearchEngine $list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($projects) + ->setShowWatching(true) + ->setShowMember(true) ->renderList(); return id(new PhabricatorApplicationSearchResultView()) diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 38c845167c..994e78ce76 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -3,6 +3,8 @@ final class PhabricatorProjectListView extends AphrontView { private $projects; + private $showMember; + private $showWatching; public function setProjects(array $projects) { $this->projects = $projects; @@ -13,6 +15,16 @@ final class PhabricatorProjectListView extends AphrontView { return $this->projects; } + public function setShowWatching($watching) { + $this->showWatching = $watching; + return $this; + } + + public function setShowMember($member) { + $this->showMember = $member; + return $this; + } + public function renderList() { $viewer = $this->getUser(); $viewer_phid = $viewer->getPHID(); @@ -48,15 +60,18 @@ final class PhabricatorProjectListView extends AphrontView { $item->setDisabled(true); } - $is_member = $project->isUserMember($viewer_phid); - $is_watcher = $project->isUserWatcher($viewer_phid); - - if ($is_member) { - $item->addIcon('fa-user', pht('Member')); + if ($this->showMember) { + $is_member = $project->isUserMember($viewer_phid); + if ($is_member) { + $item->addIcon('fa-user', pht('Member')); + } } - if ($is_watcher) { - $item->addIcon('fa-eye', pht('Watching')); + if ($this->showWatching) { + $is_watcher = $project->isUserWatcher($viewer_phid); + if ($is_watcher) { + $item->addIcon('fa-eye', pht('Watching')); + } } $list->addItem($item); From d6a620be4577941092965429e4998e98f9cc469a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 10:23:20 -0700 Subject: [PATCH 062/543] Update Maniphest for modular transactions Summary: Ref T12671. This modernized Maniphest transactions to modular transactions. Test Plan: - Create Task - Edit Task - Raise Priority - Change Status - Merge as a duplicate - Create Subtask - Claim Task - Assign Project - Move on Workboard - Set a cover image - Assign story points - Change story points - Generate lots via lipsum - Bulk edit tasks - Leave comments - Award Token I'm sure I'm missing something. Reviewers: epriestley Reviewed By: epriestley Subscribers: hazelyang, Korvin Maniphest Tasks: T12671 Differential Revision: https://secure.phabricator.com/D17844 --- .../autopatches/20140321.mstatus.2.mig.php | 3 +- src/__phutil_library_map__.php | 32 +- .../__tests__/PhabricatorFileTestCase.php | 5 +- .../__tests__/ManiphestTaskTestCase.php | 10 +- .../bulk/ManiphestTaskEditBulkJobType.php | 14 +- .../command/ManiphestAssignEmailCommand.php | 2 +- .../command/ManiphestClaimEmailCommand.php | 2 +- .../command/ManiphestCloseEmailCommand.php | 2 +- .../command/ManiphestPriorityEmailCommand.php | 2 +- .../command/ManiphestStatusEmailCommand.php | 2 +- .../conduit/ManiphestConduitAPIMethod.php | 12 +- .../controller/ManiphestReportController.php | 2 +- .../ManiphestSubpriorityController.php | 4 +- .../ManiphestTaskDetailController.php | 2 +- .../maniphest/editor/ManiphestEditEngine.php | 14 +- .../editor/ManiphestTransactionEditor.php | 345 +------- .../ManiphestTaskAssignHeraldAction.php | 2 +- .../ManiphestTaskPriorityHeraldAction.php | 2 +- .../ManiphestTaskStatusHeraldAction.php | 2 +- ...bricatorManiphestTaskTestDataGenerator.php | 10 +- .../maniphest/mail/ManiphestReplyHandler.php | 5 +- .../ManiphestTaskRelationship.php | 6 +- .../storage/ManiphestTransaction.php | 807 +----------------- .../ManiphestTaskAttachTransaction.php | 91 ++ .../ManiphestTaskCoverImageTransaction.php | 106 +++ .../ManiphestTaskDescriptionTransaction.php | 61 ++ .../xaction/ManiphestTaskEdgeTransaction.php | 31 + .../ManiphestTaskMergedFromTransaction.php | 45 + .../ManiphestTaskMergedIntoTransaction.php | 47 + .../xaction/ManiphestTaskOwnerTransaction.php | 158 ++++ .../ManiphestTaskParentTransaction.php | 60 ++ .../ManiphestTaskPointsTransaction.php | 76 ++ .../ManiphestTaskPriorityTransaction.php | 119 +++ .../ManiphestTaskStatusTransaction.php | 232 +++++ .../ManiphestTaskSubpriorityTransaction.php | 21 + .../xaction/ManiphestTaskTitleTransaction.php | 74 ++ .../xaction/ManiphestTaskTransactionType.php | 16 + .../ManiphestTaskUnblockTransaction.php | 125 +++ .../nuance/item/NuanceGitHubEventItemType.php | 6 +- .../PhabricatorProjectCoreTestCase.php | 2 +- .../PhabricatorProjectCoverController.php | 2 +- .../PhabricatorProjectMoveController.php | 5 +- ...torRepositoryCommitMessageParserWorker.php | 3 +- 43 files changed, 1401 insertions(+), 1166 deletions(-) create mode 100644 src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskTransactionType.php create mode 100644 src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php diff --git a/resources/sql/autopatches/20140321.mstatus.2.mig.php b/resources/sql/autopatches/20140321.mstatus.2.mig.php index 7f91e00e1b..654ca7881c 100644 --- a/resources/sql/autopatches/20140321.mstatus.2.mig.php +++ b/resources/sql/autopatches/20140321.mstatus.2.mig.php @@ -35,7 +35,8 @@ foreach (new LiskMigrationIterator(new ManiphestTransaction()) as $xaction) { $id = $xaction->getID(); echo pht('Migrating %d...', $id)."\n"; - if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_STATUS) { + $xn_type = ManiphestTaskStatusTransaction::TRANSACTIONTYPE; + if ($xaction->getTransactionType() == $xn_type) { $old = $xaction->getOldValue(); if ($old !== null && isset($status_map[$old])) { $old = $status_map[$old]; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 611b2c118c..024a8c93b2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1511,14 +1511,18 @@ phutil_register_library_map(array( 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', 'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php', 'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php', + 'ManiphestTaskAttachTransaction' => 'applications/maniphest/xaction/ManiphestTaskAttachTransaction.php', 'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php', 'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php', 'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php', 'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php', + 'ManiphestTaskCoverImageTransaction' => 'applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php', 'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php', 'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php', 'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php', + 'ManiphestTaskDescriptionTransaction' => 'applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', + 'ManiphestTaskEdgeTransaction' => 'applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php', 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php', @@ -1541,14 +1545,20 @@ phutil_register_library_map(array( 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php', + 'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php', + 'ManiphestTaskMergedIntoTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php', 'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php', + 'ManiphestTaskOwnerTransaction' => 'applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php', 'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php', 'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php', + 'ManiphestTaskParentTransaction' => 'applications/maniphest/xaction/ManiphestTaskParentTransaction.php', 'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php', + 'ManiphestTaskPointsTransaction' => 'applications/maniphest/xaction/ManiphestTaskPointsTransaction.php', 'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php', 'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php', 'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php', 'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php', + 'ManiphestTaskPriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php', 'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php', 'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php', 'ManiphestTaskRelationshipSource' => 'applications/search/relationship/ManiphestTaskRelationshipSource.php', @@ -1560,9 +1570,14 @@ phutil_register_library_map(array( 'ManiphestTaskStatusHeraldAction' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php', 'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php', 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', + 'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php', + 'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php', 'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php', 'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php', 'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php', + 'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php', + 'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php', + 'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php', 'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php', 'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php', @@ -6569,14 +6584,18 @@ phutil_register_library_map(array( 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskAttachTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', 'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskCoverImageTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskDescriptionTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDetailController' => 'ManiphestController', + 'ManiphestTaskEdgeTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock', @@ -6599,14 +6618,20 @@ phutil_register_library_map(array( 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship', + 'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskMergedIntoTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskOwnerTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver', 'ManiphestTaskPHIDType' => 'PhabricatorPHIDType', + 'ManiphestTaskParentTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPoints' => 'Phobject', + 'ManiphestTaskPointsTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskPriorityHeraldAction' => 'HeraldAction', 'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskPriorityTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship', 'ManiphestTaskRelationshipSource' => 'PhabricatorObjectRelationshipSource', @@ -6618,10 +6643,15 @@ phutil_register_library_map(array( 'ManiphestTaskStatusHeraldAction' => 'HeraldAction', 'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', + 'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskTestCase' => 'PhabricatorTestCase', 'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField', - 'ManiphestTransaction' => 'PhabricatorApplicationTransaction', + 'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType', + 'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTransaction' => 'PhabricatorModularTransaction', 'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php index 936676b902..65dc0a12a2 100644 --- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php +++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php @@ -94,11 +94,12 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(pht('File Scramble Test Task')); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue('{'.$file->getMonogram().'}'); id(new ManiphestTransactionEditor()) diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php index cf1da51bca..5f5c198a84 100644 --- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php +++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php @@ -133,7 +133,7 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); @@ -169,11 +169,11 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); return $this->applyTaskTransactions($viewer, $src, $xactions); @@ -192,11 +192,11 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); return $this->applyTaskTransactions($viewer, $src, $xactions); diff --git a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php index f4ca24863b..0e11530b51 100644 --- a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php +++ b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php @@ -76,9 +76,9 @@ final class ManiphestTaskEditBulkJobType $value_map = array(); $type_map = array( 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, - 'assign' => ManiphestTransaction::TYPE_OWNER, - 'status' => ManiphestTransaction::TYPE_STATUS, - 'priority' => ManiphestTransaction::TYPE_PRIORITY, + 'assign' => ManiphestTaskOwnerTransaction::TRANSACTIONTYPE, + 'status' => ManiphestTaskStatusTransaction::TRANSACTIONTYPE, + 'priority' => ManiphestTaskPriorityTransaction::TRANSACTIONTYPE, 'add_project' => PhabricatorTransactions::TYPE_EDGE, 'remove_project' => PhabricatorTransactions::TYPE_EDGE, 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, @@ -114,13 +114,13 @@ final class ManiphestTaskEditBulkJobType case PhabricatorTransactions::TYPE_COMMENT: $current = null; break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $current = $task->getOwnerPHID(); break; - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $current = $task->getStatus(); break; - case ManiphestTransaction::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: $current = $task->getPriority(); break; case PhabricatorTransactions::TYPE_EDGE: @@ -153,7 +153,7 @@ final class ManiphestTaskEditBulkJobType } $value = head($value); break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: if (empty($value)) { continue 2; } diff --git a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php index 98e8a913a8..de014b6c66 100644 --- a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php @@ -53,7 +53,7 @@ final class ManiphestAssignEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($assign_phid); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php index 4a6a348dbb..babc853b67 100644 --- a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php @@ -23,7 +23,7 @@ final class ManiphestClaimEmailCommand $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($viewer->getPHID()); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php index 8104fd8b8d..eab01ad615 100644 --- a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php @@ -24,7 +24,7 @@ final class ManiphestCloseEmailCommand $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue(ManiphestTaskStatus::getDefaultClosedStatus()); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php index f774578240..5d7bbb1eea 100644 --- a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php @@ -71,7 +71,7 @@ final class ManiphestPriorityEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($priority); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php index dace0cb255..0387cae34e 100644 --- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php @@ -73,7 +73,7 @@ final class ManiphestStatusEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue($status); return $xactions; diff --git a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php index d2a8de7d81..5a6a8cea33 100644 --- a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php @@ -60,7 +60,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { if ($is_new) { $task->setTitle((string)$request->getValue('title')); $task->setDescription((string)$request->getValue('description')); - $changes[ManiphestTransaction::TYPE_STATUS] = + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = ManiphestTaskStatus::getDefaultStatus(); $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('+' => array($request->getUser()->getPHID())); @@ -73,12 +73,12 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { $title = $request->getValue('title'); if ($title !== null) { - $changes[ManiphestTransaction::TYPE_TITLE] = $title; + $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $title; } $desc = $request->getValue('description'); if ($desc !== null) { - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $desc; + $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $desc; } $status = $request->getValue('status'); @@ -88,7 +88,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Status set to invalid value.')); } - $changes[ManiphestTransaction::TYPE_STATUS] = $status; + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $status; } } @@ -99,7 +99,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Priority set to invalid value.')); } - $changes[ManiphestTransaction::TYPE_PRIORITY] = $priority; + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $priority; } $owner_phid = $request->getValue('ownerPHID'); @@ -108,7 +108,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { array($owner_phid), PhabricatorPeopleUserPHIDType::TYPECONST, 'ownerPHID'); - $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; + $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $owner_phid; } $ccs = $request->getValue('ccPHIDs'); diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index f16281691b..3cc420a4c6 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -93,7 +93,7 @@ final class ManiphestReportController extends ManiphestController { ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, - ManiphestTransaction::TYPE_STATUS); + ManiphestTaskStatusTransaction::TRANSACTIONTYPE); $stats = array(); $day_buckets = array(); diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php index 25920212c8..e91cc65d4a 100644 --- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php +++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php @@ -43,11 +43,11 @@ final class ManiphestSubpriorityController extends ManiphestController { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); $editor = id(new ManiphestTransactionEditor()) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 514090dda8..5384825de8 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -277,7 +277,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $can_create = (bool)$edit_config; $can_reassign = $edit_engine->hasEditAccessToTransaction( - ManiphestTransaction::TYPE_OWNER); + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE); if ($can_create) { $form_key = $edit_config->getIdentifier(); diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index ad0171592c..52491ce6e6 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -150,7 +150,7 @@ EODOCS ->setConduitDescription(pht('Create as a subtask of another task.')) ->setConduitTypeDescription(pht('PHID of the parent task.')) ->setAliases(array('parentPHID')) - ->setTransactionType(ManiphestTransaction::TYPE_PARENT) + ->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new ManiphestTaskListHTTPParameterType()) ->setSingleValue(null) ->setIsReorderable(false) @@ -179,7 +179,7 @@ EODOCS ->setDescription(pht('Name of the task.')) ->setConduitDescription(pht('Rename the task.')) ->setConduitTypeDescription(pht('New task name.')) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getTitle()), id(new PhabricatorUsersEditField()) @@ -190,7 +190,7 @@ EODOCS ->setConduitDescription(pht('Reassign the task.')) ->setConduitTypeDescription( pht('New task owner, or `null` to unassign.')) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setSingleValue($object->getOwnerPHID()) ->setCommentActionLabel(pht('Assign / Claim')) @@ -201,7 +201,7 @@ EODOCS ->setDescription(pht('Status of the task.')) ->setConduitDescription(pht('Change the task status.')) ->setConduitTypeDescription(pht('New task status constant.')) - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getStatus()) ->setOptions($status_map) @@ -213,7 +213,7 @@ EODOCS ->setDescription(pht('Priority of the task.')) ->setConduitDescription(pht('Change the priority of the task.')) ->setConduitTypeDescription(pht('New task priority constant.')) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getPriority()) ->setOptions($priority_map) @@ -230,7 +230,7 @@ EODOCS ->setDescription(pht('Point value of the task.')) ->setConduitDescription(pht('Change the task point value.')) ->setConduitTypeDescription(pht('New task point value.')) - ->setTransactionType(ManiphestTransaction::TYPE_POINTS) + ->setTransactionType(ManiphestTaskPointsTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getPoints()) ->setCommentActionLabel($action_label); @@ -242,7 +242,7 @@ EODOCS ->setDescription(pht('Task description.')) ->setConduitDescription(pht('Update the task description.')) ->setConduitTypeDescription(pht('New task description.')) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType(ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()) ->setPreviewPanel( id(new PHUIRemarkupPreviewPanel()) diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 0a4ea8469a..c9e17cd4de 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -18,18 +18,6 @@ final class ManiphestTransactionEditor $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = ManiphestTransaction::TYPE_PRIORITY; - $types[] = ManiphestTransaction::TYPE_STATUS; - $types[] = ManiphestTransaction::TYPE_TITLE; - $types[] = ManiphestTransaction::TYPE_DESCRIPTION; - $types[] = ManiphestTransaction::TYPE_OWNER; - $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; - $types[] = ManiphestTransaction::TYPE_MERGED_INTO; - $types[] = ManiphestTransaction::TYPE_MERGED_FROM; - $types[] = ManiphestTransaction::TYPE_UNBLOCK; - $types[] = ManiphestTransaction::TYPE_PARENT; - $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; - $types[] = ManiphestTransaction::TYPE_POINTS; $types[] = PhabricatorTransactions::TYPE_COLUMNS; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -37,47 +25,19 @@ final class ManiphestTransactionEditor return $types; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this task.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - if ($this->getIsNewObject()) { - return null; - } - return (int)$object->getPriority(); - case ManiphestTransaction::TYPE_STATUS: - if ($this->getIsNewObject()) { - return null; - } - return $object->getStatus(); - case ManiphestTransaction::TYPE_TITLE: - if ($this->getIsNewObject()) { - return null; - } - return $object->getTitle(); - case ManiphestTransaction::TYPE_DESCRIPTION: - if ($this->getIsNewObject()) { - return null; - } - return $object->getDescription(); - case ManiphestTransaction::TYPE_OWNER: - return nonempty($object->getOwnerPHID(), null); - case ManiphestTransaction::TYPE_SUBPRIORITY: - return $object->getSubpriority(); - case ManiphestTransaction::TYPE_COVER_IMAGE: - return $object->getCoverImageFilePHID(); - case ManiphestTransaction::TYPE_POINTS: - $points = $object->getPoints(); - if ($points !== null) { - $points = (double)$points; - } - return $points; - case ManiphestTransaction::TYPE_MERGED_INTO: - case ManiphestTransaction::TYPE_MERGED_FROM: - return null; - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return null; } @@ -88,31 +48,8 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - return (int)$xaction->getNewValue(); - case ManiphestTransaction::TYPE_OWNER: - return nonempty($xaction->getNewValue(), null); - case ManiphestTransaction::TYPE_STATUS: - case ManiphestTransaction::TYPE_TITLE: - case ManiphestTransaction::TYPE_DESCRIPTION: - case ManiphestTransaction::TYPE_SUBPRIORITY: - case ManiphestTransaction::TYPE_MERGED_INTO: - case ManiphestTransaction::TYPE_MERGED_FROM: - case ManiphestTransaction::TYPE_UNBLOCK: - case ManiphestTransaction::TYPE_COVER_IMAGE: - return $xaction->getNewValue(); - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return $xaction->getNewValue(); - case ManiphestTransaction::TYPE_POINTS: - $value = $xaction->getNewValue(); - if (!strlen($value)) { - $value = null; - } - if ($value !== null) { - $value = (double)$value; - } - return $value; } } @@ -136,72 +73,6 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - return $object->setPriority($xaction->getNewValue()); - case ManiphestTransaction::TYPE_STATUS: - return $object->setStatus($xaction->getNewValue()); - case ManiphestTransaction::TYPE_TITLE: - return $object->setTitle($xaction->getNewValue()); - case ManiphestTransaction::TYPE_DESCRIPTION: - return $object->setDescription($xaction->getNewValue()); - case ManiphestTransaction::TYPE_OWNER: - $phid = $xaction->getNewValue(); - - // Update the "ownerOrdering" column to contain the full name of the - // owner, if the task is assigned. - - $handle = null; - if ($phid) { - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($phid)) - ->executeOne(); - } - - if ($handle) { - $object->setOwnerOrdering($handle->getName()); - } else { - $object->setOwnerOrdering(null); - } - - return $object->setOwnerPHID($phid); - case ManiphestTransaction::TYPE_SUBPRIORITY: - $object->setSubpriority($xaction->getNewValue()); - return; - case ManiphestTransaction::TYPE_MERGED_INTO: - $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); - return; - case ManiphestTransaction::TYPE_COVER_IMAGE: - $file_phid = $xaction->getNewValue(); - - if ($file_phid) { - $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($file_phid)) - ->executeOne(); - } else { - $file = null; - } - - if (!$file || !$file->isTransformableImage()) { - $object->setProperty('cover.filePHID', null); - $object->setProperty('cover.thumbnailPHID', null); - return; - } - - $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; - - $xform = PhabricatorFileTransform::getTransformByKey($xform_key) - ->executeTransform($file); - - $object->setProperty('cover.filePHID', $file->getPHID()); - $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); - return; - case ManiphestTransaction::TYPE_POINTS: - $object->setPoints($xaction->getNewValue()); - return; - case ManiphestTransaction::TYPE_MERGED_FROM: - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return; } @@ -212,22 +83,11 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PARENT: - $parent_phid = $xaction->getNewValue(); - $parent_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - $task_phid = $object->getPHID(); - - id(new PhabricatorEdgeEditor()) - ->addEdge($parent_phid, $parent_type, $task_phid) - ->save(); - break; case PhabricatorTransactions::TYPE_COLUMNS: foreach ($xaction->getNewValue() as $move) { $this->applyBoardMove($object, $move); } break; - default: - break; } } @@ -240,7 +100,7 @@ final class ManiphestTransactionEditor $unblock_xaction = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $unblock_xaction = $xaction; break; } @@ -265,7 +125,8 @@ final class ManiphestTransactionEditor foreach ($blocked_tasks as $blocked_task) { $parent_xaction = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK) + ->setTransactionType( + ManiphestTaskUnblockTransaction::TRANSACTIONTYPE) ->setOldValue(array($object->getPHID() => $old)) ->setNewValue(array($object->getPHID() => $new)); @@ -398,7 +259,7 @@ final class ManiphestTransactionEditor protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { - return $this->shouldSendMail($object, $xactions); + return true; } protected function supportsSearch() { @@ -426,11 +287,11 @@ final class ManiphestTransactionEditor parent::requireCapabilities($object, $xaction); $app_capability_map = array( - ManiphestTransaction::TYPE_PRIORITY => + ManiphestTaskPriorityTransaction::TRANSACTIONTYPE => ManiphestEditPriorityCapability::CAPABILITY, - ManiphestTransaction::TYPE_STATUS => + ManiphestTaskStatusTransaction::TRANSACTIONTYPE => ManiphestEditStatusCapability::CAPABILITY, - ManiphestTransaction::TYPE_OWNER => + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE => ManiphestEditAssignCapability::CAPABILITY, PhabricatorTransactions::TYPE_EDIT_POLICY => ManiphestEditPoliciesCapability::CAPABILITY, @@ -471,7 +332,7 @@ final class ManiphestTransactionEditor $copy = parent::adjustObjectForPolicyChecks($object, $xactions); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $copy->setOwnerPHID($xaction->getNewValue()); break; default: @@ -629,157 +490,6 @@ final class ManiphestTransactionEditor return array($dst->getPriority(), $sub); } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case ManiphestTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Task title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case ManiphestTransaction::TYPE_PARENT: - $with_effect = array(); - foreach ($xactions as $xaction) { - $task_phid = $xaction->getNewValue(); - if (!$task_phid) { - continue; - } - - $with_effect[] = $xaction; - - $task = id(new ManiphestTaskQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($task_phid)) - ->executeOne(); - if (!$task) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Parent task identifier "%s" does not identify a visible '. - 'task.', - $task_phid), - $xaction); - } - } - - if ($with_effect && !$this->getIsNewObject()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can only select a parent task when creating a '. - 'transaction for the first time.'), - last($with_effect)); - } - break; - case ManiphestTransaction::TYPE_OWNER: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if (!strlen($new)) { - continue; - } - - if ($new === $old) { - continue; - } - - $assignee_list = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new)) - ->execute(); - if (!$assignee_list) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'User "%s" is not a valid user.', - $new), - $xaction); - } - } - break; - case ManiphestTransaction::TYPE_COVER_IMAGE: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if (!$new) { - continue; - } - - if ($new === $old) { - continue; - } - - $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new)) - ->executeOne(); - if (!$file) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('File "%s" is not valid.', $new), - $xaction); - continue; - } - - if (!$file->isTransformableImage()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('File "%s" is not a valid image file.', $new), - $xaction); - continue; - } - } - break; - - case ManiphestTransaction::TYPE_POINTS: - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (strlen($new) && !is_numeric($new)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Points value must be numeric or empty.'), - $xaction); - continue; - } - - if ((double)$new < 0) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Points value must be nonnegative.'), - $xaction); - continue; - } - } - break; - - } - - return $errors; - } - protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { @@ -806,7 +516,8 @@ final class ManiphestTransactionEditor $any_assign = false; foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) { + if ($xaction->getTransactionType() == + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) { $any_assign = true; break; } @@ -817,7 +528,7 @@ final class ManiphestTransactionEditor $new_status = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $new_status = $xaction->getNewValue(); break; } @@ -838,7 +549,7 @@ final class ManiphestTransactionEditor // Don't claim the task if the status is configured to not claim. if ($actor_phid && $is_claim) { $results[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($actor_phid); } } @@ -881,7 +592,7 @@ final class ManiphestTransactionEditor $this->moreValidationErrors[] = $error; } break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: // If this is a no-op update, don't expand it. $old_value = $object->getOwnerPHID(); $new_value = $xaction->getNewValue(); @@ -906,20 +617,6 @@ final class ManiphestTransactionEditor return $results; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - $phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - - switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_COVER_IMAGE: - $phids[] = $xaction->getNewValue(); - break; - } - - return $phids; - } - private function buildMoveTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php index 00f539d4a7..fffd0bac7d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php @@ -40,7 +40,7 @@ abstract class ManiphestTaskAssignHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($phid); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php index c055266a44..ff8420544d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php @@ -40,7 +40,7 @@ final class ManiphestTaskPriorityHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($priority); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php index 26e212d9c7..c1ff3bcdc7 100644 --- a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php @@ -40,7 +40,7 @@ final class ManiphestTaskStatusHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue($status); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php index 404a36af8f..2d6a6807ce 100644 --- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php +++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php @@ -22,15 +22,15 @@ final class PhabricatorManiphestTaskTestDataGenerator $template = new ManiphestTransaction(); // Accumulate Transactions $changes = array(); - $changes[ManiphestTransaction::TYPE_TITLE] = + $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $this->generateTitle(); - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = + $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); - $changes[ManiphestTransaction::TYPE_OWNER] = + $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $this->loadOwnerPHID(); - $changes[ManiphestTransaction::TYPE_STATUS] = + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $this->generateTaskStatus(); - $changes[ManiphestTransaction::TYPE_PRIORITY] = + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $this->generateTaskPriority(); $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('=' => $this->getCCPHIDs()); diff --git a/src/applications/maniphest/mail/ManiphestReplyHandler.php b/src/applications/maniphest/mail/ManiphestReplyHandler.php index bc9b2456d4..bbcfe86551 100644 --- a/src/applications/maniphest/mail/ManiphestReplyHandler.php +++ b/src/applications/maniphest/mail/ManiphestReplyHandler.php @@ -25,11 +25,12 @@ final class ManiphestReplyHandler if ($is_new) { $xactions[] = $this->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(nonempty($mail->getSubject(), pht('Untitled Task'))); $xactions[] = $this->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($body); $actor_phid = $actor->getPHID(); diff --git a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php index 928d49e87d..04f78b8523 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php @@ -19,7 +19,8 @@ abstract class ManiphestTaskRelationship protected function newMergeIntoTransactions(ManiphestTask $task) { return array( id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO) + ->setTransactionType( + ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE) ->setNewValue($task->getPHID()), ); } @@ -34,7 +35,8 @@ abstract class ManiphestTaskRelationship ->setNewValue(array('+' => $subscriber_phids)); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM) + ->setTransactionType( + ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE) ->setNewValue(mpull($tasks, 'getPHID')); return $xactions; diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index b9b8560b34..0d27ddc5da 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -1,25 +1,7 @@ getTransactionType()) { - case self::TYPE_EDGE: - case self::TYPE_UNBLOCK: + case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: return false; } return parent::shouldGenerateOldValue(); } - protected function newRemarkupChanges() { - $changes = array(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $changes[] = $this->newRemarkupChange() - ->setOldValue($this->getOldValue()) - ->setNewValue($this->getNewValue()); - break; - } - - return $changes; - } - public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); @@ -75,7 +47,7 @@ final class ManiphestTransaction $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: if ($new) { $phids[] = $new; } @@ -84,13 +56,13 @@ final class ManiphestTransaction $phids[] = $old; } break; - case self::TYPE_MERGED_INTO: + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: $phids[] = $new; break; - case self::TYPE_MERGED_FROM: + case ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE: $phids = array_merge($phids, $new); break; - case self::TYPE_EDGE: + case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE: $phids = array_mergev( array( $phids, @@ -98,7 +70,7 @@ final class ManiphestTransaction array_keys(nonempty($new, array())), )); break; - case self::TYPE_ATTACH: + case ManiphestTaskAttachTransaction::TRANSACTIONTYPE: $old = nonempty($old, array()); $new = nonempty($new, array()); $phids = array_mergev( @@ -108,12 +80,12 @@ final class ManiphestTransaction array_keys(idx($old, 'FILE', array())), )); break; - case self::TYPE_UNBLOCK: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: foreach (array_keys($new) as $phid) { $phids[] = $phid; } break; - case self::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $commit_phid = $this->getMetadataValue('commitPHID'); if ($commit_phid) { $phids[] = $commit_phid; @@ -124,275 +96,27 @@ final class ManiphestTransaction return $phids; } - public function shouldHide() { - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_EDGE: - $commit_phid = $this->getMetadataValue('commitPHID'); - $edge_type = $this->getMetadataValue('edge:type'); - - if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) { - if ($commit_phid) { - return true; - } - } - break; - case self::TYPE_DESCRIPTION: - case self::TYPE_PRIORITY: - case self::TYPE_STATUS: - if ($this->getOldValue() === null) { - return true; - } else { - return false; - } - break; - case self::TYPE_SUBPRIORITY: - case self::TYPE_PARENT: - return true; - case self::TYPE_COVER_IMAGE: - // At least for now, don't show these. - return true; - case self::TYPE_POINTS: - if (!ManiphestTaskPoints::getIsEnabled()) { - return true; - } - } - - return parent::shouldHide(); - } - - public function shouldHideForMail(array $xactions) { - switch ($this->getTransactionType()) { - case self::TYPE_POINTS: - return true; - } - - return parent::shouldHideForMail($xactions); - } - - public function shouldHideForFeed() { - switch ($this->getTransactionType()) { - case self::TYPE_UNBLOCK: - // Hide "alice created X, a task blocking Y." from feed because it - // will almost always appear adjacent to "alice created Y". - $is_new = $this->getMetadataValue('blocker.new'); - if ($is_new) { - return true; - } - break; - case self::TYPE_POINTS: - return true; - } - - return parent::shouldHideForFeed(); - } - - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - return 1.4; - case self::TYPE_STATUS: - return 1.3; - case self::TYPE_OWNER: - return 1.2; - case self::TYPE_PRIORITY: - return 1.1; - } - - return parent::getActionStrength(); - } - - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_OWNER: - if ($this->getAuthorPHID() == $new) { - return 'green'; - } else if (!$new) { - return 'black'; - } else if (!$old) { - return 'green'; - } else { - return 'green'; - } - - case self::TYPE_STATUS: - $color = ManiphestTaskStatus::getStatusColor($new); - if ($color !== null) { - return $color; - } - - if (ManiphestTaskStatus::isOpenStatus($new)) { - return 'green'; - } else { - return 'indigo'; - } - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return 'green'; - } else if ($old > $new) { - return 'grey'; - } else { - return 'yellow'; - } - - case self::TYPE_MERGED_FROM: - return 'orange'; - - case self::TYPE_MERGED_INTO: - return 'indigo'; - } - - return parent::getColor(); - } - public function getActionName() { $old = $this->getOldValue(); $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht('Created'); - } - - return pht('Retitled'); - - case self::TYPE_STATUS: - $action = ManiphestTaskStatus::getStatusActionName($new); - if ($action) { - return $action; - } - - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - if ($new_closed && !$old_closed) { - return pht('Closed'); - } else if (!$new_closed && $old_closed) { - return pht('Reopened'); - } else { - return pht('Changed Status'); - } - - case self::TYPE_DESCRIPTION: - return pht('Edited'); - - case self::TYPE_OWNER: - if ($this->getAuthorPHID() == $new) { - return pht('Claimed'); - } else if (!$new) { - return pht('Unassigned'); - } else if (!$old) { - return pht('Assigned'); - } else { - return pht('Reassigned'); - } - case PhabricatorTransactions::TYPE_COLUMNS: return pht('Changed Project Column'); - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht('Triaged'); - } else if ($old > $new) { - return pht('Lowered Priority'); - } else { - return pht('Raised Priority'); - } - - case self::TYPE_EDGE: - case self::TYPE_ATTACH: - return pht('Attached'); - - case self::TYPE_UNBLOCK: - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - if ($old_closed && !$new_closed) { - return pht('Block'); - } else if (!$old_closed && $new_closed) { - return pht('Unblock'); - } else { - return pht('Blocker'); - } - - case self::TYPE_MERGED_INTO: - case self::TYPE_MERGED_FROM: - return pht('Merged'); - } return parent::getActionName(); } public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_OWNER: - return 'fa-user'; - - case self::TYPE_TITLE: - if ($old === null) { - return 'fa-pencil'; - } - - return 'fa-pencil'; - - case self::TYPE_STATUS: - $action = ManiphestTaskStatus::getStatusIcon($new); - if ($action !== null) { - return $action; - } - - if (ManiphestTaskStatus::isClosedStatus($new)) { - return 'fa-check'; - } else { - return 'fa-pencil'; - } - - case self::TYPE_DESCRIPTION: - return 'fa-pencil'; - case PhabricatorTransactions::TYPE_COLUMNS: return 'fa-columns'; - - case self::TYPE_MERGED_INTO: - return 'fa-check'; - case self::TYPE_MERGED_FROM: - return 'fa-compress'; - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return 'fa-arrow-right'; - } else if ($old > $new) { - return 'fa-arrow-down'; - } else { - return 'fa-arrow-up'; - } - - case self::TYPE_EDGE: - case self::TYPE_ATTACH: - return 'fa-thumb-tack'; - - case self::TYPE_UNBLOCK: - return 'fa-shield'; - } return parent::getIcon(); } - public function getTitle() { $author_phid = $this->getAuthorPHID(); @@ -400,244 +124,13 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this task.', - $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_SUBTYPE: return pht( '%s changed the subtype of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderSubtypeName($old), $this->renderSubtypeName($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created this task.', - $this->renderHandleLink($author_phid)); - } - return pht( - '%s changed the title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the task description.', - $this->renderHandleLink($author_phid)); - - case self::TYPE_STATUS: - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old); - $new_name = ManiphestTaskStatus::getTaskStatusName($new); - - $commit_phid = $this->getMetadataValue('commitPHID'); - - if ($new_closed && !$old_closed) { - if ($new == ManiphestTaskStatus::getDuplicateStatus()) { - if ($commit_phid) { - return pht( - '%s closed this task as a duplicate by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed this task as a duplicate.', - $this->renderHandleLink($author_phid)); - } - } else { - if ($commit_phid) { - return pht( - '%s closed this task as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed this task as "%s".', - $this->renderHandleLink($author_phid), - $new_name); - } - } - } else if (!$new_closed && $old_closed) { - if ($commit_phid) { - return pht( - '%s reopened this task as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s reopened this task as "%s".', - $this->renderHandleLink($author_phid), - $new_name); - } - } else { - if ($commit_phid) { - return pht( - '%s changed the task status from "%s" to "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $old_name, - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s changed the task status from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } - } - - case self::TYPE_UNBLOCK: - $blocker_phid = key($new); - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); - $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - - if ($this->getMetadataValue('blocker.new')) { - return pht( - '%s created subtask %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid)); - } else if ($old_closed && !$new_closed) { - return pht( - '%s reopened subtask %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $new_name); - } else if (!$old_closed && $new_closed) { - return pht( - '%s closed subtask %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $new_name); - } else { - return pht( - '%s changed the status of subtask %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $old_name, - $new_name); - } - - case self::TYPE_OWNER: - if ($author_phid == $new) { - return pht( - '%s claimed this task.', - $this->renderHandleLink($author_phid)); - } else if (!$new) { - return pht( - '%s removed %s as the assignee of this task.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old)); - } else if (!$old) { - return pht( - '%s assigned this task to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s reassigned this task from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_PRIORITY: - $old_name = ManiphestTaskPriority::getTaskPriorityName($old); - $new_name = ManiphestTaskPriority::getTaskPriorityName($new); - - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht( - '%s triaged this task as "%s" priority.', - $this->renderHandleLink($author_phid), - $new_name); - } else if ($old > $new) { - return pht( - '%s lowered the priority of this task from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } else { - return pht( - '%s raised the priority of this task from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } - - case self::TYPE_ATTACH: - $old = nonempty($old, array()); - $new = nonempty($new, array()); - $new = array_keys(idx($new, 'FILE', array())); - $old = array_keys(idx($old, 'FILE', array())); - - $added = array_diff($new, $old); - $removed = array_diff($old, $new); - if ($added && !$removed) { - return pht( - '%s attached %s file(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($added), - $this->renderHandleList($added)); - } else if ($removed && !$added) { - return pht( - '%s detached %s file(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($removed), - $this->renderHandleList($removed)); - } else { - return pht( - '%s changed file(s), attached %s: %s; detached %s: %s.', - $this->renderHandleLink($author_phid), - phutil_count($added), - $this->renderHandleList($added), - phutil_count($removed), - $this->renderHandleList($removed)); - } - - case self::TYPE_MERGED_INTO: - return pht( - '%s closed this task as a duplicate of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); break; - - case self::TYPE_MERGED_FROM: - return pht( - '%s merged %s task(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($new), - $this->renderHandleList($new)); - break; - - case self::TYPE_POINTS: - if ($old === null) { - return pht( - '%s set the point value for this task to %s.', - $this->renderHandleLink($author_phid), - $new); - } else if ($new === null) { - return pht( - '%s removed the point value for this task.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the point value for this task from %s to %s.', - $this->renderHandleLink($author_phid), - $old, - $new); - } - } return parent::getTitle(); @@ -651,236 +144,6 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - case self::TYPE_STATUS: - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old); - $new_name = ManiphestTaskStatus::getTaskStatusName($new); - - $commit_phid = $this->getMetadataValue('commitPHID'); - - if ($new_closed && !$old_closed) { - if ($new == ManiphestTaskStatus::getDuplicateStatus()) { - if ($commit_phid) { - return pht( - '%s closed %s as a duplicate by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed %s as a duplicate.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - } else { - if ($commit_phid) { - return pht( - '%s closed %s as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } - } - } else if (!$new_closed && $old_closed) { - if ($commit_phid) { - return pht( - '%s reopened %s as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s reopened %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } - } else { - if ($commit_phid) { - return pht( - '%s changed the status of %s from "%s" to "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s changed the status of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - } - - case self::TYPE_UNBLOCK: - $blocker_phid = key($new); - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); - $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - - if ($old_closed && !$new_closed) { - return pht( - '%s reopened %s, a subtask of %s, as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else if (!$old_closed && $new_closed) { - return pht( - '%s closed %s, a subtask of %s, as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else { - return pht( - '%s changed the status of %s, a subtask of %s, '. - 'from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - - case self::TYPE_OWNER: - if ($author_phid == $new) { - return pht( - '%s claimed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if (!$new) { - return pht( - '%s placed %s up for grabs.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if (!$old) { - return pht( - '%s assigned %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s reassigned %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_PRIORITY: - $old_name = ManiphestTaskPriority::getTaskPriorityName($old); - $new_name = ManiphestTaskPriority::getTaskPriorityName($new); - - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht( - '%s triaged %s as "%s" priority.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else if ($old > $new) { - return pht( - '%s lowered the priority of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } else { - return pht( - '%s raised the priority of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - - case self::TYPE_ATTACH: - $old = nonempty($old, array()); - $new = nonempty($new, array()); - $new = array_keys(idx($new, 'FILE', array())); - $old = array_keys(idx($old, 'FILE', array())); - - $added = array_diff($new, $old); - $removed = array_diff($old, $new); - if ($added && !$removed) { - return pht( - '%s attached %d file(s) of %s: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($added), - $this->renderHandleList($added)); - } else if ($removed && !$added) { - return pht( - '%s detached %d file(s) of %s: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($removed), - $this->renderHandleList($removed)); - } else { - return pht( - '%s changed file(s) for %s, attached %d: %s; detached %d: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($added), - $this->renderHandleList($added), - count($removed), - $this->renderHandleList($removed)); - } - - case self::TYPE_MERGED_INTO: - return pht( - '%s merged task %s into %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - - case self::TYPE_MERGED_FROM: - return pht( - '%s merged %s task(s) %s into %s.', - $this->renderHandleLink($author_phid), - phutil_count($new), - $this->renderHandleList($new), - $this->renderHandleLink($object_phid)); - case PhabricatorTransactions::TYPE_SUBTYPE: return pht( '%s changed the subtype of %s from "%s" to "%s".', @@ -893,39 +156,14 @@ final class ManiphestTransaction return parent::getTitleForFeed(); } - private function renderSubtypeName($value) { - $object = $this->getObject(); - $map = $object->newEditEngineSubtypeMap(); - if (!isset($map[$value])) { - return $value; - } - - return $map[$value]->getName(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_MERGED_INTO: - case self::TYPE_STATUS: + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_OWNER; break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: @@ -941,10 +179,10 @@ final class ManiphestTransaction break; } break; - case self::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_PRIORITY; break; - case self::TYPE_UNBLOCK: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_UNBLOCK; break; case PhabricatorTransactions::TYPE_COLUMNS: @@ -961,13 +199,12 @@ final class ManiphestTransaction } public function getNoEffectDescription() { - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: return pht('The task already has the selected status.'); - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: return pht('The task already has the selected owner.'); - case self::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: return pht('The task already has the selected priority.'); } diff --git a/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php new file mode 100644 index 0000000000..e794c03bdc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php @@ -0,0 +1,91 @@ +getOldValue(); + $new = $this->getNewValue(); + + $old = nonempty($old, array()); + $new = nonempty($new, array()); + $new = array_keys(idx($new, 'FILE', array())); + $old = array_keys(idx($old, 'FILE', array())); + + $added = array_diff($new, $old); + $removed = array_diff($old, $new); + if ($added && !$removed) { + return pht( + '%s attached %s file(s): %s.', + $this->renderAuthor(), + phutil_count($added), + $this->renderHandleList($added)); + } else if ($removed && !$added) { + return pht( + '%s detached %s file(s): %s.', + $this->renderAuthor(), + phutil_count($removed), + $this->renderHandleList($removed)); + } else { + return pht( + '%s changed file(s), attached %s: %s; detached %s: %s.', + $this->renderAuthor(), + phutil_count($added), + $this->renderHandleList($added), + phutil_count($removed), + $this->renderHandleList($removed)); + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old = nonempty($old, array()); + $new = nonempty($new, array()); + $new = array_keys(idx($new, 'FILE', array())); + $old = array_keys(idx($old, 'FILE', array())); + + $added = array_diff($new, $old); + $removed = array_diff($old, $new); + if ($added && !$removed) { + return pht( + '%s attached %d file(s) of %s: %s', + $this->renderAuthor(), + $this->renderObject(), + count($added), + $this->renderHandleList($added)); + } else if ($removed && !$added) { + return pht( + '%s detached %d file(s) of %s: %s', + $this->renderAuthor(), + $this->renderObject(), + count($removed), + $this->renderHandleList($removed)); + } else { + return pht( + '%s changed file(s) for %s, attached %d: %s; detached %d: %s', + $this->renderAuthor(), + $this->renderObject(), + count($added), + $this->renderHandleList($added), + count($removed), + $this->renderHandleList($removed)); + } + } + + public function getIcon() { + return 'fa-thumb-tack'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php new file mode 100644 index 0000000000..eb29c711f8 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php @@ -0,0 +1,106 @@ +getCoverImageFilePHID(); + } + + public function applyInternalEffects($object, $value) { + $file_phid = $value; + + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + } else { + $file = null; + } + + if (!$file || !$file->isTransformableImage()) { + $object->setProperty('cover.filePHID', null); + $object->setProperty('cover.thumbnailPHID', null); + return; + } + + $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; + $xform = PhabricatorFileTransform::getTransformByKey($xform_key) + ->executeTransform($file); + + $object->setProperty('cover.filePHID', $file->getPHID()); + $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the cover image to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } + + return pht( + '%s updated the cover image to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s added a cover image to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s updated the cover image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + + } + + return $errors; + } + + public function getIcon() { + return 'fa-image'; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php new file mode 100644 index 0000000000..009327ed9b --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php @@ -0,0 +1,61 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getActionName() { + return pht('Edited'); + } + + public function getTitle() { + return pht( + '%s updated the task description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the task description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO TASK DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php new file mode 100644 index 0000000000..18f9a1da1f --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php @@ -0,0 +1,31 @@ +getMetadataValue('commitPHID'); + $edge_type = $this->getMetadataValue('edge:type'); + + if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) { + if ($commit_phid) { + return true; + } + } + } + + public function getActionName() { + return pht('Attached'); + } + + public function getIcon() { + return 'fa-thumb-tack'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php new file mode 100644 index 0000000000..a4a0440604 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php @@ -0,0 +1,45 @@ +getNewValue(); + + return pht( + '%s merged %s task(s): %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new)); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s merged %s task(s) %s into %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-compress'; + } + + public function getColor() { + return 'orange'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php new file mode 100644 index 0000000000..cd0cad6a39 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php @@ -0,0 +1,47 @@ +setStatus(ManiphestTaskStatus::getDuplicateStatus()); + } + + public function getActionName() { + return pht('Merged'); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s closed this task as a duplicate of %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s merged task %s into %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } + + public function getIcon() { + return 'fa-check'; + } + + public function getColor() { + return 'indigo'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php new file mode 100644 index 0000000000..d510fe8fbc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php @@ -0,0 +1,158 @@ +getOwnerPHID(), null); + } + + public function applyInternalEffects($object, $value) { + // Update the "ownerOrdering" column to contain the full name of the + // owner, if the task is assigned. + + $handle = null; + if ($value) { + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + } + + if ($handle) { + $object->setOwnerOrdering($handle->getName()); + } else { + $object->setOwnerOrdering(null); + } + + $object->setOwnerPHID($value); + } + + public function getActionStrength() { + return 1.2; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht('Claimed'); + } else if (!$new) { + return pht('Unassigned'); + } else if (!$old) { + return pht('Assigned'); + } else { + return pht('Reassigned'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht( + '%s claimed this task.', + $this->renderAuthor()); + } else if (!$new) { + return pht( + '%s removed %s as the assignee of this task.', + $this->renderAuthor(), + $this->renderHandle($old)); + } else if (!$old) { + return pht( + '%s assigned this task to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } else { + return pht( + '%s reassigned this task from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht( + '%s claimed %s.', + $this->renderAuthor(), + $this->renderObject()); + } else if (!$new) { + return pht( + '%s placed %s up for grabs.', + $this->renderAuthor(), + $this->renderObject()); + } else if (!$old) { + return pht( + '%s assigned %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } else { + return pht( + '%s reassigned %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if (!strlen($new)) { + continue; + } + + if ($new === $old) { + continue; + } + + $assignee_list = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new)) + ->execute(); + + if (!$assignee_list) { + $errors[] = $this->newInvalidError( + pht('User "%s" is not a valid user.', + $new)); + } + } + return $errors; + } + + public function getIcon() { + return 'fa-user'; + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return 'green'; + } else if (!$new) { + return 'black'; + } else if (!$old) { + return 'green'; + } else { + return 'green'; + } + + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php new file mode 100644 index 0000000000..d703adc8b5 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php @@ -0,0 +1,60 @@ +getPHID(); + + id(new PhabricatorEdgeEditor()) + ->addEdge($parent_phid, $parent_type, $task_phid) + ->save(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $with_effect = array(); + foreach ($xactions as $xaction) { + $task_phid = $xaction->getNewValue(); + if (!$task_phid) { + continue; + } + + $with_effect[] = $xaction; + + $task = id(new ManiphestTaskQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($task_phid)) + ->executeOne(); + if (!$task) { + $errors[] = $this->newInvalidError( + pht( + 'Parent task identifier "%s" does not identify a visible '. + 'task.', + $task_phid)); + } + } + + if ($with_effect && !$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'You can only select a parent task when creating a '. + 'transaction for the first time.')); + } + + return $errors; + } +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php new file mode 100644 index 0000000000..8a5276ae7e --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -0,0 +1,76 @@ +getPoints(); + } + + public function applyInternalEffects($object, $value) { + if (!strlen($value)) { + $value = null; + } + if ($value !== null) { + $value = (double)$value; + } + $object->setPoints($value); + } + + public function shouldHideForFeed() { + return true; + } + + public function shouldHide() { + if (!ManiphestTaskPoints::getIsEnabled()) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the point value for this task to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if ($new === null) { + return pht( + '%s removed the point value for this task.', + $this->renderAuthor()); + } else { + return pht( + '%s changed the point value for this task from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (strlen($new) && !is_numeric($new)) { + $errors[] = $this->newInvalidError( + pht('Points value must be numeric or empty.')); + continue; + } + + if ((double)$new < 0) { + $errors[] = $this->newInvalidError( + pht('Points value must be nonnegative.')); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php new file mode 100644 index 0000000000..f7d58911ba --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -0,0 +1,119 @@ +isNewObject()) { + return null; + } + return $object->getPriority(); + } + + public function applyInternalEffects($object, $value) { + $object->setPriority($value); + } + + public function getActionStrength() { + return 1.1; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht('Triaged'); + } else if ($old > $new) { + return pht('Lowered Priority'); + } else { + return pht('Raised Priority'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht( + '%s triaged this task as %s priority.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } else if ($old > $new) { + return pht( + '%s lowered the priority of this task from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } else { + return pht( + '%s raised the priority of this task from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht( + '%s triaged %s as %s priority.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name)); + } else if ($old > $new) { + return pht( + '%s lowered the priority of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } else { + return pht( + '%s raised the priority of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return 'fa-arrow-right'; + } else if ($old > $new) { + return 'fa-arrow-down'; + } else { + return 'fa-arrow-up'; + } + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return 'green'; + } else if ($old > $new) { + return 'grey'; + } else { + return 'yellow'; + } + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php new file mode 100644 index 0000000000..5e1cd44611 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -0,0 +1,232 @@ +isNewObject()) { + return null; + } + return $object->getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function shouldHide() { + if ($this->getOldValue() === null) { + return true; + } else { + return false; + } + } + + public function getActionStrength() { + return 1.3; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $action = ManiphestTaskStatus::getStatusActionName($new); + if ($action) { + return $action; + } + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + if ($new_closed && !$old_closed) { + return pht('Closed'); + } else if (!$new_closed && $old_closed) { + return pht('Reopened'); + } else { + return pht('Changed Status'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old); + $new_name = ManiphestTaskStatus::getTaskStatusName($new); + + $commit_phid = $this->getMetadataValue('commitPHID'); + + if ($new_closed && !$old_closed) { + if ($new == ManiphestTaskStatus::getDuplicateStatus()) { + if ($commit_phid) { + return pht( + '%s closed this task as a duplicate by committing %s.', + $this->renderAuthor(), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed this task as a duplicate.', + $this->renderAuthor()); + } + } else { + if ($commit_phid) { + return pht( + '%s closed this task as %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed this task as %s.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } + } + } else if (!$new_closed && $old_closed) { + if ($commit_phid) { + return pht( + '%s reopened this task as %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s reopened this task as %s.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } + } else { + if ($commit_phid) { + return pht( + '%s changed the task status from %s to %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s changed the task status from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old); + $new_name = ManiphestTaskStatus::getTaskStatusName($new); + + $commit_phid = $this->getMetadataValue('commitPHID'); + + if ($new_closed && !$old_closed) { + if ($new == ManiphestTaskStatus::getDuplicateStatus()) { + if ($commit_phid) { + return pht( + '%s closed %s as a duplicate by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed %s as a duplicate.', + $this->renderAuthor(), + $this->renderObject()); + } + } else { + if ($commit_phid) { + return pht( + '%s closed %s as %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed %s as %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name)); + } + } + } else if (!$new_closed && $old_closed) { + if ($commit_phid) { + return pht( + '%s reopened %s as %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s reopened %s as "%s".', + $this->renderAuthor(), + $this->renderObject(), + $new_name); + } + } else { + if ($commit_phid) { + return pht( + '%s changed the status of %s from %s to %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s changed the status of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $action = ManiphestTaskStatus::getStatusIcon($new); + if ($action !== null) { + return $action; + } + + if (ManiphestTaskStatus::isClosedStatus($new)) { + return 'fa-check'; + } else { + return 'fa-pencil'; + } + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $color = ManiphestTaskStatus::getStatusColor($new); + if ($color !== null) { + return $color; + } + + if (ManiphestTaskStatus::isOpenStatus($new)) { + return 'green'; + } else { + return 'indigo'; + } + + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php new file mode 100644 index 0000000000..49d227b7f1 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php @@ -0,0 +1,21 @@ +getSubpriority(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubpriority($value); + } + + public function shouldHide() { + return true; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php new file mode 100644 index 0000000000..dcaf959a57 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -0,0 +1,74 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getActionStrength() { + return 1.4; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + if ($old === null) { + return pht('Created'); + } + + return pht('Retitled'); + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created this task.', + $this->renderAuthor()); + } + + return pht( + '%s changed the title from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s changed %s title from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Tasks must have a title.')); + } + + return $errors; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php new file mode 100644 index 0000000000..699ef11631 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php @@ -0,0 +1,16 @@ +getObject(); + $map = $object->newEditEngineSubtypeMap(); + if (!isset($map[$value])) { + return $value; + } + + return $map[$value]->getName(); + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php new file mode 100644 index 0000000000..905554a3a3 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php @@ -0,0 +1,125 @@ +getMetadataValue('blocker.new'); + if ($is_new) { + return true; + } + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + if ($old_closed && !$new_closed) { + return pht('Block'); + } else if (!$old_closed && $new_closed) { + return pht('Unblock'); + } else { + return pht('Blocker'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $blocker_phid = key($new); + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); + $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); + + if ($this->getMetadataValue('blocker.new')) { + return pht( + '%s created subtask %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid)); + } else if ($old_closed && !$new_closed) { + return pht( + '%s reopened subtask %s as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($new_name)); + } else if (!$old_closed && $new_closed) { + return pht( + '%s closed subtask %s as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($new_name)); + } else { + return pht( + '%s changed the status of subtask %s from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $blocker_phid = key($new); + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); + $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); + + if ($old_closed && !$new_closed) { + return pht( + '%s reopened %s, a subtask of %s, as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($new_name)); + } else if (!$old_closed && $new_closed) { + return pht( + '%s closed %s, a subtask of %s, as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($new_name)); + } else { + return pht( + '%s changed the status of %s, a subtask of %s, '. + 'from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getIcon() { + return 'fa-shield'; + } + + +} diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index 3fef8b3da7..b7b90690b7 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -390,12 +390,14 @@ final class NuanceGitHubEventItemType $state = $xobj->getProperty('task.state'); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType( + ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title) ->setDateCreated($created); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($description) ->setDateCreated($created); diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 71161b031e..3943a7c0c3 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -1337,7 +1337,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($name); if ($projects) { diff --git a/src/applications/project/controller/PhabricatorProjectCoverController.php b/src/applications/project/controller/PhabricatorProjectCoverController.php index 22f787e56b..98f6c1c995 100644 --- a/src/applications/project/controller/PhabricatorProjectCoverController.php +++ b/src/applications/project/controller/PhabricatorProjectCoverController.php @@ -36,7 +36,7 @@ final class PhabricatorProjectCoverController $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE) + ->setTransactionType(ManiphestTaskCoverImageTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); $editor = id(new ManiphestTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index c92c843204..68915664cb 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -153,10 +153,11 @@ final class PhabricatorProjectMoveController $xactions = array(); if ($pri !== null) { $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType( + ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 767cfe5290..f7e3a735a4 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -367,7 +367,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker if ($status) { if ($task->getStatus() != $status) { $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType( + ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setMetadataValue('commitPHID', $commit->getPHID()) ->setNewValue($status); From 904480dc3cfbd78615a06ed87202683d21f5cb33 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 11:16:34 -0700 Subject: [PATCH 063/543] Generate newValue for ManiphestTaskPointTransaction Summary: I think this is the correct fix, sets a consistent value for transactions, old and new, for Maniphest point values. Test Plan: Edit title, see no point feed story, set points, see point story, set points to same value, see no story, remove points, see remove point story. {F4958233} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17885 --- .../ManiphestTaskPointsTransaction.php | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index 8a5276ae7e..7f324d1bca 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -6,16 +6,14 @@ final class ManiphestTaskPointsTransaction const TRANSACTIONTYPE = 'points'; public function generateOldValue($object) { - return $object->getPoints(); + return $this->getValueForPoints($object->getPoints()); + } + + public function generateNewValue($object, $value) { + return $this->getValueForPoints($value); } public function applyInternalEffects($object, $value) { - if (!strlen($value)) { - $value = null; - } - if ($value !== null) { - $value = (double)$value; - } $object->setPoints($value); } @@ -73,4 +71,14 @@ final class ManiphestTaskPointsTransaction return $errors; } + private function getValueForPoints($value) { + if (!strlen($value)) { + $value = null; + } + if ($value !== null) { + $value = (double)$value; + } + return $value; + } + } From 78544334cd698af62650b7d3c6ee2e40330351b7 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Mon, 15 May 2017 13:22:05 -0700 Subject: [PATCH 064/543] Fix breakage of Pholio Summary: Removal of `PholioMockEditor::applyCustomInternalTransaction()` in D17868 broke creation of new mocks in Pholio. Puts the empty method back until we finish migrating Pholio to modular transactions. Test Plan: Created some mocks, observed lack of unhandled exception. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17889 --- src/applications/pholio/editor/PholioMockEditor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 6f5bf1df2b..322eaa7267 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -184,6 +184,12 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return null; } + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + return; + } + protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { From f600bc0811fa936a13cd14feaf9a20d9d01527a4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 15:44:50 -0700 Subject: [PATCH 065/543] Clean up watchers and members project page Summary: Various little fixes, mostly moves information from the "Details" section either into the curtain or into the specific watchers or members list based on user viewership. I think this page is both cleaner and more informative. Test Plan: Lock, Unlock, Watch, Join, various projects with multiple users. {F4959101} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17891 --- resources/celerity/map.php | 6 +- ...habricatorProjectMembersViewController.php | 148 +++++------------- .../view/PhabricatorProjectMemberListView.php | 33 +++- .../view/PhabricatorProjectUserListView.php | 8 + .../PhabricatorProjectWatcherListView.php | 15 +- src/view/form/PHUIInfoView.php | 12 +- webroot/rsrc/css/phui/phui-info-view.css | 8 + 7 files changed, 115 insertions(+), 115 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 930beaa0eb..8430bf43b9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '24ffbe93', + 'core.pkg.css' => 'd1bf3405', 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', @@ -160,7 +160,7 @@ return array( 'rsrc/css/phui/phui-icon.css' => '12b387a1', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => 'ec92802a', + 'rsrc/css/phui/phui-info-view.css' => '6e217679', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', 'rsrc/css/phui/phui-list.css' => '12eb8ce6', @@ -867,7 +867,7 @@ return array( 'phui-icon-view-css' => '12b387a1', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => 'ec92802a', + 'phui-info-view-css' => '6e217679', 'phui-inline-comment-view-css' => 'be663c95', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index 959927c9f3..cd80d0c724 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -20,15 +20,8 @@ final class PhabricatorProjectMembersViewController $this->setProject($project); $title = pht('Members and Watchers'); - - $properties = $this->buildProperties($project); $curtain = $this->buildCurtainView($project); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); - $member_list = id(new PhabricatorProjectMemberListView()) ->setUser($viewer) ->setProject($project) @@ -52,16 +45,18 @@ final class PhabricatorProjectMembersViewController ->setHeader($title) ->setHeaderIcon('fa-group'); + require_celerity_resource('project-view-css'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn(array( - $object_box, $member_list, $watcher_list, )); - return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) @@ -69,105 +64,6 @@ final class PhabricatorProjectMembersViewController ->appendChild($view); } - private function buildProperties(PhabricatorProject $project) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($project); - - if ($project->isMilestone()) { - $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey(); - $icon = PhabricatorProjectIconSet::getIconIcon($icon_key); - $target = PhabricatorProjectIconSet::getIconName($icon_key); - $note = pht( - 'Members of the parent project are members of this project.'); - $show_join = false; - } else if ($project->getHasSubprojects()) { - $icon = 'fa-sitemap'; - $target = pht('Parent Project'); - $note = pht( - 'Members of all subprojects are members of this project.'); - $show_join = false; - } else if ($project->getIsMembershipLocked()) { - $icon = 'fa-lock'; - $target = pht('Locked Project'); - $note = pht( - 'Users with access may join this project, but may not leave.'); - $show_join = true; - } else { - $icon = 'fa-briefcase'; - $target = pht('Normal Project'); - $note = pht('Users with access may join and leave this project.'); - $show_join = true; - } - - $item = id(new PHUIStatusItemView()) - ->setIcon($icon) - ->setTarget(phutil_tag('strong', array(), $target)) - ->setNote($note); - - $status = id(new PHUIStatusListView()) - ->addItem($item); - - $view->addProperty(pht('Membership'), $status); - - if ($show_join) { - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $project); - - $view->addProperty( - pht('Joinable By'), - $descriptions[PhabricatorPolicyCapability::CAN_JOIN]); - } - - $viewer_phid = $viewer->getPHID(); - - if ($project->isUserWatcher($viewer_phid)) { - $watch_item = id(new PHUIStatusItemView()) - ->setIcon('fa-eye green') - ->setTarget(phutil_tag('strong', array(), pht('Watching'))) - ->setNote( - pht( - 'You will receive mail about changes made to any related '. - 'object.')); - - $watch_status = id(new PHUIStatusListView()) - ->addItem($watch_item); - - $view->addProperty(pht('Watching'), $watch_status); - } - - if ($project->isUserMember($viewer_phid)) { - $is_silenced = $this->isProjectSilenced($project); - if ($is_silenced) { - $mail_icon = 'fa-envelope-o grey'; - $mail_target = pht('Disabled'); - $mail_note = pht( - 'When mail is sent to project members, you will not receive '. - 'a copy.'); - } else { - $mail_icon = 'fa-envelope-o green'; - $mail_target = pht('Enabled'); - $mail_note = pht( - 'You will receive mail that is sent to project members.'); - } - - $mail_item = id(new PHUIStatusItemView()) - ->setIcon($mail_icon) - ->setTarget(phutil_tag('strong', array(), $mail_target)) - ->setNote($mail_note); - - $mail_status = id(new PHUIStatusListView()) - ->addItem($mail_item); - - $view->addProperty(pht('Mail to Members'), $mail_status); - } - - return $view; - } - private function buildCurtainView(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); @@ -272,6 +168,42 @@ final class PhabricatorProjectMembersViewController ->setDisabled(!$can_lock) ->setWorkflow(true)); + if ($project->isMilestone()) { + $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey(); + $header = PhabricatorProjectIconSet::getIconName($icon_key); + $note = pht( + 'Members of the parent project are members of this project.'); + $show_join = false; + } else if ($project->getHasSubprojects()) { + $header = pht('Parent Project'); + $note = pht( + 'Members of all subprojects are members of this project.'); + $show_join = false; + } else if ($project->getIsMembershipLocked()) { + $header = pht('Locked Project'); + $note = pht( + 'Users with access may join this project, but may not leave.'); + $show_join = true; + } else { + $header = pht('Normal Project'); + $note = pht('Users with access may join and leave this project.'); + $show_join = true; + } + + $curtain->newPanel() + ->setHeaderText($header) + ->appendChild($note); + + if ($show_join) { + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $viewer, + $project); + + $curtain->newPanel() + ->setHeaderText(pht('Joinable By')) + ->appendChild($descriptions[PhabricatorPolicyCapability::CAN_JOIN]); + } + return $curtain; } diff --git a/src/applications/project/view/PhabricatorProjectMemberListView.php b/src/applications/project/view/PhabricatorProjectMemberListView.php index 12b7cc7a76..cf8a3a1465 100644 --- a/src/applications/project/view/PhabricatorProjectMemberListView.php +++ b/src/applications/project/view/PhabricatorProjectMemberListView.php @@ -4,7 +4,7 @@ final class PhabricatorProjectMemberListView extends PhabricatorProjectUserListView { protected function canEditList() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); if (!$project->supportsEditMembers()) { @@ -31,4 +31,35 @@ final class PhabricatorProjectMemberListView return pht('Members'); } + protected function getMembershipNote() { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + $project = $this->getProject(); + + if (!$viewer_phid) { + return null; + } + + $note = null; + if ($project->isUserMember($viewer_phid)) { + $edge_type = PhabricatorProjectSilencedEdgeType::EDGECONST; + $silenced = PhabricatorEdgeQuery::loadDestinationPHIDs( + $project->getPHID(), + $edge_type); + $silenced = array_fuse($silenced); + $is_silenced = isset($silenced[$viewer_phid]); + if ($is_silenced) { + $note = pht( + 'You have disabled mail. When mail is sent to project members, '. + 'you will not receive a copy.'); + } else { + $note = pht( + 'You are a member and you will receive mail that is sent to all '. + 'project members.'); + } + } + + return $note; + } + } diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index d590cbb559..0c0e2c1d2b 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -43,6 +43,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { abstract protected function getNoDataString(); abstract protected function getRemoveURI($phid); abstract protected function getHeaderText(); + abstract protected function getMembershipNote(); public function render() { $viewer = $this->getViewer(); @@ -135,6 +136,13 @@ abstract class PhabricatorProjectUserListView extends AphrontView { ->setHeader($header) ->setObjectList($list); + if ($this->getMembershipNote()) { + $info = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_PLAIN) + ->appendChild($this->getMembershipNote()); + $box->setInfoView($info); + } + if ($this->background) { $box->setBackground($this->background); } diff --git a/src/applications/project/view/PhabricatorProjectWatcherListView.php b/src/applications/project/view/PhabricatorProjectWatcherListView.php index 7aa9638afc..2d16cc7ec2 100644 --- a/src/applications/project/view/PhabricatorProjectWatcherListView.php +++ b/src/applications/project/view/PhabricatorProjectWatcherListView.php @@ -4,7 +4,7 @@ final class PhabricatorProjectWatcherListView extends PhabricatorProjectUserListView { protected function canEditList() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); return PhabricatorPolicyFilter::hasCapability( @@ -27,4 +27,17 @@ final class PhabricatorProjectWatcherListView return pht('Watchers'); } + protected function getMembershipNote() { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + $project = $this->getProject(); + + $note = null; + if ($project->isUserWatcher($viewer_phid)) { + $note = pht('You are watching this project and will receive mail about '. + 'changes made to any related object.'); + } + return $note; + } + } diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index ca01ff294b..8ba74056b8 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -7,6 +7,7 @@ final class PHUIInfoView extends AphrontTagView { const SEVERITY_NOTICE = 'notice'; const SEVERITY_NODATA = 'nodata'; const SEVERITY_SUCCESS = 'success'; + const SEVERITY_PLAIN = 'plain'; private $title; private $errors; @@ -52,8 +53,14 @@ final class PHUIInfoView extends AphrontTagView { return $this; } - public function setIcon(PHUIIconView $icon) { - $this->icon = $icon; + public function setIcon($icon) { + if ($icon instanceof PHUIIconView) { + $this->icon = $icon; + } else { + $icon = id(new PHUIIconView()) + ->setIcon($icon); + } + return $this; } @@ -72,6 +79,7 @@ final class PHUIInfoView extends AphrontTagView { case self::SEVERITY_NOTICE: $icon = 'fa-info-circle'; break; + case self::SEVERITY_PLAIN: case self::SEVERITY_NODATA: return null; break; diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 1642822b77..105adf9ed4 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -11,6 +11,14 @@ border-radius: 3px; } +div.phui-info-view.phui-info-severity-plain { + background: {$lightgreybackground}; + color: {$bluetext}; + border: none; + padding: 8px 12px; + margin-bottom: 4px !important; +} + .phui-info-view.phui-info-view-flush { margin: 0 0 20px 0; } From 545d6347dd8f88db0652e0ea47b2ce9740d6a152 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Mon, 15 May 2017 16:00:50 -0700 Subject: [PATCH 066/543] Convert remaining Pholio image transactions to modular transaction framework Summary: Also cleans up now-dead code relating to old transactions Test Plan: Created lots of mocks, replaced their images, added/removed images, changed the sequence, verified expected DB xaction rows, Mock updates, and correct rendering of timeline. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17892 --- src/__phutil_library_map__.php | 6 + .../controller/PholioMockEditController.php | 8 +- .../pholio/editor/PholioMockEditor.php | 181 +----------------- .../pholio/storage/PholioTransaction.php | 131 +------------ .../xaction/PholioImageFileTransaction.php | 120 ++++++++++++ .../xaction/PholioImageReplaceTransaction.php | 68 +++++++ .../PholioImageSequenceTransaction.php | 60 ++++++ 7 files changed, 267 insertions(+), 307 deletions(-) create mode 100644 src/applications/pholio/xaction/PholioImageFileTransaction.php create mode 100644 src/applications/pholio/xaction/PholioImageReplaceTransaction.php create mode 100644 src/applications/pholio/xaction/PholioImageSequenceTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 024a8c93b2..f08a02579e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4376,9 +4376,12 @@ phutil_register_library_map(array( 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 'PholioImageDescriptionTransaction' => 'applications/pholio/xaction/PholioImageDescriptionTransaction.php', + 'PholioImageFileTransaction' => 'applications/pholio/xaction/PholioImageFileTransaction.php', 'PholioImageNameTransaction' => 'applications/pholio/xaction/PholioImageNameTransaction.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', + 'PholioImageReplaceTransaction' => 'applications/pholio/xaction/PholioImageReplaceTransaction.php', + 'PholioImageSequenceTransaction' => 'applications/pholio/xaction/PholioImageSequenceTransaction.php', 'PholioImageTransactionType' => 'applications/pholio/xaction/PholioImageTransactionType.php', 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', @@ -9938,9 +9941,12 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PholioImageDescriptionTransaction' => 'PholioImageTransactionType', + 'PholioImageFileTransaction' => 'PholioImageTransactionType', 'PholioImageNameTransaction' => 'PholioImageTransactionType', 'PholioImagePHIDType' => 'PhabricatorPHIDType', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PholioImageReplaceTransaction' => 'PholioImageTransactionType', + 'PholioImageSequenceTransaction' => 'PholioImageTransactionType', 'PholioImageTransactionType' => 'PholioTransactionType', 'PholioImageUploadController' => 'PholioController', 'PholioInlineController' => 'PholioController', diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index e97af5da27..2d477c70aa 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -151,7 +151,7 @@ final class PholioMockEditController extends PholioController { ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_REPLACE) + PholioImageReplaceTransaction::TRANSACTIONTYPE) ->setNewValue($replace_image); $posted_mock_images[] = $replace_image; } else if (!$existing_image) { // this is an add @@ -162,7 +162,7 @@ final class PholioMockEditController extends PholioController { ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) + ->setTransactionType(PholioImageFileTransaction::TRANSACTIONTYPE) ->setNewValue( array('+' => array($add_image))); $posted_mock_images[] = $add_image; @@ -178,7 +178,7 @@ final class PholioMockEditController extends PholioController { array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_SEQUENCE) + PholioImageSequenceTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $sequence)); @@ -189,7 +189,7 @@ final class PholioMockEditController extends PholioController { if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) { // this is an outright delete $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) + ->setTransactionType(PholioImageFileTransaction::TRANSACTIONTYPE) ->setNewValue( array('-' => array($mock_image))); } diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 322eaa7267..ef315512ec 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -17,7 +17,8 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->newImages = $new_images; return $this; } - private function getNewImages() { + + public function getNewImages() { return $this->newImages; } @@ -31,89 +32,9 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PholioTransaction::TYPE_INLINE; - $types[] = PholioTransaction::TYPE_IMAGE_FILE; - $types[] = PholioTransaction::TYPE_IMAGE_REPLACE; - $types[] = PholioTransaction::TYPE_IMAGE_SEQUENCE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $images = $object->getImages(); - return mpull($images, 'getPHID'); - case PholioTransaction::TYPE_IMAGE_REPLACE: - $raw = $xaction->getNewValue(); - return $raw->getReplacesImagePHID(); - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $sequence = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $sequence = $image->getSequence(); - $phid = $image->getPHID(); - } - return array($phid => $sequence); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - return $xaction->getNewValue(); - case PholioTransaction::TYPE_IMAGE_REPLACE: - $raw = $xaction->getNewValue(); - return $raw->getPHID(); - case PholioTransaction::TYPE_IMAGE_FILE: - $raw_new_value = $xaction->getNewValue(); - $new_value = array(); - foreach ($raw_new_value as $key => $images) { - $new_value[$key] = mpull($images, 'getPHID'); - } - $xaction->setNewValue($new_value); - return $this->getPHIDTransactionNewValue($xaction); - } - } - - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $images = $this->getNewImages(); - $images = mpull($images, null, 'getPHID'); - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $file_phids = array(); - foreach ($xaction->getNewValue() as $image_phid) { - $image = idx($images, $image_phid); - if (!$image) { - continue; - } - $file_phids[] = $image->getFilePHID(); - } - return $file_phids; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $image_phid = $xaction->getNewValue(); - $image = idx($images, $image_phid); - - if ($image) { - return array($image->getFilePHID()); - } - break; - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - - protected function transactionHasEffect( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -132,8 +53,8 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - case PholioTransaction::TYPE_IMAGE_REPLACE: + case PholioImageFileTransaction::TRANSACTIONTYPE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: return true; break; } @@ -148,7 +69,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $new_images = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: + case PholioImageFileTransaction::TRANSACTIONTYPE: $new_value = $xaction->getNewValue(); foreach ($new_value as $key => $txn_images) { if ($key != '+') { @@ -160,7 +81,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } } break; - case PholioTransaction::TYPE_IMAGE_REPLACE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: $image = $xaction->getNewValue(); $image->save(); $new_images[] = $image; @@ -170,67 +91,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->setNewImages($new_images); } - private function getImageForXaction( - PholioMock $mock, - PhabricatorApplicationTransaction $xaction) { - $raw_new_value = $xaction->getNewValue(); - $image_phid = key($raw_new_value); - $images = $mock->getImages(); - foreach ($images as $image) { - if ($image->getPHID() == $image_phid) { - return $image; - } - } - return null; - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - - $obsolete_map = array_diff_key($old_map, $new_map); - $images = $object->getImages(); - foreach ($images as $seq => $image) { - if (isset($obsolete_map[$image->getPHID()])) { - $image->setIsObsolete(1); - $image->save(); - unset($images[$seq]); - } - } - $object->attachImages($images); - break; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $old = $xaction->getOldValue(); - $images = $object->getImages(); - foreach ($images as $seq => $image) { - if ($image->getPHID() == $old) { - $image->setIsObsolete(1); - $image->save(); - unset($images[$seq]); - } - } - $object->attachImages($images); - break; - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $image = $this->getImageForXaction($object, $xaction); - $value = (int)head($xaction->getNewValue()); - $image->setSequence($value); - $image->save(); - break; - } - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -244,35 +104,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return $xactions; } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PholioTransaction::TYPE_IMAGE_REPLACE: - $u_img = $u->getNewValue(); - $v_img = $v->getNewValue(); - if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { - return $v; - } - break; - case PholioTransaction::TYPE_IMAGE_FILE: - return $this->mergePHIDOrEdgeTransactions($u, $v); - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $raw_new_value_u = $u->getNewValue(); - $raw_new_value_v = $v->getNewValue(); - $phid_u = key($raw_new_value_u); - $phid_v = key($raw_new_value_v); - if ($phid_u == $phid_v) { - return $v; - } - break; - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 84a833b29f..346f0e4561 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -2,11 +2,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { - // Edits to images within the mock - const TYPE_IMAGE_FILE = 'image-file'; - const TYPE_IMAGE_REPLACE = 'image-replace'; - const TYPE_IMAGE_SEQUENCE = 'image-sequence'; - // Your witty commentary at the mock : image : x,y level const TYPE_INLINE = 'inline'; @@ -35,56 +30,10 @@ final class PholioTransaction extends PhabricatorModularTransaction { return new PholioTransactionView(); } - public function getRequiredHandlePHIDs() { - $phids = parent::getRequiredHandlePHIDs(); - $phids[] = $this->getObjectPHID(); - - $new = $this->getNewValue(); - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_FILE: - $phids = array_merge($phids, $new, $old); - break; - case self::TYPE_IMAGE_REPLACE: - $phids[] = $new; - $phids[] = $old; - break; - case PholioImageDescriptionTransaction::TRANSACTIONTYPE: - case PholioImageNameTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE_SEQUENCE: - $phids[] = key($new); - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - // this is boring / silly to surface; changing sequence is NBD - case self::TYPE_IMAGE_SEQUENCE: - return true; - } - - return parent::shouldHide(); - } - public function getIcon() { - - $new = $this->getNewValue(); - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { case self::TYPE_INLINE: return 'fa-comment'; - case self::TYPE_IMAGE_SEQUENCE: - return 'fa-pencil'; - case self::TYPE_IMAGE_FILE: - case self::TYPE_IMAGE_REPLACE: - return 'fa-picture-o'; } return parent::getIcon(); @@ -104,9 +53,9 @@ final class PholioTransaction extends PhabricatorModularTransaction { case PholioMockDescriptionTransaction::TRANSACTIONTYPE: case PholioImageNameTransaction::TRANSACTIONTYPE: case PholioImageDescriptionTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE_SEQUENCE: - case self::TYPE_IMAGE_FILE: - case self::TYPE_IMAGE_REPLACE: + case PholioImageSequenceTransaction::TRANSACTIONTYPE: + case PholioImageFileTransaction::TRANSACTIONTYPE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_UPDATED; break; default: @@ -137,45 +86,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { $this->renderHandleLink($author_phid), $count); break; - case self::TYPE_IMAGE_REPLACE: - return pht( - '%s replaced %s with %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - break; - case self::TYPE_IMAGE_FILE: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s edited image(s), added %d: %s; removed %d: %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add), - count($rem), - $this->renderHandleList($rem)); - } else if ($add) { - return pht( - '%s added %d image(s): %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add)); - } else { - return pht( - '%s removed %d image(s): %s.', - $this->renderHandleLink($author_phid), - count($rem), - $this->renderHandleList($rem)); - } - break; - case self::TYPE_IMAGE_SEQUENCE: - return pht( - '%s updated an image\'s (%s) sequence.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new))); - break; } return parent::getTitle(); @@ -196,44 +106,9 @@ final class PholioTransaction extends PhabricatorModularTransaction { $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case self::TYPE_IMAGE_REPLACE: - case self::TYPE_IMAGE_FILE: - return pht( - '%s updated images of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_SEQUENCE: - return pht( - '%s updated image sequence of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; } return parent::getTitleForFeed(); } - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_REPLACE: - return PhabricatorTransactions::COLOR_YELLOW; - case self::TYPE_IMAGE_FILE: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - if ($add && $rem) { - return PhabricatorTransactions::COLOR_YELLOW; - } else if ($add) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_RED; - } - } - - return parent::getColor(); - } - } diff --git a/src/applications/pholio/xaction/PholioImageFileTransaction.php b/src/applications/pholio/xaction/PholioImageFileTransaction.php new file mode 100644 index 0000000000..5f68dad9f1 --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageFileTransaction.php @@ -0,0 +1,120 @@ +getImages(); + return array_values(mpull($images, 'getPHID')); + } + + public function generateNewValue($object, $value) { + $new_value = array(); + foreach ($value as $key => $images) { + $new_value[$key] = mpull($images, 'getPHID'); + } + $old = array_fuse($this->getOldValue()); + return $this->getEditor()->getPHIDList($old, $new_value); + } + + public function applyInternalEffects($object, $value) { + $old_map = array_fuse($this->getOldValue()); + $new_map = array_fuse($this->getNewValue()); + + $obsolete_map = array_diff_key($old_map, $new_map); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if (isset($obsolete_map[$image->getPHID()])) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s edited image(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added %d image(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %d image(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderHandleList($rem)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s updated images of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-picture-o'; + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && $rem) { + return PhabricatorTransactions::COLOR_YELLOW; + } else if ($add) { + return PhabricatorTransactions::COLOR_GREEN; + } else { + return PhabricatorTransactions::COLOR_RED; + } + } + + public function extractFilePHIDs($object, $value) { + $images = $this->getEditor()->getNewImages(); + $images = mpull($images, null, 'getPHID'); + + + $file_phids = array(); + foreach ($value as $image_phid) { + $image = idx($images, $image_phid); + if (!$image) { + continue; + } + $file_phids[] = $image->getFilePHID(); + } + return $file_phids; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + return $this->getEditor()->mergePHIDOrEdgeTransactions($u, $v); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageReplaceTransaction.php b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php new file mode 100644 index 0000000000..e6d45dfc7a --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php @@ -0,0 +1,68 @@ +getNewValue(); + return $new_image->getReplacesImagePHID(); + } + + public function generateNewValue($object, $value) { + return $value->getPHID(); + } + + public function applyInternalEffects($object, $value) { + $old = $this->getOldValue(); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if ($image->getPHID() == $old) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + } + + public function getTitle() { + return pht( + '%s replaced %s with %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function getTitleForFeed() { + return pht( + '%s updated images of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-picture-o'; + } + + public function getColor() { + return PhabricatorTransactions::COLOR_YELLOW; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + $u_img = $u->getNewValue(); + $v_img = $v->getNewValue(); + if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { + return $v; + } + } + + public function extractFilePHIDs($object, $value) { + return array($value); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageSequenceTransaction.php b/src/applications/pholio/xaction/PholioImageSequenceTransaction.php new file mode 100644 index 0000000000..c98c199adf --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageSequenceTransaction.php @@ -0,0 +1,60 @@ +getImageForXaction($object); + if ($image) { + $sequence = $image->getSequence(); + $phid = $image->getPHID(); + } + return array($phid => $sequence); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (int)head($this->getNewValue()); + $image->setSequence($value); + $image->save(); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s updated an image\'s (%s) sequence.', + $this->renderAuthor(), + $this->renderHandleLink(key($new))); + } + + public function getTitleForFeed() { + return pht( + '%s updated image sequence of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function shouldHide() { + // this is boring / silly to surface; changing sequence is NBD + return true; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = key($raw_new_value_u); + $phid_v = key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + } + +} From 404fe482d91492308dc839b5635de579b17dff5b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 07:58:02 -0700 Subject: [PATCH 067/543] Add a Quicksand-aware page-level container to diffs Summary: Ref T12616. Ref T8047. Ref T11093. We currently have several bugs where diff state sticks across Quicksand pages. Add a new top-level object to handle this, with `sleep()` and `wake()` methods. In `sleep()`, future changes will remove/deacivate all the reticles/editors/etc. See T12616 for high-level discussion of plans here. This general idea is likely to become more formal eventually (e.g. for "sheets" or whatever we call them, in T10469) but I think this is probably a reasonable place to draw a line for now. Test Plan: - Added some logging to sleep(), wake() and construct(). - Viewed changes in Differential. - With Quicksand on, browsed around; saw state change logs fire properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616, T11093, T8047 Differential Revision: https://secure.phabricator.com/D17840 --- resources/celerity/map.php | 26 ++++---- .../js/application/diff/DiffChangesetList.js | 25 ++++++++ .../differential/behavior-populate.js | 60 ++++++++++++++++++- 3 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 webroot/rsrc/js/application/diff/DiffChangesetList.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8430bf43b9..a2933b83aa 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => 'ddfeb49b', + 'differential.pkg.js' => '84d27954', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,6 +390,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', + 'rsrc/js/application/diff/DiffChangesetList.js' => '58c4c0d6', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', @@ -399,7 +400,7 @@ return array( 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '4fbbc3e9', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', + 'rsrc/js/application/differential/behavior-populate.js' => 'c0c44c3e', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', @@ -628,7 +629,7 @@ return array( 'javelin-behavior-differential-edit-inline-comments' => '4fbbc3e9', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => '8694b1df', + 'javelin-behavior-differential-populate' => 'c0c44c3e', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -786,6 +787,7 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', + 'phabricator-diff-changeset-list' => '58c4c0d6', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1353,6 +1355,9 @@ return array( 'javelin-vector', 'javelin-dom', ), + '58c4c0d6' => array( + 'javelin-install', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1558,13 +1563,6 @@ return array( 'phabricator-notification', 'conpherence-thread-manager', ), - '8694b1df' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'changeset-view-manager', - ), '88236f00' => array( 'javelin-behavior', 'phabricator-keyboard-shortcut', @@ -1948,6 +1946,14 @@ return array( 'javelin-install', 'javelin-dom', ), + 'c0c44c3e' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'changeset-view-manager', + 'phabricator-diff-changeset-list', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js new file mode 100644 index 0000000000..d62c2abd7a --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -0,0 +1,25 @@ +/** + * @provides phabricator-diff-changeset-list + * @requires javelin-install + * @javelin + */ + +JX.install('DiffChangesetList', { + + construct: function() { + + }, + + members: { + + sleep: function() { + + }, + + wake: function() { + + } + + } + +}); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 6d0aabb213..a7e54ddd37 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -5,9 +5,67 @@ * javelin-stratcom * phabricator-tooltip * changeset-view-manager + * phabricator-diff-changeset-list + * @javelin */ -JX.behavior('differential-populate', function(config) { +JX.behavior('differential-populate', function(config, statics) { + + // When we perform a Quicksand navigation, deactivate the changeset lists on + // the current page and activate the changeset lists on the new page. + var onredraw = function(page_id) { + // If the current page is already active, we don't need to do anything. + if (statics.pageID === page_id) { + return; + } + + var ii; + + // Put the old lists to sleep. + var old_lists = get_lists(statics.pageID); + for (ii = 0; ii < old_lists.length; ii++) { + old_lists[ii].sleep(); + } + statics.pageID = null; + + // Awaken the new lists, if they exist. + if (statics.pages.hasOwnProperty(page_id)) { + var new_lists = get_lists(page_id); + for (ii = 0; ii < new_lists.length; ii++) { + new_lists[ii].wake(); + } + + statics.pageID = page_id; + } + }; + + // Get changeset lists on the current page. + var get_lists = function(page_id) { + if (page_id === null) { + return []; + } + + return statics.pages[page_id] || []; + }; + + if (!statics.installed) { + statics.installed = true; + statics.pages = {}; + statics.pageID = null; + + JX.Stratcom.listen('quicksand-redraw', null, function(e) { + onredraw(e.getData().newResponseID); + }); + } + + var changeset_list = new JX.DiffChangesetList(); + + // Install and activate the current page. + var page_id = JX.Quicksand.getCurrentPageID(); + statics.pages[page_id] = [changeset_list]; + onredraw(page_id); + + for (var ii = 0; ii < config.changesetViewIDs.length; ii++) { var id = config.changesetViewIDs[ii]; From 993d94211722d0114b7765ec4ace326329bac9bd Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 09:17:16 -0700 Subject: [PATCH 068/543] Gently move some listeners into DiffChangesetList Summary: Ref T12616. Put these listeners in DiffChangesetList so the wake/sleep properly for Quicksand. Test Plan: - Added some logging. - With quicksand, moved between diffs. - Saw "load" and "show more" fire exactly once on each page, with the correct changeset list listener. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17841 --- resources/celerity/map.php | 32 +++++----- .../js/application/diff/DiffChangesetList.js | 64 ++++++++++++++++++- .../differential/behavior-populate.js | 33 ---------- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a2933b83aa..8029980969 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => '84d27954', + 'differential.pkg.js' => '8532657e', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,7 +390,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangesetList.js' => '58c4c0d6', + 'rsrc/js/application/diff/DiffChangesetList.js' => '9137a890', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', @@ -400,7 +400,7 @@ return array( 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '4fbbc3e9', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => 'c0c44c3e', + 'rsrc/js/application/differential/behavior-populate.js' => 'cf707904', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', @@ -629,7 +629,7 @@ return array( 'javelin-behavior-differential-edit-inline-comments' => '4fbbc3e9', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => 'c0c44c3e', + 'javelin-behavior-differential-populate' => 'cf707904', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -787,7 +787,7 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset-list' => '58c4c0d6', + 'phabricator-diff-changeset-list' => '9137a890', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1355,9 +1355,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '58c4c0d6' => array( - 'javelin-install', - ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1621,6 +1618,9 @@ return array( 'javelin-dom', 'javelin-request', ), + '9137a890' => array( + 'javelin-install', + ), 92904457 => array( 'javelin-behavior', 'javelin-dom', @@ -1946,14 +1946,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'c0c44c3e' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'changeset-view-manager', - 'phabricator-diff-changeset-list', - ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2041,6 +2033,14 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'cf707904' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'changeset-view-manager', + 'phabricator-diff-changeset-list', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index d62c2abd7a..132a911c6a 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -8,16 +8,78 @@ JX.install('DiffChangesetList', { construct: function() { + var onload = JX.bind(this, this._ifawake, this._onload); + JX.Stratcom.listen('click', 'differential-load', onload); + + var onmore = JX.bind(this, this._ifawake, this._onmore); + JX.Stratcom.listen('click', 'show-more', onmore); }, members: { + _asleep: true, sleep: function() { - + this._asleep = true; }, wake: function() { + this._asleep = false; + }, + isAsleep: function() { + return this._asleep; + }, + + getChangesetForNode: function(node) { + return JX.ChangesetViewManager.getForNode(node); + }, + + _ifawake: function(f) { + // This function takes another function and only calls it if the + // changeset list is awake, so we basically just ignore events when we + // are asleep. This may move up the stack at some point as we do more + // with Quicksand/Sheets. + + if (this.isAsleep()) { + return; + } + + return f.apply(this, [].slice.call(arguments, 1)); + }, + + _onload: function(e) { + var data = e.getNodeData('differential-load'); + + // NOTE: We can trigger a load from either an explicit "Load" link on + // the changeset, or by clicking a link in the table of contents. If + // the event was a table of contents link, we let the anchor behavior + // run normally. + if (data.kill) { + e.kill(); + } + + var node = JX.$(data.id); + var changeset = this.getChangesetForNode(node); + + changeset.load(); + + // TODO: Move this into Changeset. + var routable = changeset.getRoutable(); + if (routable) { + routable.setPriority(2000); + } + }, + + _onmore: function(e) { + e.kill(); + + var node = e.getNode('differential-changeset'); + var changeset = this.getChangesetForNode(node); + + var data = e.getNodeData('show-more'); + var target = e.getNode('context-target'); + + changeset.loadContext(data.range, target); } } diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index a7e54ddd37..b3663b1119 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -75,39 +75,6 @@ JX.behavior('differential-populate', function(config, statics) { } } - JX.Stratcom.listen( - 'click', - 'differential-load', - function(e) { - var meta = e.getNodeData('differential-load'); - var changeset = JX.$(meta.id); - var view = JX.ChangesetViewManager.getForNode(changeset); - - view.load(); - var routable = view.getRoutable(); - if (routable) { - routable.setPriority(2000); - } - - if (meta.kill) { - e.kill(); - } - }); - - JX.Stratcom.listen( - 'click', - 'show-more', - function(e) { - e.kill(); - - var changeset = e.getNode('differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset); - var data = e.getNodeData('show-more'); - var target = e.getNode('context-target'); - - view.loadContext(data.range, target); - }); - var highlighted = null; var highlight_class = null; From 2bd25d7399c53d07de54685352b7ffde6803cfae Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 09:52:16 -0700 Subject: [PATCH 069/543] Rename "DifferentialChangesetViewManager" to "DiffChangeset" Summary: Ref T12616. This class is already mostly-reasonable as a representation of an individual changeset, so I plan to just adjust it a little bit. Test Plan: - Used `git grep` to search for `ChangesetViewManager`. - Used `git grep` to search for `changeset-view-manager`. - Browsed around and interacted with changesets. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17842 --- resources/celerity/map.php | 99 ++++++++++--------- resources/celerity/packages.php | 3 +- .../DiffChangeset.js} | 6 +- .../js/application/diff/DiffChangesetList.js | 2 +- .../differential/behavior-dropdown-menus.js | 6 +- .../behavior-edit-inline-comments.js | 4 +- .../differential/behavior-populate.js | 4 +- 7 files changed, 63 insertions(+), 61 deletions(-) rename webroot/rsrc/js/application/{differential/ChangesetViewManager.js => diff/DiffChangeset.js} (98%) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8029980969..70636c688e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => '8532657e', + 'differential.pkg.js' => '7e4a9c9c', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,17 +390,17 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangesetList.js' => '9137a890', + 'rsrc/js/application/diff/DiffChangeset.js' => 'a1189df6', + 'rsrc/js/application/diff/DiffChangesetList.js' => '2329e40e', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '4fbbc3e9', + 'rsrc/js/application/differential/behavior-dropdown-menus.js' => 'f45a2836', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'c0f1c3b5', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => 'cf707904', + 'rsrc/js/application/differential/behavior-populate.js' => '00d88bc4', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', @@ -557,7 +557,6 @@ return array( 'application-search-view-css' => '66ee5d46', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', - 'changeset-view-manager' => 'a2828756', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', @@ -625,11 +624,11 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-dropdown-menus' => '9a6b9324', - 'javelin-behavior-differential-edit-inline-comments' => '4fbbc3e9', + 'javelin-behavior-differential-dropdown-menus' => 'f45a2836', + 'javelin-behavior-differential-edit-inline-comments' => 'c0f1c3b5', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => 'cf707904', + 'javelin-behavior-differential-populate' => '00d88bc4', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -787,7 +786,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset-list' => '9137a890', + 'phabricator-diff-changeset' => 'a1189df6', + 'phabricator-diff-changeset-list' => '2329e40e', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -922,6 +922,14 @@ return array( 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( + '00d88bc4' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '013ffff9' => array( 'javelin-install', 'javelin-util', @@ -1079,6 +1087,9 @@ return array( 'javelin-workflow', 'javelin-util', ), + '2329e40e' => array( + 'javelin-install', + ), 26167537 => array( 'javelin-install', 'javelin-dom', @@ -1296,14 +1307,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '4fbbc3e9' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), '4fdb476d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1618,9 +1621,6 @@ return array( 'javelin-dom', 'javelin-request', ), - '9137a890' => array( - 'javelin-install', - ), 92904457 => array( 'javelin-behavior', 'javelin-dom', @@ -1669,18 +1669,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '9a6b9324' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'phabricator-phtize', - 'changeset-view-manager', - ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1718,12 +1706,7 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a155550f' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), - 'a2828756' => array( + 'a1189df6' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', @@ -1733,6 +1716,11 @@ return array( 'javelin-behavior-device', 'javelin-vector', ), + 'a155550f' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-reactor-dom', + ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1946,6 +1934,14 @@ return array( 'javelin-install', 'javelin-dom', ), + 'c0f1c3b5' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2033,14 +2029,6 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), - 'cf707904' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'changeset-view-manager', - 'phabricator-diff-changeset-list', - ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2211,6 +2199,18 @@ return array( 'f12cbc9f' => array( 'phui-oi-list-view-css', ), + 'f45a2836' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phuix-dropdown-menu', + 'phuix-action-list-view', + 'phuix-action-view', + 'phabricator-phtize', + 'phabricator-diff-changeset', + ), 'f50152ad' => array( 'phui-timeline-view-css', ), @@ -2469,7 +2469,8 @@ return array( 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'changeset-view-manager', + 'phabricator-diff-changeset', + 'phabricator-diff-changeset-list', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index d906c738da..ed281822a8 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -208,7 +208,8 @@ return array( 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'changeset-view-manager', + 'phabricator-diff-changeset', + 'phabricator-diff-changeset-list', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', diff --git a/webroot/rsrc/js/application/differential/ChangesetViewManager.js b/webroot/rsrc/js/application/diff/DiffChangeset.js similarity index 98% rename from webroot/rsrc/js/application/differential/ChangesetViewManager.js rename to webroot/rsrc/js/application/diff/DiffChangeset.js index 0c23e18737..27058dbc42 100644 --- a/webroot/rsrc/js/application/differential/ChangesetViewManager.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -1,5 +1,5 @@ /** - * @provides changeset-view-manager + * @provides phabricator-diff-changeset * @requires javelin-dom * javelin-util * javelin-stratcom @@ -11,7 +11,7 @@ */ -JX.install('ChangesetViewManager', { +JX.install('DiffChangeset', { construct : function(node) { this._node = node; @@ -389,7 +389,7 @@ JX.install('ChangesetViewManager', { getForNode: function(node) { var data = JX.Stratcom.getData(node); if (!data.changesetViewManager) { - data.changesetViewManager = new JX.ChangesetViewManager(node); + data.changesetViewManager = new JX.DiffChangeset(node); } return data.changesetViewManager; } diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 132a911c6a..c8282dba32 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -31,7 +31,7 @@ JX.install('DiffChangesetList', { }, getChangesetForNode: function(node) { - return JX.ChangesetViewManager.getForNode(node); + return JX.DiffChangeset.getForNode(node); }, _ifawake: function(f) { diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js index 1905e3a433..ab91818143 100644 --- a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js +++ b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js @@ -9,14 +9,14 @@ * phuix-action-list-view * phuix-action-view * phabricator-phtize - * changeset-view-manager + * phabricator-diff-changeset */ JX.behavior('differential-dropdown-menus', function(config) { var pht = JX.phtize(config.pht); function show_more(container) { - var view = JX.ChangesetViewManager.getForNode(container); + var view = JX.DiffChangeset.getForNode(container); var nodes = JX.DOM.scry(container, 'tr', 'context-target'); for (var ii = 0; ii < nodes.length; ii++) { @@ -59,7 +59,7 @@ JX.behavior('differential-dropdown-menus', function(config) { 'div', 'differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset); + var view = JX.DiffChangeset.getForNode(changeset); var menu = new JX.PHUIXDropdownMenu(button); var list = new JX.PHUIXActionListView(); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 032b8cec68..1859ca1754 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -274,7 +274,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { insert = target.parentNode; } - var view = JX.ChangesetViewManager.getForNode(root); + var view = JX.DiffChangeset.getForNode(root); editor = new JX.DifferentialInlineCommentEditor(config.uri) .setTemplates(view.getUndoTemplates()) @@ -390,7 +390,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { node, 'div', 'differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset_root); + var view = JX.DiffChangeset.getForNode(changeset_root); editor = new JX.DifferentialInlineCommentEditor(config.uri) .setTemplates(view.getUndoTemplates()) diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index b3663b1119..4c6187189f 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -4,8 +4,8 @@ * javelin-dom * javelin-stratcom * phabricator-tooltip - * changeset-view-manager * phabricator-diff-changeset-list + * phabricator-diff-changeset * @javelin */ @@ -69,7 +69,7 @@ JX.behavior('differential-populate', function(config, statics) { for (var ii = 0; ii < config.changesetViewIDs.length; ii++) { var id = config.changesetViewIDs[ii]; - var view = JX.ChangesetViewManager.getForNode(JX.$(id)); + var view = JX.DiffChangeset.getForNode(JX.$(id)); if (view.shouldAutoload()) { view.setStabilize(true).load(); } From 63450cc48eca9761f3cd292e6e33716a9b0de74a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 10:00:33 -0700 Subject: [PATCH 070/543] Remove "Show All Context" button from Diffusion Summary: Ref T12616. Diffusion, only, has a "Show All Context" button which expands the full context on all changes. I don't remember the exact history on this, but it hasn't existed in Differential for some time and no one has complained. I suspect that the "View Options > Show All Context" on each file may replace it. I can't really come up with good reasons to use it, offhand. If we want to restore it, I think global options after T1591 is promising. {F4945561} Test Plan: - Loaded a commit in Diffusion, no longer saw a button. - Grepped for relevant sigils. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17843 --- resources/celerity/map.php | 30 +++++++++---------- .../view/PHUIDiffTableOfContentsListView.php | 19 +----------- .../differential/behavior-dropdown-menus.js | 14 --------- 3 files changed, 16 insertions(+), 47 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 70636c688e..6107ae0e71 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => '7e4a9c9c', + 'differential.pkg.js' => 'ef6c7cfc', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -397,7 +397,7 @@ return array( 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-dropdown-menus.js' => 'f45a2836', + 'rsrc/js/application/differential/behavior-dropdown-menus.js' => 'c3d216cb', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'c0f1c3b5', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '00d88bc4', @@ -624,7 +624,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-dropdown-menus' => 'f45a2836', + 'javelin-behavior-differential-dropdown-menus' => 'c3d216cb', 'javelin-behavior-differential-edit-inline-comments' => 'c0f1c3b5', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', @@ -1942,6 +1942,18 @@ return array( 'javelin-vector', 'differential-inline-comment-editor', ), + 'c3d216cb' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phuix-dropdown-menu', + 'phuix-action-list-view', + 'phuix-action-view', + 'phabricator-phtize', + 'phabricator-diff-changeset', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2199,18 +2211,6 @@ return array( 'f12cbc9f' => array( 'phui-oi-list-view-css', ), - 'f45a2836' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'phabricator-phtize', - 'phabricator-diff-changeset', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index e6ca6cc53d..47c8f633e7 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -103,22 +103,6 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { } } - $reveal_link = javelin_tag( - 'a', - array( - 'sigil' => 'differential-reveal-all', - 'mustcapture' => true, - 'class' => 'button differential-toc-reveal-all', - ), - pht('Show All Context')); - - $buttons = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-buttons grouped', - ), - $reveal_link); - $table = id(new AphrontTableView($rows)) ->setRowClasses($rowc) ->setHeaders( @@ -185,8 +169,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setHeader($header) ->setBackground($this->background) ->setTable($table) - ->appendChild($anchor) - ->appendChild($buttons); + ->appendChild($anchor); if ($this->infoView) { $box->setInfoView($this->infoView); diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js index ab91818143..822f1b5ac4 100644 --- a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js +++ b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js @@ -31,20 +31,6 @@ JX.behavior('differential-dropdown-menus', function(config) { } } - JX.Stratcom.listen( - 'click', - 'differential-reveal-all', - function(e) { - var containers = JX.DOM.scry( - JX.$('differential-review-stage'), - 'div', - 'differential-changeset'); - for (var i=0; i < containers.length; i++) { - show_more(containers[i]); - } - e.kill(); - }); - var buildmenu = function(e) { var button = e.getNode('differential-view-options'); var data = JX.Stratcom.getData(button); From 64a54aac9d572e7d151382ab06f5a802188752fa Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 10:20:59 -0700 Subject: [PATCH 071/543] Merge "differential-dropdown-menus" behavior into DiffChangesetList Summary: Ref T12616. This ends up being a little messy ("one giant function") and maybe I'll clean it up a bit later, but continue consolidating the wild jungle of behaviors into a smaller set of responsible objects. Test Plan: Clicked all the menu options, saw them work properly. Grepped for removed methods. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17845 --- resources/celerity/map.php | 71 ++---- resources/celerity/packages.php | 1 - .../view/DifferentialChangesetListView.php | 45 ++-- .../rsrc/js/application/diff/DiffChangeset.js | 14 + .../js/application/diff/DiffChangesetList.js | 220 ++++++++++++++++ .../differential/behavior-dropdown-menus.js | 240 ------------------ .../differential/behavior-populate.js | 3 +- 7 files changed, 285 insertions(+), 309 deletions(-) delete mode 100644 webroot/rsrc/js/application/differential/behavior-dropdown-menus.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6107ae0e71..4d571aa9e9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => 'ef6c7cfc', + 'differential.pkg.js' => '51d9bebe', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,17 +390,16 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'a1189df6', - 'rsrc/js/application/diff/DiffChangesetList.js' => '2329e40e', + 'rsrc/js/application/diff/DiffChangeset.js' => 'ed7bc580', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'f9ea2d8b', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-dropdown-menus.js' => 'c3d216cb', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'c0f1c3b5', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => '00d88bc4', + 'rsrc/js/application/differential/behavior-populate.js' => '7356b23d', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', @@ -624,11 +623,10 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-dropdown-menus' => 'c3d216cb', 'javelin-behavior-differential-edit-inline-comments' => 'c0f1c3b5', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => '00d88bc4', + 'javelin-behavior-differential-populate' => '7356b23d', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -786,8 +784,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'a1189df6', - 'phabricator-diff-changeset-list' => '2329e40e', + 'phabricator-diff-changeset' => 'ed7bc580', + 'phabricator-diff-changeset-list' => 'f9ea2d8b', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -922,14 +920,6 @@ return array( 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( - '00d88bc4' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '013ffff9' => array( 'javelin-install', 'javelin-util', @@ -1087,9 +1077,6 @@ return array( 'javelin-workflow', 'javelin-util', ), - '2329e40e' => array( - 'javelin-install', - ), 26167537 => array( 'javelin-install', 'javelin-dom', @@ -1471,6 +1458,14 @@ return array( 'javelin-behavior', 'javelin-dom', ), + '7356b23d' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '73d09eef' => array( 'javelin-behavior', 'javelin-vector', @@ -1706,16 +1701,6 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a1189df6' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'a155550f' => array( 'javelin-install', 'javelin-dom', @@ -1942,18 +1927,6 @@ return array( 'javelin-vector', 'differential-inline-comment-editor', ), - 'c3d216cb' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'phabricator-phtize', - 'phabricator-diff-changeset', - ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2182,6 +2155,16 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'ed7bc580' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'eded9ee8' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2239,6 +2222,9 @@ return array( 'javelin-install', 'javelin-dom', ), + 'f9ea2d8b' => array( + 'javelin-install', + ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', @@ -2465,7 +2451,6 @@ return array( 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', 'differential-inline-comment-editor', - 'javelin-behavior-differential-dropdown-menus', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index ed281822a8..cac2eff5f8 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -204,7 +204,6 @@ return array( 'javelin-behavior-load-blame', 'differential-inline-comment-editor', - 'javelin-behavior-differential-dropdown-menus', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 533f579b0a..66ce4fa403 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -138,29 +138,6 @@ final class DifferentialChangesetListView extends AphrontView { ), )); - Javelin::initBehavior( - 'differential-dropdown-menus', - array( - 'pht' => array( - 'Open in Editor' => pht('Open in Editor'), - 'Show All Context' => pht('Show All Context'), - 'All Context Shown' => pht('All Context Shown'), - "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), - 'Expand File' => pht('Expand File'), - 'Collapse File' => pht('Collapse File'), - 'Browse in Diffusion' => pht('Browse in Diffusion'), - 'View Standalone' => pht('View Standalone'), - 'Show Raw File (Left)' => pht('Show Raw File (Left)'), - 'Show Raw File (Right)' => pht('Show Raw File (Right)'), - 'Configure Editor' => pht('Configure Editor'), - 'Load Changes' => pht('Load Changes'), - 'View Side-by-Side' => pht('View Side-by-Side'), - 'View Unified' => pht('View Unified'), - 'Change Text Encoding...' => pht('Change Text Encoding...'), - 'Highlight As...' => pht('Highlight As...'), - ), - )); - $renderer = DifferentialChangesetParser::getDefaultRendererForViewer( $viewer); @@ -238,8 +215,28 @@ final class DifferentialChangesetListView extends AphrontView { $this->requireResource('aphront-tooltip-css'); - $this->initBehavior('differential-populate', array( + $this->initBehavior( + 'differential-populate', + array( 'changesetViewIDs' => $ids, + 'pht' => array( + 'Open in Editor' => pht('Open in Editor'), + 'Show All Context' => pht('Show All Context'), + 'All Context Shown' => pht('All Context Shown'), + "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), + 'Expand File' => pht('Expand File'), + 'Collapse File' => pht('Collapse File'), + 'Browse in Diffusion' => pht('Browse in Diffusion'), + 'View Standalone' => pht('View Standalone'), + 'Show Raw File (Left)' => pht('Show Raw File (Left)'), + 'Show Raw File (Right)' => pht('Show Raw File (Right)'), + 'Configure Editor' => pht('Configure Editor'), + 'Load Changes' => pht('Load Changes'), + 'View Side-by-Side' => pht('View Side-by-Side'), + 'View Unified' => pht('View Unified'), + 'Change Text Encoding...' => pht('Change Text Encoding...'), + 'Highlight As...' => pht('Highlight As...'), + ), )); $this->initBehavior('differential-comment-jump', array()); diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 27058dbc42..c61be63d77 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -174,6 +174,20 @@ JX.install('DiffChangeset', { return this; }, + loadAllContext: function() { + var nodes = JX.DOM.scry(this._node, 'tr', 'context-target'); + for (var ii = 0; ii < nodes.length; ii++) { + var show = JX.DOM.scry(nodes[ii], 'a', 'show-more'); + for (var jj = 0; jj < show.length; jj++) { + var data = JX.Stratcom.getData(show[jj]); + if (data.type != 'all') { + continue; + } + this.loadContext(data.range, nodes[ii], true); + } + } + }, + _startContentWorkflow: function(workflow) { var routable = workflow.getRoutable(); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index c8282dba32..2c8710dd2c 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -13,6 +13,13 @@ JX.install('DiffChangesetList', { var onmore = JX.bind(this, this._ifawake, this._onmore); JX.Stratcom.listen('click', 'show-more', onmore); + + var onmenu = JX.bind(this, this._ifawake, this._onmenu); + JX.Stratcom.listen('click', 'differential-view-options', onmenu); + }, + + properties: { + translations: null }, members: { @@ -80,8 +87,221 @@ JX.install('DiffChangesetList', { var target = e.getNode('context-target'); changeset.loadContext(data.range, target); + }, + + _onmenu: function(e) { + var button = e.getNode('differential-view-options'); + + var data = JX.Stratcom.getData(button); + if (data.menu) { + // We've already built this menu, so we can let the menu itself handle + // the event. + return; + } + + e.prevent(); + + var pht = this.getTranslations(); + + var node = JX.DOM.findAbove( + button, + 'div', + 'differential-changeset'); + + var changeset = this.getChangesetForNode(node); + + var menu = new JX.PHUIXDropdownMenu(button); + var list = new JX.PHUIXActionListView(); + + var add_link = function(icon, name, href, local) { + if (!href) { + return; + } + + var link = new JX.PHUIXActionView() + .setIcon(icon) + .setName(name) + .setHref(href) + .setHandler(function(e) { + if (local) { + window.location.assign(href); + } else { + window.open(href); + } + menu.close(); + e.prevent(); + }); + + list.addItem(link); + return link; + }; + + var reveal_item = new JX.PHUIXActionView() + .setIcon('fa-eye'); + list.addItem(reveal_item); + + var visible_item = new JX.PHUIXActionView() + .setHandler(function(e) { + var diff = JX.DOM.scry( + JX.$(data.containerID), + 'table', + 'differential-diff'); + + JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff}); + e.prevent(); + menu.close(); + }); + list.addItem(visible_item); + + add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI); + add_link('fa-file-o', pht('View Standalone'), data.standaloneURI); + + var up_item = new JX.PHUIXActionView() + .setHandler(function(e) { + if (changeset.isLoaded()) { + var renderer = changeset.getRenderer(); + if (renderer == '1up') { + renderer = '2up'; + } else { + renderer = '1up'; + } + changeset.setRenderer(renderer); + } + changeset.reload(); + + e.prevent(); + menu.close(); + }); + list.addItem(up_item); + + var encoding_item = new JX.PHUIXActionView() + .setIcon('fa-font') + .setName(pht('Change Text Encoding...')) + .setHandler(function(e) { + var params = { + encoding: changeset.getEncoding() + }; + + new JX.Workflow('/services/encoding/', params) + .setHandler(function(r) { + changeset.setEncoding(r.encoding); + changeset.reload(); + }) + .start(); + + e.prevent(); + menu.close(); + }); + list.addItem(encoding_item); + + var highlight_item = new JX.PHUIXActionView() + .setIcon('fa-sun-o') + .setName(pht('Highlight As...')) + .setHandler(function(e) { + var params = { + highlight: changeset.getHighlight() + }; + + new JX.Workflow('/services/highlight/', params) + .setHandler(function(r) { + changeset.setHighlight(r.highlight); + changeset.reload(); + }) + .start(); + + e.prevent(); + menu.close(); + }); + list.addItem(highlight_item); + + add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI); + add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI); + add_link('fa-pencil', pht('Open in Editor'), data.editor, true); + add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure); + + menu.setContent(list.getNode()); + + menu.listen('open', function() { + // When the user opens the menu, check if there are any "Show More" + // links in the changeset body. If there aren't, disable the "Show + // Entire File" menu item since it won't change anything. + + var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more'); + if (nodes.length) { + reveal_item + .setDisabled(false) + .setName(pht('Show All Context')) + .setIcon('fa-file-o') + .setHandler(function(e) { + changeset.loadAllContext(); + e.prevent(); + menu.close(); + }); + } else { + reveal_item + .setDisabled(true) + .setIcon('fa-file') + .setName(pht('All Context Shown')) + .setHandler(function(e) { e.prevent(); }); + } + + encoding_item.setDisabled(!changeset.isLoaded()); + highlight_item.setDisabled(!changeset.isLoaded()); + + if (changeset.isLoaded()) { + if (changeset.getRenderer() == '2up') { + up_item + .setIcon('fa-list-alt') + .setName(pht('View Unified')); + } else { + up_item + .setIcon('fa-files-o') + .setName(pht('View Side-by-Side')); + } + } else { + up_item + .setIcon('fa-refresh') + .setName(pht('Load Changes')); + } + + visible_item + .setDisabled(true) + .setIcon('fa-expand') + .setName(pht('Can\'t Toggle Unloaded File')); + var diffs = JX.DOM.scry( + JX.$(data.containerID), + 'table', + 'differential-diff'); + + if (diffs.length > 1) { + JX.$E( + 'More than one node with sigil "differential-diff" was found in "'+ + data.containerID+'."'); + } else if (diffs.length == 1) { + var diff = diffs[0]; + visible_item.setDisabled(false); + if (JX.Stratcom.getData(diff).hidden) { + visible_item + .setName(pht('Expand File')) + .setIcon('fa-expand'); + } else { + visible_item + .setName(pht('Collapse File')) + .setIcon('fa-compress'); + } + } else { + // Do nothing when there is no diff shown in the table. For example, + // the file is binary. + } + + }); + + data.menu = menu; + menu.open(); } + + } }); diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js deleted file mode 100644 index 822f1b5ac4..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js +++ /dev/null @@ -1,240 +0,0 @@ -/** - * @provides javelin-behavior-differential-dropdown-menus - * @requires javelin-behavior - * javelin-dom - * javelin-util - * javelin-stratcom - * javelin-workflow - * phuix-dropdown-menu - * phuix-action-list-view - * phuix-action-view - * phabricator-phtize - * phabricator-diff-changeset - */ - -JX.behavior('differential-dropdown-menus', function(config) { - var pht = JX.phtize(config.pht); - - function show_more(container) { - var view = JX.DiffChangeset.getForNode(container); - - var nodes = JX.DOM.scry(container, 'tr', 'context-target'); - for (var ii = 0; ii < nodes.length; ii++) { - var show = JX.DOM.scry(nodes[ii], 'a', 'show-more'); - for (var jj = 0; jj < show.length; jj++) { - var data = JX.Stratcom.getData(show[jj]); - if (data.type != 'all') { - continue; - } - view.loadContext(data.range, nodes[ii], true); - } - } - } - - var buildmenu = function(e) { - var button = e.getNode('differential-view-options'); - var data = JX.Stratcom.getData(button); - if (data.menu) { - return; - } - - e.prevent(); - - var changeset = JX.DOM.findAbove( - button, - 'div', - 'differential-changeset'); - - var view = JX.DiffChangeset.getForNode(changeset); - var menu = new JX.PHUIXDropdownMenu(button); - var list = new JX.PHUIXActionListView(); - - var add_link = function(icon, name, href, local) { - if (!href) { - return; - } - - var link = new JX.PHUIXActionView() - .setIcon(icon) - .setName(name) - .setHref(href) - .setHandler(function(e) { - if (local) { - window.location.assign(href); - } else { - window.open(href); - } - menu.close(); - e.prevent(); - }); - - list.addItem(link); - return link; - }; - - var reveal_item = new JX.PHUIXActionView() - .setIcon('fa-eye'); - list.addItem(reveal_item); - - var visible_item = new JX.PHUIXActionView() - .setHandler(function(e) { - var diff = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff}); - e.prevent(); - menu.close(); - }); - list.addItem(visible_item); - - add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI); - add_link('fa-file-o', pht('View Standalone'), data.standaloneURI); - - var up_item = new JX.PHUIXActionView() - .setHandler(function(e) { - if (view.isLoaded()) { - var renderer = view.getRenderer(); - if (renderer == '1up') { - renderer = '2up'; - } else { - renderer = '1up'; - } - view.setRenderer(renderer); - } - view.reload(); - - e.prevent(); - menu.close(); - }); - list.addItem(up_item); - - var encoding_item = new JX.PHUIXActionView() - .setIcon('fa-font') - .setName(pht('Change Text Encoding...')) - .setHandler(function(e) { - var params = { - encoding: view.getEncoding() - }; - - new JX.Workflow('/services/encoding/', params) - .setHandler(function(r) { - view.setEncoding(r.encoding); - view.reload(); - }) - .start(); - - e.prevent(); - menu.close(); - }); - list.addItem(encoding_item); - - var highlight_item = new JX.PHUIXActionView() - .setIcon('fa-sun-o') - .setName(pht('Highlight As...')) - .setHandler(function(e) { - var params = { - highlight: view.getHighlight() - }; - - new JX.Workflow('/services/highlight/', params) - .setHandler(function(r) { - view.setHighlight(r.highlight); - view.reload(); - }) - .start(); - - e.prevent(); - menu.close(); - }); - list.addItem(highlight_item); - - add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI); - add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI); - add_link('fa-pencil', pht('Open in Editor'), data.editor, true); - add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure); - - menu.setContent(list.getNode()); - - menu.listen('open', function() { - // When the user opens the menu, check if there are any "Show More" - // links in the changeset body. If there aren't, disable the "Show - // Entire File" menu item since it won't change anything. - - var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more'); - if (nodes.length) { - reveal_item - .setDisabled(false) - .setName(pht('Show All Context')) - .setIcon('fa-file-o') - .setHandler(function(e) { - show_more(JX.$(data.containerID)); - e.prevent(); - menu.close(); - }); - } else { - reveal_item - .setDisabled(true) - .setIcon('fa-file') - .setName(pht('All Context Shown')) - .setHandler(function(e) { e.prevent(); }); - } - - encoding_item.setDisabled(!view.isLoaded()); - highlight_item.setDisabled(!view.isLoaded()); - - if (view.isLoaded()) { - if (view.getRenderer() == '2up') { - up_item - .setIcon('fa-list-alt') - .setName(pht('View Unified')); - } else { - up_item - .setIcon('fa-files-o') - .setName(pht('View Side-by-Side')); - } - } else { - up_item - .setIcon('fa-refresh') - .setName(pht('Load Changes')); - } - - visible_item - .setDisabled(true) - .setIcon('fa-expand') - .setName(pht('Can\'t Toggle Unloaded File')); - var diffs = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - if (diffs.length > 1) { - JX.$E( - 'More than one node with sigil "differential-diff" was found in "'+ - data.containerID+'."'); - } else if (diffs.length == 1) { - var diff = diffs[0]; - visible_item.setDisabled(false); - if (JX.Stratcom.getData(diff).hidden) { - visible_item - .setName(pht('Expand File')) - .setIcon('fa-expand'); - } else { - visible_item - .setName(pht('Collapse File')) - .setIcon('fa-compress'); - } - } else { - // Do nothing when there is no diff shown in the table. For example, - // the file is binary. - } - - }); - - data.menu = menu; - menu.open(); - }; - - JX.Stratcom.listen('click', 'differential-view-options', buildmenu); -}); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 4c6187189f..5fe219b00e 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -58,7 +58,8 @@ JX.behavior('differential-populate', function(config, statics) { }); } - var changeset_list = new JX.DiffChangesetList(); + var changeset_list = new JX.DiffChangesetList() + .setTranslations(JX.phtize(config.pht)); // Install and activate the current page. var page_id = JX.Quicksand.getCurrentPageID(); From fe44e987fb1677be2f42eb98663b2cb313bdf2f2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 11:27:11 -0700 Subject: [PATCH 072/543] Translate "Loading..." text in inline comments Summary: Ref T12616. This cements the relationship between ChangesetList (parent container) and Changeset (child) and passes translations down so Changeset can use them to translate the text "Loading..." Test Plan: Viewed loading changes. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17846 --- resources/celerity/map.php | 56 +++++++++---------- .../view/DifferentialChangesetListView.php | 2 + .../rsrc/js/application/diff/DiffChangeset.js | 12 +++- .../js/application/diff/DiffChangesetList.js | 11 ++++ .../differential/behavior-populate.js | 8 ++- 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4d571aa9e9..f7315a9829 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => '51d9bebe', + 'differential.pkg.js' => '2de0157a', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,8 +390,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'ed7bc580', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'f9ea2d8b', + 'rsrc/js/application/diff/DiffChangeset.js' => '2cbf5575', + 'rsrc/js/application/diff/DiffChangesetList.js' => '16c14b02', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', @@ -399,7 +399,7 @@ return array( 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'c0f1c3b5', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => '7356b23d', + 'rsrc/js/application/differential/behavior-populate.js' => '8991de30', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', @@ -626,7 +626,7 @@ return array( 'javelin-behavior-differential-edit-inline-comments' => 'c0f1c3b5', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => '7356b23d', + 'javelin-behavior-differential-populate' => '8991de30', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -784,8 +784,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'ed7bc580', - 'phabricator-diff-changeset-list' => 'f9ea2d8b', + 'phabricator-diff-changeset' => '2cbf5575', + 'phabricator-diff-changeset-list' => '16c14b02', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1004,6 +1004,9 @@ return array( 'javelin-dom', 'javelin-history', ), + '16c14b02' => array( + 'javelin-install', + ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1113,6 +1116,16 @@ return array( 'javelin-install', 'javelin-event', ), + '2cbf5575' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '2e3f9738' => array( 'javelin-dom', 'javelin-util', @@ -1458,14 +1471,6 @@ return array( 'javelin-behavior', 'javelin-dom', ), - '7356b23d' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '73d09eef' => array( 'javelin-behavior', 'javelin-vector', @@ -1585,6 +1590,14 @@ return array( 'phabricator-draggable-list', 'javelin-workboard-column', ), + '8991de30' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '8a41885b' => array( 'javelin-install', 'javelin-dom', @@ -2155,16 +2168,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'ed7bc580' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'eded9ee8' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2222,9 +2225,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'f9ea2d8b' => array( - 'javelin-install', - ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 66ce4fa403..313bcbe694 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -236,6 +236,8 @@ final class DifferentialChangesetListView extends AphrontView { 'View Unified' => pht('View Unified'), 'Change Text Encoding...' => pht('Change Text Encoding...'), 'Highlight As...' => pht('Highlight As...'), + + 'Loading...' => pht('Loading...'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index c61be63d77..c8a8462519 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -26,6 +26,10 @@ JX.install('DiffChangeset', { this._loaded = data.loaded; }, + properties: { + changesetList: null + }, + members: { _node: null, _loaded: false, @@ -121,6 +125,7 @@ JX.install('DiffChangeset', { this._sequence++; var params = this._getViewParameters(); + var pht = this.getChangesetList().getTranslations(); var workflow = new JX.Workflow(this._renderURI, params) .setHandler(JX.bind(this, this._onresponse, this._sequence)); @@ -132,7 +137,7 @@ JX.install('DiffChangeset', { JX.$N( 'div', {className: 'differential-loading'}, - 'Loading...')); + pht('Loading...'))); return this; }, @@ -152,9 +157,10 @@ JX.install('DiffChangeset', { var params = this._getViewParameters(); params.range = range; + var pht = this.getChangesetList().getTranslations(); + var container = JX.DOM.scry(target, 'td')[0]; - // TODO: pht() - JX.DOM.setContent(container, 'Loading...'); + JX.DOM.setContent(container, pht('Loading...')); JX.DOM.alterClass(target, 'differential-show-more-loading', true); var workflow = new JX.Workflow(this._renderURI, params) diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 2c8710dd2c..5bcbc63626 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -7,6 +7,7 @@ JX.install('DiffChangesetList', { construct: function() { + this._changesets = []; var onload = JX.bind(this, this._ifawake, this._onload); JX.Stratcom.listen('click', 'differential-load', onload); @@ -24,6 +25,7 @@ JX.install('DiffChangesetList', { members: { _asleep: true, + _changesets: null, sleep: function() { this._asleep = true; @@ -37,6 +39,15 @@ JX.install('DiffChangesetList', { return this._asleep; }, + newChangesetForNode: function(node) { + var changeset = JX.DiffChangeset.getForNode(node); + + this._changesets.push(changeset); + changeset.setChangesetList(this); + + return changeset; + }, + getChangesetForNode: function(node) { return JX.DiffChangeset.getForNode(node); }, diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 5fe219b00e..02b8c1e896 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -70,9 +70,11 @@ JX.behavior('differential-populate', function(config, statics) { for (var ii = 0; ii < config.changesetViewIDs.length; ii++) { var id = config.changesetViewIDs[ii]; - var view = JX.DiffChangeset.getForNode(JX.$(id)); - if (view.shouldAutoload()) { - view.setStabilize(true).load(); + var node = JX.$(id); + + var changeset = changeset_list.newChangesetForNode(node); + if (changeset.shouldAutoload()) { + changeset.setStabilize(true).load(); } } From 4fd4ec3d275d0d90a429419e0c5a278df87b76da Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 9 May 2017 12:09:45 -0700 Subject: [PATCH 073/543] Hide inlines one-by-one, instead of in a big group Summary: Ref T12616. Fixes T12153. Currently, when you hide inlines, they hide completely and turn into a little bubble on the previous line. Instead, collapse them to a single line one-by-one. Narrowly, this fixes T12153. In the future, I plan to make these changes so this feature makes more sense: - Introduce global "hide everything" states (T8909) so you can completely hide stuff if you want, and this represents more of a halfway state between "nuke it" and "view it". - Make the actual rendering better, so it says "epriestley: blah blah..." instead of just "..." -- and looks less dumb. The real goal here is to introduce `DiffInline` and continue moving stuff from the tangled jungle of a million top-level behaviors to sensible smooth statefulness. Test Plan: - Hid and revealed inlines in unified and two-up modes. - These look pretty junk for now: {F4948659} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616, T12153 Differential Revision: https://secure.phabricator.com/D17861 --- resources/celerity/map.php | 88 ++++++++++--------- .../DifferentialChangesetOneUpRenderer.php | 32 ------- .../DifferentialChangesetTwoUpRenderer.php | 25 ------ .../view/DifferentialChangesetDetailView.php | 2 +- .../view/DifferentialChangesetListView.php | 7 +- .../view/PHUIDiffInlineCommentDetailView.php | 15 +++- .../view/PHUIDiffInlineCommentRowScaffold.php | 23 +++-- .../view/PHUIDiffInlineCommentUndoView.php | 4 + .../diff/view/PHUIDiffInlineCommentView.php | 12 +++ .../PHUIDiffOneUpInlineCommentRowScaffold.php | 12 ++- .../diff/view/PHUIDiffRevealIconView.php | 6 +- .../PHUIDiffTwoUpInlineCommentRowScaffold.php | 13 ++- .../differential/phui-inline-comment.css | 35 ++++++-- .../rsrc/js/application/diff/DiffChangeset.js | 19 ++++ .../js/application/diff/DiffChangesetList.js | 31 ++++++- .../rsrc/js/application/diff/DiffInline.js | 56 ++++++++++++ .../behavior-edit-inline-comments.js | 83 ----------------- .../differential/behavior-populate.js | 4 +- 18 files changed, 250 insertions(+), 217 deletions(-) create mode 100644 webroot/rsrc/js/application/diff/DiffInline.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f7315a9829..5dbb7d0a59 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => 'd1bf3405', 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => '2de0157a', + 'differential.pkg.css' => '58712637', + 'differential.pkg.js' => '70685319', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -66,7 +66,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => '41af6d25', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => 'be663c95', + 'rsrc/css/application/differential/phui-inline-comment.css' => '3fd8ca64', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -390,16 +390,17 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '2cbf5575', - 'rsrc/js/application/diff/DiffChangesetList.js' => '16c14b02', + 'rsrc/js/application/diff/DiffChangeset.js' => '80ac3298', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'a34b9821', + 'rsrc/js/application/diff/DiffInline.js' => 'f9e76f2d', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'c0f1c3b5', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'e7e9551e', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => '8991de30', + 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', @@ -623,10 +624,10 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => 'c0f1c3b5', + 'javelin-behavior-differential-edit-inline-comments' => 'e7e9551e', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => '8991de30', + 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -784,8 +785,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '2cbf5575', - 'phabricator-diff-changeset-list' => '16c14b02', + 'phabricator-diff-changeset' => '80ac3298', + 'phabricator-diff-changeset-list' => 'a34b9821', + 'phabricator-diff-inline' => 'f9e76f2d', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -868,7 +870,7 @@ return array( 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', - 'phui-inline-comment-view-css' => 'be663c95', + 'phui-inline-comment-view-css' => '3fd8ca64', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => '12eb8ce6', @@ -1004,9 +1006,6 @@ return array( 'javelin-dom', 'javelin-history', ), - '16c14b02' => array( - 'javelin-install', - ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1116,16 +1115,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2cbf5575' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '2e3f9738' => array( 'javelin-dom', 'javelin-util', @@ -1388,6 +1377,14 @@ return array( 'phabricator-phtize', 'javelin-dom', ), + '5e41c819' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1538,6 +1535,17 @@ return array( 'javelin-vector', 'javelin-stratcom', ), + '80ac3298' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1590,14 +1598,6 @@ return array( 'phabricator-draggable-list', 'javelin-workboard-column', ), - '8991de30' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '8a41885b' => array( 'javelin-install', 'javelin-dom', @@ -1719,6 +1719,9 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + 'a34b9821' => array( + 'javelin-install', + ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1932,14 +1935,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'c0f1c3b5' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2161,6 +2156,14 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e7e9551e' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2225,6 +2228,9 @@ return array( 'javelin-install', 'javelin-dom', ), + 'f9e76f2d' => array( + 'javelin-dom', + ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index cbb5cbc6f4..7a694cfd98 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,8 +41,6 @@ final class DifferentialChangesetOneUpRenderer $column_width = 4; - $hidden = new PHUIDiffRevealIconView(); - $out = array(); foreach ($primitives as $k => $p) { $type = $p['type']; @@ -53,27 +51,6 @@ final class DifferentialChangesetOneUpRenderer case 'new-file': $is_old = ($type == 'old' || $type == 'old-file'); - $o_hidden = array(); - $n_hidden = array(); - - for ($look = $k + 1; isset($primitives[$look]); $look++) { - $next = $primitives[$look]; - switch ($next['type']) { - case 'inline': - $comment = $next['comment']; - if ($comment->isHidden()) { - if ($next['right']) { - $n_hidden[] = $comment; - } else { - $o_hidden[] = $comment; - } - } - break; - default: - break 2; - } - } - $cells = array(); if ($is_old) { if ($p['htype']) { @@ -93,9 +70,6 @@ final class DifferentialChangesetOneUpRenderer } $line = $p['line']; - if ($o_hidden) { - $line = array($hidden, $line); - } $cells[] = phutil_tag( 'th', @@ -122,9 +96,6 @@ final class DifferentialChangesetOneUpRenderer } $oline = $p['oline']; - if ($o_hidden) { - $oline = array($hidden, $oline); - } $cells[] = phutil_tag('th', array('id' => $left_id), $oline); } @@ -140,9 +111,6 @@ final class DifferentialChangesetOneUpRenderer } $line = $p['line']; - if ($n_hidden) { - $line = array($hidden, $line); - } $cells[] = phutil_tag( 'th', diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 9e4f9c049d..688544513e 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -69,8 +69,6 @@ final class DifferentialChangesetTwoUpRenderer $depths = $this->getDepths(); $mask = $this->getMask(); - $hidden = new PHUIDiffRevealIconView(); - for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { // If we aren't going to show this line, we've just entered a gap. @@ -241,9 +239,6 @@ final class DifferentialChangesetTwoUpRenderer $new_comments = $this->getNewComments(); $scaffolds = array(); - $o_hidden = array(); - $n_hidden = array(); - if ($o_num && isset($old_comments[$o_num])) { foreach ($old_comments[$o_num] as $comment) { $inline = $this->buildInlineComment( @@ -251,10 +246,6 @@ final class DifferentialChangesetTwoUpRenderer $on_right = false); $scaffold = $this->getRowScaffoldForInline($inline); - if ($comment->isHidden()) { - $o_hidden[] = $comment; - } - if ($n_num && isset($new_comments[$n_num])) { foreach ($new_comments[$n_num] as $key => $new_comment) { if ($comment->isCompatible($new_comment)) { @@ -262,10 +253,6 @@ final class DifferentialChangesetTwoUpRenderer $new_comment, $on_right = true); - if ($new_comment->isHidden()) { - $n_hidden = $new_comment; - } - $scaffold->addInlineView($companion); unset($new_comments[$n_num][$key]); break; @@ -284,22 +271,10 @@ final class DifferentialChangesetTwoUpRenderer $comment, $on_right = true); - if ($comment->isHidden()) { - $n_hidden[] = $comment; - } - $scaffolds[] = $this->getRowScaffoldForInline($inline); } } - if ($o_hidden) { - $o_num = array($hidden, $o_num); - } - - if ($n_hidden) { - $n_num = array($hidden, $n_num); - } - // NOTE: This is a unicode zero-width space, which we use as a hint when // intercepting 'copy' events to make sure sensible text ends up on the // clipboard. See the 'phabricator-oncopy' behavior. diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index eed5467b63..219dfaaa1f 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -166,7 +166,7 @@ final class DifferentialChangesetDetailView extends AphrontView { 'ref' => $this->getRenderingRef(), 'autoload' => $this->getAutoload(), 'loaded' => $this->getLoaded(), - 'undoTemplates' => $renderer->renderUndoTemplates(), + 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), ), 'class' => $class, 'id' => $id, diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 313bcbe694..eb60a3dee1 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -146,11 +146,6 @@ final class DifferentialChangesetListView extends AphrontView { foreach ($changesets as $key => $changeset) { $file = $changeset->getFilename(); - $class = 'differential-changeset'; - if (!$this->inlineURI) { - $class .= ' differential-changeset-noneditable'; - } - $ref = $this->references[$key]; $detail = id(new DifferentialChangesetDetailView()) @@ -219,6 +214,7 @@ final class DifferentialChangesetListView extends AphrontView { 'differential-populate', array( 'changesetViewIDs' => $ids, + 'inlineURI' => $this->inlineURI, 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show All Context' => pht('Show All Context'), @@ -247,7 +243,6 @@ final class DifferentialChangesetListView extends AphrontView { Javelin::initBehavior('differential-edit-inline-comments', array( 'uri' => $this->inlineURI, 'stage' => 'differential-review-stage', - 'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()), )); } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index f01bfbdb65..9f5616f1cc 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -450,7 +450,20 @@ final class PHUIDiffInlineCommentDetailView phutil_tag_div('phabricator-remarkup', $content)), )); - return $markup; + $summary = phutil_tag( + 'div', + array( + 'class' => 'differential-inline-summary', + ), + + // TODO: Render something a little more useful here as a hint about the + // inline content, like "alincoln: first line of text...". + pht('...')); + + return array( + $markup, + $summary, + ); } private function canHide() { diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php index b0c0b4ccac..7c6b1c54b5 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php @@ -21,21 +21,28 @@ abstract class PHUIDiffInlineCommentRowScaffold extends AphrontView { } protected function getRowAttributes() { - // TODO: This is semantic information used by the JS when placing comments - // and using keyboard navigation; we should move it out of class names. - - $style = null; + $is_hidden = false; foreach ($this->getInlineViews() as $view) { if ($view->isHidden()) { - $style = 'display: none'; + $is_hidden = true; } } - return array( - 'class' => 'inline', + $classes = array(); + $classes[] = 'inline'; + if ($is_hidden) { + $classes[] = 'inline-hidden'; + } + + $result = array( + 'class' => implode(' ', $classes), 'sigil' => 'inline-row', - 'style' => $style, + 'meta' => array( + 'hidden' => $is_hidden, + ), ); + + return $result; } } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php index c4bdd65bf8..4abdb00e0b 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php @@ -9,6 +9,10 @@ final class PHUIDiffInlineCommentUndoView extends PHUIDiffInlineCommentView { + public function isHideable() { + return false; + } + public function render() { $link = javelin_tag( 'a', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php index b62160e232..e2c89e238c 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php @@ -21,4 +21,16 @@ abstract class PHUIDiffInlineCommentView extends AphrontView { return false; } + public function isHideable() { + return true; + } + + public function newHiddenIcon() { + if ($this->isHideable()) { + return new PHUIDiffRevealIconView(); + } else { + return null; + } + } + } diff --git a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php index 708b70b360..53c2255dc8 100644 --- a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php @@ -22,9 +22,17 @@ final class PHUIDiffOneUpInlineCommentRowScaffold 'id' => $inline->getScaffoldCellID(), ); + if ($inline->getIsOnRight()) { + $left_hidden = null; + $right_hidden = $inline->newHiddenIcon(); + } else { + $left_hidden = $inline->newHiddenIcon(); + $right_hidden = null; + } + $cells = array( - phutil_tag('th', array()), - phutil_tag('th', array()), + phutil_tag('th', array(), $left_hidden), + phutil_tag('th', array(), $right_hidden), phutil_tag('td', $attrs, $inline), ); diff --git a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php index 284b72b2be..8ca3eae2c1 100644 --- a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php +++ b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php @@ -8,7 +8,7 @@ final class PHUIDiffRevealIconView extends AphrontView { ->addSigil('has-tooltip') ->setMetadata( array( - 'tip' => pht('Show Hidden Comments'), + 'tip' => pht('Show Hidden Comment'), 'align' => 'E', 'size' => 275, )); @@ -17,8 +17,8 @@ final class PHUIDiffRevealIconView extends AphrontView { 'a', array( 'href' => '#', - 'class' => 'reveal-inlines', - 'sigil' => 'reveal-inlines', + 'class' => 'reveal-inline', + 'sigil' => 'reveal-inline', 'mustcapture' => true, ), $icon); diff --git a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php index 4fac5088d1..81b0edaf49 100644 --- a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php @@ -27,9 +27,15 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold if ($inline->getIsOnRight()) { $left_side = null; $right_side = $inline; + + $left_hidden = null; + $right_hidden = $inline->newHiddenIcon(); } else { $left_side = $inline; $right_side = null; + + $left_hidden = $inline->newHiddenIcon(); + $right_hidden = null; } } else { list($u, $v) = $inlines; @@ -48,6 +54,9 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold $left_side = $v; $right_side = $u; } + + $left_hidden = null; + $right_hidden = null; } $left_attrs = array( @@ -62,9 +71,9 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold ); $cells = array( - phutil_tag('th', array()), + phutil_tag('th', array(), $left_hidden), phutil_tag('td', $left_attrs, $left_side), - phutil_tag('th', array()), + phutil_tag('th', array(), $right_hidden), phutil_tag('td', $right_attrs, $right_side), ); diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index f93b2c8d6c..3e28bcd0d4 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -374,17 +374,36 @@ /* - Hiding Inlines ------------------------------------------------------------ */ -.reveal-inlines { - float: left; - margin-left: 4px; +.reveal-inline { + color: {$lightbluetext}; + margin: 4px 0; + display: none; +} + +.inline-hidden .reveal-inline { + display: block; +} + +.inline-hidden .differential-inline-comment { + display: none; +} + +.differential-inline-summary { + background: {$greybackground}; + padding: 0 4px; + color: {$greytext}; + display: none; +} + +.inline-hidden .differential-inline-summary { + display: block; +} + +.reveal-inline span.phui-icon-view { color: {$lightbluetext}; } -.reveal-inlines span.phui-icon-view { - color: {$lightbluetext}; -} - -.reveal-inlines:hover span.phui-icon-view { +.reveal-inline:hover span.phui-icon-view { color: {$darkbluetext}; } diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index c8a8462519..107df87b0d 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -8,6 +8,8 @@ * javelin-router * javelin-behavior-device * javelin-vector + * phabricator-diff-inline + * @javelin */ @@ -24,6 +26,8 @@ JX.install('DiffChangeset', { this._highlight = data.highlight; this._encoding = data.encoding; this._loaded = data.loaded; + + this._inlines = []; }, properties: { @@ -44,6 +48,7 @@ JX.install('DiffChangeset', { _encoding: null, _undoTemplates: null, + _inlines: null, /** * Has the content of this changeset been loaded? @@ -401,6 +406,20 @@ JX.install('DiffChangeset', { _getRoutableKey: function() { return 'changeset-view.' + this._ref + '.' + this._sequence; + }, + + getInlineForRow: function(node) { + var data = JX.Stratcom.getData(node); + + if (!data.inline) { + var inline = new JX.DiffInline(node) + .setChangeset(this); + + this._inlines.push(inline); + data.inline = inline; + } + + return data.inline; } }, diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 5bcbc63626..36208bb9bf 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -17,10 +17,17 @@ JX.install('DiffChangesetList', { var onmenu = JX.bind(this, this._ifawake, this._onmenu); JX.Stratcom.listen('click', 'differential-view-options', onmenu); + + var onhide = JX.bind(this, this._ifawake, this._onhide); + JX.Stratcom.listen('click', 'hide-inline', onhide); + + var onreveal = JX.bind(this, this._ifawake, this._onreveal); + JX.Stratcom.listen('click', 'reveal-inline', onreveal); }, properties: { - translations: null + translations: null, + inlineURI: null }, members: { @@ -309,10 +316,28 @@ JX.install('DiffChangesetList', { data.menu = menu; menu.open(); + }, + + _onhide: function(e) { + this._onhidereveal(e, true); + }, + + _onreveal: function(e) { + this._onhidereveal(e, false); + }, + + _onhidereveal: function(e, is_hide) { + e.kill(); + + var node = e.getNode('differential-changeset'); + var changeset = this.getChangesetForNode(node); + + var inline_node = e.getNode('inline-row'); + var inline = changeset.getInlineForRow(inline_node); + + inline.setHidden(is_hide); } - - } }); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js new file mode 100644 index 0000000000..67f2e3f116 --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -0,0 +1,56 @@ +/** + * @provides phabricator-diff-inline + * @requires javelin-dom + * @javelin + */ + +JX.install('DiffInline', { + + construct : function(row) { + this._row = row; + + var data = JX.Stratcom.getData(row); + this._hidden = data.hidden || false; + + // TODO: Get smarter about this once we do more editing, this is pretty + // hacky. + var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); + this._id = JX.Stratcom.getData(comment).id; + }, + + properties: { + changeset: null + }, + + members: { + _id: null, + _row: null, + _hidden: false, + + setHidden: function(hidden) { + this._hidden = hidden; + + JX.DOM.alterClass(this._row, 'inline-hidden', this._hidden); + + var op; + if (hidden) { + op = 'hide'; + } else { + op = 'show'; + } + + var inline_uri = this._getChangesetList().getInlineURI(); + var comment_id = this._id; + + new JX.Workflow(inline_uri, {op: op, ids: comment_id}) + .setHandler(JX.bag) + .start(); + }, + + _getChangesetList: function() { + var changeset = this.getChangeset(); + return changeset.getChangesetList(); + } + } + +}); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 1859ca1754..942ef74775 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -425,87 +425,4 @@ JX.behavior('differential-edit-inline-comments', function(config) { handle_inline_action(data.node, data.op); }); - // Respond to the user clicking the "Hide Inline" button on an inline - // comment. - JX.Stratcom.listen('click', 'hide-inline', function(e) { - e.kill(); - - var row = e.getNode('inline-row'); - JX.DOM.hide(row); - - var prev = row.previousSibling; - while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { - prev = prev.previousSibling; - } - - if (!prev) { - return; - } - - var comment = e.getNodeData('differential-inline-comment'); - - var slots = []; - for (var ii = 0; ii < prev.childNodes.length; ii++) { - if (JX.DOM.isType(prev.childNodes[ii], 'th')) { - slots.push(prev.childNodes[ii]); - } - } - - // Select the right-hand side if the comment is on the right. - var slot = (comment.on_right && slots[1]) || slots[0]; - - var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; - if (!reveal) { - reveal = JX.$N( - 'a', - { - className: 'reveal-inlines', - sigil: 'reveal-inlines' - }, - JX.$H(config.revealIcon)); - - JX.DOM.prependContent(slot, reveal); - } - - new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) - .setHandler(JX.bag) - .start(); - }); - - JX.Stratcom.listen('click', 'reveal-inlines', function(e) { - e.kill(); - - var row = e.getNode('tag:tr'); - var next = row.nextSibling; - - var ids = []; - var ii; - - // Show any hidden inline comment rows directly below this one. - while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { - JX.DOM.show(next); - - var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); - for (ii = 0; ii < comments.length; ii++) { - var id = JX.Stratcom.getData(comments[ii]).id; - if (id) { - ids.push(id); - } - } - - next = next.nextSibling; - } - - // Remove any "reveal" icons on the row. - var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); - for (ii = 0; ii < reveals.length; ii++) { - JX.DOM.remove(reveals[ii]); - } - - new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) - .setHandler(JX.bag) - .start(); - }); - - }); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 02b8c1e896..e5b0d5039d 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -59,7 +59,8 @@ JX.behavior('differential-populate', function(config, statics) { } var changeset_list = new JX.DiffChangesetList() - .setTranslations(JX.phtize(config.pht)); + .setTranslations(JX.phtize(config.pht)) + .setInlineURI(config.inlineURI); // Install and activate the current page. var page_id = JX.Quicksand.getCurrentPageID(); @@ -71,7 +72,6 @@ JX.behavior('differential-populate', function(config, statics) { for (var ii = 0; ii < config.changesetViewIDs.length; ii++) { var id = config.changesetViewIDs[ii]; var node = JX.$(id); - var changeset = changeset_list.newChangesetForNode(node); if (changeset.shouldAutoload()) { changeset.setStabilize(true).load(); From 798c8ba696433cf57fd09764a4b723df4a784377 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 12:48:55 -0700 Subject: [PATCH 074/543] Mostly move inline editing to DiffInline Summary: Ref T12616. This doesn't pull over everything (some UI feedback didn't make it yet, and you can't cancel + undo cancelling edits yet) but editing comments technically works. This is a little shaky, but feels less shaky than every other approach I've tried, so I think I'm finally on a reasonable track here. Test Plan: Edited some inline comments. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17887 --- resources/celerity/map.php | 42 ++-- .../js/application/diff/DiffChangesetList.js | 33 +++- .../rsrc/js/application/diff/DiffInline.js | 180 +++++++++++++++++- .../behavior-edit-inline-comments.js | 37 ++-- 4 files changed, 249 insertions(+), 43 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5dbb7d0a59..40f90cde8c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '70685319', + 'differential.pkg.js' => 'aa750623', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,14 +391,14 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '80ac3298', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'a34b9821', - 'rsrc/js/application/diff/DiffInline.js' => 'f9e76f2d', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'ecc9542e', + 'rsrc/js/application/diff/DiffInline.js' => '586c15ff', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'e7e9551e', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '974bab6a', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', @@ -624,7 +624,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => 'e7e9551e', + 'javelin-behavior-differential-edit-inline-comments' => '974bab6a', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', 'javelin-behavior-differential-populate' => '5e41c819', @@ -786,8 +786,8 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '80ac3298', - 'phabricator-diff-changeset-list' => 'a34b9821', - 'phabricator-diff-inline' => 'f9e76f2d', + 'phabricator-diff-changeset-list' => 'ecc9542e', + 'phabricator-diff-inline' => '586c15ff', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1347,6 +1347,9 @@ return array( 'javelin-vector', 'javelin-dom', ), + '586c15ff' => array( + 'javelin-dom', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1672,6 +1675,14 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), + '974bab6a' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), '988040b4' => array( 'javelin-install', 'javelin-dom', @@ -1719,9 +1730,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - 'a34b9821' => array( - 'javelin-install', - ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -2156,14 +2164,6 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), - 'e7e9551e' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2171,6 +2171,9 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'ecc9542e' => array( + 'javelin-install', + ), 'eded9ee8' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2228,9 +2231,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'f9e76f2d' => array( - 'javelin-dom', - ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 36208bb9bf..701a9f8461 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -23,6 +23,12 @@ JX.install('DiffChangesetList', { var onreveal = JX.bind(this, this._ifawake, this._onreveal); JX.Stratcom.listen('click', 'reveal-inline', onreveal); + + var onedit = JX.bind(this, this._ifawake, this._onaction, 'edit'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-edit'], + onedit); }, properties: { @@ -329,13 +335,32 @@ JX.install('DiffChangesetList', { _onhidereveal: function(e, is_hide) { e.kill(); + var inline = this._getInlineForEvent(e); + + inline.setHidden(is_hide); + }, + + _onaction: function(action, e) { + // TODO: This can become a kill once things fully switch over.. + e.prevent(); + + var inline = this._getInlineForEvent(e); + + // TODO: For normal operations, highlight the inline range here. + + switch (action) { + case 'edit': + inline.edit(); + break; + } + }, + + _getInlineForEvent: function(e) { var node = e.getNode('differential-changeset'); var changeset = this.getChangesetForNode(node); - var inline_node = e.getNode('inline-row'); - var inline = changeset.getInlineForRow(inline_node); - - inline.setHidden(is_hide); + var inline_row = e.getNode('inline-row'); + return changeset.getInlineForRow(inline_row); } } diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 67f2e3f116..c6bc71a5f4 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -9,13 +9,30 @@ JX.install('DiffInline', { construct : function(row) { this._row = row; - var data = JX.Stratcom.getData(row); - this._hidden = data.hidden || false; + var row_data = JX.Stratcom.getData(row); + this._hidden = row_data.hidden || false; // TODO: Get smarter about this once we do more editing, this is pretty // hacky. var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); - this._id = JX.Stratcom.getData(comment).id; + var data = JX.Stratcom.getData(comment); + + this._id = data.id; + + // TODO: This is very, very, very, very, very, very, very hacky. + var td = comment.parentNode; + var th = td.previousSibling; + if (th.parentNode.firstChild != th) { + this._displaySide = 'right'; + } else { + this._displaySide = 'left'; + } + + this._number = data.number; + this._length = data.length; + this._isNewFile = + (this.getDisplaySide() == 'right') || + (data.left != data.right); }, properties: { @@ -26,6 +43,10 @@ JX.install('DiffInline', { _id: null, _row: null, _hidden: false, + _number: null, + _length: null, + _displaySide: null, + _isNewFile: null, setHidden: function(hidden) { this._hidden = hidden; @@ -47,6 +68,159 @@ JX.install('DiffInline', { .start(); }, + edit: function() { + var handler = JX.bind(this, this._oneditresponse); + var uri = this.getChangeset().getChangesetList().getInlineURI(); + var data = this._newRequestData(); + + // TODO: Set state to "loading". + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + + getDisplaySide: function() { + return this._displaySide; + }, + + getLineNumber: function() { + return this._number; + }, + + getLineLength: function() { + return this._length; + }, + + isNewFile: function() { + return this._isNewFile; + }, + + _newRequestData: function() { + return { + op: 'edit', + id: this._id, + on_right: ((this.getDisplaySide() == 'right') ? 1 : 0), + renderer: this.getChangeset().getRenderer(), + number: this.getLineNumber(), + length: this.getLineLength(), + is_new: this.isNewFile(), + replyToCommentPHID: '' + }; + }, + + _oneditresponse: function(response) { + var rows = JX.$H(response).getNode(); + + this._drawEditRows(rows); + + // TODO: Set the row state to "hidden". + }, + + _drawEditRows: function(rows) { + var first_row = JX.DOM.scry(rows, 'tr')[0]; + var row = first_row; + var cursor = this._row; + + while (row) { + cursor.parentNode.insertBefore(row, cursor.nextSibling); + cursor = row; + + var row_meta = { + node: row, + type: 'edit', + listeners: [] + }; + + row_meta.listeners.push( + JX.DOM.listen( + row, + ['submit', 'didSyntheticSubmit'], + 'inline-edit-form', + JX.bind(this, this._onsubmit, row_meta))); + + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'inline-edit-cancel', + JX.bind(this, this._oncancel, row_meta))); + + row = row.nextSibling; + } + + return first_row; + }, + + _onsubmit: function(row, e) { + e.kill(); + + var handler = JX.bind(this, this._onsubmitresponse, row); + + JX.Workflow.newFromForm(e.getTarget()) + .setHandler(handler) + .start(); + + // TODO: Set state to "loading". + }, + + _oncancel: function(row, e) { + e.kill(); + + // TODO: Capture edited text and offer "undo". + + JX.DOM.remove(row.node); + this._removeListeners(row.listeners); + + // TODO: Restore state to "normal". + }, + + _onsubmitresponse: function(row, response) { + + JX.DOM.remove(row.node); + this._removeListeners(row.listeners); + + // TODO: Restore state to "normal". + + this._onupdate(response); + }, + + _onupdate: function(response) { + var new_row; + if (response.markup) { + new_row = this._drawEditRows(JX.$H(response.markup).getNode()); + } + + // TODO: Save the old row so the action it's undo-able if it was a + // delete. + var remove_old = true; + if (remove_old) { + JX.DOM.remove(this._row); + } + + this._row = new_row; + + this._didUpdate(); + }, + + _didUpdate: function() { + // After making changes to inline comments, refresh the transaction + // preview at the bottom of the page. + + // TODO: This isn't the cleanest way to find the preview form, but + // rendering no longer has direct access to it. + var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); + if (forms.length) { + JX.DOM.invoke(forms[0], 'shouldRefresh'); + } + }, + + _removeListeners: function(listeners) { + for (var ii = 0; ii < listeners.length; ii++) { + listeners[ii].remove(); + } + }, + _getChangesetList: function() { var changeset = this.getChangeset(); return changeset.getChangesetList(); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 942ef74775..904d8f655e 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -310,7 +310,9 @@ JX.behavior('differential-edit-inline-comments', function(config) { }); var action_handler = function(op, e) { - e.kill(); + // NOTE: We prevent this event, rather than killing it, because some + // actions are now handled by DiffChangesetList. + e.prevent(); if (editor) { return; @@ -392,20 +394,25 @@ JX.behavior('differential-edit-inline-comments', function(config) { 'differential-changeset'); var view = JX.DiffChangeset.getForNode(changeset_root); - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation(op) - .setID(data.id) - .setChangesetID(data.changesetID) - .setLineNumber(data.number) - .setLength(data.length) - .setOnRight(data.on_right) - .setOriginalText(original) - .setRow(row) - .setTable(row.parentNode) - .setReplyToCommentPHID(reply_phid) - .setRenderer(view.getRenderer()) - .start(); + if (op == 'edit') { + // This is now handled by DiffChangesetList. + editor = true; + } else { + editor = new JX.DifferentialInlineCommentEditor(config.uri) + .setTemplates(view.getUndoTemplates()) + .setOperation(op) + .setID(data.id) + .setChangesetID(data.changesetID) + .setLineNumber(data.number) + .setLength(data.length) + .setOnRight(data.on_right) + .setOriginalText(original) + .setRow(row) + .setTable(row.parentNode) + .setReplyToCommentPHID(reply_phid) + .setRenderer(view.getRenderer()) + .start(); + } set_link_state(true); }; From 3c18cb77fb66b19c41f87754f183c15e18c4d32a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 13:08:55 -0700 Subject: [PATCH 075/543] Move inline "done" checkboxing to DiffInline Summary: Ref T12616. This updates clicking the "Done" checkbox for the new stuff. This one is pretty clean since the "Done" checkbox doesn't do too much weird magic. Test Plan: Clicked the box a few times. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17888 --- resources/celerity/map.php | 48 +++++++++---------- .../PhabricatorInlineCommentController.php | 4 ++ .../js/application/diff/DiffChangesetList.js | 9 ++++ .../rsrc/js/application/diff/DiffInline.js | 46 ++++++++++++++++-- .../DifferentialInlineCommentEditor.js | 22 --------- .../behavior-edit-inline-comments.js | 9 +--- 6 files changed, 80 insertions(+), 58 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 40f90cde8c..484d0ca1c5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'aa750623', + 'differential.pkg.js' => 'e01579c4', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,14 +391,14 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '80ac3298', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'ecc9542e', - 'rsrc/js/application/diff/DiffInline.js' => '586c15ff', + 'rsrc/js/application/diff/DiffChangesetList.js' => '12575699', + 'rsrc/js/application/diff/DiffInline.js' => 'bd1b3258', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', + 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '9ed8d2b6', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '974bab6a', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '97f363fc', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', @@ -572,7 +572,7 @@ return array( 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '41af6d25', 'differential-core-view-css' => '5b7b8ff4', - 'differential-inline-comment-editor' => '2e3f9738', + 'differential-inline-comment-editor' => '9ed8d2b6', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', @@ -624,7 +624,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => '974bab6a', + 'javelin-behavior-differential-edit-inline-comments' => '97f363fc', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', 'javelin-behavior-differential-populate' => '5e41c819', @@ -786,8 +786,8 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '80ac3298', - 'phabricator-diff-changeset-list' => 'ecc9542e', - 'phabricator-diff-inline' => '586c15ff', + 'phabricator-diff-changeset-list' => '12575699', + 'phabricator-diff-inline' => 'bd1b3258', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1000,6 +1000,9 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), + 12575699 => array( + 'javelin-install', + ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1115,14 +1118,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2e3f9738' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-request', - 'javelin-workflow', - ), '2ee659ce' => array( 'javelin-install', ), @@ -1347,9 +1342,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '586c15ff' => array( - 'javelin-dom', - ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1675,7 +1667,7 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), - '974bab6a' => array( + '97f363fc' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', @@ -1711,6 +1703,14 @@ return array( '9d9685d6' => array( 'phui-oi-list-view-css', ), + '9ed8d2b6' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-request', + 'javelin-workflow', + ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1910,6 +1910,9 @@ return array( 'javelin-vector', 'phui-hovercard', ), + 'bd1b3258' => array( + 'javelin-dom', + ), 'bdaf4d04' => array( 'javelin-behavior', 'javelin-dom', @@ -2171,9 +2174,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'ecc9542e' => array( - 'javelin-install', - ), 'eded9ee8' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 1c92b167b0..1c624c1ad8 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -130,12 +130,14 @@ abstract class PhabricatorInlineCommentController $inline = $this->loadCommentForDone($this->getCommentID()); $is_draft_state = false; + $is_checked = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DRAFT: $next_state = PhabricatorInlineCommentInterface::STATE_UNDONE; break; case PhabricatorInlineCommentInterface::STATE_UNDRAFT: $next_state = PhabricatorInlineCommentInterface::STATE_DONE; + $is_checked = true; break; case PhabricatorInlineCommentInterface::STATE_DONE: $next_state = PhabricatorInlineCommentInterface::STATE_UNDRAFT; @@ -145,6 +147,7 @@ abstract class PhabricatorInlineCommentController case PhabricatorInlineCommentInterface::STATE_UNDONE: $next_state = PhabricatorInlineCommentInterface::STATE_DRAFT; $is_draft_state = true; + $is_checked = true; break; } @@ -153,6 +156,7 @@ abstract class PhabricatorInlineCommentController return id(new AphrontAjaxResponse()) ->setContent( array( + 'isChecked' => $is_checked, 'draftState' => $is_draft_state, )); case 'delete': diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 701a9f8461..70b360d427 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -29,6 +29,12 @@ JX.install('DiffChangesetList', { 'click', ['differential-inline-comment', 'differential-inline-edit'], onedit); + + var ondone = JX.bind(this, this._ifawake, this._onaction, 'done'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-done'], + ondone); }, properties: { @@ -352,6 +358,9 @@ JX.install('DiffChangesetList', { case 'edit': inline.edit(); break; + case 'done': + inline.toggleDone(); + break; } }, diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index c6bc71a5f4..5566fe0f7c 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -60,7 +60,7 @@ JX.install('DiffInline', { op = 'show'; } - var inline_uri = this._getChangesetList().getInlineURI(); + var inline_uri = this._getInlineURI(); var comment_id = this._id; new JX.Workflow(inline_uri, {op: op, ids: comment_id}) @@ -68,9 +68,46 @@ JX.install('DiffInline', { .start(); }, + toggleDone: function() { + var uri = this._getInlineURI(); + var data = { + op: 'done', + id: this._id + }; + + var ondone = JX.bind(this, this._ondone); + + new JX.Workflow(uri, data) + .setHandler(ondone) + .start(); + }, + + _ondone: function(response) { + var checkbox = JX.DOM.find( + this._row, + 'input', + 'differential-inline-done'); + + checkbox.checked = (response.isChecked ? 'checked' : null); + + var comment = JX.DOM.findAbove( + checkbox, + 'div', + 'differential-inline-comment'); + + JX.DOM.alterClass(comment, 'inline-is-done', response.isChecked); + + // NOTE: This is marking the inline as having an unsubmitted checkmark, + // as opposed to a submitted checkmark. This is different from the + // top-level "draft" state of unsubmitted comments. + JX.DOM.alterClass(comment, 'inline-state-is-draft', response.draftState); + + this._didUpdate(); + }, + edit: function() { var handler = JX.bind(this, this._oneditresponse); - var uri = this.getChangeset().getChangesetList().getInlineURI(); + var uri = this._getInlineURI(); var data = this._newRequestData(); // TODO: Set state to "loading". @@ -221,9 +258,10 @@ JX.install('DiffInline', { } }, - _getChangesetList: function() { + _getInlineURI: function() { var changeset = this.getChangeset(); - return changeset.getChangesetList(); + var list = changeset.getChangesetList(); + return list.getInlineURI(); } } diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js index 7e6015d969..7068e94c14 100644 --- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js +++ b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js @@ -312,28 +312,6 @@ JX.install('DifferentialInlineCommentEditor', { .start(); }, - toggleCheckbox: function(id, checkbox) { - var data = { - op: 'done', - id: id - }; - - new JX.Workflow(this._uri, data) - .setHandler(JX.bind(this, function(r) { - checkbox.checked = !checkbox.checked; - - var comment = JX.DOM.findAbove( - checkbox, - 'div', - 'differential-inline-comment'); - JX.DOM.alterClass(comment, 'inline-is-done', !!checkbox.checked); - JX.DOM.alterClass(comment, 'inline-state-is-draft', r.draftState); - - this._didUpdate(); - })) - .start(); - }, - _didUpdate: function() { // After making changes to inline comments, refresh the transaction // preview at the bottom of the page. diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 904d8f655e..20eec1a3d5 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -371,13 +371,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { } } - if (op == 'done') { - var checkbox = JX.DOM.find(node, 'input', 'differential-inline-done'); - new JX.DifferentialInlineCommentEditor(config.uri) - .toggleCheckbox(data.id, checkbox); - return; - } - var original = data.original; var reply_phid = null; if (op == 'reply') { @@ -417,7 +410,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { set_link_state(true); }; - for (var op in {'edit': 1, 'delete': 1, 'reply': 1, 'done': 1}) { + for (var op in {'edit': 1, 'delete': 1, 'reply': 1}) { JX.Stratcom.listen( 'click', ['differential-inline-comment', 'differential-inline-' + op], From d97f80bc901832af7471d96fde7977ecfd4854ae Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 15:03:30 -0700 Subject: [PATCH 076/543] Make new DiffInline code handle most "delete" operations Summary: Ref T12616. This moves the delete actions to the new, more stateful way of doing things. These are a little tricky because you can click "Delete" on an inline, but you can also click "Delete" from the preview area at the bottom of the page. If you do, the inline you are deleting may or may not be present on the page. This has a few bugs -- notably, deleting from the preview without interacting with the on-page inline first won't actually delete the on-page inline yet -- but nothing too serious. Test Plan: Deleted inlines, undid deletion, deleted from preview. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17890 --- resources/celerity/map.php | 68 +++--- .../rsrc/js/application/diff/DiffChangeset.js | 18 +- .../js/application/diff/DiffChangesetList.js | 76 +++++++ .../rsrc/js/application/diff/DiffInline.js | 213 ++++++++++++++---- .../behavior-edit-inline-comments.js | 75 ++---- 5 files changed, 312 insertions(+), 138 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 484d0ca1c5..5b33419132 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'e01579c4', + 'differential.pkg.js' => '271a1e1e', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,15 +390,15 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '80ac3298', - 'rsrc/js/application/diff/DiffChangesetList.js' => '12575699', - 'rsrc/js/application/diff/DiffInline.js' => 'bd1b3258', + 'rsrc/js/application/diff/DiffChangeset.js' => '57b29223', + 'rsrc/js/application/diff/DiffChangesetList.js' => '50bc5b50', + 'rsrc/js/application/diff/DiffInline.js' => '64dfc791', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '9ed8d2b6', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '97f363fc', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '9d9dbc38', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', @@ -624,7 +624,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => '97f363fc', + 'javelin-behavior-differential-edit-inline-comments' => '9d9dbc38', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', 'javelin-behavior-differential-populate' => '5e41c819', @@ -785,9 +785,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '80ac3298', - 'phabricator-diff-changeset-list' => '12575699', - 'phabricator-diff-inline' => 'bd1b3258', + 'phabricator-diff-changeset' => '57b29223', + 'phabricator-diff-changeset-list' => '50bc5b50', + 'phabricator-diff-inline' => '64dfc791', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1000,9 +1000,6 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - 12575699 => array( - 'javelin-install', - ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1301,6 +1298,9 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '50bc5b50' => array( + 'javelin-install', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1342,6 +1342,17 @@ return array( 'javelin-vector', 'javelin-dom', ), + '57b29223' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1411,6 +1422,9 @@ return array( 'javelin-workflow', 'javelin-dom', ), + '64dfc791' => array( + 'javelin-dom', + ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', @@ -1530,17 +1544,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '80ac3298' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1667,14 +1670,6 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), - '97f363fc' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), '988040b4' => array( 'javelin-install', 'javelin-dom', @@ -1703,6 +1698,14 @@ return array( '9d9685d6' => array( 'phui-oi-list-view-css', ), + '9d9dbc38' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), '9ed8d2b6' => array( 'javelin-dom', 'javelin-util', @@ -1910,9 +1913,6 @@ return array( 'javelin-vector', 'phui-hovercard', ), - 'bd1b3258' => array( - 'javelin-dom', - ), 'bdaf4d04' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 107df87b0d..b52e54aaed 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -322,7 +322,6 @@ JX.install('DiffChangeset', { return JX.Stratcom.getData(this._node); }, - _onresponse: function(sequence, response) { if (sequence != this._sequence) { // If this isn't the most recent request, ignore it. This normally @@ -420,8 +419,25 @@ JX.install('DiffChangeset', { } return data.inline; + }, + + getInlineByID: function(id) { + // TODO: Currently, this will only find inlines which the user has + // already interacted with! Inlines are built lazily as events arrive. + // This can not yet find inlines which are passively present in the + // document. + + for (var ii = 0; ii < this._inlines.length; ii++) { + var inline = this._inlines[ii]; + if (inline.getID() == id) { + return inline; + } + } + + return null; } + }, statics: { diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 70b360d427..e46a33bb56 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -35,6 +35,12 @@ JX.install('DiffChangesetList', { 'click', ['differential-inline-comment', 'differential-inline-done'], ondone); + + var ondelete = JX.bind(this, this._ifawake, this._onaction, 'delete'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-delete'], + ondelete); }, properties: { @@ -71,6 +77,19 @@ JX.install('DiffChangesetList', { return JX.DiffChangeset.getForNode(node); }, + getInlineByID: function(id) { + var inline = null; + + for (var ii = 0; ii < this._changesets.length; ii++) { + inline = this._changesets[ii].getInlineByID(id); + if (inline) { + break; + } + } + + return inline; + }, + _ifawake: function(f) { // This function takes another function and only calls it if the // changeset list is awake, so we basically just ignore events when we @@ -351,6 +370,33 @@ JX.install('DiffChangesetList', { e.prevent(); var inline = this._getInlineForEvent(e); + var is_ref = false; + + // If we don't have a natural inline object, the user may have clicked + // an action (like "Delete") inside a preview element at the bottom of + // the page. + + // If they did, try to find an associated normal inline to act on, and + // pretend they clicked that instead. This makes the overall state of + // the page more consistent. + + // However, there may be no normal inline (for example, because it is + // on a version of the diff which is not visible). In this case, we + // act by reference. + + if (inline === null) { + var data = e.getNodeData('differential-inline-comment'); + inline = this.getInlineByID(data.id); + if (inline) { + is_ref = true; + } else { + switch (action) { + case 'delete': + this._deleteInlineByID(data.id); + return; + } + } + } // TODO: For normal operations, highlight the inline range here. @@ -361,11 +407,41 @@ JX.install('DiffChangesetList', { case 'done': inline.toggleDone(); break; + case 'delete': + inline.delete(is_ref); + break; } }, + redrawPreview: function() { + // TODO: This isn't the cleanest way to find the preview form, but + // rendering no longer has direct access to it. + var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); + if (forms.length) { + JX.DOM.invoke(forms[0], 'shouldRefresh'); + } + }, + + _deleteInlineByID: function(id) { + var uri = this.getInlineURI(); + var data = { + op: 'refdelete', + id: id + }; + + var handler = JX.bind(this, this.redrawPreview); + + new JX.Workflow(uri, data) + .setHandler(handler) + .start(); + }, + _getInlineForEvent: function(e) { var node = e.getNode('differential-changeset'); + if (!node) { + return null; + } + var changeset = this.getChangesetForNode(node); var inline_row = e.getNode('inline-row'); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 5566fe0f7c..efa16bf9cd 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -47,6 +47,11 @@ JX.install('DiffInline', { _length: null, _displaySide: null, _isNewFile: null, + _undoRow: null, + + _isDeleted: false, + _isInvisible: false, + _isLoading: false, setHidden: function(hidden) { this._hidden = hidden; @@ -106,17 +111,47 @@ JX.install('DiffInline', { }, edit: function() { - var handler = JX.bind(this, this._oneditresponse); var uri = this._getInlineURI(); - var data = this._newRequestData(); + var handler = JX.bind(this, this._oneditresponse); + var data = this._newRequestData('edit'); - // TODO: Set state to "loading". + this.setLoading(true); new JX.Request(uri, handler) .setData(data) .send(); }, + delete: function(is_ref) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._ondeleteresponse); + + // NOTE: This may be a direct delete (the user clicked on the inline + // itself) or a "refdelete" (the user clicked somewhere else, like the + // preview, but the inline is present on the page). + + // For a "refdelete", we prompt the user to confirm that they want to + // delete the comment, because they can not undo deletions from the + // preview. We could jump the user to the inline instead, but this would + // be somewhat disruptive and make deleting several comments more + // difficult. + + var op; + if (is_ref) { + op = 'refdelete'; + } else { + op = 'delete'; + } + + var data = this._newRequestData(op); + + this.setLoading(true); + + new JX.Workflow(uri, data) + .setHandler(handler) + .start(); + }, + getDisplaySide: function() { return this._displaySide; }, @@ -133,9 +168,31 @@ JX.install('DiffInline', { return this._isNewFile; }, - _newRequestData: function() { + getID: function() { + return this._id; + }, + + setDeleted: function(deleted) { + this._isDeleted = deleted; + this._redraw(); + return this; + }, + + setInvisible: function(invisible) { + this._isInvisible = invisible; + this._redraw(); + return this; + }, + + setLoading: function(loading) { + this._isLoading = loading; + this._redraw(); + return this; + }, + + _newRequestData: function(operation) { return { - op: 'edit', + op: operation, id: this._id, on_right: ((this.getDisplaySide() == 'right') ? 1 : 0), renderer: this.getChangeset().getRenderer(), @@ -151,42 +208,89 @@ JX.install('DiffInline', { this._drawEditRows(rows); - // TODO: Set the row state to "hidden". + this.setLoading(false); + this.setInvisible(true); + }, + + _ondeleteresponse: function() { + this._drawUndoRows(); + + this.setLoading(false); + this.setDeleted(true); + + this._didUpdate(); + }, + + _drawUndoRows: function() { + var templates = this.getChangeset().getUndoTemplates(); + + var template; + if (this.getDisplaySide() == 'right') { + template = templates.r; + } else { + template = templates.l; + } + template = JX.$H(template).getNode(); + + this._undoRow = this._drawRows(template, this._row, 'undo'); }, _drawEditRows: function(rows) { - var first_row = JX.DOM.scry(rows, 'tr')[0]; - var row = first_row; - var cursor = this._row; + return this._drawRows(rows, null, 'edit'); + }, + _drawRows: function(rows, cursor, type) { + var first_row = JX.DOM.scry(rows, 'tr')[0]; + var first_meta; + var row = first_row; + cursor = cursor || this._row.nextSibling; + + var next_row; while (row) { + // Grab this first, since it's going to change once we insert the row + // into the document. + next_row = row.nextSibling; + cursor.parentNode.insertBefore(row, cursor.nextSibling); cursor = row; var row_meta = { node: row, - type: 'edit', + type: type, listeners: [] }; - row_meta.listeners.push( - JX.DOM.listen( - row, - ['submit', 'didSyntheticSubmit'], - 'inline-edit-form', - JX.bind(this, this._onsubmit, row_meta))); + if (!first_meta) { + first_meta = row_meta; + } - row_meta.listeners.push( - JX.DOM.listen( - row, - 'click', - 'inline-edit-cancel', - JX.bind(this, this._oncancel, row_meta))); + if (type == 'edit') { + row_meta.listeners.push( + JX.DOM.listen( + row, + ['submit', 'didSyntheticSubmit'], + 'inline-edit-form', + JX.bind(this, this._onsubmit, row_meta))); - row = row.nextSibling; + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'inline-edit-cancel', + JX.bind(this, this._oncancel, row_meta))); + } else { + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'differential-inline-comment-undo', + JX.bind(this, this._onundo, row_meta))); + } + + row = next_row; } - return first_row; + return first_meta; }, _onsubmit: function(row, e) { @@ -194,11 +298,33 @@ JX.install('DiffInline', { var handler = JX.bind(this, this._onsubmitresponse, row); + this.setLoading(true); + JX.Workflow.newFromForm(e.getTarget()) .setHandler(handler) .start(); + }, - // TODO: Set state to "loading". + _onundo: function(row, e) { + e.kill(); + + this._removeRow(row); + + var uri = this._getInlineURI(); + var data = this._newRequestData('undelete'); + var handler = JX.bind(this, this._onundelete); + + this.setDeleted(false); + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + + _onundelete: function() { + this.setLoading(false); + this._didUpdate(); }, _oncancel: function(row, e) { @@ -206,18 +332,15 @@ JX.install('DiffInline', { // TODO: Capture edited text and offer "undo". - JX.DOM.remove(row.node); - this._removeListeners(row.listeners); + this._removeRow(row); - // TODO: Restore state to "normal". + this.setInvisible(false); }, _onsubmitresponse: function(row, response) { + this._removeRow(row); - JX.DOM.remove(row.node); - this._removeListeners(row.listeners); - - // TODO: Restore state to "normal". + this.setInvisible(false); this._onupdate(response); }, @@ -225,7 +348,7 @@ JX.install('DiffInline', { _onupdate: function(response) { var new_row; if (response.markup) { - new_row = this._drawEditRows(JX.$H(response.markup).getNode()); + new_row = this._drawEditRows(JX.$H(response.markup).getNode()).node; } // TODO: Save the old row so the action it's undo-able if it was a @@ -243,18 +366,22 @@ JX.install('DiffInline', { _didUpdate: function() { // After making changes to inline comments, refresh the transaction // preview at the bottom of the page. - - // TODO: This isn't the cleanest way to find the preview form, but - // rendering no longer has direct access to it. - var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); - if (forms.length) { - JX.DOM.invoke(forms[0], 'shouldRefresh'); - } + this.getChangeset().getChangesetList().redrawPreview(); }, - _removeListeners: function(listeners) { - for (var ii = 0; ii < listeners.length; ii++) { - listeners[ii].remove(); + _redraw: function() { + var is_invisible = (this._isInvisible || this._isDeleted); + var is_loading = (this._isLoading); + + var row = this._row; + JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); + JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); + }, + + _removeRow: function(row) { + JX.DOM.remove(row.node); + for (var ii = 0; ii < row.listeners.length; ii++) { + row.listeners[ii].remove(); } }, diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 20eec1a3d5..b6aaeb3cf4 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -331,46 +331,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { var handle_inline_action = function(node, op) { var data = JX.Stratcom.getData(node); - // If you click an action in the preview at the bottom of the page, we - // find the corresponding node and simulate clicking that, if it's - // present on the page. This gives the editor a more consistent view - // of the document. - if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) { - var nodes = JX.DOM.scry( - JX.DOM.getContentFrame(), - 'div', - 'differential-inline-comment'); - - var found = false; - var node_data; - for (var ii = 0; ii < nodes.length; ++ii) { - if (nodes[ii] == node) { - // Don't match the preview itself. - continue; - } - node_data = JX.Stratcom.getData(nodes[ii]); - if (node_data.id == data.id) { - node = nodes[ii]; - data = node_data; - found = true; - break; - } - } - - if (!found) { - switch (op) { - case 'delete': - new JX.DifferentialInlineCommentEditor(config.uri) - .deleteByID(data.id); - return; - } - } - - if (op == 'delete') { - op = 'refdelete'; - } - } - var original = data.original; var reply_phid = null; if (op == 'reply') { @@ -387,30 +347,25 @@ JX.behavior('differential-edit-inline-comments', function(config) { 'differential-changeset'); var view = JX.DiffChangeset.getForNode(changeset_root); - if (op == 'edit') { - // This is now handled by DiffChangesetList. - editor = true; - } else { - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation(op) - .setID(data.id) - .setChangesetID(data.changesetID) - .setLineNumber(data.number) - .setLength(data.length) - .setOnRight(data.on_right) - .setOriginalText(original) - .setRow(row) - .setTable(row.parentNode) - .setReplyToCommentPHID(reply_phid) - .setRenderer(view.getRenderer()) - .start(); - } + editor = new JX.DifferentialInlineCommentEditor(config.uri) + .setTemplates(view.getUndoTemplates()) + .setOperation(op) + .setID(data.id) + .setChangesetID(data.changesetID) + .setLineNumber(data.number) + .setLength(data.length) + .setOnRight(data.on_right) + .setOriginalText(original) + .setRow(row) + .setTable(row.parentNode) + .setReplyToCommentPHID(reply_phid) + .setRenderer(view.getRenderer()) + .start(); set_link_state(true); }; - for (var op in {'edit': 1, 'delete': 1, 'reply': 1}) { + for (var op in {'reply': 1}) { JX.Stratcom.listen( 'click', ['differential-inline-comment', 'differential-inline-' + op], From 58dded555bdedc234b6d52850f7dc9c38f0d9c4b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 16:26:45 -0700 Subject: [PATCH 077/543] Move inline comment creation to new DiffInline code Summary: Ref T12616. This makes creating inlines use the new code. Creation and editing is now slightly more consistent in how it uses nodes. This will simplify the next change (replies), which I ran into some trouble with in an earlier iteration. Note that this (and other changes in the series) allow you to create and edit multiple inlines simultaneously. This is mostly a feature, although I expect we'll need to lock it down a little bit. I have some UI ideas to help avoid errors. Test Plan: Created inlines on a single line; on a range of lines; on the same line; multiple inlines at the same time. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17893 --- resources/celerity/map.php | 58 +++---- .../rsrc/js/application/diff/DiffChangeset.js | 18 ++- .../rsrc/js/application/diff/DiffInline.js | 143 ++++++++++++++---- .../behavior-edit-inline-comments.js | 25 +-- 4 files changed, 171 insertions(+), 73 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5b33419132..e32bccdcda 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '271a1e1e', + 'differential.pkg.js' => '3a7c5866', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,15 +390,15 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '57b29223', + 'rsrc/js/application/diff/DiffChangeset.js' => '454cfe59', 'rsrc/js/application/diff/DiffChangesetList.js' => '50bc5b50', - 'rsrc/js/application/diff/DiffInline.js' => '64dfc791', + 'rsrc/js/application/diff/DiffInline.js' => '38a957be', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '9ed8d2b6', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '9d9dbc38', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '995c805a', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', @@ -624,7 +624,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => '9d9dbc38', + 'javelin-behavior-differential-edit-inline-comments' => '995c805a', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', 'javelin-behavior-differential-populate' => '5e41c819', @@ -785,9 +785,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '57b29223', + 'phabricator-diff-changeset' => '454cfe59', 'phabricator-diff-changeset-list' => '50bc5b50', - 'phabricator-diff-inline' => '64dfc791', + 'phabricator-diff-inline' => '38a957be', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1132,6 +1132,9 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '38a957be' => array( + 'javelin-dom', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1211,6 +1214,17 @@ return array( 'javelin-behavior', 'javelin-dom', ), + '454cfe59' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1342,17 +1356,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '57b29223' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1422,9 +1425,6 @@ return array( 'javelin-workflow', 'javelin-dom', ), - '64dfc791' => array( - 'javelin-dom', - ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', @@ -1675,6 +1675,14 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + '995c805a' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1698,14 +1706,6 @@ return array( '9d9685d6' => array( 'phui-oi-list-view-css', ), - '9d9dbc38' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), '9ed8d2b6' => array( 'javelin-dom', 'javelin-util', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index b52e54aaed..b114198b4f 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -411,16 +411,28 @@ JX.install('DiffChangeset', { var data = JX.Stratcom.getData(node); if (!data.inline) { - var inline = new JX.DiffInline(node) - .setChangeset(this); + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToRow(node); this._inlines.push(inline); - data.inline = inline; } return data.inline; }, + newInlineForRange: function(data) { + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToRange(data); + + this._inlines.push(inline); + + inline.create(); + + return inline; + }, + getInlineByID: function(id) { // TODO: Currently, this will only find inlines which the user has // already interacted with! Inlines are built lazily as events arrive. diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index efa16bf9cd..9eaf177ca8 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -6,33 +6,7 @@ JX.install('DiffInline', { - construct : function(row) { - this._row = row; - - var row_data = JX.Stratcom.getData(row); - this._hidden = row_data.hidden || false; - - // TODO: Get smarter about this once we do more editing, this is pretty - // hacky. - var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); - var data = JX.Stratcom.getData(comment); - - this._id = data.id; - - // TODO: This is very, very, very, very, very, very, very hacky. - var td = comment.parentNode; - var th = td.previousSibling; - if (th.parentNode.firstChild != th) { - this._displaySide = 'right'; - } else { - this._displaySide = 'left'; - } - - this._number = data.number; - this._length = data.length; - this._isNewFile = - (this.getDisplaySide() == 'right') || - (data.left != data.right); + construct : function() { }, properties: { @@ -41,6 +15,8 @@ JX.install('DiffInline', { members: { _id: null, + _phid: null, + _changesetID: null, _row: null, _hidden: false, _number: null, @@ -53,6 +29,80 @@ JX.install('DiffInline', { _isInvisible: false, _isLoading: false, + bindToRow: function(row) { + this._row = row; + + var row_data = JX.Stratcom.getData(row); + row_data.inline = this; + + this._hidden = row_data.hidden || false; + + // TODO: Get smarter about this once we do more editing, this is pretty + // hacky. + var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); + var data = JX.Stratcom.getData(comment); + + this._id = data.id; + this._phid = data.phid; + + // TODO: This is very, very, very, very, very, very, very hacky. + var td = comment.parentNode; + var th = td.previousSibling; + if (th.parentNode.firstChild != th) { + this._displaySide = 'right'; + } else { + this._displaySide = 'left'; + } + + this._number = data.number; + this._length = data.length; + this._isNewFile = + (this.getDisplaySide() == 'right') || + (data.left != data.right); + + this.setInvisible(false); + + return this; + }, + + bindToRange: function(data) { + this._id = null; + this._phid = null; + + this._hidden = false; + + this._displaySide = data.displaySide; + this._number = data.number; + this._length = data.length; + this._isNewFile = data.isNewFile; + this._changesetID = data.changesetID; + + var row = this._newRow(); + JX.Stratcom.getData(row).inline = this; + this._row = row; + + this.setInvisible(true); + + // Insert the comment after any other comments which already appear on + // the same row. + var parent_row = JX.DOM.findAbove(data.target, 'tr'); + var target_row = parent_row.nextSibling; + while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + target_row = target_row.nextSibling; + } + parent_row.parentNode.insertBefore(row, target_row); + + return this; + }, + + _newRow: function() { + var attributes = { + sigil: 'inline-row' + }; + + return JX.$N('tr', attributes); + }, + setHidden: function(hidden) { this._hidden = hidden; @@ -110,6 +160,18 @@ JX.install('DiffInline', { this._didUpdate(); }, + create: function() { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._oncreateresponse); + var data = this._newRequestData('new'); + + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + edit: function() { var uri = this._getInlineURI(); var handler = JX.bind(this, this._oneditresponse); @@ -172,6 +234,10 @@ JX.install('DiffInline', { return this._id; }, + getChangesetID: function() { + return this._changesetID; + }, + setDeleted: function(deleted) { this._isDeleted = deleted; this._redraw(); @@ -199,6 +265,7 @@ JX.install('DiffInline', { number: this.getLineNumber(), length: this.getLineLength(), is_new: this.isNewFile(), + changesetID: this.getChangesetID(), replyToCommentPHID: '' }; }, @@ -212,6 +279,12 @@ JX.install('DiffInline', { this.setInvisible(true); }, + _oncreateresponse: function(response) { + var rows = JX.$H(response).getNode(); + + this._drawEditRows(rows); + }, + _ondeleteresponse: function() { this._drawUndoRows(); @@ -251,7 +324,7 @@ JX.install('DiffInline', { // into the document. next_row = row.nextSibling; - cursor.parentNode.insertBefore(row, cursor.nextSibling); + cursor.parentNode.insertBefore(row, cursor); cursor = row; var row_meta = { @@ -287,6 +360,17 @@ JX.install('DiffInline', { JX.bind(this, this._onundo, row_meta))); } + // If the row has a textarea, focus it. This allows the user to start + // typing a comment immediately after a "new", "edit", or "reply" + // action. + var textareas = JX.DOM.scry( + row, + 'textarea', + 'differential-inline-comment-edit-textarea'); + if (textareas.length) { + textareas[0].focus(); + } + row = next_row; } @@ -340,6 +424,7 @@ JX.install('DiffInline', { _onsubmitresponse: function(row, response) { this._removeRow(row); + this.setLoading(false); this.setInvisible(false); this._onupdate(response); @@ -358,7 +443,7 @@ JX.install('DiffInline', { JX.DOM.remove(this._row); } - this._row = new_row; + this.bindToRow(new_row); this._didUpdate(); }, diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index b6aaeb3cf4..9b34234e0e 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -276,18 +276,19 @@ JX.behavior('differential-edit-inline-comments', function(config) { var view = JX.DiffChangeset.getForNode(root); - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation('new') - .setChangesetID(changeset) - .setLineNumber(o) - .setLength(len) - .setIsNew(isNewFile(target) ? 1 : 0) - .setOnRight(isOnRight(target) ? 1 : 0) - .setRow(insert.nextSibling) - .setTable(insert.parentNode) - .setRenderer(view.getRenderer()) - .start(); + view.newInlineForRange({ + origin: origin, + target: target, + number: o, + length: len, + changesetID: changeset, + isNewFile: isNewFile(target), + displaySide: isOnRight(target) ? 'right' : 'left' + }); + + selecting = false; + origin = null; + target = null; set_link_state(true); From 41379f39debbd0c80f0376a41e92ca36f48b867b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 16:35:06 -0700 Subject: [PATCH 078/543] Move inline replies to new code and remove DifferentialInlineEditor Summary: Ref T12616. This moves "reply" to the new stuff and deletes DifferentialInlineEditor, which no longer does anything. (This breaks some keyboard shortcuts, but I'll rebase D17859 shortly.) Test Plan: Replied to inlines; things seemed to work properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17894 --- resources/celerity/map.php | 75 ++-- resources/celerity/packages.php | 3 +- .../PhabricatorInlineCommentController.php | 13 - .../rsrc/js/application/diff/DiffChangeset.js | 12 + .../js/application/diff/DiffChangesetList.js | 9 + .../rsrc/js/application/diff/DiffInline.js | 63 ++- .../DifferentialInlineCommentEditor.js | 360 ------------------ .../behavior-edit-inline-comments.js | 93 ----- 8 files changed, 105 insertions(+), 523 deletions(-) delete mode 100644 webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e32bccdcda..70b982beae 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '3a7c5866', + 'differential.pkg.js' => '6375358e', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,15 +390,14 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '454cfe59', - 'rsrc/js/application/diff/DiffChangesetList.js' => '50bc5b50', - 'rsrc/js/application/diff/DiffInline.js' => '38a957be', + 'rsrc/js/application/diff/DiffChangeset.js' => '4c9c47ad', + 'rsrc/js/application/diff/DiffChangesetList.js' => '589a30aa', + 'rsrc/js/application/diff/DiffInline.js' => '98c12b2f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '9ed8d2b6', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '995c805a', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '89d11432', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', @@ -572,7 +571,6 @@ return array( 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '41af6d25', 'differential-core-view-css' => '5b7b8ff4', - 'differential-inline-comment-editor' => '9ed8d2b6', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', @@ -624,7 +622,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => '995c805a', + 'javelin-behavior-differential-edit-inline-comments' => '89d11432', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '92904457', 'javelin-behavior-differential-populate' => '5e41c819', @@ -785,9 +783,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '454cfe59', - 'phabricator-diff-changeset-list' => '50bc5b50', - 'phabricator-diff-inline' => '38a957be', + 'phabricator-diff-changeset' => '4c9c47ad', + 'phabricator-diff-changeset-list' => '589a30aa', + 'phabricator-diff-inline' => '98c12b2f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1132,9 +1130,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '38a957be' => array( - 'javelin-dom', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1214,17 +1209,6 @@ return array( 'javelin-behavior', 'javelin-dom', ), - '454cfe59' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1286,6 +1270,17 @@ return array( 'javelin-uri', 'phabricator-notification', ), + '4c9c47ad' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '4d863052' => array( 'javelin-dom', 'javelin-util', @@ -1312,9 +1307,6 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), - '50bc5b50' => array( - 'javelin-install', - ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1356,6 +1348,9 @@ return array( 'javelin-vector', 'javelin-dom', ), + '589a30aa' => array( + 'javelin-install', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1596,6 +1591,13 @@ return array( 'phabricator-draggable-list', 'javelin-workboard-column', ), + '89d11432' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + ), '8a41885b' => array( 'javelin-install', 'javelin-dom', @@ -1675,13 +1677,8 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '995c805a' => array( - 'javelin-behavior', - 'javelin-stratcom', + '98c12b2f' => array( 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', ), '9a6dd75c' => array( 'javelin-behavior', @@ -1706,14 +1703,6 @@ return array( '9d9685d6' => array( 'phui-oi-list-view-css', ), - '9ed8d2b6' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-request', - 'javelin-workflow', - ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2456,10 +2445,10 @@ return array( 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'differential-inline-comment-editor', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', + 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', ), diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index cac2eff5f8..08752c1b53 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -203,10 +203,11 @@ return array( 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'differential-inline-comment-editor', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', + + 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', ), diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 1c624c1ad8..d7f1a9dd3a 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -94,19 +94,6 @@ abstract class PhabricatorInlineCommentController $op = $this->getOperation(); switch ($op) { - case 'busy': - if ($request->isFormPost()) { - return new AphrontAjaxResponse(); - } - - return $this->newDialog() - ->setTitle(pht('Already Editing')) - ->appendParagraph( - pht( - 'You are already editing an inline comment. Finish editing '. - 'your current comment before adding new comments.')) - ->addCancelButton('/') - ->addSubmitButton(pht('Jump to Inline')); case 'hide': case 'show': if (!$request->validateCSRF()) { diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index b114198b4f..b1552cf8ce 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -433,6 +433,18 @@ JX.install('DiffChangeset', { return inline; }, + newInlineReply: function(original) { + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToReply(original); + + this._inlines.push(inline); + + inline.create(); + + return inline; + }, + getInlineByID: function(id) { // TODO: Currently, this will only find inlines which the user has // already interacted with! Inlines are built lazily as events arrive. diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index e46a33bb56..0f07f4447b 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -41,6 +41,12 @@ JX.install('DiffChangesetList', { 'click', ['differential-inline-comment', 'differential-inline-delete'], ondelete); + + var onreply = JX.bind(this, this._ifawake, this._onaction, 'reply'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-reply'], + onreply); }, properties: { @@ -410,6 +416,9 @@ JX.install('DiffChangesetList', { case 'delete': inline.delete(is_ref); break; + case 'reply': + inline.reply(); + break; } }, diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 9eaf177ca8..530f621caf 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -24,6 +24,7 @@ JX.install('DiffInline', { _displaySide: null, _isNewFile: null, _undoRow: null, + _replyToCommentPHID: null, _isDeleted: false, _isInvisible: false, @@ -66,23 +67,12 @@ JX.install('DiffInline', { }, bindToRange: function(data) { - this._id = null; - this._phid = null; - - this._hidden = false; - this._displaySide = data.displaySide; this._number = data.number; this._length = data.length; this._isNewFile = data.isNewFile; this._changesetID = data.changesetID; - var row = this._newRow(); - JX.Stratcom.getData(row).inline = this; - this._row = row; - - this.setInvisible(true); - // Insert the comment after any other comments which already appear on // the same row. var parent_row = JX.DOM.findAbove(data.target, 'tr'); @@ -90,8 +80,37 @@ JX.install('DiffInline', { while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { target_row = target_row.nextSibling; } + + var row = this._newRow(); parent_row.parentNode.insertBefore(row, target_row); + this.setInvisible(true); + + return this; + }, + + bindToReply: function(inline) { + this._displaySide = inline._displaySide; + this._number = inline._number; + this._length = inline._length; + this._isNewFile = inline._isNewFile; + this._changesetID = inline._changesetID; + + this._replyToCommentPHID = inline._phid; + + // TODO: This should insert correctly into the thread, not just at the + // bottom. + var parent_row = inline._row; + var target_row = parent_row.nextSibling; + while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + target_row = target_row.nextSibling; + } + + var row = this._newRow(); + parent_row.parentNode.insertBefore(row, target_row); + + this.setInvisible(true); + return this; }, @@ -100,7 +119,16 @@ JX.install('DiffInline', { sigil: 'inline-row' }; - return JX.$N('tr', attributes); + var row = JX.$N('tr', attributes); + + JX.Stratcom.getData(row).inline = this; + this._row = row; + + this._id = null; + this._phid = null; + this._hidden = false; + + return row; }, setHidden: function(hidden) { @@ -172,6 +200,11 @@ JX.install('DiffInline', { .send(); }, + reply: function() { + var changeset = this.getChangeset(); + return changeset.newInlineReply(this); + }, + edit: function() { var uri = this._getInlineURI(); var handler = JX.bind(this, this._oneditresponse); @@ -238,6 +271,10 @@ JX.install('DiffInline', { return this._changesetID; }, + getReplyToCommentPHID: function() { + return this._replyToCommentPHID; + }, + setDeleted: function(deleted) { this._isDeleted = deleted; this._redraw(); @@ -266,7 +303,7 @@ JX.install('DiffInline', { length: this.getLineLength(), is_new: this.isNewFile(), changesetID: this.getChangesetID(), - replyToCommentPHID: '' + replyToCommentPHID: this.getReplyToCommentPHID() || '', }; }, diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js deleted file mode 100644 index 7068e94c14..0000000000 --- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js +++ /dev/null @@ -1,360 +0,0 @@ -/** - * @provides differential-inline-comment-editor - * @requires javelin-dom - * javelin-util - * javelin-stratcom - * javelin-install - * javelin-request - * javelin-workflow - */ - -JX.install('DifferentialInlineCommentEditor', { - - construct : function(uri) { - this._uri = uri; - }, - - events : ['done'], - - members : { - _uri : null, - _undoText : null, - _completed: false, - _skipOverInlineCommentRows : function(node) { - // TODO: Move this semantic information out of class names. - while (node && node.className.indexOf('inline') !== -1) { - node = node.nextSibling; - } - return node; - }, - _buildRequestData : function() { - return { - op : this.getOperation(), - on_right : this.getOnRight(), - id : this.getID(), - number : this.getLineNumber(), - is_new : (this.getIsNew() ? 1 : 0), - length : this.getLength(), - changesetID : this.getChangesetID(), - text : this.getText() || '', - renderer: this.getRenderer(), - replyToCommentPHID: this.getReplyToCommentPHID() || '', - }; - }, - _draw : function(content, exact_row) { - var row = this.getRow(); - var table = this.getTable(); - var target = exact_row ? row : this._skipOverInlineCommentRows(row); - - function copyRows(dst, src, before) { - var rows = JX.DOM.scry(src, 'tr'); - for (var ii = 0; ii < rows.length; ii++) { - - // Find the table this belongs to. If it's a sub-table, like a - // table in an inline comment, don't copy it. - if (JX.DOM.findAbove(rows[ii], 'table') !== src) { - continue; - } - - if (before) { - dst.insertBefore(rows[ii], before); - } else { - dst.appendChild(rows[ii]); - } - } - return rows; - } - - return copyRows(table, content, target); - }, - _removeUndoLink : function() { - var rows = JX.DifferentialInlineCommentEditor._undoRows; - if (rows) { - for (var ii = 0; ii < rows.length; ii++) { - JX.DOM.remove(rows[ii]); - } - } - JX.DifferentialInlineCommentEditor._undoRows = []; - }, - _undo : function() { - this._removeUndoLink(); - - if (this._undoText) { - this.setText(this._undoText); - } else { - this.setOperation('undelete'); - } - - this.start(); - }, - _registerUndoListener : function() { - if (!JX.DifferentialInlineCommentEditor._activeEditor) { - JX.Stratcom.listen( - 'click', - 'differential-inline-comment-undo', - function(e) { - JX.DifferentialInlineCommentEditor._activeEditor._undo(); - e.kill(); - }); - } - JX.DifferentialInlineCommentEditor._activeEditor = this; - }, - _setRowState : function(state) { - var is_hidden = (state == 'hidden'); - var is_loading = (state == 'loading'); - var row = this.getRow(); - JX.DOM.alterClass(row, 'differential-inline-hidden', is_hidden); - JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); - }, - _didContinueWorkflow : function(response) { - var drawn = this._draw(JX.$H(response).getNode()); - - var op = this.getOperation(); - if (op == 'edit') { - this._setRowState('hidden'); - } - - JX.DOM.find( - drawn[0], - 'textarea', - 'differential-inline-comment-edit-textarea').focus(); - - var oncancel = JX.bind(this, function(e) { - e.kill(); - - this._didCancelWorkflow(); - - if (op == 'edit') { - this._setRowState('visible'); - } - - JX.DOM.remove(drawn[0]); - }); - JX.DOM.listen(drawn[0], 'click', 'inline-edit-cancel', oncancel); - - var onsubmit = JX.bind(this, function(e) { - e.kill(); - - JX.Workflow.newFromForm(e.getTarget()) - .setHandler(JX.bind(this, function(response) { - JX.DOM.remove(drawn[0]); - if (op == 'edit') { - this._setRowState('visible'); - } - this._didCompleteWorkflow(response); - })) - .start(); - - JX.DOM.alterClass(drawn[0], 'differential-inline-loading', true); - }); - JX.DOM.listen( - drawn[0], - ['submit', 'didSyntheticSubmit'], - 'inline-edit-form', - onsubmit); - }, - - - _didCompleteWorkflow : function(response) { - var op = this.getOperation(); - - // We don't get any markup back if the user deletes a comment, or saves - // an empty comment (which effects a delete). - if (response.markup) { - this._draw(JX.$H(response.markup).getNode()); - } - - if (op == 'delete' || op == 'refdelete') { - this._undoText = null; - this._drawUndo(); - } else { - this._removeUndoLink(); - } - - // These operations remove the old row (edit adds a new row first). - var remove_old = (op == 'edit' || op == 'delete' || op == 'refdelete'); - if (remove_old) { - this._setRowState('hidden'); - } - - if (op == 'undelete') { - this._setRowState('visible'); - } - - this._completed = true; - - this._didUpdate(); - this.invoke('done'); - }, - - - _didCancelWorkflow : function() { - this.invoke('done'); - - switch (this.getOperation()) { - case 'delete': - case 'refdelete': - if (!this._completed) { - this._setRowState('visible'); - } - return; - case 'undelete': - return; - } - - var textarea; - try { - textarea = JX.DOM.find( - document.body, // TODO: use getDialogRootNode() when available - 'textarea', - 'differential-inline-comment-edit-textarea'); - } catch (ex) { - // The close handler is called whenever the dialog closes, even if the - // user closed it by completing the workflow with "Save". The - // JX.Workflow API should probably be refined to allow programmatic - // distinction of close caused by 'cancel' vs 'submit'. Testing for - // presence of the textarea serves as a proxy for detecting a 'cancel'. - return; - } - - var text = textarea.value; - - // If the user hasn't edited the text (i.e., no change from original for - // 'edit' or no text at all), don't offer them an undo. - if (text == this.getOriginalText() || text === '') { - return; - } - - // Save the text so we can 'undo' back to it. - this._undoText = text; - - this._drawUndo(); - }, - - _drawUndo: function() { - var templates = this.getTemplates(); - var template = this.getOnRight() ? templates.r : templates.l; - template = JX.$H(template).getNode(); - - // NOTE: Operation order matters here; we can't remove anything until - // after we draw the new rows because _draw uses the old rows to figure - // out where to place the comment. - - // We use 'exact_row' to put the "undo" text directly above the affected - // comment. - var exact_row = true; - var rows = this._draw(template, exact_row); - - this._removeUndoLink(); - - JX.DifferentialInlineCommentEditor._undoRows = rows; - }, - - _onBusyWorkflow: function() { - // If the user clicks the "Jump to Inline" button, scroll to the row - // being edited. - JX.DOM.scrollTo(this.getRow()); - }, - - start : function() { - var op = this.getOperation(); - - // The user is already editing a comment, we're going to give them an - // error message. - if (op == 'busy') { - var onbusy = JX.bind(this, this._onBusyWorkflow); - - new JX.Workflow(this._uri, {op: op}) - .setHandler(onbusy) - .start(); - - return this; - } - - this._registerUndoListener(); - var data = this._buildRequestData(); - - if (op == 'delete' || op == 'refdelete' || op == 'undelete') { - this._setRowState('loading'); - - var oncomplete = JX.bind(this, this._didCompleteWorkflow); - var oncancel = JX.bind(this, this._didCancelWorkflow); - - new JX.Workflow(this._uri, data) - .setHandler(oncomplete) - .setCloseHandler(oncancel) - .start(); - } else { - var handler = JX.bind(this, this._didContinueWorkflow); - - if (op == 'edit') { - this._setRowState('loading'); - } - - new JX.Request(this._uri, handler) - .setData(data) - .send(); - } - - return this; - }, - - deleteByID: function(id) { - var data = { - op: 'refdelete', - id: id - }; - - new JX.Workflow(this._uri, data) - .setHandler(JX.bind(this, function() { - this._didUpdate(); - })) - .start(); - }, - - _didUpdate: function() { - // After making changes to inline comments, refresh the transaction - // preview at the bottom of the page. - - // TODO: This isn't the cleanest way to find the preview form, but - // rendering no longer has direct access to it. - var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); - if (forms.length) { - JX.DOM.invoke(forms[0], 'shouldRefresh'); - } - } - - }, - - statics : { - /** - * Global refernece to the 'undo' rows currently rendered in the document. - */ - _undoRows : null, - - /** - * Global listener for the 'undo' click associated with the currently - * displayed 'undo' link. When an editor is start()ed, it becomes the active - * editor. - */ - _activeEditor : null - }, - - properties : { - operation : null, - row : null, - table : null, - onRight : null, - ID : null, - lineNumber : null, - changesetID : null, - length : null, - isNew : null, - text : null, - templates : null, - originalText : null, - renderer: null, - replyToCommentPHID: null - } - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 9b34234e0e..05410c8c7a 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -5,7 +5,6 @@ * javelin-dom * javelin-util * javelin-vector - * differential-inline-comment-editor */ JX.behavior('differential-edit-inline-comments', function(config) { @@ -126,13 +125,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { setSelectedCells([]); } - JX.DifferentialInlineCommentEditor.listen('done', function() { - selecting = false; - editor = false; - hideReticle(); - set_link_state(false); - }); - function isOnRight(node) { return node.parentNode.firstChild != node; } @@ -150,10 +142,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { } } - var set_link_state = function(active) { - JX.DOM.alterClass(JX.$(config.stage), 'inline-editor-active', active); - }; - JX.Stratcom.listen( 'mousedown', ['differential-changeset', 'tag:th'], @@ -163,14 +151,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { return; } - if (editor) { - new JX.DifferentialInlineCommentEditor(config.uri) - .setOperation('busy') - .setRow(editor.getRow().previousSibling) - .start(); - return; - } - if (selecting) { return; } @@ -290,8 +270,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { origin = null; target = null; - set_link_state(true); - e.kill(); }); @@ -310,75 +288,4 @@ JX.behavior('differential-edit-inline-comments', function(config) { } }); - var action_handler = function(op, e) { - // NOTE: We prevent this event, rather than killing it, because some - // actions are now handled by DiffChangesetList. - e.prevent(); - - if (editor) { - return; - } - - var node = e.getNode('differential-inline-comment'); - - // If we're on a touch device, we didn't highlight the affected lines - // earlier because we can't use hover events to mutate the document. - // Highlight them now. - updateReticleForComment(e); - - handle_inline_action(node, op); - }; - - var handle_inline_action = function(node, op) { - var data = JX.Stratcom.getData(node); - - var original = data.original; - var reply_phid = null; - if (op == 'reply') { - // If the user hit "reply", the original text is empty (a new reply), not - // the text of the comment they're replying to. - original = ''; - reply_phid = data.phid; - } - - var row = JX.DOM.findAbove(node, 'tr'); - var changeset_root = JX.DOM.findAbove( - node, - 'div', - 'differential-changeset'); - var view = JX.DiffChangeset.getForNode(changeset_root); - - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation(op) - .setID(data.id) - .setChangesetID(data.changesetID) - .setLineNumber(data.number) - .setLength(data.length) - .setOnRight(data.on_right) - .setOriginalText(original) - .setRow(row) - .setTable(row.parentNode) - .setReplyToCommentPHID(reply_phid) - .setRenderer(view.getRenderer()) - .start(); - - set_link_state(true); - }; - - for (var op in {'reply': 1}) { - JX.Stratcom.listen( - 'click', - ['differential-inline-comment', 'differential-inline-' + op], - JX.bind(null, action_handler, op)); - } - - JX.Stratcom.listen( - 'differential-inline-action', - null, - function(e) { - var data = e.getData(); - handle_inline_action(data.node, data.op); - }); - }); From 588a66c04d0332140ed41a23f03b04e07d7e4a05 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 May 2017 13:24:15 -0700 Subject: [PATCH 079/543] Move most Differetial keyboard shortcuts into DiffChangesetList Summary: Ref T12616. This moves most keyboard shortcuts into DiffChangesetList. It breaks some shortcuts that I plan to restore later, noted in T12616 (toggle file, edit inline, reply to inline), since I think ripping them out now and rebuilding them in a little bit will make things much simpler. Test Plan: - Used j, k, n, p, J, K shortcuts to navigate a revision. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17859 --- resources/celerity/map.php | 47 ++-- resources/celerity/packages.php | 1 - .../DifferentialRevisionViewController.php | 1 - .../view/DifferentialChangesetListView.php | 10 + .../controller/DiffusionCommitController.php | 2 - .../rsrc/js/application/diff/DiffChangeset.js | 82 ++++++ .../js/application/diff/DiffChangesetList.js | 162 +++++++++++ .../differential/behavior-keyboard-nav.js | 264 ------------------ 8 files changed, 273 insertions(+), 296 deletions(-) delete mode 100644 webroot/rsrc/js/application/differential/behavior-keyboard-nav.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 70b982beae..a06069612a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '6375358e', + 'differential.pkg.js' => 'e6129b80', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,15 +390,14 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '4c9c47ad', - 'rsrc/js/application/diff/DiffChangesetList.js' => '589a30aa', + 'rsrc/js/application/diff/DiffChangeset.js' => '3d4b3c5e', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'e2c315d9', 'rsrc/js/application/diff/DiffInline.js' => '98c12b2f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '89d11432', - 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', @@ -624,7 +623,6 @@ return array( 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-edit-inline-comments' => '89d11432', 'javelin-behavior-differential-feedback-preview' => 'b064af76', - 'javelin-behavior-differential-keyboard-navigation' => '92904457', 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', @@ -783,8 +781,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '4c9c47ad', - 'phabricator-diff-changeset-list' => '589a30aa', + 'phabricator-diff-changeset' => '3d4b3c5e', + 'phabricator-diff-changeset-list' => 'e2c315d9', 'phabricator-diff-inline' => '98c12b2f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1160,6 +1158,17 @@ return array( 'javelin-util', 'javelin-uri', ), + '3d4b3c5e' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '3dbf94d5' => array( 'javelin-behavior', 'javelin-dom', @@ -1270,17 +1279,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - '4c9c47ad' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '4d863052' => array( 'javelin-dom', 'javelin-util', @@ -1348,9 +1346,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '589a30aa' => array( - 'javelin-install', - ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1629,12 +1624,6 @@ return array( 'javelin-dom', 'javelin-request', ), - 92904457 => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-keyboard-shortcut', - ), '92b9ec77' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2119,6 +2108,9 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + 'e2c315d9' => array( + 'javelin-install', + ), 'e2e0a072' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2440,7 +2432,6 @@ return array( 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-differential-comment-jump', - 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 08752c1b53..364a695f18 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -197,7 +197,6 @@ return array( 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-differential-comment-jump', - 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 6cb39c1510..769ac37e1a 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -465,7 +465,6 @@ final class DifferentialRevisionViewController extends DifferentialController { } Javelin::initBehavior('differential-user-select'); - Javelin::initBehavior('differential-keyboard-navigation'); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index eb60a3dee1..715865d7b2 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -234,6 +234,16 @@ final class DifferentialChangesetListView extends AphrontView { 'Highlight As...' => pht('Highlight As...'), 'Loading...' => pht('Loading...'), + + 'Jump to next change.' => pht('Jump to next change.'), + 'Jump to previous change.' => pht('Jump to previous change.'), + 'Jump to next file.' => pht('Jump to next file.'), + 'Jump to previous file.' => pht('Jump to previous file.'), + 'Jump to next inline comment.' => pht('Jump to next inline comment.'), + 'Jump to previous inline comment.' => + pht('Jump to previous inline comment.'), + 'Jump to the table of contents.' => + pht('Jump to the table of contents.'), ), )); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 10677b762f..554ad64e5e 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -720,8 +720,6 @@ final class DiffusionCommitController extends DiffusionController { $request = $this->getRequest(); $viewer = $request->getUser(); - Javelin::initBehavior('differential-keyboard-navigation'); - // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index b1552cf8ce..b592eb0cec 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -318,6 +318,88 @@ JX.install('DiffChangeset', { return this._highlight; }, + getSelectableItems: function() { + var items = []; + + items.push({ + type: 'file', + changeset: this, + target: this, + nodes: { + begin: this._node, + end: null + } + }); + + var rows = JX.DOM.scry(this._node, 'tr'); + + var blocks = []; + var block; + var ii; + for (ii = 0; ii < rows.length; ii++) { + var type = this._getRowType(rows[ii]); + + if (!block || (block.type !== type)) { + block = { + type: type, + items: [] + }; + blocks.push(block); + } + + block.items.push(rows[ii]); + } + + for (ii = 0; ii < blocks.length; ii++) { + block = blocks[ii]; + + if (block.type == 'change') { + items.push({ + type: block.type, + changeset: this, + target: block.items[0], + nodes: { + begin: block.items[0], + end: block.items[block.items.length - 1] + } + }); + } + + if (block.type == 'comment') { + for (var jj = 0; jj < block.items.length; jj++) { + items.push({ + type: block.type, + changeset: this, + target: block.items[jj], + nodes: { + begin: block.items[jj], + end: block.items[jj] + } + }); + } + } + } + + return items; + }, + + _getRowType: function(row) { + // NOTE: Don't do "className.indexOf()" elsewhere. This is evil legacy + // magic. + + if (row.className.indexOf('inline') !== -1) { + return 'comment'; + } + + var cells = JX.DOM.scry(row, 'td'); + for (var ii = 0; ii < cells.length; ii++) { + if (cells[ii].className.indexOf('old') !== -1 || + cells[ii].className.indexOf('new') !== -1) { + return 'change'; + } + } + }, + _getNodeData: function() { return JX.Stratcom.getData(this._node); }, diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 0f07f4447b..39bc6aa289 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -55,15 +55,49 @@ JX.install('DiffChangesetList', { }, members: { + _initialized: false, _asleep: true, _changesets: null, + _cursorItem: null, + _lastKeyboardManager: null, + sleep: function() { this._asleep = true; }, wake: function() { this._asleep = false; + + if (this._initialized) { + return; + } + + this._initialized = true; + var pht = this.getTranslations(); + + var label; + + label = pht('Jump to next change.'); + this._installJumpKey('j', label, 1); + + label = pht('Jump to previous change.'); + this._installJumpKey('k', label, -1); + + label = pht('Jump to next file.'); + this._installJumpKey('J', label, 1, 'file'); + + label = pht('Jump to previous file.'); + this._installJumpKey('K', label, -1, 'file'); + + label = pht('Jump to next inline comment.'); + this._installJumpKey('n', label, 1, 'comment'); + + label = pht('Jump to previous inline comment.'); + this._installJumpKey('p', label, -1, 'comment'); + + label = pht('Jump to the table of contents.'); + this._installKey('t', label, this._ontoc); }, isAsleep: function() { @@ -132,6 +166,134 @@ JX.install('DiffChangesetList', { } }, + _installKey: function(key, label, handler) { + handler = JX.bind(this, this._ifawake, handler); + + return new JX.KeyboardShortcut(key, label) + .setHandler(handler) + .register(); + }, + + _installJumpKey: function(key, label, delta, filter) { + filter = filter || null; + var handler = JX.bind(this, this._onjumpkey, delta, filter); + return this._installKey(key, label, handler); + }, + + _ontoc: function(manager) { + var toc = JX.$('toc'); + manager.scrollTo(toc); + }, + + _onjumpkey: function(delta, filter, manager) { + var state = this._getSelectionState(); + + var cursor = state.cursor; + var items = state.items; + + // If there's currently no selection and the user tries to go back, + // don't do anything. + if ((cursor === null) && (delta < 0)) { + return; + } + + while (true) { + if (cursor === null) { + cursor = 0; + } else { + cursor = cursor + delta; + } + + // If we've gone backward past the first change, bail out. + if (cursor < 0) { + return; + } + + // If we've gone forward off the end of the list, bail out. + if (cursor >= items.length) { + return; + } + + // If we're selecting things of a particular type (like only files) + // and the next item isn't of that type, move past it. + if (filter !== null) { + if (items[cursor].type !== filter) { + continue; + } + } + + // Otherwise, we've found a valid item to select. + break; + } + + this._setSelectionState(items[cursor], manager); + }, + + _getSelectionState: function() { + var items = this._getSelectableItems(); + + var cursor = null; + if (this._cursorItem !== null) { + for (var ii = 0; ii < items.length; ii++) { + var item = items[ii]; + if (this._cursorItem.target === item.target) { + cursor = ii; + break; + } + } + } + + return { + cursor: cursor, + items: items + }; + }, + + _setSelectionState: function(item, manager) { + this._cursorItem = item; + + this._redrawSelection(manager, true); + + return this; + }, + + _redrawSelection: function(manager, scroll) { + manager = manager || this._lastKeyboardManager; + this._lastKeyboardManager = manager; + + if (this.isAsleep()) { + manager.focusOn(null); + return; + } + + var cursor = this._cursorItem; + if (!cursor) { + manager.focusOn(null); + return; + } + + manager.focusOn(cursor.nodes.begin, cursor.nodes.end); + + if (scroll) { + manager.scrollTo(cursor.nodes.begin); + } + + return this; + }, + + _getSelectableItems: function() { + var result = []; + + for (var ii = 0; ii < this._changesets.length; ii++) { + var items = this._changesets[ii].getSelectableItems(); + for (var jj = 0; jj < items.length; jj++) { + result.push(items[jj]); + } + } + + return result; + }, + _onmore: function(e) { e.kill(); diff --git a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js b/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js deleted file mode 100644 index f48df4d3dc..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js +++ /dev/null @@ -1,264 +0,0 @@ -/** - * @provides javelin-behavior-differential-keyboard-navigation - * @requires javelin-behavior - * javelin-dom - * javelin-stratcom - * phabricator-keyboard-shortcut - */ - -JX.behavior('differential-keyboard-navigation', function(config) { - - var cursor = -1; - var changesets; - - var selection_begin = null; - var selection_end = null; - - var refreshFocus = function() {}; - - function init() { - if (changesets) { - return; - } - changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset'); - } - - function getBlocks(cursor) { - // TODO: This might not be terribly fast; we can't currently memoize it - // because it can change as ajax requests come in (e.g., content loads). - - var rows = JX.DOM.scry(changesets[cursor], 'tr'); - var blocks = [[changesets[cursor], changesets[cursor]]]; - var start = null; - var type; - var ii; - - // Don't show code blocks inside a collapsed file. - var diff = JX.DOM.scry(changesets[cursor], 'table', 'differential-diff'); - if (diff.length == 1 && JX.Stratcom.getData(diff[0]).hidden) { - return blocks; - } - - function push() { - if (start) { - blocks.push([start, rows[ii - 1]]); - } - start = null; - } - - for (ii = 0; ii < rows.length; ii++) { - type = getRowType(rows[ii]); - if (type == 'comment') { - // If we see these types of rows, make a block for each one. - push(); - } - if (!type) { - push(); - } else if (type && !start) { - start = rows[ii]; - } - } - push(); - - return blocks; - } - - function getRowType(row) { - // NOTE: Being somewhat over-general here to allow other types of objects - // to be easily focused in the future (inline comments, 'show more..'). - - if (row.className.indexOf('inline') !== -1) { - return 'comment'; - } - - if (row.className.indexOf('differential-changeset') !== -1) { - return 'file'; - } - - var cells = JX.DOM.scry(row, 'td'); - - for (var ii = 0; ii < cells.length; ii++) { - // NOTE: The semantic use of classnames here is for performance; don't - // emulate this elsewhere since it's super terrible. - if (cells[ii].className.indexOf('old') !== -1 || - cells[ii].className.indexOf('new') !== -1) { - return 'change'; - } - } - - return null; - } - - function jump(manager, delta, jump_to_type) { - init(); - - if (cursor < 0) { - if (delta < 0) { - // If the user goes "back" without a selection, just reject the action. - return; - } else { - cursor = 0; - } - } - - while (true) { - var blocks = getBlocks(cursor); - var focus; - if (delta < 0) { - focus = blocks.length; - } else { - focus = -1; - } - - for (var ii = 0; ii < blocks.length; ii++) { - if (blocks[ii][0] == selection_begin) { - focus = ii; - break; - } - } - - while (true) { - focus += delta; - - if (blocks[focus]) { - var row_type = getRowType(blocks[focus][0]); - if (jump_to_type && row_type != jump_to_type) { - continue; - } - - selection_begin = blocks[focus][0]; - selection_end = blocks[focus][1]; - - manager.scrollTo(selection_begin); - - refreshFocus = function() { - manager.focusOn(selection_begin, selection_end); - }; - - refreshFocus(); - - return; - } else { - var adjusted = (cursor + delta); - if (adjusted < 0 || adjusted >= changesets.length) { - // Stop cursor movement when the user reaches either end. - return; - } - cursor = adjusted; - - // Break the inner loop and go to the next file. - break; - } - } - } - - } - - // When inline comments are updated, wipe out our cache of blocks since - // comments may have been added or deleted. - JX.Stratcom.listen( - null, - 'differential-inline-comment-update', - function() { - changesets = null; - }); - // Same thing when a file is hidden or shown; don't want to highlight - // invisible code. - JX.Stratcom.listen( - 'differential-toggle-file-toggled', - null, - function() { - changesets = null; - init(); - refreshFocus(); - }); - - new JX.KeyboardShortcut('j', 'Jump to next change.') - .setHandler(function(manager) { - jump(manager, 1); - }) - .register(); - - new JX.KeyboardShortcut('k', 'Jump to previous change.') - .setHandler(function(manager) { - jump(manager, -1); - }) - .register(); - - new JX.KeyboardShortcut('J', 'Jump to next file.') - .setHandler(function(manager) { - jump(manager, 1, 'file'); - }) - .register(); - - new JX.KeyboardShortcut('K', 'Jump to previous file.') - .setHandler(function(manager) { - jump(manager, -1, 'file'); - }) - .register(); - - new JX.KeyboardShortcut('n', 'Jump to next inline comment.') - .setHandler(function(manager) { - jump(manager, 1, 'comment'); - }) - .register(); - - new JX.KeyboardShortcut('p', 'Jump to previous inline comment.') - .setHandler(function(manager) { - jump(manager, -1, 'comment'); - }) - .register(); - - - new JX.KeyboardShortcut('t', 'Jump to the table of contents.') - .setHandler(function(manager) { - var toc = JX.$('toc'); - manager.scrollTo(toc); - }) - .register(); - - new JX.KeyboardShortcut( - 'h', - 'Collapse or expand the file display (after jump).') - .setHandler(function() { - if (!changesets || !changesets[cursor]) { - return; - } - JX.Stratcom.invoke('differential-toggle-file', null, { - diff: JX.DOM.scry(changesets[cursor], 'table', 'differential-diff') - }); - }) - .register(); - - - function inline_op(node, op) { - // nothing selected - if (!node) { - return; - } - if (!JX.DOM.scry(node, 'a', 'differential-inline-' + op)) { - // No link for this operation, e.g. editing a comment you can't edit. - return; - } - - var data = { - node: JX.DOM.find(node, 'div', 'differential-inline-comment'), - op: op - }; - - JX.Stratcom.invoke('differential-inline-action', null, data); - } - - new JX.KeyboardShortcut('r', 'Reply to selected inline comment.') - .setHandler(function() { - inline_op(selection_begin, 'reply'); - }) - .register(); - - new JX.KeyboardShortcut('e', 'Edit selected inline comment.') - .setHandler(function() { - inline_op(selection_begin, 'edit'); - }) - .register(); - -}); From 2fb1edfeb198bc279f2d956635a649ca95b77440 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 17:31:58 -0700 Subject: [PATCH 080/543] Restore the Differential "edit" and "reply" keyboard shortcuts Summary: Ref T12616. This makes "edit" and "reply" work again. Test Plan: Used "e" and "r" to edit and reply. Also used them in bogus ways and got useful UI feedback. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17895 --- resources/celerity/map.php | 48 ++++++++-------- .../view/DifferentialChangesetListView.php | 8 +++ .../rsrc/js/application/diff/DiffChangeset.js | 9 +++ .../js/application/diff/DiffChangesetList.js | 55 ++++++++++++++++++- .../rsrc/js/application/diff/DiffInline.js | 25 +++++++++ 5 files changed, 119 insertions(+), 26 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a06069612a..13f0f6bc61 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'e6129b80', + 'differential.pkg.js' => '5ee318c2', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,9 +390,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '3d4b3c5e', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'e2c315d9', - 'rsrc/js/application/diff/DiffInline.js' => '98c12b2f', + 'rsrc/js/application/diff/DiffChangeset.js' => 'f7100923', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'f10fd7a3', + 'rsrc/js/application/diff/DiffInline.js' => '00db3c3a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -781,9 +781,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '3d4b3c5e', - 'phabricator-diff-changeset-list' => 'e2c315d9', - 'phabricator-diff-inline' => '98c12b2f', + 'phabricator-diff-changeset' => 'f7100923', + 'phabricator-diff-changeset-list' => 'f10fd7a3', + 'phabricator-diff-inline' => '00db3c3a', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -918,6 +918,9 @@ return array( 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( + '00db3c3a' => array( + 'javelin-dom', + ), '013ffff9' => array( 'javelin-install', 'javelin-util', @@ -1158,17 +1161,6 @@ return array( 'javelin-util', 'javelin-uri', ), - '3d4b3c5e' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '3dbf94d5' => array( 'javelin-behavior', 'javelin-dom', @@ -1666,9 +1658,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '98c12b2f' => array( - 'javelin-dom', - ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2108,9 +2097,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'e2c315d9' => array( - 'javelin-install', - ), 'e2e0a072' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2181,6 +2167,9 @@ return array( 'javelin-workflow', 'javelin-json', ), + 'f10fd7a3' => array( + 'javelin-install', + ), 'f12cbc9f' => array( 'phui-oi-list-view-css', ), @@ -2199,6 +2188,17 @@ return array( 'phuix-icon-view', 'phabricator-prefab', ), + 'f7100923' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 715865d7b2..fea7a2da55 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -244,6 +244,14 @@ final class DifferentialChangesetListView extends AphrontView { pht('Jump to previous inline comment.'), 'Jump to the table of contents.' => pht('Jump to the table of contents.'), + 'Reply to selected inline comment.' => + pht('Reply to selected inline comment.'), + 'Edit selected inline comment.' => + pht('Edit selected inline comment.'), + 'You must select a comment to reply to.' => + pht('You must select a comment to reply to.'), + 'You must select a comment to edit.' => + pht('You must select a comment to edit.'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index b592eb0cec..e74adfcb79 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -367,10 +367,19 @@ JX.install('DiffChangeset', { if (block.type == 'comment') { for (var jj = 0; jj < block.items.length; jj++) { + var inline = this.getInlineForRow(block.items[jj]); + + // If this inline has been collapsed, don't select it with the + // keyboard cursor. + if (inline.isHidden()) { + continue; + } + items.push({ type: block.type, changeset: this, target: block.items[jj], + inline: inline, nodes: { begin: block.items[jj], end: block.items[jj] diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 39bc6aa289..27b539a311 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -98,6 +98,12 @@ JX.install('DiffChangesetList', { label = pht('Jump to the table of contents.'); this._installKey('t', label, this._ontoc); + + label = pht('Reply to selected inline comment.'); + this._installKey('r', label, this._onreply); + + label = pht('Edit selected inline comment.'); + this._installKey('e', label, this._onedit); }, isAsleep: function() { @@ -185,6 +191,52 @@ JX.install('DiffChangesetList', { manager.scrollTo(toc); }, + _onreply: function(manager) { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.inline; + if (inline.canReply()) { + manager.focusOn(null); + + inline.reply(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to reply to.')); + }, + + _onedit: function(manager) { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.inline; + if (inline.canEdit()) { + manager.focusOn(null); + + inline.edit(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to edit.')); + }, + + _warnUser: function(message) { + new JX.Notification() + .setContent(message) + .alterClassName('jx-notification-alert', true) + .setDuration(1000) + .show(); + }, + _onjumpkey: function(delta, filter, manager) { var state = this._getSelectionState(); @@ -534,8 +586,7 @@ JX.install('DiffChangesetList', { }, _onaction: function(action, e) { - // TODO: This can become a kill once things fully switch over.. - e.prevent(); + e.kill(); var inline = this._getInlineForEvent(e); var is_ref = false; diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 530f621caf..76706fd8f1 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -114,6 +114,27 @@ JX.install('DiffInline', { return this; }, + canReply: function() { + if (!this._hasAction('reply')) { + return false; + } + + return true; + }, + + canEdit: function() { + if (!this._hasAction('edit')) { + return false; + } + + return true; + }, + + _hasAction: function(action) { + var nodes = JX.DOM.scry(this._row, 'a', 'differential-inline-' + action); + return (nodes.length > 0); + }, + _newRow: function() { var attributes = { sigil: 'inline-row' @@ -151,6 +172,10 @@ JX.install('DiffInline', { .start(); }, + isHidden: function() { + return this._hidden; + }, + toggleDone: function() { var uri = this._getInlineURI(); var data = { From 1e47ba2481e1f696b280081bf35d2bb621f46520 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 21:41:47 -0700 Subject: [PATCH 081/543] Use setDrag UI for reordering workboard columns Summary: This UI can use the setDrag call to reduce clutter on the reodering dialog. Test Plan: Reorder some columns, save. {F4959906} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17898 --- resources/celerity/map.php | 12 ++++++------ .../PhabricatorProjectBoardReorderController.php | 8 ++------ .../rsrc/css/phui/object-item/phui-oi-drag-ui.css | 8 ++++++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 13f0f6bc61..b457445caa 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd1bf3405', + 'core.pkg.css' => 'ee5f28cd', 'core.pkg.js' => '2ff7879f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', @@ -129,7 +129,7 @@ return array( 'rsrc/css/phui/calendar/phui-calendar.css' => '477acfaa', 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '19f9369b', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', - 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'f12cbc9f', + 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '7c8ec27a', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', @@ -873,7 +873,7 @@ return array( 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', - 'phui-oi-drag-ui-css' => 'f12cbc9f', + 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', 'phui-oi-list-view-css' => '7c8ec27a', 'phui-oi-simple-ui-css' => 'a8beebea', @@ -977,6 +977,9 @@ return array( 'javelin-stratcom', 'javelin-vector', ), + '08f4ccc3' => array( + 'phui-oi-list-view-css', + ), '0a0b10e9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2170,9 +2173,6 @@ return array( 'f10fd7a3' => array( 'javelin-install', ), - 'f12cbc9f' => array( - 'phui-oi-list-view-css', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php index fc348bb02f..8cf75ab2a4 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php @@ -94,7 +94,8 @@ final class PhabricatorProjectBoardReorderController $list = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setID($list_id) - ->setFlush(true); + ->setFlush(true) + ->setDrag(true); foreach ($columns as $column) { // Don't allow milestone columns to be reordered. @@ -134,14 +135,9 @@ final class PhabricatorProjectBoardReorderController 'reorderURI' => $reorder_uri, )); - $note = id(new PHUIInfoView()) - ->appendChild(pht('Drag and drop columns to reorder them.')) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - return $this->newDialog() ->setTitle(pht('Reorder Columns')) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($note) ->appendChild($list) ->addSubmitButton(pht('Done')); } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css index caf9ff8dd0..5f24e2b983 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css @@ -23,6 +23,10 @@ margin-top: 4px; } +.phui-oi-drag .phui-oi-name { + padding-left: 0; +} + .phui-oi-drag.phui-oi-with-image-icon .phui-oi-frame, .phui-oi-drag.phui-oi-with-image .phui-oi-frame, .phui-oi-drag .phui-oi-frame { @@ -57,3 +61,7 @@ .phui-oi-list-drag .drag-ghost { margin-top: 4px; } + +.phui-oi-list-drag .phui-object-icon-pane { + padding-right: 8px; +} From ef839192aa5a351c6b28575320f27efa0aa15826 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 19:50:59 -0700 Subject: [PATCH 082/543] Only show member/watcher notes on Members page in Projects Summary: Restricts the view of the membership privileges to just the Members page itself, and not other pages like Home/Details. Test Plan: Test Home, Test Members, see correct layouts. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17896 --- ...PhabricatorProjectMembersViewController.php | 6 ++++-- .../view/PhabricatorProjectUserListView.php | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index cd80d0c724..30ff425a51 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -26,13 +26,15 @@ final class PhabricatorProjectMembersViewController ->setUser($viewer) ->setProject($project) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUserPHIDs($project->getMemberPHIDs()); + ->setUserPHIDs($project->getMemberPHIDs()) + ->setShowNote(true); $watcher_list = id(new PhabricatorProjectWatcherListView()) ->setUser($viewer) ->setProject($project) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUserPHIDs($project->getWatcherPHIDs()); + ->setUserPHIDs($project->getWatcherPHIDs()) + ->setShowNote(true); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_MEMBERS); diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index 0c0e2c1d2b..51c2ced6d1 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -6,6 +6,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { private $userPHIDs; private $limit; private $background; + private $showNote; public function setProject(PhabricatorProject $project) { $this->project = $project; @@ -39,6 +40,11 @@ abstract class PhabricatorProjectUserListView extends AphrontView { return $this; } + public function setShowNote($show) { + $this->showNote = $show; + return $this; + } + abstract protected function canEditList(); abstract protected function getNoDataString(); abstract protected function getRemoveURI($phid); @@ -136,11 +142,13 @@ abstract class PhabricatorProjectUserListView extends AphrontView { ->setHeader($header) ->setObjectList($list); - if ($this->getMembershipNote()) { - $info = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_PLAIN) - ->appendChild($this->getMembershipNote()); - $box->setInfoView($info); + if ($this->showNote) { + if ($this->getMembershipNote()) { + $info = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_PLAIN) + ->appendChild($this->getMembershipNote()); + $box->setInfoView($info); + } } if ($this->background) { From 06c933781e9704e167084e9dd6d642905ff7f4a3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 06:36:03 -0700 Subject: [PATCH 083/543] Move keyboard focus reticle code to Differential Summary: Ref T12634. Using keyboard shortcuts in Differential currently relies on focus behavior in `KeyboardShortcutManager`. This possibly made sense long ago, but no longer does, and leads to a whole slew of bugs where the reticle doesn't interact properly with anything else. Move it to Differential so it can be made reasonably aware of edit operations, Quicksand navigation, etc. This just moves the code; future diffs will actually fix bugs. Test Plan: Used "n", "j", etc., saw the same behavior as before. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634 Differential Revision: https://secure.phabricator.com/D17899 --- .../js/application/diff/DiffChangesetList.js | 62 +++++++++++++++---- .../rsrc/js/core/KeyboardShortcutManager.js | 43 ------------- 2 files changed, 49 insertions(+), 56 deletions(-) diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 27b539a311..b5fde20329 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -60,7 +60,10 @@ JX.install('DiffChangesetList', { _changesets: null, _cursorItem: null, - _lastKeyboardManager: null, + + _focusNode: null, + _focusStart: null, + _focusEnd: null, sleep: function() { this._asleep = true; @@ -198,7 +201,7 @@ JX.install('DiffChangesetList', { if (cursor.type == 'comment') { var inline = cursor.inline; if (inline.canReply()) { - manager.focusOn(null); + this.setFocus(null); inline.reply(); return; @@ -217,7 +220,7 @@ JX.install('DiffChangesetList', { if (cursor.type == 'comment') { var inline = cursor.inline; if (inline.canEdit()) { - manager.focusOn(null); + this.setFocus(null); inline.edit(); return; @@ -310,21 +313,13 @@ JX.install('DiffChangesetList', { }, _redrawSelection: function(manager, scroll) { - manager = manager || this._lastKeyboardManager; - this._lastKeyboardManager = manager; - - if (this.isAsleep()) { - manager.focusOn(null); - return; - } - var cursor = this._cursorItem; if (!cursor) { - manager.focusOn(null); + this.setFocus(null); return; } - manager.focusOn(cursor.nodes.begin, cursor.nodes.end); + this.setFocus(cursor.nodes.begin, cursor.nodes.end); if (scroll) { manager.scrollTo(cursor.nodes.begin); @@ -644,6 +639,47 @@ JX.install('DiffChangesetList', { } }, + setFocus: function(node, extended_node) { + this._focusStart = node; + this._focusEnd = extended_node; + this._redrawFocus(); + }, + + _redrawFocus: function() { + var node = this._focusStart; + var extended_node = this._focusEnd || node; + + var reticle = this._getFocusNode(); + if (!node) { + JX.DOM.remove(reticle); + return; + } + + // Outset the reticle some pixels away from the element, so there's some + // space between the focused element and the outline. + var p = JX.Vector.getPos(node); + var s = JX.Vector.getAggregateScrollForNode(node); + + p.add(s).add(-4, -4).setPos(reticle); + // Compute the size we need to extend to the full extent of the focused + // nodes. + JX.Vector.getPos(extended_node) + .add(-p.x, -p.y) + .add(JX.Vector.getDim(extended_node)) + .add(8, 8) + .setDim(reticle); + + JX.DOM.getContentFrame().appendChild(reticle); + }, + + _getFocusNode: function() { + if (!this._focusNode) { + var node = JX.$N('div', {className : 'keyboard-focus-focus-reticle'}); + this._focusNode = node; + } + return this._focusNode; + }, + _deleteInlineByID: function(id) { var uri = this.getInlineURI(); var data = { diff --git a/webroot/rsrc/js/core/KeyboardShortcutManager.js b/webroot/rsrc/js/core/KeyboardShortcutManager.js index c9a1fbe64e..281c12a8f3 100644 --- a/webroot/rsrc/js/core/KeyboardShortcutManager.js +++ b/webroot/rsrc/js/core/KeyboardShortcutManager.js @@ -54,7 +54,6 @@ JX.install('KeyboardShortcutManager', { members : { _shortcuts : null, - _focusReticle : null, /** * Instead of calling this directly, you should call @@ -83,48 +82,6 @@ JX.install('KeyboardShortcutManager', { JX.DOM.scrollToPosition(0, node_position.y + scroll_distance.y - 60); }, - /** - * Move the keyboard shortcut focus to an element. - * - * @param Node Node to focus, or pass null to clear the focus. - * @param Node To focus multiple nodes (like rows in a table), specify the - * top-left node as the first parameter and the bottom-right - * node as the focus extension. - * @return void - */ - focusOn : function(node, extended_node) { - this._clearReticle(); - - if (!node) { - return; - } - - var r = JX.$N('div', {className : 'keyboard-focus-focus-reticle'}); - - extended_node = extended_node || node; - - // Outset the reticle some pixels away from the element, so there's some - // space between the focused element and the outline. - var p = JX.Vector.getPos(node); - var s = JX.Vector.getAggregateScrollForNode(node); - - p.add(s).add(-4, -4).setPos(r); - // Compute the size we need to extend to the full extent of the focused - // nodes. - JX.Vector.getPos(extended_node) - .add(-p.x, -p.y) - .add(JX.Vector.getDim(extended_node)) - .add(8, 8) - .setDim(r); - JX.DOM.getContentFrame().appendChild(r); - - this._focusReticle = r; - }, - - _clearReticle : function() { - this._focusReticle && JX.DOM.remove(this._focusReticle); - this._focusReticle = null; - }, _onkeypress : function(e) { if (!(this._getKey(e) in JX.KeyboardShortcutManager._downkeys)) { this._onkeyhit(e); From 5d7202526f3dcbe09513985ee1aa89fdf8f7dbe9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 06:40:50 -0700 Subject: [PATCH 084/543] Hide the Differential keyboard focus reticle after Quicksand navigation Summary: Ref T8047. Ref T12634. When we sleep, hide the reticle. Restore it when we wake. Test Plan: - With Quicksand enabled.. - Used "j" to select a change in a revision. - Navigated away by clicking a link. - WOW! Reticle vanished properly! - Used "back" to return. - Reticle returned properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634, T8047 Differential Revision: https://secure.phabricator.com/D17900 --- resources/celerity/map.php | 32 +++++++++---------- .../js/application/diff/DiffChangesetList.js | 6 +++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b457445caa..0a321e9a59 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => 'ee5f28cd', - 'core.pkg.js' => '2ff7879f', + 'core.pkg.js' => '115cb4da', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '5ee318c2', + 'differential.pkg.js' => 'd831041b', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'f7100923', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'f10fd7a3', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'c457058f', 'rsrc/js/application/diff/DiffInline.js' => '00db3c3a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', @@ -474,7 +474,7 @@ return array( 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', - 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', + 'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => 'c5af80a2', @@ -782,7 +782,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'f7100923', - 'phabricator-diff-changeset-list' => 'f10fd7a3', + 'phabricator-diff-changeset-list' => 'c457058f', 'phabricator-diff-inline' => '00db3c3a', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -793,7 +793,7 @@ return array( 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', - 'phabricator-keyboard-shortcut-manager' => '4a021c10', + 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', 'phabricator-main-menu-view' => '5294060f', 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', @@ -1253,13 +1253,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - '4a021c10' => array( - 'javelin-install', - 'javelin-util', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - ), '4b3c4443' => array( 'phuix-icon-view', ), @@ -1916,12 +1909,22 @@ return array( 'javelin-install', 'javelin-dom', ), + 'c19dd9b9' => array( + 'javelin-install', + 'javelin-util', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'phabricator-tooltip', ), + 'c457058f' => array( + 'javelin-install', + ), 'c587b80f' => array( 'javelin-install', ), @@ -2170,9 +2173,6 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f10fd7a3' => array( - 'javelin-install', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index b5fde20329..f9a3c5d5d9 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -67,11 +67,15 @@ JX.install('DiffChangesetList', { sleep: function() { this._asleep = true; + + this._redrawFocus(); }, wake: function() { this._asleep = false; + this._redrawFocus(); + if (this._initialized) { return; } @@ -650,7 +654,7 @@ JX.install('DiffChangesetList', { var extended_node = this._focusEnd || node; var reticle = this._getFocusNode(); - if (!node) { + if (!node || this.isAsleep()) { JX.DOM.remove(reticle); return; } From 7d6133929a6650bc910761e7360f07a69880a0e5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 06:50:47 -0700 Subject: [PATCH 085/543] Resize the Differential keyboard focus reticle when the window is resized Summary: Fixes T12632. Ref T12634. Currently, the keyboard focus reticle does not redraw properly after a window resize. Test Plan: - Used "n" to select a block. - Resized the window. - Saw the reticle also resize properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634, T12632 Differential Revision: https://secure.phabricator.com/D17901 --- resources/celerity/map.php | 12 ++++++------ .../rsrc/js/application/diff/DiffChangesetList.js | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0a321e9a59..ab3628f264 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '115cb4da', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'd831041b', + 'differential.pkg.js' => '14ef6888', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'f7100923', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'c457058f', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'e5c5e171', 'rsrc/js/application/diff/DiffInline.js' => '00db3c3a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', @@ -782,7 +782,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'f7100923', - 'phabricator-diff-changeset-list' => 'c457058f', + 'phabricator-diff-changeset-list' => 'e5c5e171', 'phabricator-diff-inline' => '00db3c3a', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1922,9 +1922,6 @@ return array( 'javelin-stratcom', 'phabricator-tooltip', ), - 'c457058f' => array( - 'javelin-install', - ), 'c587b80f' => array( 'javelin-install', ), @@ -2140,6 +2137,9 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e5c5e171' => array( + 'javelin-install', + ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index f9a3c5d5d9..cdb61c0a15 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -47,6 +47,9 @@ JX.install('DiffChangesetList', { 'click', ['differential-inline-comment', 'differential-inline-reply'], onreply); + + var onresize = JX.bind(this, this._ifawake, this._onresize); + JX.Stratcom.listen('resize', null, onresize); }, properties: { @@ -584,6 +587,10 @@ JX.install('DiffChangesetList', { inline.setHidden(is_hide); }, + _onresize: function() { + this._redrawFocus(); + }, + _onaction: function(action, e) { e.kill(); From 3c0da816196741ec790fa15878fc5623dcae767a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 06:59:32 -0700 Subject: [PATCH 086/543] When the Differential filetree is toggled, resize the keyboard reticle properly Summary: Ref T9270. Ref T12634. Emit a resize event after toggling the filetree so that things can recalculate layout. This just does the keyboard reticle, not the mouse/edit reticle. Test Plan: - Used "n" to select a block. - Used "f" to toggle the filetree. - Saw reticle resize properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634, T9270 Differential Revision: https://secure.phabricator.com/D17902 --- resources/celerity/map.php | 20 +++++++++---------- .../rsrc/js/core/behavior-phabricator-nav.js | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ab3628f264..c662ac7c88 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => 'ee5f28cd', - 'core.pkg.js' => '115cb4da', + 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', 'differential.pkg.js' => '14ef6888', @@ -506,7 +506,7 @@ return array( 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', - 'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d', + 'rsrc/js/core/behavior-phabricator-nav.js' => '08163386', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', @@ -664,7 +664,7 @@ return array( 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', - 'javelin-behavior-phabricator-nav' => '08675c6d', + 'javelin-behavior-phabricator-nav' => '08163386', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', @@ -955,13 +955,7 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), - '0825c27a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), - '08675c6d' => array( + '08163386' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', @@ -971,6 +965,12 @@ return array( 'javelin-request', 'javelin-util', ), + '0825c27a' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), '087e919c' => array( 'javelin-install', 'javelin-dom', diff --git a/webroot/rsrc/js/core/behavior-phabricator-nav.js b/webroot/rsrc/js/core/behavior-phabricator-nav.js index ca7fc0d14a..cd132550a8 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/core/behavior-phabricator-nav.js @@ -113,6 +113,10 @@ JX.behavior('phabricator-nav', function(config) { new JX.Request('/settings/adjust/', JX.bag) .setData({ key : 'nav-collapsed', value : (collapsed ? 1 : 0) }) .send(); + + // Invoke a resize event so page elements can redraw if they need to. One + // example is the selection reticles in Differential. + JX.Stratcom.invoke('resize'); }); From 665ff4fdf6e7501832859df5ffd2112cbc711753 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 07:03:28 -0700 Subject: [PATCH 087/543] Redraw the Differential keyboard reticle after collapsing/un-collapsing an inline Summary: Ref T12634. Fixes T10049. Toggling an inline currently leaves the reticle oddly-positioned. Test Plan: - Selected a comment with the keyboard. - Collapsed it. - Saw reticle behave reasonably. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634, T10049 Differential Revision: https://secure.phabricator.com/D17903 --- resources/celerity/map.php | 12 ++++++------ webroot/rsrc/js/application/diff/DiffInline.js | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c662ac7c88..cf4ec03237 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '14ef6888', + 'differential.pkg.js' => 'a185599e', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'f7100923', 'rsrc/js/application/diff/DiffChangesetList.js' => 'e5c5e171', - 'rsrc/js/application/diff/DiffInline.js' => '00db3c3a', + 'rsrc/js/application/diff/DiffInline.js' => 'f3af20b1', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -783,7 +783,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'f7100923', 'phabricator-diff-changeset-list' => 'e5c5e171', - 'phabricator-diff-inline' => '00db3c3a', + 'phabricator-diff-inline' => 'f3af20b1', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -918,9 +918,6 @@ return array( 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( - '00db3c3a' => array( - 'javelin-dom', - ), '013ffff9' => array( 'javelin-install', 'javelin-util', @@ -2173,6 +2170,9 @@ return array( 'javelin-workflow', 'javelin-json', ), + 'f3af20b1' => array( + 'javelin-dom', + ), 'f50152ad' => array( 'phui-timeline-view-css', ), diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 76706fd8f1..50ec18e452 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -170,6 +170,8 @@ JX.install('DiffInline', { new JX.Workflow(inline_uri, {op: op, ids: comment_id}) .setHandler(JX.bag) .start(); + + JX.Stratcom.invoke('resize'); }, isHidden: function() { From 1493f08272dce974667e57c43b84f0e70c0dc187 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 07:11:43 -0700 Subject: [PATCH 088/543] Emit resize events after making document changes during inline editing Summary: Ref T12634. Fixes T12633. These events allow the keyboard reticle to resize properly. (I expect to possibly hide/disable the reticle in the future during edits, but at least make the behavior sensible for now.) Test Plan: - Used "n" to select a block. - Clicked a line number in that block to start a new inline comment. - Saw reticle resize properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634, T12633 Differential Revision: https://secure.phabricator.com/D17904 --- resources/celerity/map.php | 12 ++++++------ webroot/rsrc/js/application/diff/DiffInline.js | 6 ++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cf4ec03237..2d5db2671d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'a185599e', + 'differential.pkg.js' => '6ee9a850', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'f7100923', 'rsrc/js/application/diff/DiffChangesetList.js' => 'e5c5e171', - 'rsrc/js/application/diff/DiffInline.js' => 'f3af20b1', + 'rsrc/js/application/diff/DiffInline.js' => '4bbefc49', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -783,7 +783,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'f7100923', 'phabricator-diff-changeset-list' => 'e5c5e171', - 'phabricator-diff-inline' => 'f3af20b1', + 'phabricator-diff-inline' => '4bbefc49', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1259,6 +1259,9 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + '4bbefc49' => array( + 'javelin-dom', + ), '4c193c96' => array( 'javelin-behavior', 'javelin-uri', @@ -2170,9 +2173,6 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f3af20b1' => array( - 'javelin-dom', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 50ec18e452..16dc024d6e 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -438,6 +438,8 @@ JX.install('DiffInline', { row = next_row; } + JX.Stratcom.invoke('resize'); + return first_meta; }, @@ -516,6 +518,10 @@ JX.install('DiffInline', { // After making changes to inline comments, refresh the transaction // preview at the bottom of the page. this.getChangeset().getChangesetList().redrawPreview(); + + // Emit a resize event so that UI elements like the keyboad focus + // reticle can redraw properly. + JX.Stratcom.invoke('resize'); }, _redraw: function() { From a154407efb324f4c023e15d8353f0aa708e53287 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 07:24:20 -0700 Subject: [PATCH 089/543] Retain keyboard cursor state across more inline edit operations in Differential Summary: Ref T12634. Fixes T8131. Currently, most edit operations (edit, reply, collapse, mark done) lose the keyboard cursor state. Instead, bind the state more tighlty to the inline object itself (instead of the rows which happen to be in the document), and then do a bit of recalculation to try to keep it selected across edits. Test Plan: - Used "n" to select an inline. - Clicked "Done" checkbox. - Pressed "n". - Went to the next inline (previously: lost position in document). - Behavior is also better for: edit, reply, collapse/expand. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12634, T8131 Differential Revision: https://secure.phabricator.com/D17905 --- resources/celerity/map.php | 48 +++++++++---------- .../rsrc/js/application/diff/DiffChangeset.js | 10 +--- .../js/application/diff/DiffChangesetList.js | 27 +++++++++-- .../rsrc/js/application/diff/DiffInline.js | 2 + 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2d5db2671d..7741821c83 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '6ee9a850', + 'differential.pkg.js' => '0bfd141c', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,9 +390,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'f7100923', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'e5c5e171', - 'rsrc/js/application/diff/DiffInline.js' => '4bbefc49', + 'rsrc/js/application/diff/DiffChangeset.js' => '145c34e2', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'ca3b6387', + 'rsrc/js/application/diff/DiffInline.js' => 'b5b1f167', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -781,9 +781,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'f7100923', - 'phabricator-diff-changeset-list' => 'e5c5e171', - 'phabricator-diff-inline' => '4bbefc49', + 'phabricator-diff-changeset' => '145c34e2', + 'phabricator-diff-changeset-list' => 'ca3b6387', + 'phabricator-diff-inline' => 'b5b1f167', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -999,6 +999,17 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), + '145c34e2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1259,9 +1270,6 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), - '4bbefc49' => array( - 'javelin-dom', - ), '4c193c96' => array( 'javelin-behavior', 'javelin-uri', @@ -1834,6 +1842,9 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'b5b1f167' => array( + 'javelin-dom', + ), 'b5c256b8' => array( 'javelin-install', 'javelin-dom', @@ -1973,6 +1984,9 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), + 'ca3b6387' => array( + 'javelin-install', + ), 'ca3f91eb' => array( 'javelin-behavior', 'javelin-dom', @@ -2137,9 +2151,6 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), - 'e5c5e171' => array( - 'javelin-install', - ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2188,17 +2199,6 @@ return array( 'phuix-icon-view', 'phabricator-prefab', ), - 'f7100923' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index e74adfcb79..ba018478a6 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -369,17 +369,11 @@ JX.install('DiffChangeset', { for (var jj = 0; jj < block.items.length; jj++) { var inline = this.getInlineForRow(block.items[jj]); - // If this inline has been collapsed, don't select it with the - // keyboard cursor. - if (inline.isHidden()) { - continue; - } - items.push({ type: block.type, changeset: this, - target: block.items[jj], - inline: inline, + target: inline, + hidden: inline.isHidden(), nodes: { begin: block.items[jj], end: block.items[jj] diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index cdb61c0a15..a83e21e0ae 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -206,7 +206,7 @@ JX.install('DiffChangesetList', { if (cursor) { if (cursor.type == 'comment') { - var inline = cursor.inline; + var inline = cursor.target; if (inline.canReply()) { this.setFocus(null); @@ -225,7 +225,7 @@ JX.install('DiffChangesetList', { if (cursor) { if (cursor.type == 'comment') { - var inline = cursor.inline; + var inline = cursor.target; if (inline.canEdit()) { this.setFocus(null); @@ -284,6 +284,12 @@ JX.install('DiffChangesetList', { } } + // If the item is hidden, don't select it when iterating with jump + // keys. It can still potentially be selected in other ways. + if (items[cursor].hidden) { + continue; + } + // Otherwise, we've found a valid item to select. break; } @@ -328,13 +334,28 @@ JX.install('DiffChangesetList', { this.setFocus(cursor.nodes.begin, cursor.nodes.end); - if (scroll) { + if (manager && scroll) { manager.scrollTo(cursor.nodes.begin); } return this; }, + redrawCursor: function() { + // NOTE: This is setting the cursor to the current cursor. Usually, this + // would have no effect. + + // However, if the old cursor pointed at an inline and the inline has + // been edited so the rows have changed, this updates the cursor to point + // at the new inline with the proper rows for the current state, and + // redraws the reticle correctly. + + var state = this._getSelectionState(); + if (state.cursor !== null) { + this._setSelectionState(state.items[state.cursor]); + } + }, + _getSelectableItems: function() { var result = []; diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 16dc024d6e..2a4c444a86 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -519,6 +519,8 @@ JX.install('DiffInline', { // preview at the bottom of the page. this.getChangeset().getChangesetList().redrawPreview(); + this.getChangeset().getChangesetList().redrawCursor(); + // Emit a resize event so that UI elements like the keyboad focus // reticle can redraw properly. JX.Stratcom.invoke('resize'); From 1b5a276a02d664ff2c48929ccea895022dd61a49 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 08:03:43 -0700 Subject: [PATCH 090/543] Add Differential keyboard shortcuts for "mark done" and "hide/show" Summary: Fixes T8130. Allows selected comments to be shown/hidden (with "q") or marked done/not-done (with "w"). (These key selections are because "qwer" are right next to each other on QWERTY keyboards, and now mean "hide, done, edit, reply".) Also, allow "N" and "P" to do next/previous inline, including hidden inlines. This makes "q" to hide/show a little more powerful and a little easier to undo. Test Plan: Used "q", "w", "N" and "P" to navigate and interact with comments. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8130 Differential Revision: https://secure.phabricator.com/D17906 --- resources/celerity/map.php | 22 +++--- .../view/DifferentialChangesetListView.php | 15 ++++ .../js/application/diff/DiffChangesetList.js | 71 ++++++++++++++++--- .../rsrc/js/application/diff/DiffInline.js | 24 ++++++- 4 files changed, 109 insertions(+), 23 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7741821c83..c16ac91ac1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => '0bfd141c', + 'differential.pkg.js' => 'e486afd0', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,8 +391,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '145c34e2', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'ca3b6387', - 'rsrc/js/application/diff/DiffInline.js' => 'b5b1f167', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'd93e34c2', + 'rsrc/js/application/diff/DiffInline.js' => 'bdf6b568', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -782,8 +782,8 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '145c34e2', - 'phabricator-diff-changeset-list' => 'ca3b6387', - 'phabricator-diff-inline' => 'b5b1f167', + 'phabricator-diff-changeset-list' => 'd93e34c2', + 'phabricator-diff-inline' => 'bdf6b568', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1842,9 +1842,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'b5b1f167' => array( - 'javelin-dom', - ), 'b5c256b8' => array( 'javelin-install', 'javelin-dom', @@ -1893,6 +1890,9 @@ return array( 'javelin-util', 'javelin-request', ), + 'bdf6b568' => array( + 'javelin-dom', + ), 'bea6e7f4' => array( 'javelin-install', 'javelin-dom', @@ -1984,9 +1984,6 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), - 'ca3b6387' => array( - 'javelin-install', - ), 'ca3f91eb' => array( 'javelin-behavior', 'javelin-dom', @@ -2082,6 +2079,9 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + 'd93e34c2' => array( + 'javelin-install', + ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index fea7a2da55..48b2ea5e12 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -252,6 +252,21 @@ final class DifferentialChangesetListView extends AphrontView { pht('You must select a comment to reply to.'), 'You must select a comment to edit.' => pht('You must select a comment to edit.'), + + 'Mark or unmark selected inline comment as done.' => + pht('Mark or unmark selected inline comment as done.'), + 'You must select a comment to mark done.' => + pht('You must select a comment to mark done.'), + + 'Hide or show inline comment.' => + pht('Hide or show inline comment.'), + 'You must select a comment to hide.' => + pht('You must select a comment to hide.'), + + 'Jump to next inline comment, including hidden comments.' => + pht('Jump to next inline comment, including hidden comments.'), + 'Jump to previous inline comment, including hidden comments.' => + pht('Jump to previous inline comment, including hidden comments.'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index a83e21e0ae..01275578fe 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -106,14 +106,27 @@ JX.install('DiffChangesetList', { label = pht('Jump to previous inline comment.'); this._installJumpKey('p', label, -1, 'comment'); + label = pht('Jump to next inline comment, including hidden comments.'); + this._installJumpKey('N', label, 1, 'comment', true); + + label = pht( + 'Jump to previous inline comment, including hidden comments.'); + this._installJumpKey('P', label, -1, 'comment', true); + label = pht('Jump to the table of contents.'); this._installKey('t', label, this._ontoc); label = pht('Reply to selected inline comment.'); - this._installKey('r', label, this._onreply); + this._installKey('r', label, this._onkeyreply); label = pht('Edit selected inline comment.'); - this._installKey('e', label, this._onedit); + this._installKey('e', label, this._onkeyedit); + + label = pht('Mark or unmark selected inline comment as done.'); + this._installKey('w', label, this._onkeydone); + + label = pht('Hide or show inline comment.'); + this._installKey('q', label, this._onkeyhide); }, isAsleep: function() { @@ -190,9 +203,9 @@ JX.install('DiffChangesetList', { .register(); }, - _installJumpKey: function(key, label, delta, filter) { + _installJumpKey: function(key, label, delta, filter, show_hidden) { filter = filter || null; - var handler = JX.bind(this, this._onjumpkey, delta, filter); + var handler = JX.bind(this, this._onjumpkey, delta, filter, show_hidden); return this._installKey(key, label, handler); }, @@ -201,7 +214,7 @@ JX.install('DiffChangesetList', { manager.scrollTo(toc); }, - _onreply: function(manager) { + _onkeyreply: function() { var cursor = this._cursorItem; if (cursor) { @@ -220,7 +233,7 @@ JX.install('DiffChangesetList', { this._warnUser(pht('You must select a comment to reply to.')); }, - _onedit: function(manager) { + _onkeyedit: function() { var cursor = this._cursorItem; if (cursor) { @@ -239,6 +252,44 @@ JX.install('DiffChangesetList', { this._warnUser(pht('You must select a comment to edit.')); }, + _onkeydone: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canDone()) { + this.setFocus(null); + + inline.toggleDone(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to mark done.')); + }, + + _onkeyhide: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canHide()) { + this.setFocus(null); + + inline.setHidden(!inline.isHidden()); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to hide.')); + }, + _warnUser: function(message) { new JX.Notification() .setContent(message) @@ -247,7 +298,7 @@ JX.install('DiffChangesetList', { .show(); }, - _onjumpkey: function(delta, filter, manager) { + _onjumpkey: function(delta, filter, show_hidden, manager) { var state = this._getSelectionState(); var cursor = state.cursor; @@ -286,8 +337,10 @@ JX.install('DiffChangesetList', { // If the item is hidden, don't select it when iterating with jump // keys. It can still potentially be selected in other ways. - if (items[cursor].hidden) { - continue; + if (!show_hidden) { + if (items[cursor].hidden) { + continue; + } } // Otherwise, we've found a valid item to select. diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 2a4c444a86..04f9a03db1 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -130,6 +130,22 @@ JX.install('DiffInline', { return true; }, + canDone: function() { + if (!JX.DOM.scry(this._row, 'input', 'differential-inline-done').length) { + return false; + } + + return true; + }, + + canHide: function() { + if (!JX.DOM.scry(this._row, 'a', 'hide-inline').length) { + return false; + } + + return true; + }, + _hasAction: function(action) { var nodes = JX.DOM.scry(this._row, 'a', 'differential-inline-' + action); return (nodes.length > 0); @@ -171,7 +187,7 @@ JX.install('DiffInline', { .setHandler(JX.bag) .start(); - JX.Stratcom.invoke('resize'); + this._didUpdate(true); }, isHidden: function() { @@ -514,10 +530,12 @@ JX.install('DiffInline', { this._didUpdate(); }, - _didUpdate: function() { + _didUpdate: function(local_only) { // After making changes to inline comments, refresh the transaction // preview at the bottom of the page. - this.getChangeset().getChangesetList().redrawPreview(); + if (!local_only) { + this.getChangeset().getChangesetList().redrawPreview(); + } this.getChangeset().getChangesetList().redrawCursor(); From bf753c8b5a0859ce0d89d1ec5d71b60a017a3862 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 08:50:55 -0700 Subject: [PATCH 091/543] Make passing an object to newCurtain optional Summary: We seem to already support this, just takes it fully there. We don't need to see things like "Flag", etc, on certain subpages of projects/people/etc. Test Plan: Review Members, Subproject pages, no longer see "Flag for Later" which only is for the Project itself. Check manage, still there. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17897 --- .../base/controller/PhabricatorController.php | 10 ++++++---- .../PhabricatorProjectMembersViewController.php | 2 +- .../PhabricatorProjectSubprojectsController.php | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 58d8f6cf6f..c4d1b47c40 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -471,7 +471,7 @@ abstract class PhabricatorController extends AphrontController { ->setViewer($this->getViewer()); } - public function newCurtainView($object) { + public function newCurtainView($object = null) { $viewer = $this->getViewer(); $action_id = celerity_generate_unique_node_id(); @@ -491,9 +491,11 @@ abstract class PhabricatorController extends AphrontController { ->setViewer($viewer) ->setActionList($action_list); - $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); - foreach ($panels as $panel) { - $curtain->addPanel($panel); + if ($object) { + $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); + foreach ($panels as $panel) { + $curtain->addPanel($panel); + } } return $curtain; diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index 30ff425a51..ad553eb664 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -70,7 +70,7 @@ final class PhabricatorProjectMembersViewController $viewer = $this->getViewer(); $id = $project->getID(); - $curtain = $this->newCurtainView($project); + $curtain = $this->newCurtainView(); $is_locked = $project->getIsMembershipLocked(); diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 4a89bb4cde..1aa0c0baac 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -203,7 +203,7 @@ final class PhabricatorProjectSubprojectsController $allows_milestones = $project->supportsMilestones(); $allows_subprojects = $project->supportsSubprojects(); - $curtain = $this->newCurtainView($project); + $curtain = $this->newCurtainView(); if ($allows_milestones && $milestones) { $milestone_text = pht('Create Next Milestone'); From 29cfcc82ef7f84580e798aebeb2abcb8ffec57d7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 09:26:15 -0700 Subject: [PATCH 092/543] Update Milestone/Subproject page Summary: Cleans up the UI, moves details over to curtain, adds some fallback no data strings. Test Plan: Review with and without subprojects, milestones. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17907 --- ...habricatorProjectSubprojectsController.php | 161 +++++++----------- .../view/PhabricatorProjectListView.php | 14 +- 2 files changed, 76 insertions(+), 99 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 1aa0c0baac..f516f87e32 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -26,6 +26,9 @@ final class PhabricatorProjectSubprojectsController $allows_subprojects = $project->supportsSubprojects(); $allows_milestones = $project->supportsMilestones(); + $subproject_list = null; + $milestone_list = null; + if ($allows_subprojects) { $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) @@ -33,6 +36,16 @@ final class PhabricatorProjectSubprojectsController ->needImages(true) ->withIsMilestone(false) ->execute(); + + $subproject_list = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('%s Subprojects', $project->getName())) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->setNoDataString(pht('This project has no subprojects.')) + ->renderList()); } else { $subprojects = array(); } @@ -45,52 +58,25 @@ final class PhabricatorProjectSubprojectsController ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); - } else { - $milestones = array(); - } - if ($milestones) { $milestone_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Milestones')) + ->setHeaderText(pht('%s Milestones', $project->getName())) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList( id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($milestones) + ->setNoDataString(pht('This project has no milestones.')) ->renderList()); } else { - $milestone_list = null; + $milestones = array(); } - if ($subprojects) { - $subproject_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Subprojects')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList( - id(new PhabricatorProjectListView()) - ->setUser($viewer) - ->setProjects($subprojects) - ->renderList()); - } else { - $subproject_list = null; - } - - $property_list = $this->buildPropertyList( - $project, - $milestones, - $subprojects); - $curtain = $this->buildCurtainView( $project, $milestones, $subprojects); - - $details = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($property_list); - $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_SUBPROJECTS); @@ -102,11 +88,24 @@ final class PhabricatorProjectSubprojectsController ->setHeader(pht('Subprojects and Milestones')) ->setHeaderIcon('fa-sitemap'); + require_celerity_resource('project-view-css'); + + // This page isn't reachable via UI, but make it pretty anyways. + $info_view = null; + if (!$milestone_list && !$subproject_list) { + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Milestone projects do not support subprojects '. + 'or milestones.')); + } + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn(array( - $details, + $info_view, $milestone_list, $subproject_list, )); @@ -118,73 +117,6 @@ final class PhabricatorProjectSubprojectsController ->appendChild($view); } - private function buildPropertyList( - PhabricatorProject $project, - array $milestones, - array $subprojects) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $view->addProperty( - pht('Prototype'), - $this->renderStatus( - 'fa-exclamation-triangle red', - pht('Warning'), - pht('Subprojects and milestones are only partially implemented.'))); - - if (!$project->supportsMilestones()) { - $milestone_status = $this->renderStatus( - 'fa-times grey', - pht('Already Milestone'), - pht( - 'This project is already a milestone, and milestones may not '. - 'have their own milestones.')); - } else { - if (!$milestones) { - $milestone_status = $this->renderStatus( - 'fa-check grey', - pht('None Created'), - pht( - 'You can create milestones for this project.')); - } else { - $milestone_status = $this->renderStatus( - 'fa-check green', - pht('Has Milestones'), - pht('This project has milestones.')); - } - } - - $view->addProperty(pht('Milestones'), $milestone_status); - - if (!$project->supportsSubprojects()) { - $subproject_status = $this->renderStatus( - 'fa-times grey', - pht('Milestone'), - pht( - 'This project is a milestone, and milestones may not have '. - 'subprojects.')); - } else { - if (!$subprojects) { - $subproject_status = $this->renderStatus( - 'fa-check grey', - pht('None Created'), - pht('You can create subprojects for this project.')); - } else { - $subproject_status = $this->renderStatus( - 'fa-check green', - pht('Has Subprojects'), - pht( - 'This project has subprojects.')); - } - } - - $view->addProperty(pht('Subprojects'), $subproject_status); - - return $view; - } - private function buildCurtainView( PhabricatorProject $project, array $milestones, @@ -244,6 +176,39 @@ final class PhabricatorProjectSubprojectsController ->setDisabled($subproject_disabled) ->setWorkflow($subproject_workflow)); + + if (!$project->supportsMilestones()) { + $note = pht( + 'This project is already a milestone, and milestones may not '. + 'have their own milestones.'); + } else { + if (!$milestones) { + $note = pht('You can create milestones for this project.'); + } else { + $note = pht('This project has milestones.'); + } + } + + $curtain->newPanel() + ->setHeaderText(pht('Milestones')) + ->appendChild($note); + + if (!$project->supportsSubprojects()) { + $note = pht( + 'This project is a milestone, and milestones may not have '. + 'subprojects.'); + } else { + if (!$subprojects) { + $note = pht('You can create subprojects for this project.'); + } else { + $note = pht('This project has subprojects.'); + } + } + + $curtain->newPanel() + ->setHeaderText(pht('Subprojects')) + ->appendChild($note); + return $curtain; } diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 994e78ce76..d1d0792855 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -5,6 +5,7 @@ final class PhabricatorProjectListView extends AphrontView { private $projects; private $showMember; private $showWatching; + private $noDataString; public function setProjects(array $projects) { $this->projects = $projects; @@ -25,6 +26,11 @@ final class PhabricatorProjectListView extends AphrontView { return $this; } + public function setNoDataString($text) { + $this->noDataString = $text; + return $this; + } + public function renderList() { $viewer = $this->getUser(); $viewer_phid = $viewer->getPHID(); @@ -32,8 +38,14 @@ final class PhabricatorProjectListView extends AphrontView { $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); + $no_data = pht('No projects found.'); + if ($this->noDataString) { + $no_data = $this->noDataString; + } + $list = id(new PHUIObjectItemListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setNoDataString($no_data); foreach ($projects as $key => $project) { $id = $project->getID(); From 8052ab84bfd8b3f3cb571d693ea2fe566b1f9b65 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 09:29:05 -0700 Subject: [PATCH 093/543] Remove "^" (Prev) and "V" (Next) actions on Differential inline comments Summary: Ref T12616. Fixes T12715. I suspect these are very rarely used. (I think you tried to get rid of them before but I pushed back since we couldn't really offer great alternatives at the time?) Now that the code is in a better place: - Click an inline's header (just the colored part) to select it with the keyboard selection cursor. - Click again to deselect it. - You can use "n" and "p" to jump to comments, so "click + n" is the same as the old "V" action. - This also makes it easier to swap between keyboard and mouse workflows, since you can jump into things with the keyboard at any inline. Also, make "Reply" render more consistently. Test Plan: - Did all that stuff, things seemed to work OK. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12715, T12616 Differential Revision: https://secure.phabricator.com/D17908 --- resources/celerity/map.php | 20 ++-- resources/celerity/packages.php | 1 - .../view/DifferentialChangesetListView.php | 2 - .../view/PHUIDiffInlineCommentDetailView.php | 100 ++++++++---------- .../js/application/diff/DiffChangesetList.js | 47 ++++++++ .../differential/behavior-comment-jump.js | 32 ------ 6 files changed, 95 insertions(+), 107 deletions(-) delete mode 100644 webroot/rsrc/js/application/differential/behavior-comment-jump.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c16ac91ac1..7e2a517ed8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'e486afd0', + 'differential.pkg.js' => 'bd321b6e', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,10 +391,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '145c34e2', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'd93e34c2', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'f1101e6e', 'rsrc/js/application/diff/DiffInline.js' => 'bdf6b568', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '89d11432', @@ -619,7 +618,6 @@ return array( 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', - 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-edit-inline-comments' => '89d11432', 'javelin-behavior-differential-feedback-preview' => 'b064af76', @@ -782,7 +780,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '145c34e2', - 'phabricator-diff-changeset-list' => 'd93e34c2', + 'phabricator-diff-changeset-list' => 'f1101e6e', 'phabricator-diff-inline' => 'bdf6b568', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1291,11 +1289,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '4fdb476d' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - ), '503e17fd' => array( 'javelin-install', 'javelin-typeahead-source', @@ -2079,9 +2072,6 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), - 'd93e34c2' => array( - 'javelin-install', - ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', @@ -2184,6 +2174,9 @@ return array( 'javelin-workflow', 'javelin-json', ), + 'f1101e6e' => array( + 'javelin-install', + ), 'f50152ad' => array( 'phui-timeline-view-css', ), @@ -2431,7 +2424,6 @@ return array( 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', - 'javelin-behavior-differential-comment-jump', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 364a695f18..9ea3fa6b35 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -196,7 +196,6 @@ return array( 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', - 'javelin-behavior-differential-comment-jump', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 48b2ea5e12..c69eef008f 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -270,8 +270,6 @@ final class DifferentialChangesetListView extends AphrontView { ), )); - $this->initBehavior('differential-comment-jump', array()); - if ($this->inlineURI) { Javelin::initBehavior('differential-edit-inline-comments', array( 'uri' => $this->inlineURI, diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 9f5616f1cc..cd8c3202b7 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -190,61 +190,31 @@ final class PHUIDiffInlineCommentDetailView } } - $nextprev = null; - if (!$this->preview) { - $nextprev = new PHUIButtonBarView(); - $nextprev->setBorderless(true); - $nextprev->addClass('inline-button-divider'); - - - $up = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Previous')) - ->setIcon('fa-chevron-up') - ->addSigil('differential-inline-prev') - ->setMustCapture(true); - - $down = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Next')) - ->setIcon('fa-chevron-down') - ->addSigil('differential-inline-next') - ->setMustCapture(true); - - if ($this->canHide()) { - $hide = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Hide Comment')) - ->setIcon('fa-times') - ->addSigil('hide-inline') - ->setMustCapture(true); - - $nextprev->addButton($hide); - } - - $nextprev->addButton($up); - $nextprev->addButton($down); - - $action_buttons = array(); - if ($this->allowReply) { - if (!$is_synthetic) { - // NOTE: No product reason why you can't reply to these, but the reply - // mechanism currently sends the inline comment ID to the server, not - // file/line information, and synthetic comments don't have an inline - // comment ID. - - $action_buttons[] = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-reply') - ->setTooltip(pht('Reply')) - ->addSigil('differential-inline-reply') - ->setMustCapture(true); - } - } - } - $anchor_name = $this->getAnchorName(); + $action_buttons = array(); + + $can_reply = + (!$this->editable) && + (!$this->preview) && + ($this->allowReply) && + + // NOTE: No product reason why you can't reply to synthetic comments, + // but the reply mechanism currently sends the inline comment ID to the + // server, not file/line information, and synthetic comments don't have + // an inline comment ID. + (!$is_synthetic); + + + if ($can_reply) { + $action_buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-reply') + ->setTooltip(pht('Reply')) + ->addSigil('differential-inline-reply') + ->setMustCapture(true); + } + if ($this->editable && !$this->preview) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') @@ -280,6 +250,15 @@ final class PHUIDiffInlineCommentDetailView ->setMustCapture(true); } + if (!$this->preview && $this->canHide()) { + $action_buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setTooltip(pht('Hide Comment')) + ->setIcon('fa-times') + ->addSigil('hide-inline') + ->setMustCapture(true); + } + $done_button = null; if (!$is_synthetic) { @@ -430,7 +409,6 @@ final class PHUIDiffInlineCommentDetailView $done_button, $links, $actions, - $nextprev, )); $markup = javelin_tag( @@ -441,10 +419,16 @@ final class PHUIDiffInlineCommentDetailView 'meta' => $metadata, ), array( - phutil_tag_div('differential-inline-comment-head grouped', array( - $group_left, - $group_right, - )), + javelin_tag( + 'div', + array( + 'class' => 'differential-inline-comment-head grouped', + 'sigil' => 'differential-inline-header', + ), + array( + $group_left, + $group_right, + )), phutil_tag_div( 'differential-inline-comment-content', phutil_tag_div('phabricator-remarkup', $content)), diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 01275578fe..6380ccbf42 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -50,6 +50,12 @@ JX.install('DiffChangesetList', { var onresize = JX.bind(this, this._ifawake, this._onresize); JX.Stratcom.listen('resize', null, onresize); + + var onselect = JX.bind(this, this._ifawake, this._onselect); + JX.Stratcom.listen( + 'mousedown', + ['differential-inline-comment', 'differential-inline-header'], + onselect); }, properties: { @@ -665,6 +671,47 @@ JX.install('DiffChangesetList', { this._redrawFocus(); }, + _onselect: function(e) { + // If the user clicked some element inside the header, like an action + // icon, ignore the event. They have to click the header element itself. + if (e.getTarget() !== e.getNode('differential-inline-header')) { + return; + } + + var inline = this._getInlineForEvent(e); + if (!inline) { + return; + } + + // The user definitely clicked an inline, so we're going to handle the + // event. + e.kill(); + + var selection = this._getSelectionState(); + var item; + + // If the comment the user clicked is currently selected, deselect it. + // This makes it easy to undo things if you clicked by mistake. + if (selection.cursor !== null) { + item = selection.items[selection.cursor]; + if (item.target === inline) { + this._setSelectionState(null); + return; + } + } + + // Otherwise, select the item that the user clicked. This makes it + // easier to resume keyboard operations after using the mouse to do + // something else. + var items = selection.items; + for (var ii = 0; ii < items.length; ii++) { + item = items[ii]; + if (item.target === inline) { + this._setSelectionState(item); + } + } + }, + _onaction: function(action, e) { e.kill(); diff --git a/webroot/rsrc/js/application/differential/behavior-comment-jump.js b/webroot/rsrc/js/application/differential/behavior-comment-jump.js deleted file mode 100644 index 53d43fd05e..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-comment-jump.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @provides javelin-behavior-differential-comment-jump - * @requires javelin-behavior - * javelin-stratcom - * javelin-dom - */ - -JX.behavior('differential-comment-jump', function() { - function handle_jump(offset) { - return function(e) { - var parent = JX.$('differential-review-stage'); - var clicked = e.getNode('differential-inline-comment'); - var inlines = JX.DOM.scry(parent, 'div', 'differential-inline-comment'); - var jumpto = null; - - for (var ii = 0; ii < inlines.length; ii++) { - if (inlines[ii] == clicked) { - jumpto = inlines[(ii + offset + inlines.length) % inlines.length]; - break; - } - } - JX.Stratcom.invoke('differential-toggle-file-request', null, { - element: jumpto - }); - JX.DOM.scrollTo(jumpto); - e.kill(); - }; - } - - JX.Stratcom.listen('click', 'differential-inline-prev', handle_jump(-1)); - JX.Stratcom.listen('click', 'differential-inline-next', handle_jump(+1)); -}); From fdf001739c3af403a37695fd99d8b542c14b2578 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 09:53:38 -0700 Subject: [PATCH 094/543] Normalize headers and actions in Project sub pages Summary: Run through all the pages in projects and make sure they all feel similar. Adds back curtain on board manage page, even though it is sad for only having a single action. Test Plan: Test all pages on a project for consistency in UI. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17909 --- ...habricatorProjectBoardManageController.php | 41 +++++++++++-------- ...abricatorProjectColumnDetailController.php | 3 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index 4607b50c2a..5c71dcfb61 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -31,6 +31,7 @@ final class PhabricatorProjectBoardManageController $board_id = $board->getID(); $header = $this->buildHeaderView($board); + $curtain = $this->buildCurtainView($board); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$board_id}/"); @@ -46,7 +47,8 @@ final class PhabricatorProjectBoardManageController ->setHeader($header) ->addClass('project-view-home') ->addClass('project-view-people-home') - ->setFooter($columns_list); + ->setCurtain($curtain) + ->setMainColumn($columns_list); $title = array( pht('Manage Workboard'), @@ -63,29 +65,35 @@ final class PhabricatorProjectBoardManageController private function buildHeaderView(PhabricatorProject $board) { $viewer = $this->getViewer(); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Workboard: %s', $board->getDisplayName())) + ->setUser($viewer); + + return $header; + } + + private function buildCurtainView(PhabricatorProject $board) { + $viewer = $this->getViewer(); + $id = $board->getID(); + + $curtain = $this->newCurtainView(); + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $board, PhabricatorPolicyCapability::CAN_EDIT); - $id = $board->getID(); $disable_uri = $this->getApplicationURI("board/{$id}/disable/"); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-ban') - ->setText(pht('Disable Board')) - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-ban') + ->setName(pht('Disable Workboard')) + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Workboard: %s', $board->getDisplayName())) - ->setUser($viewer) - ->setProfileHeader(true) - ->addActionLink($button); - - return $header; + return $curtain; } private function buildColumnsList( @@ -126,6 +134,7 @@ final class PhabricatorProjectBoardManageController return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Columns')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($view); } diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index 56eb16613f..24efec5ebb 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -75,8 +75,7 @@ final class PhabricatorProjectColumnDetailController $header = id(new PHUIHeaderView()) ->setHeader(pht('Column: %s', $column->getDisplayName())) - ->setUser($viewer) - ->setProfileHeader(true); + ->setUser($viewer); if ($column->isHidden()) { $header->setStatus('fa-ban', 'dark', pht('Hidden')); From 86b9deb8a9e9f0de568c75971f6d4acd4e677cb9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 10:02:09 -0700 Subject: [PATCH 095/543] Move inline anchors up, to dolumn-level Summary: Fixes T8420. Now that hidden inlines no longer fold into a big clump, anchors can just jump to them in a normal way. Move the anchors up a smidge so thing work. Test Plan: Clicked an anchor pointed at a hidden inline, ended up in the right place. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8420 Differential Revision: https://secure.phabricator.com/D17910 --- .../diff/view/PHUIDiffInlineCommentDetailView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index cd8c3202b7..9c3c55292e 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -405,7 +405,6 @@ final class PHUIDiffInlineCommentDetailView 'class' => 'inline-head-right', ), array( - $anchor, $done_button, $links, $actions, @@ -445,6 +444,7 @@ final class PHUIDiffInlineCommentDetailView pht('...')); return array( + $anchor, $markup, $summary, ); From 325682248a8e461f1ab59223815f8c8cf08bf1a7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 10:18:31 -0700 Subject: [PATCH 096/543] If there's an anchor in the URL in Differential, don't stick to the bottom of the page as content loads Summary: Fixes T11784. A lot of things are interacting here, but this probably gets slightly better results slightly more often? Basically: - When we load content, we try to keep the viewport "stable" on the page, so the page doesn't jump around like crazy. - If you're near the top or bottom of the page, we try to stick to the top (e.g., reading the summary) or bottom (e.g., writing a comment). - But, if you followed an anchor to a comment that's close to the bottom of the page, we might stick to the bottom intead of staying with the anchor. Kind of do a better job by not sticking to the bottom if you have an anchor. This will get things wrong if you follow an anchor, scroll down, start writing a comment, etc. But this whole thing is a pile of guesses anyway. Test Plan: - Followed an anchor, saw non-sticky stabilization. - Loaded the page normally, scrolled to the bottom real fast, saw sticky stabilization. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11784 Differential Revision: https://secure.phabricator.com/D17911 --- resources/celerity/map.php | 28 +++++++++---------- .../rsrc/js/application/diff/DiffChangeset.js | 6 ++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7e2a517ed8..b389a84ba5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '58712637', - 'differential.pkg.js' => 'bd321b6e', + 'differential.pkg.js' => 'f1b636fb', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,7 +390,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '145c34e2', + 'rsrc/js/application/diff/DiffChangeset.js' => 'c5742feb', 'rsrc/js/application/diff/DiffChangesetList.js' => 'f1101e6e', 'rsrc/js/application/diff/DiffInline.js' => 'bdf6b568', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -779,7 +779,7 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '145c34e2', + 'phabricator-diff-changeset' => 'c5742feb', 'phabricator-diff-changeset-list' => 'f1101e6e', 'phabricator-diff-inline' => 'bdf6b568', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', @@ -997,17 +997,6 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - '145c34e2' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1926,6 +1915,17 @@ return array( 'javelin-stratcom', 'phabricator-tooltip', ), + 'c5742feb' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'c587b80f' => array( 'javelin-install', ), diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index ba018478a6..3130fced44 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -440,6 +440,12 @@ JX.install('DiffChangeset', { var near_top = (old_pos.y <= sticky); var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky)); + // If we have an anchor in the URL, never stick to the bottom of the + // page. See T11784 for discussion. + if (window.location.hash) { + near_bot = false; + } + var target_pos = JX.Vector.getPos(target); var target_dim = JX.Vector.getDim(target); var target_mid = (target_pos.y + (target_dim.y / 2)); From a53d387ea6a08498fd19b8f53a9d3526fc2df68b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 10:53:01 -0700 Subject: [PATCH 097/543] Move Phriction Title transaction to Modular Transactions Summary: Ref T12625. Moves TYPE_TITLE to modular transaction. Test Plan: New Document, Edit Document, test validation, verify feed stories. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12625 Differential Revision: https://secure.phabricator.com/D17912 --- src/__phutil_library_map__.php | 6 +- .../PhrictionCreateConduitAPIMethod.php | 2 +- .../conduit/PhrictionEditConduitAPIMethod.php | 2 +- .../controller/PhrictionEditController.php | 5 +- .../editor/PhrictionTransactionEditor.php | 46 ++------- .../storage/PhrictionTransaction.php | 68 ++------------ .../PhrictionDocumentTitleTransaction.php | 94 +++++++++++++++++++ .../PhrictionDocumentTransactionType.php | 4 + .../storage/PhabricatorModularTransaction.php | 4 +- 9 files changed, 125 insertions(+), 106 deletions(-) create mode 100644 src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php create mode 100644 src/applications/phriction/xaction/PhrictionDocumentTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f08a02579e..ba74adb658 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4621,6 +4621,8 @@ phutil_register_library_map(array( 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', + 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', + 'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php', 'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php', 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', 'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php', @@ -10257,6 +10259,8 @@ phutil_register_library_map(array( 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentStatus' => 'PhrictionConstants', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', + 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', 'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod', @@ -10270,7 +10274,7 @@ phutil_register_library_map(array( 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhrictionTransaction' => 'PhabricatorApplicationTransaction', + 'PhrictionTransaction' => 'PhabricatorModularTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index 36c74f4a68..4b05874410 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -47,7 +47,7 @@ final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index e99a866529..d4c2b63b5a 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -42,7 +42,7 @@ final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 038861d136..1917348c24 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -133,7 +133,7 @@ final class PhrictionEditController $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) @@ -174,7 +174,8 @@ final class PhrictionEditController } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = nonempty( - $ex->getShortMessage(PhrictionTransaction::TYPE_TITLE), + $ex->getShortMessage( + PhrictionDocumentTitleTransaction::TRANSACTIONTYPE), true); $e_content = nonempty( $ex->getShortMessage(PhrictionTransaction::TYPE_CONTENT), diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index af238dfcf9..95575a7619 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -29,7 +29,7 @@ final class PhrictionTransactionEditor return $this; } - private function getOldContent() { + public function getOldContent() { return $this->oldContent; } @@ -38,7 +38,7 @@ final class PhrictionTransactionEditor return $this; } - private function getNewContent() { + public function getNewContent() { return $this->newContent; } @@ -80,7 +80,6 @@ final class PhrictionTransactionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhrictionTransaction::TYPE_TITLE; $types[] = PhrictionTransaction::TYPE_CONTENT; $types[] = PhrictionTransaction::TYPE_DELETE; $types[] = PhrictionTransaction::TYPE_MOVE_TO; @@ -99,11 +98,6 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - if ($this->getIsNewObject()) { - return null; - } - return $this->getOldContent()->getTitle(); case PhrictionTransaction::TYPE_CONTENT: if ($this->getIsNewObject()) { return null; @@ -121,7 +115,6 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: return $xaction->getNewValue(); @@ -154,7 +147,7 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: case PhrictionTransaction::TYPE_MOVE_TO: @@ -178,7 +171,6 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_MOVE_TO: $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); @@ -232,9 +224,6 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - $this->getNewContent()->setTitle($xaction->getNewValue()); - break; case PhrictionTransaction::TYPE_CONTENT: $this->getNewContent()->setContent($xaction->getNewValue()); break; @@ -270,7 +259,7 @@ final class PhrictionTransactionEditor $save_content = false; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: case PhrictionTransaction::TYPE_MOVE_AWAY: @@ -312,7 +301,8 @@ final class PhrictionTransactionEditor $slug); $stub_xactions = array(); $stub_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType( + PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue(PhabricatorSlug::getDefaultTitle($slug)) ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) @@ -477,30 +467,6 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($type) { - case PhrictionTransaction::TYPE_TITLE: - $title = $object->getContent()->getTitle(); - $missing = $this->validateIsEmptyTextField( - $title, - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Document title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($this->getProcessContentVersionError()) { - $error = $this->validateContentVersion($object, $type, $xaction); - if ($error) { - $this->setProcessContentVersionError(false); - $errors[] = $error; - } - } - break; - case PhrictionTransaction::TYPE_CONTENT: if ($xaction->getMetadataValue('stub:create:phid')) { continue; diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php index a3ae6adb9d..40e7b4499e 100644 --- a/src/applications/phriction/storage/PhrictionTransaction.php +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -1,9 +1,8 @@ getNewValue(); @@ -35,14 +38,13 @@ final class PhrictionTransaction case self::TYPE_MOVE_AWAY: $phids[] = $new['phid']; break; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: if ($this->getMetadataValue('stub:create:phid')) { $phids[] = $this->getMetadataValue('stub:create:phid'); } break; } - return $phids; } @@ -77,7 +79,7 @@ final class PhrictionTransaction case self::TYPE_MOVE_TO: case self::TYPE_MOVE_AWAY: return true; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); } return parent::shouldHideForMail($xactions); @@ -88,7 +90,7 @@ final class PhrictionTransaction case self::TYPE_MOVE_TO: case self::TYPE_MOVE_AWAY: return true; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); } return parent::shouldHideForFeed(); @@ -96,8 +98,6 @@ final class PhrictionTransaction public function getActionStrength() { switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - return 1.4; case self::TYPE_CONTENT: return 1.3; case self::TYPE_DELETE: @@ -115,29 +115,14 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - if ($this->getMetadataValue('stub:create:phid')) { - return pht('Stubbed'); - } else { - return pht('Created'); - } - } - - return pht('Retitled'); - case self::TYPE_CONTENT: return pht('Edited'); - case self::TYPE_DELETE: return pht('Deleted'); - case self::TYPE_MOVE_TO: return pht('Moved'); - case self::TYPE_MOVE_AWAY: return pht('Moved Away'); - } return parent::getActionName(); @@ -148,7 +133,6 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: case self::TYPE_CONTENT: return 'fa-pencil'; case self::TYPE_DELETE: @@ -169,26 +153,6 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - if ($this->getMetadataValue('stub:create:phid')) { - return pht( - '%s stubbed out this document when creating %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink( - $this->getMetadataValue('stub:create:phid'))); - } else { - return pht( - '%s created this document.', - $this->renderHandleLink($author_phid)); - } - } - return pht( - '%s changed the title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_CONTENT: return pht( '%s edited the document content.', @@ -224,20 +188,6 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); case self::TYPE_CONTENT: return pht( @@ -273,7 +223,7 @@ final class PhrictionTransaction public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_TITLE; break; case self::TYPE_CONTENT: diff --git a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php new file mode 100644 index 0000000000..730b062f34 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php @@ -0,0 +1,94 @@ +isNewObject()) { + return null; + } + return $this->getEditor()->getOldContent()->getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + $this->getEditor()->getNewContent()->setTitle($value); + } + + public function getActionStrength() { + return 1.4; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + if ($this->getMetadataValue('stub:create:phid')) { + return pht('Stubbed'); + } else { + return pht('Created'); + } + } + return pht('Retitled'); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + if ($this->getMetadataValue('stub:create:phid')) { + return pht( + '%s stubbed out this document when creating %s.', + $this->renderAuthor(), + $this->renderHandleLink( + $this->getMetadataValue('stub:create:phid'))); + } else { + return pht( + '%s created this document.', + $this->renderAuthor()); + } + } + + return pht( + '%s changed the title from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $title = $object->getContent()->getTitle(); + if ($this->isEmptyTextTransaction($title, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have a title.')); + } + + return $errors; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php b/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php new file mode 100644 index 0000000000..d99b53a303 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php @@ -0,0 +1,4 @@ +getTransactionImplementation()->hasChangeDetailView()) { return true; } @@ -168,7 +168,7 @@ abstract class PhabricatorModularTransaction return parent::hasChangeDetails(); } - final public function renderChangeDetails(PhabricatorUser $viewer) { + /* final */ public function renderChangeDetails(PhabricatorUser $viewer) { $impl = $this->getTransactionImplementation(); $impl->setViewer($viewer); $view = $impl->newChangeDetailView(); From 6a9dd61c427b57b85a8f1fd4f7c0a8569a307563 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 10:57:30 -0700 Subject: [PATCH 098/543] Make collapsed inlines more useful and anchor target highlights more accurate Summary: Ref T12616. Fixes T11648. Currently, we snug up replies with a negative margin (from T10563) but this throws off the anchor highlighting. Instead: - Remove padding from these dolumns. - Use margins on the stuff inside them instead. - Less margins for replies. - Less margins for collapsed comments. - Show some text for collapsed comments. Test Plan: {F4960890} {F4960891} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616, T11648 Differential Revision: https://secure.phabricator.com/D17913 --- resources/celerity/map.php | 16 ++++++------ .../view/PHUIDiffInlineCommentDetailView.php | 13 +++++++--- .../differential/changeset-view.css | 6 +---- .../differential/phui-inline-comment.css | 26 ++++++++++++++----- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b389a84ba5..b9fb14d72c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,7 +12,7 @@ return array( 'core.pkg.css' => 'ee5f28cd', 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '58712637', + 'differential.pkg.css' => '7b1c772c', 'differential.pkg.js' => 'f1b636fb', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', @@ -64,9 +64,9 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '41af6d25', + 'rsrc/css/application/differential/changeset-view.css' => '69a3c268', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => '3fd8ca64', + 'rsrc/css/application/differential/phui-inline-comment.css' => 'e0a2b52e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -567,7 +567,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '41af6d25', + 'differential-changeset-view-css' => '69a3c268', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -864,7 +864,7 @@ return array( 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', - 'phui-inline-comment-view-css' => '3fd8ca64', + 'phui-inline-comment-view-css' => 'e0a2b52e', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => '12eb8ce6', @@ -1182,9 +1182,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '41af6d25' => array( - 'phui-inline-comment-view-css', - ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1401,6 +1398,9 @@ return array( '6882e80a' => array( 'javelin-dom', ), + '69a3c268' => array( + 'phui-inline-comment-view-css', + ), '69adf288' => array( 'javelin-install', ), diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 9c3c55292e..91f27f0e2b 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -433,15 +433,20 @@ final class PHUIDiffInlineCommentDetailView phutil_tag_div('phabricator-remarkup', $content)), )); + $snippet = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(96) + ->truncateString($inline->getContent()); + $summary = phutil_tag( 'div', array( 'class' => 'differential-inline-summary', ), - - // TODO: Render something a little more useful here as a hint about the - // inline content, like "alincoln: first line of text...". - pht('...')); + array( + phutil_tag('strong', array(), pht('%s:', $author)), + ' ', + $snippet, + )); return array( $anchor, diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 1bd7d48635..1c14883a36 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -67,10 +67,6 @@ padding: 1px 4px; } -.device .differential-diff .inline > td { - padding: 4px; -} - .differential-diff td .zwsp { position: absolute; width: 0; @@ -315,7 +311,7 @@ td.cov-I { } .differential-diff .inline > td { - padding: 8px 12px; + padding: 0; } .differential-loading { diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 3e28bcd0d4..11585d67ce 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -28,14 +28,17 @@ background: #fff; border: 1px solid {$sh-yellowborder}; font: {$basefont}; - margin: 0; - width: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; overflow: hidden; white-space: normal; border-radius: 3px; + margin: 8px 12px; +} + +.device .differential-inline-comment { + margin: 4px; } .inline-state-is-draft { @@ -61,7 +64,7 @@ /* Tighten up spacing on replies */ .differential-inline-comment.inline-comment-is-reply { - margin-top: -12px; + margin-top: 0; } .differential-inline-comment .inline-head-right { @@ -315,6 +318,7 @@ .differential-inline-undo { padding: 8px; + margin: 8px 12px; text-align: center; background: {$sh-yellowbackground}; border: 1px solid {$sh-yellowborder}; @@ -389,10 +393,20 @@ } .differential-inline-summary { - background: {$greybackground}; - padding: 0 4px; - color: {$greytext}; + background: {$lightgreybackground}; + padding: 2px 16px; + color: {$lightgreytext}; + font-size: {$smallerfontsize}; display: none; + font: {$basefont}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.device .differential-inline-summary { + padding-left: 4px; + padding-right: 4px; } .inline-hidden .differential-inline-summary { From 6888472b567e175b9d9b3aaf1ee7df44e80d9064 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 16 May 2017 12:10:00 -0700 Subject: [PATCH 099/543] Migrate Pholio inline comments to modular transactions Summary: Fixes T12626. Test Plan: Made lots of comments, confirmed no UI changes Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T12626 Differential Revision: https://secure.phabricator.com/D17914 --- src/__phutil_library_map__.php | 2 + .../PholioMockCommentController.php | 2 +- .../pholio/editor/PholioMockEditor.php | 20 +------ .../pholio/storage/PholioTransaction.php | 60 +------------------ .../pholio/view/PholioTransactionView.php | 9 +-- .../xaction/PholioMockInlineTransaction.php | 33 ++++++++++ 6 files changed, 45 insertions(+), 81 deletions(-) create mode 100644 src/applications/pholio/xaction/PholioMockInlineTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ba74adb658..0b4e330338 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4401,6 +4401,7 @@ phutil_register_library_map(array( 'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php', 'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php', 'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php', + 'PholioMockInlineTransaction' => 'applications/pholio/xaction/PholioMockInlineTransaction.php', 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php', @@ -9981,6 +9982,7 @@ phutil_register_library_map(array( 'PholioMockHeraldField' => 'HeraldField', 'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup', 'PholioMockImagesView' => 'AphrontView', + 'PholioMockInlineTransaction' => 'PholioMockTransactionType', 'PholioMockListController' => 'PholioController', 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 'PholioMockNameHeraldField' => 'PholioMockHeraldField', diff --git a/src/applications/pholio/controller/PholioMockCommentController.php b/src/applications/pholio/controller/PholioMockCommentController.php index b127d0b1da..1888c7369e 100644 --- a/src/applications/pholio/controller/PholioMockCommentController.php +++ b/src/applications/pholio/controller/PholioMockCommentController.php @@ -45,7 +45,7 @@ final class PholioMockCommentController extends PholioController { foreach ($inline_comments as $inline_comment) { $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_INLINE) + ->setTransactionType(PholioMockInlineTransaction::TRANSACTIONTYPE) ->attachComment($inline_comment); } diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index ef315512ec..ebd5703170 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -30,23 +30,9 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PholioTransaction::TYPE_INLINE; - return $types; } - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: - return true; - } - - return parent::transactionHasEffect($object, $xaction); - } - protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -147,7 +133,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $comment = $xaction->getComment(); switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: if ($comment && strlen($comment->getContent())) { $inline_comments[] = $comment; } @@ -237,7 +223,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { // Move inline comments to the end, so the comments precede them. foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); - if ($type == PholioTransaction::TYPE_INLINE) { + if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { $tail[] = $xaction; } else { $head[] = $xaction; @@ -252,7 +238,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 346f0e4561..a932cf2a60 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -2,9 +2,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { - // Your witty commentary at the mock : image : x,y level - const TYPE_INLINE = 'inline'; - const MAILTAG_STATUS = 'pholio-status'; const MAILTAG_COMMENT = 'pholio-comment'; const MAILTAG_UPDATED = 'pholio-updated'; @@ -30,19 +27,10 @@ final class PholioTransaction extends PhabricatorModularTransaction { return new PholioTransactionView(); } - public function getIcon() { - switch ($this->getTransactionType()) { - case self::TYPE_INLINE: - return 'fa-comment'; - } - - return parent::getIcon(); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; @@ -65,50 +53,4 @@ final class PholioTransaction extends PhabricatorModularTransaction { return $tags; } - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_INLINE: - $count = 1; - foreach ($this->getTransactionGroup() as $xaction) { - if ($xaction->getTransactionType() == $type) { - $count++; - } - } - - return pht( - '%s added %d inline comment(s).', - $this->renderHandleLink($author_phid), - $count); - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_INLINE: - return pht( - '%s added an inline comment to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - - return parent::getTitleForFeed(); - } - } diff --git a/src/applications/pholio/view/PholioTransactionView.php b/src/applications/pholio/view/PholioTransactionView.php index 92624feec6..69613c5428 100644 --- a/src/applications/pholio/view/PholioTransactionView.php +++ b/src/applications/pholio/view/PholioTransactionView.php @@ -30,14 +30,14 @@ final class PholioTransactionView switch ($u->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: break; default: return false; } switch ($v->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } @@ -50,7 +50,8 @@ final class PholioTransactionView $out = array(); $group = $xaction->getTransactionGroup(); - if ($xaction->getTransactionType() == PholioTransaction::TYPE_INLINE) { + $type = $xaction->getTransactionType(); + if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { array_unshift($group, $xaction); } else { $out[] = parent::renderTransactionContent($xaction); @@ -63,7 +64,7 @@ final class PholioTransactionView $inlines = array(); foreach ($group as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: $inlines[] = $xaction; break; default: diff --git a/src/applications/pholio/xaction/PholioMockInlineTransaction.php b/src/applications/pholio/xaction/PholioMockInlineTransaction.php new file mode 100644 index 0000000000..f89fed0c3a --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockInlineTransaction.php @@ -0,0 +1,33 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s added an inline comment to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-comment'; + } + + public function getTransactionHasEffect($object, $old, $new) { + return true; + } + +} From 2aa146ffaca4eb6e2e8adb977dc8b1b907910a76 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 12:38:11 -0700 Subject: [PATCH 100/543] Set an icon for Maniphest story points Summary: It's an icon. For story points. Test Plan: Set some points, see icon. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17915 --- .../maniphest/xaction/ManiphestTaskPointsTransaction.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index 7f324d1bca..6d8548fdb4 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -71,6 +71,10 @@ final class ManiphestTaskPointsTransaction return $errors; } + public function getIcon() { + return 'fa-calculator'; + } + private function getValueForPoints($value) { if (!strlen($value)) { $value = null; From 772afc5ed82c6b869dbfd911ec172da42f8a3b3d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 13:03:45 -0700 Subject: [PATCH 101/543] Allow cancelled inlines, edits, and replies to be undone to get the text back again Summary: Ref T12616. The ability to do {nav Edit > Cancel > Undo} to get your text back on inlines got dropped during the conversion. Restore it. Test Plan: Created, replied, and edited inlines, typed text, then cancelled. Was able to undo. Also undid normal deletion. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17916 --- resources/celerity/map.php | 18 ++--- .../differential/phui-inline-comment.css | 4 +- .../rsrc/js/application/diff/DiffInline.js | 77 ++++++++++++++----- 3 files changed, 69 insertions(+), 30 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b9fb14d72c..b1d28cf453 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => 'ee5f28cd', 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '7b1c772c', - 'differential.pkg.js' => 'f1b636fb', + 'differential.pkg.css' => '4ff77743', + 'differential.pkg.js' => '85543704', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -66,7 +66,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => '69a3c268', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => 'e0a2b52e', + 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'c5742feb', 'rsrc/js/application/diff/DiffChangesetList.js' => 'f1101e6e', - 'rsrc/js/application/diff/DiffInline.js' => 'bdf6b568', + 'rsrc/js/application/diff/DiffInline.js' => 'a81c29d4', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -781,7 +781,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'c5742feb', 'phabricator-diff-changeset-list' => 'f1101e6e', - 'phabricator-diff-inline' => 'bdf6b568', + 'phabricator-diff-inline' => 'a81c29d4', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -864,7 +864,7 @@ return array( 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', - 'phui-inline-comment-view-css' => 'e0a2b52e', + 'phui-inline-comment-view-css' => 'ffd1a542', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => '12eb8ce6', @@ -1718,6 +1718,9 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + 'a81c29d4' => array( + 'javelin-dom', + ), 'a8beebea' => array( 'phui-oi-list-view-css', ), @@ -1872,9 +1875,6 @@ return array( 'javelin-util', 'javelin-request', ), - 'bdf6b568' => array( - 'javelin-dom', - ), 'bea6e7f4' => array( 'javelin-install', 'javelin-dom', diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 11585d67ce..146117b1cb 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -318,11 +318,10 @@ .differential-inline-undo { padding: 8px; - margin: 8px 12px; + margin: 4px 12px; text-align: center; background: {$sh-yellowbackground}; border: 1px solid {$sh-yellowborder}; - margin: 4px 0; color: {$darkgreytext}; font: {$basefont}; font-size: {$normalfontsize}; @@ -396,7 +395,6 @@ background: {$lightgreybackground}; padding: 2px 16px; color: {$lightgreytext}; - font-size: {$smallerfontsize}; display: none; font: {$basefont}; white-space: nowrap; diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 04f9a03db1..b7034fa653 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -25,6 +25,7 @@ JX.install('DiffInline', { _isNewFile: null, _undoRow: null, _replyToCommentPHID: null, + _originalText: null, _isDeleted: false, _isInvisible: false, @@ -57,6 +58,7 @@ JX.install('DiffInline', { this._number = data.number; this._length = data.length; + this._originalText = data.original; this._isNewFile = (this.getDisplaySide() == 'right') || (data.left != data.right); @@ -165,6 +167,8 @@ JX.install('DiffInline', { this._phid = null; this._hidden = false; + this._originalText = null; + return row; }, @@ -231,10 +235,10 @@ JX.install('DiffInline', { this._didUpdate(); }, - create: function() { + create: function(text) { var uri = this._getInlineURI(); var handler = JX.bind(this, this._oncreateresponse); - var data = this._newRequestData('new'); + var data = this._newRequestData('new', text); this.setLoading(true); @@ -248,10 +252,10 @@ JX.install('DiffInline', { return changeset.newInlineReply(this); }, - edit: function() { + edit: function(text) { var uri = this._getInlineURI(); var handler = JX.bind(this, this._oneditresponse); - var data = this._newRequestData('edit'); + var data = this._newRequestData('edit', text || null); this.setLoading(true); @@ -336,7 +340,7 @@ JX.install('DiffInline', { return this; }, - _newRequestData: function(operation) { + _newRequestData: function(operation, text) { return { op: operation, id: this._id, @@ -347,6 +351,7 @@ JX.install('DiffInline', { is_new: this.isNewFile(), changesetID: this.getChangesetID(), replyToCommentPHID: this.getReplyToCommentPHID() || '', + text: text || '' }; }, @@ -366,7 +371,7 @@ JX.install('DiffInline', { }, _ondeleteresponse: function() { - this._drawUndoRows(); + this._drawUndeleteRows(); this.setLoading(false); this.setDeleted(true); @@ -374,7 +379,15 @@ JX.install('DiffInline', { this._didUpdate(); }, - _drawUndoRows: function() { + _drawUndeleteRows: function() { + return this._drawUndoRows('undelete', this._row); + }, + + _drawUneditRows: function(text) { + return this._drawUndoRows('unedit', null, text); + }, + + _drawUndoRows: function(mode, cursor, text) { var templates = this.getChangeset().getUndoTemplates(); var template; @@ -385,14 +398,14 @@ JX.install('DiffInline', { } template = JX.$H(template).getNode(); - this._undoRow = this._drawRows(template, this._row, 'undo'); + this._undoRow = this._drawRows(template, cursor, mode, text); }, _drawEditRows: function(rows) { return this._drawRows(rows, null, 'edit'); }, - _drawRows: function(rows, cursor, type) { + _drawRows: function(rows, cursor, type, text) { var first_row = JX.DOM.scry(rows, 'tr')[0]; var first_meta; var row = first_row; @@ -410,6 +423,7 @@ JX.install('DiffInline', { var row_meta = { node: row, type: type, + text: text || null, listeners: [] }; @@ -476,16 +490,26 @@ JX.install('DiffInline', { this._removeRow(row); - var uri = this._getInlineURI(); - var data = this._newRequestData('undelete'); - var handler = JX.bind(this, this._onundelete); + if (row.type == 'undelete') { + var uri = this._getInlineURI(); + var data = this._newRequestData('undelete'); + var handler = JX.bind(this, this._onundelete); - this.setDeleted(false); - this.setLoading(true); + this.setDeleted(false); + this.setLoading(true); - new JX.Request(uri, handler) - .setData(data) - .send(); + new JX.Request(uri, handler) + .setData(data) + .send(); + } + + if (row.type == 'unedit') { + if (this.getID()) { + this.edit(row.text); + } else { + this.create(row.text); + } + } }, _onundelete: function() { @@ -496,13 +520,30 @@ JX.install('DiffInline', { _oncancel: function(row, e) { e.kill(); - // TODO: Capture edited text and offer "undo". + var text = this._readText(row.node); + if (text && text.length && (text != this._originalText)) { + this._drawUneditRows(text); + } this._removeRow(row); this.setInvisible(false); }, + _readText: function(row) { + var textarea; + try { + textarea = JX.DOM.find( + row, + 'textarea', + 'differential-inline-comment-edit-textarea'); + } catch (ex) { + return null; + } + + return textarea.value; + }, + _onsubmitresponse: function(row, response) { this._removeRow(row); From 0ed496de228271add3a63a7f08f3228d21427ec8 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 16 May 2017 14:53:26 -0700 Subject: [PATCH 102/543] Throw an exception if `local.json` can't be read Summary: Our `local.json` configuration file contains various secrets, including database usernames and passwords. As such, we recently changed the permissions on this file from `0644` to `0640`. After doing so, however, I constantly forget to run commands with `sudo`. This is made worse by the fact that `PhabricatorConfigLocalSource` seems to simply ignore `local.json` is it isn't readable, whereas throwing an `Exception` would have saved me a lot of debugging. Test Plan: ```name=Before > /usr/local/src/phabricator/bin/config get mysql.pass { "config": [ { "key": "mysql.pass", "source": "local", "value": null, "status": "unset", "errorInfo": null }, { "key": "mysql.pass", "source": "database", "value": null, "status": "error", "errorInfo": "Database source is not configured properly" } ] } ``` ```name=After > /usr/local/src/phabricator/bin/config get mysql.pass [2017-05-16 21:49:26] EXCEPTION: (FilesystemException) Path '/usr/local/src/phabricator/conf/local/local.json' is not readable. at [/src/filesystem/Filesystem.php:1124] arcanist(head=stable, ref.master=3c4735795a29, ref.stable=20ad47f27331), phabricator(head=stable, ref.master=3dae9701298f, ref.stable=fcebaa5097f3), phutil(head=stable, ref.master=a900d7b63e95, ref.stable=d02cc05931b0) #0 Filesystem::assertReadable(string) called at [/src/filesystem/Filesystem.php:39] #1 Filesystem::readFile(string) called at [/src/infrastructure/env/PhabricatorConfigLocalSource.php:25] #2 PhabricatorConfigLocalSource::loadConfig() called at [/src/infrastructure/env/PhabricatorConfigLocalSource.php:6] #3 PhabricatorConfigLocalSource::__construct() called at [/src/infrastructure/env/PhabricatorEnv.php:195] #4 PhabricatorEnv::buildConfigurationSourceStack(boolean) called at [/src/infrastructure/env/PhabricatorEnv.php:95] #5 PhabricatorEnv::initializeCommonEnvironment(boolean) called at [/src/infrastructure/env/PhabricatorEnv.php:75] #6 PhabricatorEnv::initializeScriptEnvironment(boolean) called at [/scripts/init/lib.php:22] #7 init_phabricator_script(array) called at [/scripts/init/init-setup.php:11] #8 require_once(string) called at [/scripts/setup/manage_config.php:5] ``` Reviewers: #blessed_reviewers, joshuaspence Reviewed By: joshuaspence Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17917 --- .../env/PhabricatorConfigLocalSource.php | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/infrastructure/env/PhabricatorConfigLocalSource.php b/src/infrastructure/env/PhabricatorConfigLocalSource.php index 3a2295eb5e..16dc43a9bc 100644 --- a/src/infrastructure/env/PhabricatorConfigLocalSource.php +++ b/src/infrastructure/env/PhabricatorConfigLocalSource.php @@ -21,17 +21,35 @@ final class PhabricatorConfigLocalSource extends PhabricatorConfigProxySource { private function loadConfig() { $path = $this->getConfigPath(); - if (@file_exists($path)) { - $data = @file_get_contents($path); - if ($data) { - $data = json_decode($data, true); - if (is_array($data)) { - return $data; - } - } + + if (!Filesystem::pathExists($path)) { + return array(); } - return array(); + try { + $data = Filesystem::readFile($path); + } catch (FilesystemException $ex) { + throw new PhutilProxyException( + pht( + 'Configuration file "%s" exists, but could not be read.', + $path), + $ex); + } + + try { + $result = phutil_json_decode($data); + } catch (PhutilJSONParserException $ex) { + throw new PhutilProxyException( + pht( + 'Configuration file "%s" exists and is readable, but the content '. + 'is not valid JSON. You may have edited this file manually and '. + 'introduced a syntax error by mistake. Correct the file syntax '. + 'to continue.', + $path), + $ex); + } + + return $result; } private function saveConfig() { From abc9bc77b27bfccf2800fc2b11fce5eeb6f5ece3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 15:52:21 -0700 Subject: [PATCH 103/543] Move Phriction MOVE_TO transaction to Modular Transactions Summary: Moves this transaction over to modular transactions. Test Plan: Move a document, re-title a document, try to move over an existing document. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17918 --- src/__phutil_library_map__.php | 2 + .../controller/PhrictionMoveController.php | 6 +- .../editor/PhrictionTransactionEditor.php | 58 ++-------- .../storage/PhrictionTransaction.php | 17 +-- .../PhrictionDocumentMoveToTransaction.php | 102 ++++++++++++++++++ 5 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0b4e330338..fbc972328c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4617,6 +4617,7 @@ phutil_register_library_map(array( 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', 'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php', + 'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', @@ -10256,6 +10257,7 @@ phutil_register_library_map(array( 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', + 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', diff --git a/src/applications/phriction/controller/PhrictionMoveController.php b/src/applications/phriction/controller/PhrictionMoveController.php index 53e5ae16ec..f25465c3c0 100644 --- a/src/applications/phriction/controller/PhrictionMoveController.php +++ b/src/applications/phriction/controller/PhrictionMoveController.php @@ -64,7 +64,8 @@ final class PhrictionMoveController extends PhrictionController { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_MOVE_TO) + ->setTransactionType( + PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE) ->setNewValue($document); $target_document = id(new PhrictionDocumentQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -88,7 +89,8 @@ final class PhrictionMoveController extends PhrictionController { return id(new AphrontRedirectResponse())->setURI($redir_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_slug = $ex->getShortMessage(PhrictionTransaction::TYPE_MOVE_TO); + $e_slug = $ex->getShortMessage( + PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE); } } diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 95575a7619..cbe6e426d0 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -82,7 +82,6 @@ final class PhrictionTransactionEditor $types[] = PhrictionTransaction::TYPE_CONTENT; $types[] = PhrictionTransaction::TYPE_DELETE; - $types[] = PhrictionTransaction::TYPE_MOVE_TO; $types[] = PhrictionTransaction::TYPE_MOVE_AWAY; $types[] = PhabricatorTransactions::TYPE_EDGE; @@ -104,7 +103,6 @@ final class PhrictionTransactionEditor } return $this->getOldContent()->getContent(); case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_TO: case PhrictionTransaction::TYPE_MOVE_AWAY: return null; } @@ -118,17 +116,11 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: return $xaction->getNewValue(); - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $document = $xaction->getNewValue(); // grab the real object now for the sub-editor to come $this->moveAwayDocument = $document; - $dict = array( - 'id' => $document->getID(), - 'phid' => $document->getPHID(), - 'content' => $document->getContent()->getContent(), - 'title' => $document->getContent()->getTitle(), - ); - return $dict; + return; case PhrictionTransaction::TYPE_MOVE_AWAY: $document = $xaction->getNewValue(); $dict = array( @@ -150,7 +142,7 @@ final class PhrictionTransactionEditor case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_MOVE_AWAY: return true; } @@ -172,9 +164,6 @@ final class PhrictionTransactionEditor switch ($xaction->getTransactionType()) { case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_MOVE_TO: - $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - return; case PhrictionTransaction::TYPE_MOVE_AWAY: $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); return; @@ -202,7 +191,7 @@ final class PhrictionTransactionEditor ->setMetadataValue('contentDelete', true); } break; - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $document = $xaction->getNewValue(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -232,14 +221,6 @@ final class PhrictionTransactionEditor $this->getNewContent()->setChangeType( PhrictionChangeType::CHANGE_DELETE); break; - case PhrictionTransaction::TYPE_MOVE_TO: - $dict = $xaction->getNewValue(); - $this->getNewContent()->setContent($dict['content']); - $this->getNewContent()->setTitle($dict['title']); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_HERE); - $this->getNewContent()->setChangeRef($dict['id']); - break; case PhrictionTransaction::TYPE_MOVE_AWAY: $dict = $xaction->getNewValue(); $this->getNewContent()->setContent(''); @@ -263,7 +244,7 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: case PhrictionTransaction::TYPE_MOVE_AWAY: - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $save_content = true; break; default: @@ -448,7 +429,7 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $dict = $xaction->getNewValue(); $phids[] = $dict['phid']; break; @@ -510,31 +491,8 @@ final class PhrictionTransactionEditor break; - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $source_document = $xaction->getNewValue(); - switch ($source_document->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - $e_text = pht('A deleted document can not be moved.'); - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $e_text = pht('A moved document can not be moved again.'); - break; - case PhrictionDocumentStatus::STATUS_STUB: - $e_text = pht('A stub document can not be moved.'); - break; - default: - $e_text = null; - break; - } - - if ($e_text) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Can not move document.'), - $e_text, - $xaction); - $errors[] = $error; - } $ancestry_errors = $this->validateAncestry( $object, @@ -611,7 +569,7 @@ final class PhrictionTransactionEditor return $errors; } - private function validateAncestry( + public function validateAncestry( PhabricatorLiskDAO $object, $type, PhabricatorApplicationTransaction $xaction, diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php index 40e7b4499e..d3d78ff665 100644 --- a/src/applications/phriction/storage/PhrictionTransaction.php +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -5,7 +5,6 @@ final class PhrictionTransaction const TYPE_CONTENT = 'content'; const TYPE_DELETE = 'delete'; - const TYPE_MOVE_TO = 'move-to'; const TYPE_MOVE_AWAY = 'move-away'; const MAILTAG_TITLE = 'phriction-title'; @@ -34,7 +33,7 @@ final class PhrictionTransaction $phids = parent::getRequiredHandlePHIDs(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case self::TYPE_MOVE_AWAY: $phids[] = $new['phid']; break; @@ -76,7 +75,7 @@ final class PhrictionTransaction public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case self::TYPE_MOVE_AWAY: return true; case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: @@ -87,7 +86,7 @@ final class PhrictionTransaction public function shouldHideForFeed() { switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case self::TYPE_MOVE_AWAY: return true; case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: @@ -102,7 +101,6 @@ final class PhrictionTransaction return 1.3; case self::TYPE_DELETE: return 1.5; - case self::TYPE_MOVE_TO: case self::TYPE_MOVE_AWAY: return 1.0; } @@ -119,8 +117,6 @@ final class PhrictionTransaction return pht('Edited'); case self::TYPE_DELETE: return pht('Deleted'); - case self::TYPE_MOVE_TO: - return pht('Moved'); case self::TYPE_MOVE_AWAY: return pht('Moved Away'); } @@ -137,7 +133,6 @@ final class PhrictionTransaction return 'fa-pencil'; case self::TYPE_DELETE: return 'fa-times'; - case self::TYPE_MOVE_TO: case self::TYPE_MOVE_AWAY: return 'fa-arrows'; } @@ -163,12 +158,6 @@ final class PhrictionTransaction '%s deleted this document.', $this->renderHandleLink($author_phid)); - case self::TYPE_MOVE_TO: - return pht( - '%s moved this document from %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new['phid'])); - case self::TYPE_MOVE_AWAY: return pht( '%s moved this document to %s', diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php new file mode 100644 index 0000000000..a513b8fe36 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php @@ -0,0 +1,102 @@ + $document->getID(), + 'phid' => $document->getPHID(), + 'content' => $document->getContent()->getContent(), + 'title' => $document->getContent()->getTitle(), + ); + return $dict; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + $this->getEditor()->getNewContent()->setTitle($value); + } + + public function applyExternalEffects($object, $value) { + $dict = $value; + $this->getEditor()->getNewContent()->setContent($dict['content']); + $this->getEditor()->getNewContent()->setTitle($dict['title']); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_MOVE_HERE); + $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + } + + public function getActionStrength() { + return 1.0; + } + + public function getActionName() { + return pht('Moved'); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s moved this document from %s', + $this->renderAuthor(), + $this->renderHandle($new['phid'])); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s moved %s from %s', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new['phid'])); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $e_text = null; + foreach ($xactions as $xaction) { + $source_document = $xaction->getNewValue(); + switch ($source_document->getStatus()) { + case PhrictionDocumentStatus::STATUS_DELETED: + $e_text = pht('A deleted document can not be moved.'); + break; + case PhrictionDocumentStatus::STATUS_MOVED: + $e_text = pht('A moved document can not be moved again.'); + break; + case PhrictionDocumentStatus::STATUS_STUB: + $e_text = pht('A stub document can not be moved.'); + break; + default: + $e_text = null; + break; + } + + if ($e_text !== null) { + $errors[] = $this->newInvalidError($e_text); + } + + } + + // TODO: Move Ancestry validation here once all types are converted. + + return $errors; + } + + public function getIcon() { + return 'fa-arrows'; + } + +} From 0ca49fbeb95103295db921a9a54e682f962b2522 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 14:53:21 -0700 Subject: [PATCH 104/543] Move "hover over an inline to see the affected lines" code to the new class tree Summary: Fixes T8047. Ref T12616. Fixes T9270. This moves the "hover" part of the hover/drag behavior to the new code, leaving the "drag" part for a followup change. The new hover UI behaves properly with Quicksand (T8047) and the filetree (T9270). Test Plan: Hovered over inlines, saw lines select properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616, T9270, T8047 Differential Revision: https://secure.phabricator.com/D17919 --- resources/celerity/map.php | 78 +++++----- .../differential/changeset-view.css | 5 +- .../rsrc/js/application/diff/DiffChangeset.js | 14 ++ .../js/application/diff/DiffChangesetList.js | 142 ++++++++++++++++++ .../rsrc/js/application/diff/DiffInline.js | 24 ++- .../behavior-edit-inline-comments.js | 73 +-------- 6 files changed, 216 insertions(+), 120 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b1d28cf453..575ce9a2b1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => 'ee5f28cd', 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '4ff77743', - 'differential.pkg.js' => '85543704', + 'differential.pkg.css' => 'ea471cb0', + 'differential.pkg.js' => '7f24021f', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '69a3c268', + 'rsrc/css/application/differential/changeset-view.css' => '6a77323e', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -390,13 +390,13 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'c5742feb', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'f1101e6e', - 'rsrc/js/application/diff/DiffInline.js' => 'a81c29d4', + 'rsrc/js/application/diff/DiffChangeset.js' => '34e513e2', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'fcbf80e9', + 'rsrc/js/application/diff/DiffInline.js' => 'c2e9ff4c', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '89d11432', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'd59300f5', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', @@ -567,7 +567,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '69a3c268', + 'differential-changeset-view-css' => '6a77323e', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -619,7 +619,7 @@ return array( 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => '89d11432', + 'javelin-behavior-differential-edit-inline-comments' => 'd59300f5', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', @@ -779,9 +779,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'c5742feb', - 'phabricator-diff-changeset-list' => 'f1101e6e', - 'phabricator-diff-inline' => 'a81c29d4', + 'phabricator-diff-changeset' => '34e513e2', + 'phabricator-diff-changeset-list' => 'fcbf80e9', + 'phabricator-diff-inline' => 'c2e9ff4c', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1129,6 +1129,17 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '34e513e2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1398,12 +1409,12 @@ return array( '6882e80a' => array( 'javelin-dom', ), - '69a3c268' => array( - 'phui-inline-comment-view-css', - ), '69adf288' => array( 'javelin-install', ), + '6a77323e' => array( + 'phui-inline-comment-view-css', + ), '6ad39b6f' => array( 'javelin-install', 'javelin-event', @@ -1564,13 +1575,6 @@ return array( 'phabricator-draggable-list', 'javelin-workboard-column', ), - '89d11432' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - ), '8a41885b' => array( 'javelin-install', 'javelin-dom', @@ -1718,9 +1722,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'a81c29d4' => array( - 'javelin-dom', - ), 'a8beebea' => array( 'phui-oi-list-view-css', ), @@ -1909,23 +1910,15 @@ return array( 'javelin-dom', 'javelin-vector', ), + 'c2e9ff4c' => array( + 'javelin-dom', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'phabricator-tooltip', ), - 'c5742feb' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'c587b80f' => array( 'javelin-install', ), @@ -2043,6 +2036,13 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + 'd59300f5' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + ), 'd5a2d665' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2174,9 +2174,6 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f1101e6e' => array( - 'javelin-install', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), @@ -2224,6 +2221,9 @@ return array( 'javelin-dom', 'phortune-credit-card-form', ), + 'fcbf80e9' => array( + 'javelin-install', + ), 'fe287620' => array( 'javelin-install', 'javelin-dom', diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 1c14883a36..92a544c24c 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -109,10 +109,6 @@ color: {$darkgreytext}; } -.differential-diff th.selected { - background-color: {$sh-yellowbackground}; -} - .differential-changeset-immutable .differential-diff th { cursor: auto; } @@ -308,6 +304,7 @@ td.cov-I { top: 0; left: 0; box-sizing: border-box; + pointer-events: none; } .differential-diff .inline > td { diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 3130fced44..8176e4f12b 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -27,6 +27,9 @@ JX.install('DiffChangeset', { this._encoding = data.encoding; this._loaded = data.loaded; + this._leftID = data.left; + this._rightID = data.right; + this._inlines = []; }, @@ -48,8 +51,19 @@ JX.install('DiffChangeset', { _encoding: null, _undoTemplates: null, + _leftID: null, + _rightID: null, + _inlines: null, + getLeftChangesetID: function() { + return this._leftID; + }, + + getRightChangesetID: function() { + return this._rightID; + }, + /** * Has the content of this changeset been loaded? * diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 6380ccbf42..d9f1e35991 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -56,6 +56,12 @@ JX.install('DiffChangesetList', { 'mousedown', ['differential-inline-comment', 'differential-inline-header'], onselect); + + var onhover = JX.bind(this, this._ifawake, this._onhover); + JX.Stratcom.listen( + ['mouseover', 'mouseout'], + 'differential-inline-comment', + onhover); }, properties: { @@ -74,16 +80,24 @@ JX.install('DiffChangesetList', { _focusStart: null, _focusEnd: null, + _hoverNode: null, + _hoverInline: null, + _hoverOrigin: null, + _hoverTarget: null, + sleep: function() { this._asleep = true; this._redrawFocus(); + this._redrawSelection(); + this.resetHover(); }, wake: function() { this._asleep = false; this._redrawFocus(); + this._redrawSelection(); if (this._initialized) { return; @@ -428,6 +442,21 @@ JX.install('DiffChangesetList', { return result; }, + _onhover: function(e) { + if (e.getIsTouchEvent()) { + return; + } + + var inline; + if (e.getType() == 'mouseout') { + inline = null; + } else { + inline = this._getInlineForEvent(e); + } + + this._setHoverInline(inline); + }, + _onmore: function(e) { e.kill(); @@ -669,6 +698,8 @@ JX.install('DiffChangesetList', { _onresize: function() { this._redrawFocus(); + this._redrawSelection(); + this._redrawHover(); }, _onselect: function(e) { @@ -769,6 +800,11 @@ JX.install('DiffChangesetList', { if (forms.length) { JX.DOM.invoke(forms[0], 'shouldRefresh'); } + + // Clear the mouse hover reticle after a substantive edit: we don't get + // a "mouseout" event if the row vanished because of row being removed + // after an edit. + this.reseHover(); }, setFocus: function(node, extended_node) { @@ -812,6 +848,112 @@ JX.install('DiffChangesetList', { return this._focusNode; }, + _setHoverInline: function(inline) { + this._hoverInline = inline; + + if (inline) { + var changeset = inline.getChangeset(); + + var changeset_id; + var side = inline.getDisplaySide(); + if (side == 'right') { + changeset_id = changeset.getRightChangesetID(); + } else { + changeset_id = changeset.getLeftChangesetID(); + } + + var new_part; + if (inline.isNewFile()) { + new_part = 'N'; + } else { + new_part = 'O'; + } + + var prefix = 'C' + changeset_id + new_part + 'L'; + + var number = inline.getLineNumber(); + var length = inline.getLineLength(); + + var origin = JX.$(prefix + number); + var target = JX.$(prefix + (number + length)); + + this._hoverOrigin = origin; + this._hoverTarget = target; + } else { + this._hoverOrigin = null; + this._hoverTarget = null; + } + + this._redrawHover(); + }, + + resetHover: function() { + this._setHoverInline(null); + }, + + _redrawHover: function() { + var reticle = this._getHoverNode(); + if (!this._hoverOrigin || this.isAsleep()) { + JX.DOM.remove(reticle); + return; + } + + JX.DOM.getContentFrame().appendChild(reticle); + + var top = this._hoverOrigin; + var bot = this._hoverTarget; + if (JX.$V(top).y > JX.$V(bot).y) { + var tmp = top; + top = bot; + bot = tmp; + } + + // Find the leftmost cell that we're going to highlight: this is the next + // in the row. In 2up views, it should be directly adjacent. In + // 1up views, we may have to skip over the other line number column. + var l = top; + while (JX.DOM.isType(l, 'th')) { + l = l.nextSibling; + } + + // Find the rightmost cell that we're going to highlight: this is the + // farthest consecutive, adjacent in the row. Sometimes the left + // and right nodes are the same (left side of 2up view); sometimes we're + // going to highlight several nodes (copy + code + coverage). + var r = l; + while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { + r = r.nextSibling; + } + + var pos = JX.$V(l) + .add(JX.Vector.getAggregateScrollForNode(l)); + + var dim = JX.$V(r) + .add(JX.Vector.getAggregateScrollForNode(r)) + .add(-pos.x, -pos.y) + .add(JX.Vector.getDim(r)); + + var bpos = JX.$V(bot) + .add(JX.Vector.getAggregateScrollForNode(bot)); + dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; + + pos.setPos(reticle); + dim.setDim(reticle); + + JX.DOM.show(reticle); + }, + + _getHoverNode: function() { + if (!this._hoverNode) { + var attributes = { + className: 'differential-reticle' + }; + this._hoverNode = JX.$N('div', attributes); + } + + return this._hoverNode; + }, + _deleteInlineByID: function(id) { var uri = this.getInlineURI(); var data = { diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index b7034fa653..38c2d7c82b 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -56,8 +56,8 @@ JX.install('DiffInline', { this._displaySide = 'left'; } - this._number = data.number; - this._length = data.length; + this._number = parseInt(data.number, 10); + this._length = parseInt(data.length, 10); this._originalText = data.original; this._isNewFile = (this.getDisplaySide() == 'right') || @@ -70,8 +70,8 @@ JX.install('DiffInline', { bindToRange: function(data) { this._displaySide = data.displaySide; - this._number = data.number; - this._length = data.length; + this._number = parseInt(data.number, 10); + this._length = parseInt(data.length, 10); this._isNewFile = data.isNewFile; this._changesetID = data.changesetID; @@ -365,9 +365,13 @@ JX.install('DiffInline', { }, _oncreateresponse: function(response) { + try { var rows = JX.$H(response).getNode(); this._drawEditRows(rows); + } catch (e) { + JX.log(e); + } }, _ondeleteresponse: function() { @@ -409,6 +413,7 @@ JX.install('DiffInline', { var first_row = JX.DOM.scry(rows, 'tr')[0]; var first_meta; var row = first_row; + var anchor = cursor || this._row; cursor = cursor || this._row.nextSibling; var next_row; @@ -417,7 +422,11 @@ JX.install('DiffInline', { // into the document. next_row = row.nextSibling; - cursor.parentNode.insertBefore(row, cursor); + // Bind edit and undo rows to this DiffInline object so that + // interactions like hovering work properly. + JX.Stratcom.getData(row).inline = this; + + anchor.parentNode.insertBefore(row, cursor); cursor = row; var row_meta = { @@ -528,6 +537,8 @@ JX.install('DiffInline', { this._removeRow(row); this.setInvisible(false); + + this._didUpdate(true); }, _readText: function(row) { @@ -579,8 +590,9 @@ JX.install('DiffInline', { } this.getChangeset().getChangesetList().redrawCursor(); + this.getChangeset().getChangesetList().resetHover(); - // Emit a resize event so that UI elements like the keyboad focus + // Emit a resize event so that UI elements like the keyboard focus // reticle can redraw properly. JX.Stratcom.invoke('resize'); }, diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 05410c8c7a..4b7b59decb 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -11,7 +11,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { var selecting = false; var reticle = JX.$N('div', {className: 'differential-reticle'}); - var old_cells = []; JX.DOM.hide(reticle); var origin = null; @@ -19,28 +18,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { var root = null; var changeset = null; - var editor = null; - - function updateReticleForComment(e) { - root = e.getNode('differential-changeset'); - if (!root) { - return; - } - - var data = e.getNodeData('differential-inline-comment'); - var change = e.getNodeData('differential-changeset'); - - var id_part = data.on_right ? change.right : change.left; - var new_part = data.isNewFile ? 'N' : 'O'; - var prefix = 'C' + id_part + new_part + 'L'; - - origin = JX.$(prefix + data.number); - target = JX.$(prefix + (parseInt(data.number, 10) + - parseInt(data.length, 10))); - - updateReticle(); - } - function updateReticle() { JX.DOM.getContentFrame().appendChild(reticle); @@ -85,44 +62,10 @@ JX.behavior('differential-edit-inline-comments', function(config) { dim.setDim(reticle); JX.DOM.show(reticle); - - // Find all the cells in the same row position between the top and bottom - // cell, so we can highlight them. - var seq = 0; - var row = top.parentNode; - for (seq = 0; seq < row.childNodes.length; seq++) { - if (row.childNodes[seq] == top) { - break; - } - } - - var cells = []; - while (true) { - cells.push(row.childNodes[seq]); - if (row.childNodes[seq] == bot) { - break; - } - row = row.nextSibling; - } - - setSelectedCells(cells); - } - - function setSelectedCells(new_cells) { - updateSelectedCellsClass(old_cells, false); - updateSelectedCellsClass(new_cells, true); - old_cells = new_cells; - } - - function updateSelectedCellsClass(cells, selected) { - for (var ii = 0; ii < cells.length; ii++) { - JX.DOM.alterClass(cells[ii], 'selected', selected); - } } function hideReticle() { JX.DOM.hide(reticle); - setSelectedCells([]); } function isOnRight(node) { @@ -180,13 +123,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { return; } - if (editor) { - // Don't update the reticle if we're editing a comment, since this - // would be distracting and we want to keep the lines corresponding - // to the comment highlighted during the edit. - return; - } - if (getRowNumber(e.getTarget()) === undefined) { // Don't update the reticle if this "" doesn't correspond to a // line number. For instance, this may be a dead line number, like the @@ -236,7 +172,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { 'mouseup', null, function(e) { - if (editor || !selecting) { + if (!selecting) { return; } @@ -280,12 +216,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { if (e.getIsTouchEvent()) { return; } - - if (e.getType() == 'mouseout') { - hideReticle(); - } else { - updateReticleForComment(e); - } + hideReticle(); }); }); From e4e91ebf6f15936a90ab7fff4e249b3fb6dd0058 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 May 2017 16:52:31 -0700 Subject: [PATCH 105/543] In Differential, allow "r" to create comments and "R" to quote Summary: Ref T11401. Fixes T5232. Ref T12616. Partly, this moves more code over to the new stuff. This also allows "r" to work if you have code selected (not just comments). If you "reply" to code, you start a new comment. You can "R" a comment to quote it. This just starts a new comment normally if you "R" a block of code. This is sort of a power-user version of "quote" since it seems like it probably doesn't really make sense to put it in the UI ever (maybe). With the new click-to-select, you can click + "R" to reply-with-quote. Test Plan: Used "r" and "R" to reply to comments and code. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616, T11401, T5232 Differential Revision: https://secure.phabricator.com/D17920 --- resources/celerity/map.php | 66 +++++++------- .../view/DifferentialChangesetListView.php | 12 ++- .../rsrc/js/application/diff/DiffChangeset.js | 36 +++++++- .../js/application/diff/DiffChangesetList.js | 91 +++++++++++++++++-- .../rsrc/js/application/diff/DiffInline.js | 18 ++-- .../behavior-edit-inline-comments.js | 15 +-- 6 files changed, 170 insertions(+), 68 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 575ce9a2b1..125568ab64 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '8c5f913d', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'ea471cb0', - 'differential.pkg.js' => '7f24021f', + 'differential.pkg.js' => 'ae6f5198', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,13 +390,13 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '34e513e2', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'fcbf80e9', - 'rsrc/js/application/diff/DiffInline.js' => 'c2e9ff4c', + 'rsrc/js/application/diff/DiffChangeset.js' => '68758d99', + 'rsrc/js/application/diff/DiffChangesetList.js' => '57c491b5', + 'rsrc/js/application/diff/DiffInline.js' => '1afe9760', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'd59300f5', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '1c6bc8cf', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', @@ -619,7 +619,7 @@ return array( 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => 'd59300f5', + 'javelin-behavior-differential-edit-inline-comments' => '1c6bc8cf', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', @@ -779,9 +779,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '34e513e2', - 'phabricator-diff-changeset-list' => 'fcbf80e9', - 'phabricator-diff-inline' => 'c2e9ff4c', + 'phabricator-diff-changeset' => '68758d99', + 'phabricator-diff-changeset-list' => '57c491b5', + 'phabricator-diff-inline' => '1afe9760', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1030,6 +1030,9 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), + '1afe9760' => array( + 'javelin-dom', + ), '1bd28176' => array( 'javelin-install', 'javelin-dom', @@ -1037,6 +1040,13 @@ return array( 'javelin-request', 'javelin-uri', ), + '1c6bc8cf' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1129,17 +1139,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '34e513e2' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1332,6 +1331,9 @@ return array( 'javelin-vector', 'javelin-dom', ), + '57c491b5' => array( + 'javelin-install', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1406,6 +1408,17 @@ return array( 'javelin-dom', 'phabricator-notification', ), + '68758d99' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '6882e80a' => array( 'javelin-dom', ), @@ -1910,9 +1923,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - 'c2e9ff4c' => array( - 'javelin-dom', - ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2036,13 +2046,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - 'd59300f5' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - ), 'd5a2d665' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2221,9 +2224,6 @@ return array( 'javelin-dom', 'phortune-credit-card-form', ), - 'fcbf80e9' => array( - 'javelin-install', - ), 'fe287620' => array( 'javelin-install', 'javelin-dom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index c69eef008f..1e40595bd0 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -244,15 +244,19 @@ final class DifferentialChangesetListView extends AphrontView { pht('Jump to previous inline comment.'), 'Jump to the table of contents.' => pht('Jump to the table of contents.'), - 'Reply to selected inline comment.' => - pht('Reply to selected inline comment.'), + 'Edit selected inline comment.' => pht('Edit selected inline comment.'), - 'You must select a comment to reply to.' => - pht('You must select a comment to reply to.'), 'You must select a comment to edit.' => pht('You must select a comment to edit.'), + 'Reply to selected inline comment or change.' => + pht('Reply to selected inline comment or change.'), + 'You must select a comment or change to reply to.' => + pht('You must select a comment or change to reply to.'), + 'Reply and quote selected inline comment.' => + pht('Reply and quote selected inline comment.'), + 'Mark or unmark selected inline comment as done.' => pht('Mark or unmark selected inline comment as done.'), 'You must select a comment to mark done.' => diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 8176e4f12b..f6d1200c67 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -526,7 +526,37 @@ JX.install('DiffChangeset', { return data.inline; }, - newInlineForRange: function(data) { + newInlineForRange: function(origin, target) { + var list = this.getChangesetList(); + + var src = list.getLineNumberFromHeader(origin); + var dst = list.getLineNumberFromHeader(target); + + var changeset_id = null; + var side = list.getDisplaySideFromHeader(origin); + if (side == 'right') { + changeset_id = this.getRightChangesetID(); + } else { + changeset_id = this.getLeftChangesetID(); + } + + var is_new = false; + if (side == 'right') { + is_new = true; + } else if (this.getRightChangesetID() != this.getLeftChangesetID()) { + is_new = true; + } + + var data = { + origin: origin, + target: target, + number: src, + length: dst - src, + changesetID: changeset_id, + displaySide: side, + isNewFile: is_new + }; + var inline = new JX.DiffInline() .setChangeset(this) .bindToRange(data); @@ -538,14 +568,14 @@ JX.install('DiffChangeset', { return inline; }, - newInlineReply: function(original) { + newInlineReply: function(original, text) { var inline = new JX.DiffInline() .setChangeset(this) .bindToReply(original); this._inlines.push(inline); - inline.create(); + inline.create(text); return inline; }, diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index d9f1e35991..6067217419 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -136,8 +136,11 @@ JX.install('DiffChangesetList', { label = pht('Jump to the table of contents.'); this._installKey('t', label, this._ontoc); - label = pht('Reply to selected inline comment.'); - this._installKey('r', label, this._onkeyreply); + label = pht('Reply to selected inline comment or change.'); + this._installKey('r', label, JX.bind(this, this._onkeyreply, false)); + + label = pht('Reply and quote selected inline comment.'); + this._installKey('R', label, JX.bind(this, this._onkeyreply, true)); label = pht('Edit selected inline comment.'); this._installKey('e', label, this._onkeyedit); @@ -234,7 +237,7 @@ JX.install('DiffChangesetList', { manager.scrollTo(toc); }, - _onkeyreply: function() { + _onkeyreply: function(is_quote) { var cursor = this._cursorItem; if (cursor) { @@ -243,14 +246,77 @@ JX.install('DiffChangesetList', { if (inline.canReply()) { this.setFocus(null); - inline.reply(); + var text; + if (is_quote) { + text = inline.getRawText(); + text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n'; + } else { + text = ''; + } + + inline.reply(text); return; } } + + // If the keyboard cursor is selecting a range of lines, we may have + // a mixture of old and new changes on the selected rows. It is not + // entirely unambiguous what the user means when they say they want + // to reply to this, but we use this logic: reply on the new file if + // there are any new lines. Otherwise (if there are only removed + // lines) reply on the old file. + + if (cursor.type == 'change') { + var origin = cursor.nodes.begin; + var target = cursor.nodes.end; + + // The "origin" and "target" are entire rows, but we need to find + // a range of "" nodes to actually create an inline, so go + // fishing. + + var old_list = []; + var new_list = []; + + var row = origin; + while (row) { + var header = row.firstChild; + while (header) { + if (JX.DOM.isType(header, 'th')) { + if (header.className.indexOf('old') !== -1) { + old_list.push(header); + } else if (header.className.indexOf('new') !== -1) { + new_list.push(header); + } + } + header = header.nextSibling; + } + + if (row == target) { + break; + } + + row = row.nextSibling; + } + + var use_list; + if (new_list.length) { + use_list = new_list; + } else { + use_list = old_list; + } + + var src = use_list[0]; + var dst = use_list[use_list.length - 1]; + + cursor.changeset.newInlineForRange(src, dst); + + this.setFocus(null); + return; + } } var pht = this.getTranslations(); - this._warnUser(pht('You must select a comment to reply to.')); + this._warnUser(pht('You must select a comment or change to reply to.')); }, _onkeyedit: function() { @@ -804,7 +870,7 @@ JX.install('DiffChangesetList', { // Clear the mouse hover reticle after a substantive edit: we don't get // a "mouseout" event if the row vanished because of row being removed // after an edit. - this.reseHover(); + this.resetHover(); }, setFocus: function(node, extended_node) { @@ -978,8 +1044,19 @@ JX.install('DiffChangesetList', { var inline_row = e.getNode('inline-row'); return changeset.getInlineForRow(inline_row); - } + }, + getLineNumberFromHeader: function(th) { + try { + return parseInt(th.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); + } catch (x) { + return null; + } + }, + + getDisplaySideFromHeader: function(th) { + return (th.parentNode.firstChild != th) ? 'right' : 'left'; + } } }); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 38c2d7c82b..edd31942f9 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -148,6 +148,10 @@ JX.install('DiffInline', { return true; }, + getRawText: function() { + return this._originalText; + }, + _hasAction: function(action) { var nodes = JX.DOM.scry(this._row, 'a', 'differential-inline-' + action); return (nodes.length > 0); @@ -247,9 +251,9 @@ JX.install('DiffInline', { .send(); }, - reply: function() { + reply: function(text) { var changeset = this.getChangeset(); - return changeset.newInlineReply(this); + return changeset.newInlineReply(this, text); }, edit: function(text) { @@ -365,13 +369,9 @@ JX.install('DiffInline', { }, _oncreateresponse: function(response) { - try { var rows = JX.$H(response).getNode(); this._drawEditRows(rows); - } catch (e) { - JX.log(e); - } }, _ondeleteresponse: function() { @@ -471,7 +471,11 @@ JX.install('DiffInline', { 'textarea', 'differential-inline-comment-edit-textarea'); if (textareas.length) { - textareas[0].focus(); + var area = textareas[0]; + area.focus(); + + var length = area.value.length; + JX.TextAreaUtils.setSelectionRange(area, length, length); } row = next_row; diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 4b7b59decb..08c09d6626 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -72,11 +72,6 @@ JX.behavior('differential-edit-inline-comments', function(config) { return node.parentNode.firstChild != node; } - function isNewFile(node) { - var data = JX.Stratcom.getData(root); - return isOnRight(node) || (data.left != data.right); - } - function getRowNumber(th_node) { try { return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); @@ -192,15 +187,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { var view = JX.DiffChangeset.getForNode(root); - view.newInlineForRange({ - origin: origin, - target: target, - number: o, - length: len, - changesetID: changeset, - isNewFile: isNewFile(target), - displaySide: isOnRight(target) ? 'right' : 'left' - }); + view.newInlineForRange(origin, target); selecting = false; origin = null; From 422eb9db837dd4f6ae4de49acbea61554e653476 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 05:52:45 -0700 Subject: [PATCH 106/543] Correct the generation of "" IDs on left-hand-side of image changesets Summary: Fixes T7682. The left-hand-side "" row did not generate with the correct ID. (I couldn't reproduce the exact issue described in T7682, but hovering comments on either side now works properly for me.) Test Plan: {F4962479} Reviewers: chad Reviewed By: chad Maniphest Tasks: T7682 Differential Revision: https://secure.phabricator.com/D17926 --- .../render/DifferentialChangesetTwoUpRenderer.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 688544513e..5d476f5136 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -323,6 +323,12 @@ final class DifferentialChangesetTwoUpRenderer $new = $this->renderImageStage($new_file); } + // If we don't have an explicit "vs" changeset, it's the left side of the + // "id" changeset. + if (!$vs) { + $vs = $id; + } + $html_old = array(); $html_new = array(); foreach ($this->getOldComments() as $on_line => $comment_group) { From 6ecd6980a1420633d5e02d09f67262d9dc876089 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 16 May 2017 20:13:34 -0700 Subject: [PATCH 107/543] Allow setting of button colors in headers Summary: We currently override button color for headers, since the default is blue, but if a developer sets a specific color, we should respect that. Test Plan: Set a button in the header to green and see green. See grey everywhere else. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17922 --- src/view/phui/PHUIButtonView.php | 4 ++++ src/view/phui/PHUIHeaderView.php | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index c4ee7a190c..ee6975357c 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -64,6 +64,10 @@ final class PHUIButtonView extends AphrontTagView { return $this; } + public function getColor() { + return $this->color; + } + public function setDisabled($disabled) { $this->disabled = $disabled; return $this; diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0ae2c558d6..0cd8379b42 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -267,7 +267,9 @@ final class PHUIHeaderView extends AphrontTagView { if ($this->actionLinks) { $actions = array(); foreach ($this->actionLinks as $button) { - $button->setColor(PHUIButtonView::GREY); + if (!$button->getColor()) { + $button->setColor(PHUIButtonView::GREY); + } $button->addClass(PHUI::MARGIN_SMALL_LEFT); $button->addClass('phui-header-action-link'); $actions[] = $button; From 51df02821b86a7d48ddb708750328ad44279bdc6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 07:00:42 -0700 Subject: [PATCH 108/543] Move the "select a line range" inline code to DiffInline Summary: Ref T12616. This makes line range selection use the new code, and removes the remainder of the old "hover a line number" / "select a line range" code. Test Plan: Hovered line numbers; selected line ranges. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17927 --- resources/celerity/map.php | 42 ++-- resources/celerity/packages.php | 1 - .../view/DifferentialChangesetListView.php | 7 - webroot/rsrc/externals/javelin/lib/DOM.js | 4 +- .../js/application/diff/DiffChangesetList.js | 149 +++++++++++++ .../behavior-edit-inline-comments.js | 209 ------------------ 6 files changed, 167 insertions(+), 245 deletions(-) delete mode 100644 webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 125568ab64..794b117e82 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => 'ee5f28cd', - 'core.pkg.js' => '8c5f913d', + 'core.pkg.js' => '0f87a6eb', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'ea471cb0', - 'differential.pkg.js' => 'ae6f5198', + 'differential.pkg.js' => '85c19957', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -237,7 +237,7 @@ return array( 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9', 'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03', - 'rsrc/externals/javelin/lib/DOM.js' => '805b806a', + 'rsrc/externals/javelin/lib/DOM.js' => '4976858c', 'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 'rsrc/externals/javelin/lib/Leader.js' => '7f243deb', @@ -391,12 +391,11 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '68758d99', - 'rsrc/js/application/diff/DiffChangesetList.js' => '57c491b5', + 'rsrc/js/application/diff/DiffChangesetList.js' => '842e2676', 'rsrc/js/application/diff/DiffInline.js' => '1afe9760', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '1c6bc8cf', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', @@ -619,7 +618,6 @@ return array( 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-edit-inline-comments' => '1c6bc8cf', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', @@ -715,7 +713,7 @@ return array( 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', 'javelin-diffusion-locate-file-source' => 'c93358e3', - 'javelin-dom' => '805b806a', + 'javelin-dom' => '4976858c', 'javelin-dynval' => 'f6555212', 'javelin-event' => '2ee659ce', 'javelin-fx' => '54b612ba', @@ -780,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '68758d99', - 'phabricator-diff-changeset-list' => '57c491b5', + 'phabricator-diff-changeset-list' => '842e2676', 'phabricator-diff-inline' => '1afe9760', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1040,13 +1038,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '1c6bc8cf' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1250,6 +1241,13 @@ return array( 'javelin-uri', 'phabricator-notification', ), + '4976858c' => array( + 'javelin-magical-init', + 'javelin-install', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + ), '49ae8328' => array( 'javelin-behavior', 'javelin-dom', @@ -1331,9 +1329,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '57c491b5' => array( - 'javelin-install', - ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1529,13 +1524,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '805b806a' => array( - 'javelin-magical-init', - 'javelin-install', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1544,6 +1532,9 @@ return array( 'javelin-install', 'javelin-dom', ), + '842e2676' => array( + 'javelin-install', + ), '8499b6ab' => array( 'javelin-behavior', 'javelin-dom', @@ -2421,7 +2412,6 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', - 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-aphront-drag-and-drop-textarea', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 9ea3fa6b35..619a5d6389 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -193,7 +193,6 @@ return array( 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', - 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-aphront-drag-and-drop-textarea', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 1e40595bd0..aa34b0415f 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -274,13 +274,6 @@ final class DifferentialChangesetListView extends AphrontView { ), )); - if ($this->inlineURI) { - Javelin::initBehavior('differential-edit-inline-comments', array( - 'uri' => $this->inlineURI, - 'stage' => 'differential-review-stage', - )); - } - if ($this->header) { $header = $this->header; } else { diff --git a/webroot/rsrc/externals/javelin/lib/DOM.js b/webroot/rsrc/externals/javelin/lib/DOM.js index 189d778ad5..e0e3d764e0 100644 --- a/webroot/rsrc/externals/javelin/lib/DOM.js +++ b/webroot/rsrc/externals/javelin/lib/DOM.js @@ -891,7 +891,7 @@ JX.install('DOM', { * it. * * @param Node Node to look above. - * @param string Tag name, like 'a' or 'textarea'. + * @param string Optional tag name, like 'a' or 'textarea'. * @param string Optionally, sigil which selected node must have. * @return Node Matching node. * @@ -911,7 +911,7 @@ JX.install('DOM', { if (!result) { break; } - if (JX.DOM.isType(result, tagname)) { + if (!tagname || JX.DOM.isType(result, tagname)) { if (!sigil || JX.Stratcom.hasSigil(result, sigil)) { break; } diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 6067217419..8c2ff6e90f 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -62,6 +62,21 @@ JX.install('DiffChangesetList', { ['mouseover', 'mouseout'], 'differential-inline-comment', onhover); + + var onrangedown = JX.bind(this, this._ifawake, this._onrangedown); + JX.Stratcom.listen( + 'mousedown', + ['differential-changeset', 'tag:th'], + onrangedown); + + var onrangemove = JX.bind(this, this._ifawake, this._onrangemove); + JX.Stratcom.listen( + ['mouseover', 'mouseout'], + ['differential-changeset', 'tag:th'], + onrangemove); + + var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); + JX.Stratcom.listen('mouseup', null, onrangeup); }, properties: { @@ -85,6 +100,10 @@ JX.install('DiffChangesetList', { _hoverOrigin: null, _hoverTarget: null, + _rangeActive: false, + _rangeOrigin: null, + _rangeTarget: null, + sleep: function() { this._asleep = true; @@ -953,8 +972,18 @@ JX.install('DiffChangesetList', { this._redrawHover(); }, + _setHoverRange: function(origin, target) { + this._hoverOrigin = origin; + this._hoverTarget = target; + + this._redrawHover(); + }, + resetHover: function() { this._setHoverInline(null); + + this._hoverOrigin = null; + this._hoverTarget = null; }, _redrawHover: function() { @@ -1056,6 +1085,126 @@ JX.install('DiffChangesetList', { getDisplaySideFromHeader: function(th) { return (th.parentNode.firstChild != th) ? 'right' : 'left'; + }, + + _onrangedown: function(e) { + if (!e.isNormalMouseEvent()) { + return; + } + + if (e.getIsTouchEvent()) { + return; + } + + if (this._rangeActive) { + return; + } + + var target = e.getTarget(); + var number = this.getLineNumberFromHeader(target); + if (!number) { + return; + } + + e.kill(); + this._rangeActive = true; + + this._rangeOrigin = target; + this._rangeTarget = target; + + this._setHoverRange(this._rangeOrigin, this._rangeTarget); + }, + + _onrangemove: function(e) { + if (e.getIsTouchEvent()) { + return; + } + + var target = e.getTarget(); + + // Don't update the range if this "" doesn't correspond to a line + // number. For instance, this may be a dead line number, like the empty + // line numbers on the left hand side of a newly added file. + var number = this.getLineNumberFromHeader(target); + if (!number) { + return; + } + + if (this._rangeActive) { + var origin = this._hoverOrigin; + + // Don't update the reticle if we're selecting a line range and the + // "" under the cursor is on the wrong side of the file. You can + // only leave inline comments on the left or right side of a file, not + // across lines on both sides. + var origin_side = this.getDisplaySideFromHeader(origin); + var target_side = this.getDisplaySideFromHeader(target); + if (origin_side != target_side) { + return; + } + + // Don't update the reticle if we're selecting a line range and the + // "" under the cursor corresponds to a different file. You can + // only leave inline comments on lines in a single file, not across + // multiple files. + var origin_table = JX.DOM.findAbove(origin, 'table'); + var target_table = JX.DOM.findAbove(target, 'table'); + if (origin_table != target_table) { + return; + } + } + + var is_out = (e.getType() == 'mouseout'); + if (is_out) { + if (this._rangeActive) { + // If we're dragging a range, just leave the state as it is. This + // allows you to drag over something invalid while selecting a + // range without the range flickering or getting lost. + } else { + // Otherwise, clear the current range. + this.resetHover(); + } + return; + } + + if (this._rangeActive) { + this._rangeTarget = target; + } else { + this._rangeOrigin = target; + this._rangeTarget = target; + } + + this._setHoverRange(this._rangeOrigin, this._rangeTarget); + }, + + _onrangeup: function(e) { + if (!this._rangeActive) { + return; + } + + e.kill(); + + var origin = this._rangeOrigin; + var target = this._rangeTarget; + + // If the user dragged a range from the bottom to the top, swap the node + // order around. + if (JX.$V(origin).y > JX.$V(target).y) { + var tmp = target; + target = origin; + origin = tmp; + } + + var node = JX.DOM.findAbove(origin, null, 'differential-changeset'); + var changeset = this.getChangesetForNode(node); + + changeset.newInlineForRange(origin, target); + + this._rangeActive = false; + this._rangeOrigin = null; + this._rangeTarget = null; + + this.resetHover(); } } diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js deleted file mode 100644 index 08c09d6626..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ /dev/null @@ -1,209 +0,0 @@ -/** - * @provides javelin-behavior-differential-edit-inline-comments - * @requires javelin-behavior - * javelin-stratcom - * javelin-dom - * javelin-util - * javelin-vector - */ - -JX.behavior('differential-edit-inline-comments', function(config) { - - var selecting = false; - var reticle = JX.$N('div', {className: 'differential-reticle'}); - JX.DOM.hide(reticle); - - var origin = null; - var target = null; - var root = null; - var changeset = null; - - function updateReticle() { - JX.DOM.getContentFrame().appendChild(reticle); - - var top = origin; - var bot = target; - if (JX.$V(top).y > JX.$V(bot).y) { - var tmp = top; - top = bot; - bot = tmp; - } - - // Find the leftmost cell that we're going to highlight: this is the next - // in the row. In 2up views, it should be directly adjacent. In - // 1up views, we may have to skip over the other line number column. - var l = top; - while (JX.DOM.isType(l, 'th')) { - l = l.nextSibling; - } - - // Find the rightmost cell that we're going to highlight: this is the - // farthest consecutive, adjacent in the row. Sometimes the left - // and right nodes are the same (left side of 2up view); sometimes we're - // going to highlight several nodes (copy + code + coverage). - var r = l; - while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { - r = r.nextSibling; - } - - var pos = JX.$V(l) - .add(JX.Vector.getAggregateScrollForNode(l)); - - var dim = JX.$V(r) - .add(JX.Vector.getAggregateScrollForNode(r)) - .add(-pos.x, -pos.y) - .add(JX.Vector.getDim(r)); - - var bpos = JX.$V(bot) - .add(JX.Vector.getAggregateScrollForNode(bot)); - dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; - - pos.setPos(reticle); - dim.setDim(reticle); - - JX.DOM.show(reticle); - } - - function hideReticle() { - JX.DOM.hide(reticle); - } - - function isOnRight(node) { - return node.parentNode.firstChild != node; - } - - function getRowNumber(th_node) { - try { - return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); - } catch (x) { - return undefined; - } - } - - JX.Stratcom.listen( - 'mousedown', - ['differential-changeset', 'tag:th'], - function(e) { - if (e.isRightButton() || - getRowNumber(e.getTarget()) === undefined) { - return; - } - - if (selecting) { - return; - } - - selecting = true; - root = e.getNode('differential-changeset'); - - origin = target = e.getTarget(); - - var data = e.getNodeData('differential-changeset'); - if (isOnRight(target)) { - changeset = data.right; - } else { - changeset = data.left; - } - - updateReticle(); - - e.kill(); - }); - - JX.Stratcom.listen( - ['mouseover', 'mouseout'], - ['differential-changeset', 'tag:th'], - function(e) { - if (e.getIsTouchEvent()) { - return; - } - - if (getRowNumber(e.getTarget()) === undefined) { - // Don't update the reticle if this "" doesn't correspond to a - // line number. For instance, this may be a dead line number, like the - // empty line numbers on the left hand side of a newly added file. - return; - } - - if (selecting) { - if (isOnRight(e.getTarget()) != isOnRight(origin)) { - // Don't update the reticle if we're selecting a line range and the - // "" under the cursor is on the wrong side of the file. You - // can only leave inline comments on the left or right side of a - // file, not across lines on both sides. - return; - } - - if (e.getNode('differential-changeset') !== root) { - // Don't update the reticle if we're selecting a line range and - // the "" under the cursor corresponds to a different file. - // You can only leave inline comments on lines in a single file, - // not across multiple files. - return; - } - } - - if (e.getType() == 'mouseout') { - if (selecting) { - // Don't hide the reticle if we're selecting, since we want to - // keep showing the line range that will be used if the mouse is - // released. - return; - } - hideReticle(); - } else { - target = e.getTarget(); - if (!selecting) { - // If we're just hovering the mouse and not selecting a line range, - // set the origin to the current row so we highlight it. - origin = target; - } - - updateReticle(); - } - }); - - JX.Stratcom.listen( - 'mouseup', - null, - function(e) { - if (!selecting) { - return; - } - - var o = getRowNumber(origin); - var t = getRowNumber(target); - - var insert; - var len; - if (t < o) { - len = (o - t); - o = t; - insert = origin.parentNode; - } else { - len = (t - o); - insert = target.parentNode; - } - - var view = JX.DiffChangeset.getForNode(root); - - view.newInlineForRange(origin, target); - - selecting = false; - origin = null; - target = null; - - e.kill(); - }); - - JX.Stratcom.listen( - ['mouseover', 'mouseout'], - 'differential-inline-comment', - function(e) { - if (e.getIsTouchEvent()) { - return; - } - hideReticle(); - }); - -}); From 343f7cac72bbd9c64deeb71cdccd03444169a02b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 07:38:16 -0700 Subject: [PATCH 109/543] Improve mobile/device behaviors for inline comments Summary: Fixes T1026. Ref T12616. Allows drag-to-select on devices to add inlines on a range of lines, using dark magic that I copy/pasted from StackOverflow. Test Plan: Left a comment on a range of lines on iPhone simulator. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616, T1026 Differential Revision: https://secure.phabricator.com/D17928 --- resources/celerity/map.php | 12 ++--- .../js/application/diff/DiffChangesetList.js | 52 ++++++++++++++++--- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 794b117e82..40b61fca95 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '0f87a6eb', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'ea471cb0', - 'differential.pkg.js' => '85c19957', + 'differential.pkg.js' => '58457c19', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '68758d99', - 'rsrc/js/application/diff/DiffChangesetList.js' => '842e2676', + 'rsrc/js/application/diff/DiffChangesetList.js' => '204e4bfc', 'rsrc/js/application/diff/DiffInline.js' => '1afe9760', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '68758d99', - 'phabricator-diff-changeset-list' => '842e2676', + 'phabricator-diff-changeset-list' => '204e4bfc', 'phabricator-diff-inline' => '1afe9760', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1066,6 +1066,9 @@ return array( 'javelin-install', 'javelin-dom', ), + '204e4bfc' => array( + 'javelin-install', + ), '21df4ff5' => array( 'javelin-install', 'javelin-workboard-card', @@ -1532,9 +1535,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '842e2676' => array( - 'javelin-install', - ), '8499b6ab' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 8c2ff6e90f..e57ebc1674 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -65,7 +65,7 @@ JX.install('DiffChangesetList', { var onrangedown = JX.bind(this, this._ifawake, this._onrangedown); JX.Stratcom.listen( - 'mousedown', + ['touchstart', 'mousedown'], ['differential-changeset', 'tag:th'], onrangedown); @@ -75,8 +75,17 @@ JX.install('DiffChangesetList', { ['differential-changeset', 'tag:th'], onrangemove); + var onrangetouchmove = JX.bind(this, this._ifawake, this._onrangetouchmove); + JX.Stratcom.listen( + 'touchmove', + null, + onrangetouchmove); + var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); - JX.Stratcom.listen('mouseup', null, onrangeup); + JX.Stratcom.listen( + ['touchend', 'mouseup'], + null, + onrangeup); }, properties: { @@ -1088,11 +1097,9 @@ JX.install('DiffChangesetList', { }, _onrangedown: function(e) { - if (!e.isNormalMouseEvent()) { - return; - } - - if (e.getIsTouchEvent()) { + // NOTE: We're allowing touch events through, including "touchstart". We + // need to kill the "touchstart" event so the page doesn't scroll. + if (e.isRightButton()) { return; } @@ -1120,8 +1127,13 @@ JX.install('DiffChangesetList', { return; } + var is_out = (e.getType() == 'mouseout'); var target = e.getTarget(); + this._updateRange(target, is_out); + }, + + _updateRange: function(target, is_out) { // Don't update the range if this "" doesn't correspond to a line // number. For instance, this may be a dead line number, like the empty // line numbers on the left hand side of a newly added file. @@ -1154,7 +1166,6 @@ JX.install('DiffChangesetList', { } } - var is_out = (e.getType() == 'mouseout'); if (is_out) { if (this._rangeActive) { // If we're dragging a range, just leave the state as it is. This @@ -1177,6 +1188,31 @@ JX.install('DiffChangesetList', { this._setHoverRange(this._rangeOrigin, this._rangeTarget); }, + _onrangetouchmove: function(e) { + if (!this._rangeActive) { + return; + } + + // NOTE: The target of a "touchmove" event is bogus. Use dark magic to + // identify the actual target. Some day, this might move into the core + // libraries. If this doesn't work, just bail. + + var target; + try { + var raw_event = e.getRawEvent(); + var touch = raw_event.touches[0]; + target = document.elementFromPoint(touch.clientX, touch.clientY); + } catch (ex) { + return; + } + + if (!JX.DOM.isType(target, 'th')) { + return; + } + + this._updateRange(target, false); + }, + _onrangeup: function(e) { if (!this._rangeActive) { return; From 9eb285f4aabb27b8e3cb144a9e020ee1ac0fa747 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 08:25:39 -0700 Subject: [PATCH 110/543] Fix "left"/"right" changeset ID selection for synthetic deletions Summary: Fixes T8323. See that task for a description. We were using `nonempty()`, but that rule doesn't cover synthetic deletions (file present in an earlier diff, but no longer present in the later diff). Test Plan: Followed the steps in T8323, got a clean comment. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8323 Differential Revision: https://secure.phabricator.com/D17929 --- .../view/DifferentialChangesetDetailView.php | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index 219dfaaa1f..42be1c6444 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -150,15 +150,31 @@ final class DifferentialChangesetDetailView extends AphrontView { $renderer = DifferentialChangesetHTMLRenderer::getHTMLRendererByKey( $this->getRenderer()); + $changeset_id = $this->changeset->getID(); + + $vs_id = $this->getVsChangesetID(); + if (!$vs_id) { + // Showing a changeset normally. + $left_id = $changeset_id; + $right_id = $changeset_id; + } else if ($vs_id == -1) { + // Showing a synthetic "deleted" changeset for a file which was + // removed between changes. + $left_id = $changeset_id; + $right_id = null; + } else { + // Showing a diff-of-diffs. + $left_id = $vs_id; + $right_id = $changeset_id; + } + return javelin_tag( 'div', array( 'sigil' => 'differential-changeset', 'meta' => array( - 'left' => nonempty( - $this->getVsChangesetID(), - $this->changeset->getID()), - 'right' => $this->changeset->getID(), + 'left' => $left_id, + 'right' => $right_id, 'renderURI' => $this->getRenderURI(), 'whitespace' => $this->getWhitespace(), 'highlight' => null, From dde63af1cc86c5eedc1cbe492d8c721aa59a67b5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 08:35:13 -0700 Subject: [PATCH 111/543] Fix a JS console warning when hovering over replies to ghosts on lines which no longer exist Summary: Fixes T11662. In the very obscure situation described in that task, quiet a JS console warning. The actual edit operation appears to work correctly after changes elsewhere. There aren't really any legitimate lines for us to highlight in this case so I'm just giving up rather than trying to do something approximate. Test Plan: - Wrote `long.txt`. - Created revision. - Added an inline near the bottom. - Removed most of `long.txt`. - Updated revsion. - Replied to the ghost inline. - Edited the reply to the ghost inline, worked. - Hovered the reply to the ghost inline: no line highlight, but no errors. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11662 Differential Revision: https://secure.phabricator.com/D17930 --- resources/celerity/map.php | 12 ++++++------ .../js/application/diff/DiffChangesetList.js | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 40b61fca95..e82602aa0b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '0f87a6eb', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'ea471cb0', - 'differential.pkg.js' => '58457c19', + 'differential.pkg.js' => '4a466790', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '68758d99', - 'rsrc/js/application/diff/DiffChangesetList.js' => '204e4bfc', + 'rsrc/js/application/diff/DiffChangesetList.js' => '796922e0', 'rsrc/js/application/diff/DiffInline.js' => '1afe9760', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '68758d99', - 'phabricator-diff-changeset-list' => '204e4bfc', + 'phabricator-diff-changeset-list' => '796922e0', 'phabricator-diff-inline' => '1afe9760', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1066,9 +1066,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '204e4bfc' => array( - 'javelin-install', - ), '21df4ff5' => array( 'javelin-install', 'javelin-workboard-card', @@ -1496,6 +1493,9 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), + '796922e0' => array( + 'javelin-install', + ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index e57ebc1674..6e3853a9d1 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -968,11 +968,22 @@ JX.install('DiffChangesetList', { var number = inline.getLineNumber(); var length = inline.getLineLength(); - var origin = JX.$(prefix + number); - var target = JX.$(prefix + (number + length)); + try { + var origin = JX.$(prefix + number); + var target = JX.$(prefix + (number + length)); - this._hoverOrigin = origin; - this._hoverTarget = target; + this._hoverOrigin = origin; + this._hoverTarget = target; + } catch (error) { + // There may not be any nodes present in the document. A case where + // this occurs is when you reply to a ghost inline which was made + // on lines near the bottom of "long.txt" in an earlier diff, and + // the file was later shortened so those lines no longer exist. For + // more details, see T11662. + + this._hoverOrigin = null; + this._hoverTarget = null; + } } else { this._hoverOrigin = null; this._hoverTarget = null; From 75fb1a0327cb199cdff7d56ffe95217a9d227711 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 17 May 2017 09:06:07 -0700 Subject: [PATCH 112/543] Don't render an action list without actions Summary: Skips rendering of partial elements if no actions are present. Test Plan: Tested on profile menu item page, maniphest curtain, phriction dropdown, and instance backups page (no actions at all). Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17931 --- resources/celerity/map.php | 6 +++--- src/view/layout/PhabricatorActionListView.php | 4 ++++ webroot/rsrc/css/phui/phui-curtain-view.css | 7 ++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e82602aa0b..4e06cdc7f2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'ee5f28cd', + 'core.pkg.css' => 'a5a2d647', 'core.pkg.js' => '0f87a6eb', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'ea471cb0', @@ -145,7 +145,7 @@ return array( 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => '679743bb', + 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', 'rsrc/css/phui/phui-document-pro.css' => '62c4dcbf', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', @@ -844,7 +844,7 @@ return array( 'phui-comment-form-css' => '57af2e14', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => '679743bb', + 'phui-curtain-view-css' => '55dd0e59', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => '62c4dcbf', diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php index faf30555eb..134c336735 100644 --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -16,6 +16,10 @@ final class PhabricatorActionListView extends AphrontTagView { } protected function getTagName() { + if (!$this->actions) { + return null; + } + return 'ul'; } diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index dbc706994a..0d7d4f942f 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -7,12 +7,17 @@ margin: 0 4px; } +.phui-two-column-properties > .phui-curtain-panel:first-child { + padding-top: 6px; +} + .device .phui-curtain-panel { padding: 8px 0; margin: 0; } -.device-desktop .phui-curtain-panel { +.device-desktop .phui-curtain-panel + .phui-curtain-panel, +.device-desktop .phabricator-action-list-view + .phui-curtain-panel { border-top: 1px solid {$greybackground}; } From d03a497616cf5be0ace7235e6d1c83558eea655f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 11:39:56 -0700 Subject: [PATCH 113/543] Allow any inline in the document to be queried by ID Summary: Ref T12616. When you "Delete" a comment from the preview, we try to delete the comment on screen too. It may or may not be present on screen: if you just added it it's usually visible. However, you might also have hidden the file it contains or it could be on an older diff in a file which is no longer present in the current diff. After updates in T12616, we could only find the comment if you'd previously interacted with it for some reason. Update this code to be able to find all inlines present in the document. Test Plan: - Write a draft comment. - Reload the page. - DO NOT INTERACT WITH THE COMMENT! - Delete it from the preview. - After patch: Comment is deleted from the document, too. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17937 --- .../rsrc/js/application/diff/DiffChangeset.js | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index f6d1200c67..f591eebb67 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -581,11 +581,19 @@ JX.install('DiffChangeset', { }, getInlineByID: function(id) { - // TODO: Currently, this will only find inlines which the user has - // already interacted with! Inlines are built lazily as events arrive. - // This can not yet find inlines which are passively present in the - // document. + // First, look for the inline in the objects we've already built. + var inline = this._findInlineByID(id); + if (inline) { + return inline; + } + // If we haven't found a matching inline yet, rebuild all the inlines + // present in the document, then look again. + this._rebuildAllInlines(); + return this._findInlineByID(id); + }, + + _findInlineByID: function(id) { for (var ii = 0; ii < this._inlines.length; ii++) { var inline = this._inlines[ii]; if (inline.getID() == id) { @@ -594,8 +602,21 @@ JX.install('DiffChangeset', { } return null; - } + }, + _rebuildAllInlines: function() { + var rows = JX.DOM.scry(this._node, 'tr'); + for (var ii = 0; ii < rows.length; ii++) { + var row = rows[ii]; + if (this._getRowType(row) != 'comment') { + continue; + } + + // As a side effect, this builds any missing inline objects and adds + // them to this Changeset's list of inlines. + this.getInlineForRow(row); + } + } }, From 80c329c94294445f2fbf6ac8f18ca4810fa2168d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 11:59:00 -0700 Subject: [PATCH 114/543] When replying to a threaded inline, put the new inline in the right place in the thread Summary: Fixes T10563. If you have a thread like this: ``` > A > B > C ``` ...and you reply to "B", we should put the new inline below "B". We currently do when you reload the page, but the initial edit goes at the bottom always (below "C"). Test Plan: {F4963015} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10563 Differential Revision: https://secure.phabricator.com/D17938 --- resources/celerity/map.php | 38 ++++++++-------- .../rsrc/js/application/diff/DiffChangeset.js | 27 ++++++++++-- .../rsrc/js/application/diff/DiffInline.js | 44 ++++++++++++++++++- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4e06cdc7f2..10ff2b3e47 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '0f87a6eb', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'ea471cb0', - 'differential.pkg.js' => '4a466790', + 'differential.pkg.js' => '31e1b646', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -390,9 +390,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '68758d99', + 'rsrc/js/application/diff/DiffChangeset.js' => 'ec32b333', 'rsrc/js/application/diff/DiffChangesetList.js' => '796922e0', - 'rsrc/js/application/diff/DiffInline.js' => '1afe9760', + 'rsrc/js/application/diff/DiffInline.js' => '3337c065', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -777,9 +777,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '68758d99', + 'phabricator-diff-changeset' => 'ec32b333', 'phabricator-diff-changeset-list' => '796922e0', - 'phabricator-diff-inline' => '1afe9760', + 'phabricator-diff-inline' => '3337c065', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1028,9 +1028,6 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), - '1afe9760' => array( - 'javelin-dom', - ), '1bd28176' => array( 'javelin-install', 'javelin-dom', @@ -1130,6 +1127,9 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '3337c065' => array( + 'javelin-dom', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1403,17 +1403,6 @@ return array( 'javelin-dom', 'phabricator-notification', ), - '68758d99' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '6882e80a' => array( 'javelin-dom', ), @@ -2142,6 +2131,17 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'ec32b333' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'eded9ee8' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index f591eebb67..aca035ea0c 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -581,8 +581,16 @@ JX.install('DiffChangeset', { }, getInlineByID: function(id) { + return this._queryInline('id', id); + }, + + getInlineByPHID: function(phid) { + return this._queryInline('phid', phid); + }, + + _queryInline: function(field, value) { // First, look for the inline in the objects we've already built. - var inline = this._findInlineByID(id); + var inline = this._findInline(field, value); if (inline) { return inline; } @@ -590,13 +598,24 @@ JX.install('DiffChangeset', { // If we haven't found a matching inline yet, rebuild all the inlines // present in the document, then look again. this._rebuildAllInlines(); - return this._findInlineByID(id); + return this._findInline(field, value); }, - _findInlineByID: function(id) { + _findInline: function(field, value) { for (var ii = 0; ii < this._inlines.length; ii++) { var inline = this._inlines[ii]; - if (inline.getID() == id) { + + var target; + switch (field) { + case 'id': + target = inline.getID(); + break; + case 'phid': + target = inline.getPHID(); + break; + } + + if (target == value) { return inline; } } diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index edd31942f9..b46bc00b4f 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -63,6 +63,8 @@ JX.install('DiffInline', { (this.getDisplaySide() == 'right') || (data.left != data.right); + this._replyToCommentPHID = data.replyToCommentPHID; + this.setInvisible(false); return this; @@ -100,11 +102,45 @@ JX.install('DiffInline', { this._replyToCommentPHID = inline._phid; - // TODO: This should insert correctly into the thread, not just at the - // bottom. + var changeset = this.getChangeset(); + + // We're going to figure out where in the document to position the new + // inline. Normally, it goes after any existing inline rows (so if + // several inlines reply to the same line, they appear in chronological + // order). + + // However: if inlines are threaded, we want to put the new inline in + // the right place in the thread. This might be somewhere in the middle, + // so we need to do a bit more work to figure it out. + + // To find the right place in the thread, we're going to look for any + // inline which is at or above the level of the comment we're replying + // to. This means we've reached a new fork of the thread, and should + // put our new inline before the comment we found. + var ancestor_map = {}; + var ancestor = inline; + var reply_phid; + while (ancestor) { + reply_phid = ancestor.getReplyToCommentPHID(); + if (!reply_phid) { + break; + } + ancestor_map[reply_phid] = true; + ancestor = changeset.getInlineByPHID(reply_phid); + } + var parent_row = inline._row; var target_row = parent_row.nextSibling; while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + var target = changeset.getInlineForRow(target_row); + reply_phid = target.getReplyToCommentPHID(); + + // If we found an inline which is replying directly to some ancestor + // of this new comment, this is where the new rows go. + if (ancestor_map.hasOwnProperty(reply_phid)) { + break; + } + target_row = target_row.nextSibling; } @@ -318,6 +354,10 @@ JX.install('DiffInline', { return this._id; }, + getPHID: function() { + return this._phid; + }, + getChangesetID: function() { return this._changesetID; }, From 0e8f72a0d992ee5b27f807b65e4ce83ef2ec5078 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 17 May 2017 14:48:15 -0700 Subject: [PATCH 115/543] Clarify Subscription page language Summary: This says "Edit Subscription" but the page only lets you update the payment method for autopay. I think this is confusing users. I tried to find the best language here, but suggest something else if you prefer. Test Plan: Review language on sample subscription page. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12451 Differential Revision: https://secure.phabricator.com/D17944 --- .../subscription/PhortuneSubscriptionViewController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php index daf3daa11d..2e78d37d5c 100644 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php +++ b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php @@ -43,8 +43,8 @@ final class PhortuneSubscriptionViewController extends PhortuneController { $curtain->addAction( id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Subscription')) + ->setIcon('fa-credit-card') + ->setName(pht('Manage Autopay')) ->setHref($edit_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); From d12be0fc2190c9442b9eec388b915cb4e37ed19e Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 17 May 2017 16:11:04 -0700 Subject: [PATCH 116/543] Change Pholio editor access modifier Summary: Used by `PholioImageFileTransaction::mergeTransactions()`. I forgot to test adding multiple images to a Mock at the same time after migrating `mergeTransactions` over to the modular framework. Test Plan: Added multiple images in a single transaction and didn't get an exception about accessing a protected function. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17946 --- .../editor/PhabricatorApplicationTransactionEditor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index b5aa399521..3f639d8aba 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1743,7 +1743,7 @@ abstract class PhabricatorApplicationTransactionEditor return array_values($result); } - protected function mergePHIDOrEdgeTransactions( + public function mergePHIDOrEdgeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { From 1e3c8df1c89db591891ecfba9945df6894fb98ca Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 16 May 2017 14:40:15 -0700 Subject: [PATCH 117/543] Migrate Project names to modular transactions Summary: Also changes access modifiers on `PhabricatorProjectTransactionEditor` and sets up `storage` for `applyExternalEffects`. Test Plan: Created new projects, attempted to create without name, with too long of a name, and with a name that conflicts with other projects and observed expected errors. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T12673 Differential Revision: https://secure.phabricator.com/D17947 --- .../sql/patches/20131020.pxactionmig.php | 2 +- src/__phutil_library_map__.php | 6 +- .../PhabricatorProjectCoreTestCase.php | 13 +- .../conduit/ProjectCreateConduitAPIMethod.php | 2 +- .../PhabricatorProjectTransactionEditor.php | 80 +------------ .../engine/PhabricatorProjectEditEngine.php | 2 +- .../PhabricatorProjectTestDataGenerator.php | 2 +- .../storage/PhabricatorProjectTransaction.php | 35 +----- .../PhabricatorProjectNameTransaction.php | 112 ++++++++++++++++++ .../PhabricatorProjectTransactionType.php | 4 + ...habricatorApplicationTransactionEditor.php | 2 + 11 files changed, 142 insertions(+), 118 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectNameTransaction.php create mode 100644 src/applications/project/xaction/PhabricatorProjectTransactionType.php diff --git a/resources/sql/patches/20131020.pxactionmig.php b/resources/sql/patches/20131020.pxactionmig.php index 3d593d6e34..a6d305b77a 100644 --- a/resources/sql/patches/20131020.pxactionmig.php +++ b/resources/sql/patches/20131020.pxactionmig.php @@ -32,7 +32,7 @@ foreach ($rows as $row) { $project_phid = $project_row['phid']; $type_map = array( - 'name' => PhabricatorProjectTransaction::TYPE_NAME, + 'name' => PhabricatorProjectNameTransaction::TRANSACTIONTYPE, 'members' => PhabricatorProjectTransaction::TYPE_MEMBERS, 'status' => PhabricatorProjectTransaction::TYPE_STATUS, 'canview' => PhabricatorTransactions::TYPE_VIEW_POLICY, diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fbc972328c..cc4794d251 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3644,6 +3644,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMenuItemController' => 'applications/project/controller/PhabricatorProjectMenuItemController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', + 'PhabricatorProjectNameTransaction' => 'applications/project/xaction/PhabricatorProjectNameTransaction.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', @@ -3674,6 +3675,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', + 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -9053,6 +9055,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMenuItemController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhabricatorProjectNameTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -9083,9 +9086,10 @@ phutil_register_library_map(array( 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', - 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorProjectTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 3943a7c0c3..0f97d88f1e 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -356,7 +356,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $this->applyTransactions($project, $user, $xactions); @@ -382,7 +382,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name2); $xactions[] = id(new PhabricatorProjectTransaction()) @@ -503,7 +503,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) @@ -601,7 +601,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) @@ -1290,7 +1290,8 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $new_name = $proj->getName().' '.mt_rand(); $xaction = new PhabricatorProjectTransaction(); - $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME); + $xaction->setTransactionType( + PhabricatorProjectNameTransaction::TRANSACTIONTYPE); $xaction->setNewValue($new_name); $this->applyTransactions($proj, $user, array($xaction)); @@ -1440,7 +1441,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); if ($parent) { diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index 66075e7248..edd2606a77 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -42,7 +42,7 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { $user); $project = PhabricatorProject::initializeNewProject($user); - $type_name = PhabricatorProjectTransaction::TYPE_NAME; + $type_name = PhabricatorProjectNameTransaction::TRANSACTIONTYPE; $members = $request->getValue('members'); $xactions = array(); diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index aa8c0852ab..7a38c3bbdf 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -10,7 +10,7 @@ final class PhabricatorProjectTransactionEditor return $this; } - private function getIsMilestone() { + public function getIsMilestone() { return $this->isMilestone; } @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_NAME; $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; $types[] = PhabricatorProjectTransaction::TYPE_STATUS; $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; @@ -52,8 +51,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - return $object->getName(); case PhabricatorProjectTransaction::TYPE_SLUGS: $slugs = $object->getSlugs(); $slugs = mpull($slugs, 'getSlug', 'getSlug'); @@ -90,7 +87,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: @@ -121,13 +117,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - $name = $xaction->getNewValue(); - $object->setName($name); - if (!$this->getIsMilestone()) { - $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); - } - return; case PhabricatorProjectTransaction::TYPE_SLUGS: return; case PhabricatorProjectTransaction::TYPE_STATUS: @@ -178,16 +167,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - // First, add the old name as a secondary slug; this is helpful - // for renames and generally a good thing to do. - if (!$this->getIsMilestone()) { - if ($old !== null) { - $this->addSlug($object, $old, false); - } - $this->addSlug($object, $new, false); - } - return; case PhabricatorProjectTransaction::TYPE_SLUGS: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); @@ -299,59 +278,6 @@ final class PhabricatorProjectTransactionEditor $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case PhabricatorProjectTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Project name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - if (!$xactions) { - break; - } - - if ($this->getIsMilestone()) { - break; - } - - $name = last($xactions)->getNewValue(); - - if (!PhabricatorSlug::isValidProjectSlug($name)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Project names must contain at least one letter or number.'), - last($xactions)); - break; - } - - $slug = PhabricatorSlug::normalizeProjectSlug($name); - - $slug_used_already = id(new PhabricatorProjectSlug()) - ->loadOneWhere('slug = %s', $slug); - if ($slug_used_already && - $slug_used_already->getProjectPHID() != $object->getPHID()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht( - 'Project name generates the same hashtag ("%s") as another '. - 'existing project. Choose a unique name.', - '#'.$slug), - nonempty(last($xactions), null)); - $errors[] = $error; - } - break; case PhabricatorProjectTransaction::TYPE_SLUGS: if (!$xactions) { break; @@ -497,7 +423,7 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: @@ -717,7 +643,7 @@ final class PhabricatorProjectTransactionEditor return parent::applyFinalEffects($object, $xactions); } - private function addSlug(PhabricatorProject $project, $slug, $force) { + public function addSlug(PhabricatorProject $project, $slug, $force) { $slug = PhabricatorSlug::normalizeProjectSlug($slug); $table = new PhabricatorProjectSlug(); $project_phid = $project->getPHID(); diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 286e9bab0f..8f855074d6 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -235,7 +235,7 @@ final class PhabricatorProjectEditEngine id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setDescription(pht('Project name.')) ->setConduitDescription(pht('Rename the project')) diff --git a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php index 0fd5bd66e7..8032749f94 100644 --- a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php +++ b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php @@ -16,7 +16,7 @@ final class PhabricatorProjectTestDataGenerator $xactions = array(); $xactions[] = $this->newTransaction( - PhabricatorProjectTransaction::TYPE_NAME, + PhabricatorProjectNameTransaction::TRANSACTIONTYPE, $this->newProjectTitle()); $xactions[] = $this->newTransaction( diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index fb9a25eda6..0ad6230a5e 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -1,9 +1,8 @@ getOldValue(); $new = $this->getNewValue(); @@ -149,20 +152,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this project.', - $author_handle); - } else { - return pht( - '%s renamed this project from "%s" to "%s".', - $author_handle, - $old, - $new); - } - break; - case self::TYPE_STATUS: if ($old == 0) { return pht( @@ -329,20 +318,6 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s renamed %s from "%s" to "%s".', - $author_handle, - $object_handle, - $old, - $new); - } case self::TYPE_STATUS: if ($old == 0) { return pht( diff --git a/src/applications/project/xaction/PhabricatorProjectNameTransaction.php b/src/applications/project/xaction/PhabricatorProjectNameTransaction.php new file mode 100644 index 0000000000..512d899f0b --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectNameTransaction.php @@ -0,0 +1,112 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + if (!$this->getEditor()->getIsMilestone()) { + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($value)); + } + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + + // First, add the old name as a secondary slug; this is helpful + // for renames and generally a good thing to do. + if (!$this->getEditor()->getIsMilestone()) { + if ($old !== null) { + $this->getEditor()->addSlug($object, $old, false); + } + $this->getEditor()->addSlug($object, $value, false); + } + return; + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created this project.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this project from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Projects must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Project names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + if ($this->getEditor()->getIsMilestone() || !$xactions) { + return $errors; + } + + $name = last($xactions)->getNewValue(); + + if (!PhabricatorSlug::isValidProjectSlug($name)) { + $errors[] = $this->newInvalidError( + pht('Project names must contain at least one letter or number.')); + } + + $slug = PhabricatorSlug::normalizeProjectSlug($name); + + $slug_used_already = id(new PhabricatorProjectSlug()) + ->loadOneWhere('slug = %s', $slug); + if ($slug_used_already && + $slug_used_already->getProjectPHID() != $object->getPHID()) { + + $errors[] = $this->newInvalidError( + pht( + 'Project name generates the same hashtag ("%s") as another '. + 'existing project. Choose a unique name.', + '#'.$slug)); + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectTransactionType.php b/src/applications/project/xaction/PhabricatorProjectTransactionType.php new file mode 100644 index 0000000000..9ce02dc450 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectTransactionType.php @@ -0,0 +1,4 @@ +getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->applyExternalEffects($object, $xaction->getNewValue()); } From f78ce156f1144fccd356803eb2dcb88ab200585c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 12:51:29 -0700 Subject: [PATCH 118/543] Restore "h" to hide or show files, and modernize file visibility toggling Summary: Ref T12616. This puts "h" back to collapse or expand the current file. This removes some very complicated/messy code around following links in the table of contents and getting files auto-expanded. I suspect no one will miss this, but we can restore it if ayone notices. Test Plan: Pressed "h" to collapse/expand a file. Also used the menu items. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12616 Differential Revision: https://secure.phabricator.com/D17940 --- resources/celerity/map.php | 59 ++++---- resources/celerity/packages.php | 1 - .../view/DifferentialChangesetListView.php | 16 ++- .../differential/changeset-view.css | 1 + .../rsrc/js/application/diff/DiffChangeset.js | 61 ++++++++- .../js/application/diff/DiffChangesetList.js | 25 +++- .../differential/behavior-toggle-files.js | 128 ------------------ 7 files changed, 114 insertions(+), 177 deletions(-) delete mode 100644 webroot/rsrc/js/application/differential/behavior-toggle-files.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 10ff2b3e47..2f9026785d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => 'a5a2d647', 'core.pkg.js' => '0f87a6eb', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => 'ea471cb0', - 'differential.pkg.js' => '31e1b646', + 'differential.pkg.css' => '697405d4', + 'differential.pkg.js' => '07c56ffc', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '6a77323e', + 'rsrc/css/application/differential/changeset-view.css' => '15be1064', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -390,14 +390,13 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'ec32b333', - 'rsrc/js/application/diff/DiffChangesetList.js' => '796922e0', + 'rsrc/js/application/diff/DiffChangeset.js' => '731125f3', + 'rsrc/js/application/diff/DiffChangesetList.js' => '59d1ceb1', 'rsrc/js/application/diff/DiffInline.js' => '3337c065', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', - 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -566,7 +565,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '6a77323e', + 'differential-changeset-view-css' => '15be1064', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -620,7 +619,6 @@ return array( 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-populate' => '5e41c819', - 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -777,8 +775,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'ec32b333', - 'phabricator-diff-changeset-list' => '796922e0', + 'phabricator-diff-changeset' => '731125f3', + 'phabricator-diff-changeset-list' => '59d1ceb1', 'phabricator-diff-inline' => '3337c065', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1001,6 +999,9 @@ return array( 'javelin-dom', 'javelin-history', ), + '15be1064' => array( + 'phui-inline-comment-view-css', + ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1348,6 +1349,9 @@ return array( 'javelin-vector', 'javelin-dom', ), + '59d1ceb1' => array( + 'javelin-install', + ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1409,9 +1413,6 @@ return array( '69adf288' => array( 'javelin-install', ), - '6a77323e' => array( - 'phui-inline-comment-view-css', - ), '6ad39b6f' => array( 'javelin-install', 'javelin-event', @@ -1449,6 +1450,17 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), + '731125f3' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '7319e029' => array( 'javelin-behavior', 'javelin-dom', @@ -1482,9 +1494,6 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), - '796922e0' => array( - 'javelin-install', - ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1960,12 +1969,6 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), - 'ca3f91eb' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-phtize', - ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', @@ -2131,17 +2134,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'ec32b333' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'eded9ee8' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2418,7 +2410,6 @@ return array( 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', 'phabricator-diff-inline', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 619a5d6389..e756e696cb 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -200,7 +200,6 @@ return array( 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index aa34b0415f..4ac2612ecd 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -131,13 +131,6 @@ final class DifferentialChangesetListView extends AphrontView { $changesets = $this->changesets; - Javelin::initBehavior('differential-toggle-files', array( - 'pht' => array( - 'undo' => pht('Undo'), - 'collapsed' => pht('This file content has been collapsed.'), - ), - )); - $renderer = DifferentialChangesetParser::getDefaultRendererForViewer( $viewer); @@ -271,6 +264,15 @@ final class DifferentialChangesetListView extends AphrontView { pht('Jump to next inline comment, including hidden comments.'), 'Jump to previous inline comment, including hidden comments.' => pht('Jump to previous inline comment, including hidden comments.'), + + 'This file content has been collapsed.' => + pht('This file content has been collapsed.'), + 'Show Content' => pht('Show Content'), + + 'Hide or show the current file.' => + pht('Hide or show the current file.'), + 'You must select a file to hide or show.' => + pht('You must select a file to hide or show.'), ), )); diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 92a544c24c..0cec38ffdc 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -325,6 +325,7 @@ td.cov-I { border: 1px solid {$blue}; text-align: center; background-color: {$lightblue}; + margin: 8px; } .differential-collapse-undo a { diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index aca035ea0c..c3359c2007 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -55,6 +55,9 @@ JX.install('DiffChangeset', { _rightID: null, _inlines: null, + _visible: true, + + _undoNode: null, getLeftChangesetID: function() { return this._leftID; @@ -345,6 +348,10 @@ JX.install('DiffChangeset', { } }); + if (!this._visible) { + return items; + } + var rows = JX.DOM.scry(this._node, 'tr'); var blocks = []; @@ -635,8 +642,60 @@ JX.install('DiffChangeset', { // them to this Changeset's list of inlines. this.getInlineForRow(row); } - } + }, + toggleVisibility: function() { + this._visible = !this._visible; + + var diff = JX.DOM.find(this._node, 'table', 'differential-diff'); + var undo = this._getUndoNode(); + + if (this._visible) { + JX.DOM.show(diff); + JX.DOM.remove(undo); + } else { + JX.DOM.hide(diff); + JX.DOM.appendContent(diff.parentNode, undo); + } + + JX.Stratcom.invoke('resize'); + }, + + _getUndoNode: function() { + if (!this._undoNode) { + var pht = this.getChangesetList().getTranslations(); + + var link_attributes = { + href: '#' + }; + + var undo_link = JX.$N('a', link_attributes, pht('Show Content')); + + var onundo = JX.bind(this, this._onundo); + JX.DOM.listen(undo_link, 'click', null, onundo); + + var node_attributes = { + className: 'differential-collapse-undo' + }; + + var node_content = [ + pht('This file content has been collapsed.'), + ' ', + undo_link + ]; + + var undo_node = JX.$N('div', node_attributes, node_content); + + this._undoNode = undo_node; + } + + return this._undoNode; + }, + + _onundo: function(e) { + e.kill(); + this.toggleVisibility(); + } }, statics: { diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 6e3853a9d1..ff51cd4666 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -161,6 +161,9 @@ JX.install('DiffChangesetList', { 'Jump to previous inline comment, including hidden comments.'); this._installJumpKey('P', label, -1, 'comment', true); + label = pht('Hide or show the current file.'); + this._installKey('h', label, this._onkeytogglefile); + label = pht('Jump to the table of contents.'); this._installKey('t', label, this._ontoc); @@ -385,6 +388,20 @@ JX.install('DiffChangesetList', { this._warnUser(pht('You must select a comment to mark done.')); }, + _onkeytogglefile: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'file') { + cursor.changeset.toggleVisibility(); + return; + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a file to hide or show.')); + }, + _onkeyhide: function() { var cursor = this._cursorItem; @@ -616,14 +633,10 @@ JX.install('DiffChangesetList', { var visible_item = new JX.PHUIXActionView() .setHandler(function(e) { - var diff = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff}); e.prevent(); menu.close(); + + changeset.toggleVisibility(); }); list.addItem(visible_item); diff --git a/webroot/rsrc/js/application/differential/behavior-toggle-files.js b/webroot/rsrc/js/application/differential/behavior-toggle-files.js deleted file mode 100644 index 296b6f91b2..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-toggle-files.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @provides javelin-behavior-differential-toggle-files - * @requires javelin-behavior - * javelin-dom - * javelin-stratcom - * phabricator-phtize - */ - -JX.behavior('differential-toggle-files', function(config) { - var pht = JX.phtize(config.pht); - - JX.Stratcom.listen( - 'differential-toggle-file', - null, - function(e) { - if (e.getData().diff.length != 1) { - return; - } - - var diff = e.getData().diff[0], - data = JX.Stratcom.getData(diff); - if (data.hidden) { - data.hidden = false; - JX.DOM.show(diff); - JX.DOM.remove(data.undo); - data.undo = null; - } else { - data.hidden = true; - data.undo = render_collapse_undo(); - JX.DOM.hide(diff); - JX.DOM.listen( - data.undo, - 'click', - 'differential-collapse-undo', - function(e) { - e.kill(); - data.hidden = false; - JX.DOM.show(diff); - JX.DOM.remove(data.undo); - data.undo = null; - }); - JX.DOM.appendContent(diff.parentNode, data.undo); - } - JX.Stratcom.invoke('differential-toggle-file-toggled'); - }); - - JX.Stratcom.listen( - 'differential-toggle-file-request', - null, - function(e) { - var elt = e.getData().element; - while (elt !== document.body) { - if (JX.Stratcom.hasSigil(elt, 'differential-changeset')) { - var diffs = JX.DOM.scry(elt, 'table', 'differential-diff'); - var invoked = false; - for (var i = 0; i < diffs.length; ++i) { - if (JX.Stratcom.getData(diffs[i]).hidden) { - JX.Stratcom.invoke('differential-toggle-file', null, { - diff: [ diffs[i] ] - }); - invoked = true; - } - } - if (!invoked) { - e.prevent(); - } - return; - } - elt = elt.parentNode; - } - e.prevent(); - }); - - JX.Stratcom.listen( - 'click', - 'tag:a', - function(e) { - var link = e.getNode('tag:a'); - var id = link.getAttribute('href'); - if (!id || !id.match(/^#.+/)) { - return; - } - var raw = e.getRawEvent(); - if (raw.altKey || raw.ctrlKey || raw.metaKey || raw.shiftKey) { - return; - } - // The target may have either a matching name or a matching id. - var target; - try { - target = JX.$(id.substr(1)); - } catch(err) { - var named = document.getElementsByName(id.substr(1)); - for (var i = 0; i < named.length; ++i) { - if (named[i].tagName.toLowerCase() == 'a') { - if (target) { - return; - } - target = named[i]; - } - } - if (!target) { - return; - } - } - var event = JX.Stratcom.invoke('differential-toggle-file-request', null, { - element: target - }); - if (!event.getPrevented()) { - // This event is processed after the hash has changed, so it doesn't - // automatically jump there like we want. - JX.DOM.scrollTo(target); - } - }); - - var render_collapse_undo = function() { - var link = JX.$N( - 'a', - {href: '#', sigil: 'differential-collapse-undo'}, - pht('undo')); - - return JX.$N( - 'div', - {className: 'differential-collapse-undo', - sigil: 'differential-collapse-undo-div'}, - [pht('collapsed'), ' ', link]); - }; - -}); From fb9f3cc0b4cae3decd115da2a773978d43b26e4c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 May 2017 14:49:26 -0700 Subject: [PATCH 119/543] Restore the "buoyant" header in Differential Summary: Fixes T1591. This was removed long ago because it was a mess to implement and caused a bunch of weird issues, and also my tolerance for dealing with weird JS issues was much, much lower. I have now survived the fires of JX.Scrollbar and would love to address 200 small nitpicks about obscure browser behaviors on Linux, so open the floodgates again. A secondary goal here is to create room to add a global view state menu on the right, with 300 options like "hide all inlines", "hide done inlines", "hide collapsed inlines", "hide ghosts", "show ghosts", "enable filetree", "disable filetree", etc, etc. Not sure how much of this I'll actually do. I have one more experiment I want to try first. Test Plan: {F4963294} Reviewers: chad Reviewed By: chad Maniphest Tasks: T1591 Differential Revision: https://secure.phabricator.com/D17945 --- resources/celerity/map.php | 82 +++++++++---------- .../view/DifferentialChangesetDetailView.php | 1 + .../differential/changeset-view.css | 18 ++++ webroot/rsrc/css/core/z-index.css | 4 + .../rsrc/js/application/diff/DiffChangeset.js | 14 ++++ .../js/application/diff/DiffChangesetList.js | 82 +++++++++++++++++++ .../rsrc/js/core/behavior-phabricator-nav.js | 11 +++ 7 files changed, 171 insertions(+), 41 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2f9026785d..d175534828 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,11 +9,11 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'a5a2d647', - 'core.pkg.js' => '0f87a6eb', + 'core.pkg.css' => '4937a7d7', + 'core.pkg.js' => 'a0c8fb20', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '697405d4', - 'differential.pkg.js' => '07c56ffc', + 'differential.pkg.css' => '52b014e7', + 'differential.pkg.js' => '1efe85bf', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '15be1064', + 'rsrc/css/application/differential/changeset-view.css' => 'e7bd2a79', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -116,7 +116,7 @@ return array( 'rsrc/css/core/core.css' => '9f4cb463', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', - 'rsrc/css/core/z-index.css' => '0233d039', + 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', @@ -390,8 +390,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '731125f3', - 'rsrc/js/application/diff/DiffChangesetList.js' => '59d1ceb1', + 'rsrc/js/application/diff/DiffChangeset.js' => '3268dd83', + 'rsrc/js/application/diff/DiffChangesetList.js' => '0a4f7809', 'rsrc/js/application/diff/DiffInline.js' => '3337c065', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', @@ -503,7 +503,7 @@ return array( 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', - 'rsrc/js/core/behavior-phabricator-nav.js' => '08163386', + 'rsrc/js/core/behavior-phabricator-nav.js' => '947753e0', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', @@ -565,7 +565,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '15be1064', + 'differential-changeset-view-css' => 'e7bd2a79', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -658,7 +658,7 @@ return array( 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', - 'javelin-behavior-phabricator-nav' => '08163386', + 'javelin-behavior-phabricator-nav' => '947753e0', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', @@ -775,8 +775,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '731125f3', - 'phabricator-diff-changeset-list' => '59d1ceb1', + 'phabricator-diff-changeset' => '3268dd83', + 'phabricator-diff-changeset-list' => '0a4f7809', 'phabricator-diff-inline' => '3337c065', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -816,7 +816,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '0233d039', + 'phabricator-zindex-css' => '9d8f7c4b', 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -946,16 +946,6 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), - '08163386' => array( - 'javelin-behavior', - 'javelin-behavior-device', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-magical-init', - 'javelin-vector', - 'javelin-request', - 'javelin-util', - ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -983,6 +973,9 @@ return array( 'javelin-dom', 'javelin-router', ), + '0a4f7809' => array( + 'javelin-install', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -999,9 +992,6 @@ return array( 'javelin-dom', 'javelin-history', ), - '15be1064' => array( - 'phui-inline-comment-view-css', - ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1122,6 +1112,17 @@ return array( 'javelin-dom', 'javelin-vector', ), + '3268dd83' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '327a00d1' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1349,9 +1350,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '59d1ceb1' => array( - 'javelin-install', - ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1450,17 +1448,6 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), - '731125f3' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '7319e029' => array( 'javelin-behavior', 'javelin-dom', @@ -1619,6 +1606,16 @@ return array( 'javelin-workflow', 'javelin-dom', ), + '947753e0' => array( + 'javelin-behavior', + 'javelin-behavior-device', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-magical-init', + 'javelin-vector', + 'javelin-request', + 'javelin-util', + ), '949c0fe5' => array( 'javelin-install', ), @@ -2127,6 +2124,9 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e7bd2a79' => array( + 'phui-inline-comment-view-css', + ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index 42be1c6444..86fba06c40 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -183,6 +183,7 @@ final class DifferentialChangesetDetailView extends AphrontView { 'autoload' => $this->getAutoload(), 'loaded' => $this->getLoaded(), 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), + 'path' => $display_filename, ), 'class' => $class, 'id' => $id, diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 0cec38ffdc..1931ce354b 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -386,3 +386,21 @@ tr.differential-inline-loading { .differential-review-stage { position: relative; } + +.diff-banner { + position: fixed; + top: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); + border-bottom: 1px solid {$lightgreyborder}; + padding: 12px 18px; + vertical-align: middle; + font-weight: bold; + font-size: {$biggerfontsize}; +} + +.diff-banner .phui-icon-view { + margin-right: 4px; +} diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index bdab2e97ed..75cd394988 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -93,6 +93,10 @@ div.phui-calendar-day-event { z-index: 6; } +.diff-banner { + z-index: 6; +} + .conpherence-durable-column { z-index: 7; } diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index c3359c2007..148be860b9 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -19,6 +19,7 @@ JX.install('DiffChangeset', { this._node = node; var data = this._getNodeData(); + this._renderURI = data.renderURI; this._ref = data.ref; this._whitespace = data.whitespace; @@ -30,6 +31,8 @@ JX.install('DiffChangeset', { this._leftID = data.left; this._rightID = data.right; + this._path = data.path; + this._inlines = []; }, @@ -58,6 +61,7 @@ JX.install('DiffChangeset', { _visible: true, _undoNode: null, + _path: null, getLeftChangesetID: function() { return this._leftID; @@ -227,6 +231,9 @@ JX.install('DiffChangeset', { JX.Router.getInstance().queue(routable); }, + getPath: function() { + return this._path; + }, /** * Receive a response to a context request. @@ -428,6 +435,13 @@ JX.install('DiffChangeset', { return JX.Stratcom.getData(this._node); }, + getVectors: function() { + return { + pos: JX.$V(this._node), + dim: JX.Vector.getDim(this._node) + }; + }, + _onresponse: function(sequence, response) { if (sequence != this._sequence) { // If this isn't the most recent request, ignore it. This normally diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index ff51cd4666..2798d0d1c6 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -51,6 +51,9 @@ JX.install('DiffChangesetList', { var onresize = JX.bind(this, this._ifawake, this._onresize); JX.Stratcom.listen('resize', null, onresize); + var onscroll = JX.bind(this, this._ifawake, this._onscroll); + JX.Stratcom.listen('scroll', null, onscroll); + var onselect = JX.bind(this, this._ifawake, this._onselect); JX.Stratcom.listen( 'mousedown', @@ -113,6 +116,8 @@ JX.install('DiffChangesetList', { _rangeOrigin: null, _rangeTarget: null, + _bannerNode: null, + sleep: function() { this._asleep = true; @@ -807,6 +812,12 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); this._redrawHover(); + + this._redrawBanner(); + }, + + _onscroll: function() { + this._redrawBanner(); }, _onselect: function(e) { @@ -1265,6 +1276,77 @@ JX.install('DiffChangesetList', { this._rangeTarget = null; this.resetHover(); + }, + + _redrawBanner: function() { + var node = this._getBannerNode(); + var changeset = this._getVisibleChangeset(); + + // Don't do anything if nothing has changed. This seems to avoid some + // flickering issues in Safari, at least. + if (this._bannerChangeset === changeset) { + return; + } + this._bannerChangeset = changeset; + + if (!changeset) { + JX.DOM.remove(node); + return; + } + + var icon = new JX.PHUIXIconView() + .setIcon('fa-file') + .getNode(); + JX.DOM.setContent(node, [icon, ' ', changeset.getPath()]); + + document.body.appendChild(node); + }, + + _getBannerNode: function() { + if (!this._bannerNode) { + var attributes = { + className: 'diff-banner', + id: 'diff-banner' + }; + + this._bannerNode = JX.$N('div', attributes); + } + + return this._bannerNode; + }, + + _getVisibleChangeset: function() { + if (this.isAsleep()) { + return null; + } + + if (JX.Device.getDevice() != 'desktop') { + return null; + } + + // Never show the banner if we're very near the top of the page. + var margin = 480; + var s = JX.Vector.getScroll(); + if (s.y < margin) { + return null; + } + + var v = JX.Vector.getViewport(); + for (var ii = 0; ii < this._changesets.length; ii++) { + var changeset = this._changesets[ii]; + var c = changeset.getVectors(); + + // If the changeset starts above the upper half of the screen... + if (c.pos.y < (s.y + (v.y / 2))) { + // ...and ends below the lower half of the screen, this is the + // current visible changeset. + if ((c.pos.y + c.dim.y) > (s.y + (v.y / 2))) { + return changeset; + } + } + } + + return null; } } diff --git a/webroot/rsrc/js/core/behavior-phabricator-nav.js b/webroot/rsrc/js/core/behavior-phabricator-nav.js index cd132550a8..e37680abf0 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/core/behavior-phabricator-nav.js @@ -130,8 +130,19 @@ JX.behavior('phabricator-nav', function(config) { return; } + // When the buoyant header is visible, move the menu down below it. This + // is a bit of a hack. + var banner_height = 0; + try { + var banner = JX.$('diff-banner'); + banner_height = JX.Vector.getDim(banner).y; + } catch (error) { + // Ignore if there's no banner on the page. + } + local.style.top = Math.max( 0, + banner_height, JX.$V(content).y - Math.max(0, JX.Vector.getScroll().y)) + 'px'; } From 1599c56217a1262d0e16b52553939e248a7f3529 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 18 May 2017 10:32:10 -0700 Subject: [PATCH 120/543] Add Pinboard Items to Timeline Summary: This allows adding of pinboard items to a timeline. I'm hoping we can get this in for Maniphest (Pholio, Cover Image) and Macro (because, Macro), but unsure how to scalably do this. Anyways, here's the front end. Test Plan: Make some fake timeline items in UIExamples, test mobile, tablet, and desktop breakpoints. {F4965798} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17950 --- resources/celerity/map.php | 6 +-- .../examples/PHUITimelineExample.php | 38 +++++++++++++------ src/view/phui/PHUITimelineEventView.php | 17 ++++++++- webroot/rsrc/css/phui/phui-timeline-view.css | 6 +++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d175534828..614824d9c6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '4937a7d7', + 'core.pkg.css' => '0ab752b2', 'core.pkg.js' => 'a0c8fb20', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '52b014e7', @@ -173,7 +173,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', - 'rsrc/css/phui/phui-timeline-view.css' => '1d7ef61d', + 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -880,7 +880,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => 'cc4fd402', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => '1d7ef61d', + 'phui-timeline-view-css' => '313c7f22', 'phui-two-column-view-css' => 'ce9fa0b7', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', diff --git a/src/applications/uiexample/examples/PHUITimelineExample.php b/src/applications/uiexample/examples/PHUITimelineExample.php index 9bedffd9eb..42c680ea7b 100644 --- a/src/applications/uiexample/examples/PHUITimelineExample.php +++ b/src/applications/uiexample/examples/PHUITimelineExample.php @@ -80,13 +80,35 @@ final class PHUITimelineExample extends PhabricatorUIExample { ->setTitle(pht('Minor Red Event')) ->setColor(PhabricatorTransactions::COLOR_RED); + // Pinboard!! + $pin1 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user0.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user0.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user0.png')) + ->setImageSize(280, 210); + + $pin2 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user1.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setImageSize(280, 210); + + $pin3 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user2.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user2.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setImageSize(280, 210); + $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) - ->setIcon('fa-check') - ->setTitle(pht('Historically Important Action')) - ->setColor(PhabricatorTransactions::COLOR_BLACK) - ->setReallyMajorEvent(true); - + ->setIcon('fa-camera-retro') + ->setTitle(pht('Pinboard Image Event')) + ->addPinboardItem($pin1) + ->addPinboardItem($pin2) + ->addPinboardItem($pin3); $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) @@ -102,12 +124,6 @@ final class PHUITimelineExample extends PhabricatorUIExample { ->appendChild(str_repeat(pht('Long Text Body').' ', 64)) ->setColor(PhabricatorTransactions::COLOR_ORANGE); - $events[] = id(new PHUITimelineEventView()) - ->setUserHandle($handle) - ->setTitle(str_repeat('LongTextEventNoSpaces', 1024)) - ->appendChild(str_repeat('LongTextNoSpaces', 1024)) - ->setColor(PhabricatorTransactions::COLOR_RED); - $colors = array( PhabricatorTransactions::COLOR_RED, PhabricatorTransactions::COLOR_ORANGE, diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index 142ea1e87d..51a0ea5aae 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -28,6 +28,7 @@ final class PHUITimelineEventView extends AphrontView { private $hideCommentOptions = false; private $authorPHID; private $badges = array(); + private $pinboardItems = array(); public function setAuthorPHID($author_phid) { $this->authorPHID = $author_phid; @@ -190,6 +191,11 @@ final class PHUITimelineEventView extends AphrontView { return $this->hideCommentOptions; } + public function addPinboardItem(PHUIPinboardItemView $item) { + $this->pinboardItems[] = $item; + return $this; + } + public function setToken($token, $removed = false) { $this->token = $token; $this->tokenRemoved = $removed; @@ -441,12 +447,21 @@ final class PHUITimelineEventView extends AphrontView { ), $content); + // Image Events + $pinboard = null; + if ($this->pinboardItems) { + $pinboard = new PHUIPinboardView(); + foreach ($this->pinboardItems as $item) { + $pinboard->addItem($item); + } + } + $content = phutil_tag( 'div', array( 'class' => implode(' ', $content_classes), ), - array($image, $badges, $wedge, $content)); + array($image, $badges, $wedge, $content, $pinboard)); $outer_classes = $this->classes; $outer_classes[] = 'phui-timeline-shell'; diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index c4f568411a..da6b5950c4 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -429,3 +429,9 @@ a.phui-timeline-menu .phui-icon-view { .phui-comment-preview-view { margin-bottom: 20px; } + +.phui-timeline-view .phui-pinboard-view { + margin: 8px 0 0 0; + padding: 0; + text-align: left; +} From 91eb22cb3a8ba3873a760b2036781e17190b7072 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 18 May 2017 11:03:31 -0700 Subject: [PATCH 121/543] Migrate Project slugs to modular transactions Test Plan: Unit tests all pass. Added/removed/altered some project hashtags and observed expected transactions in timeline. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17952 --- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectCoreTestCase.php | 12 +- .../conduit/ProjectCreateConduitAPIMethod.php | 3 +- .../PhabricatorProjectTransactionEditor.php | 85 +-------- .../engine/PhabricatorProjectEditEngine.php | 3 +- .../storage/PhabricatorProjectTransaction.php | 68 +------- .../PhabricatorProjectSlugsTransaction.php | 165 ++++++++++++++++++ 7 files changed, 181 insertions(+), 157 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cc4794d251..85120f9fe8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3666,6 +3666,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php', 'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php', 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', + 'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', @@ -9077,6 +9078,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSilenceController' => 'PhabricatorProjectController', 'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', + 'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectStandardCustomField' => array( 'PhabricatorProjectCustomField', 'PhabricatorStandardCustomFieldInterface', diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 0f97d88f1e..0bec2fe53c 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -362,7 +362,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($name)); $this->applyTransactions($project, $user, $xactions); @@ -386,7 +386,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { ->setNewValue($name2); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($name2)); $this->applyTransactions($project, $user, $xactions); @@ -416,7 +416,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input, $input)); $this->applyTransactions($project, $user, $xactions); @@ -448,7 +448,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input)); $this->applyTransactions($project, $user, $xactions); @@ -474,7 +474,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input)); $caught = null; @@ -605,7 +605,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($slug)); $this->applyTransactions($project, $user, $xactions); diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index edd2606a77..896cc9a07b 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -64,7 +64,8 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { if ($request->getValue('tags')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType( + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('tags')); } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 7a38c3bbdf..f5c4f9fab2 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; $types[] = PhabricatorProjectTransaction::TYPE_STATUS; $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; $types[] = PhabricatorProjectTransaction::TYPE_ICON; @@ -51,11 +50,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_SLUGS: - $slugs = $object->getSlugs(); - $slugs = mpull($slugs, 'getSlug', 'getSlug'); - unset($slugs[$object->getPrimarySlug()]); - return array_keys($slugs); case PhabricatorProjectTransaction::TYPE_STATUS: return $object->getStatus(); case PhabricatorProjectTransaction::TYPE_IMAGE: @@ -105,8 +99,6 @@ final class PhabricatorProjectTransactionEditor return null; } return $value; - case PhabricatorProjectTransaction::TYPE_SLUGS: - return $this->normalizeSlugs($xaction->getNewValue()); } return parent::getCustomTransactionNewValue($object, $xaction); @@ -117,8 +109,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_SLUGS: - return; case PhabricatorProjectTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); return; @@ -167,18 +157,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_SLUGS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - foreach ($add as $slug) { - $this->addSlug($object, $slug, true); - } - - $this->removeSlugs($object, $rem); - return; case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: @@ -278,65 +256,6 @@ final class PhabricatorProjectTransactionEditor $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case PhabricatorProjectTransaction::TYPE_SLUGS: - if (!$xactions) { - break; - } - - $slug_xaction = last($xactions); - - $new = $slug_xaction->getNewValue(); - - $invalid = array(); - foreach ($new as $slug) { - if (!PhabricatorSlug::isValidProjectSlug($slug)) { - $invalid[] = $slug; - } - } - - if ($invalid) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Hashtags must contain at least one letter or number. %s '. - 'project hashtag(s) are invalid: %s.', - phutil_count($invalid), - implode(', ', $invalid)), - $slug_xaction); - break; - } - - $new = $this->normalizeSlugs($new); - - if ($new) { - $slugs_used_already = id(new PhabricatorProjectSlug()) - ->loadAllWhere('slug IN (%Ls)', $new); - } else { - // The project doesn't have any extra slugs. - $slugs_used_already = array(); - } - - $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); - foreach ($slugs_used_already as $project_phid => $used_slugs) { - if ($project_phid == $object->getPHID()) { - continue; - } - - $used_slug_strs = mpull($used_slugs, 'getSlug'); - - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - '%s project hashtag(s) are already used by other projects: %s.', - phutil_count($used_slug_strs), - implode(', ', $used_slug_strs)), - $slug_xaction); - $errors[] = $error; - } - - break; case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: if (!$xactions) { @@ -674,7 +593,7 @@ final class PhabricatorProjectTransactionEditor ->save(); } - private function removeSlugs(PhabricatorProject $project, array $slugs) { + public function removeSlugs(PhabricatorProject $project, array $slugs) { if (!$slugs) { return; } @@ -696,7 +615,7 @@ final class PhabricatorProjectTransactionEditor } } - private function normalizeSlugs(array $slugs) { + public function normalizeSlugs(array $slugs) { foreach ($slugs as $key => $slug) { $slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug); } diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 8f855074d6..b0e3384d4a 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -262,7 +262,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorStringListEditField()) ->setKey('slugs') ->setLabel(pht('Additional Hashtags')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType( + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setDescription(pht('Additional project slugs.')) ->setConduitDescription(pht('Change project slugs.')) ->setConduitTypeDescription(pht('New list of slugs.')) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 0ad6230a5e..2abd7a19ed 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_SLUGS = 'project:slugs'; const TYPE_STATUS = 'project:status'; const TYPE_IMAGE = 'project:image'; const TYPE_ICON = 'project:icon'; @@ -134,8 +133,6 @@ final class PhabricatorProjectTransaction return 'fa-photo'; case self::TYPE_MEMBERS: return 'fa-user'; - case self::TYPE_SLUGS: - return 'fa-tag'; } return parent::getIcon(); } @@ -212,33 +209,6 @@ final class PhabricatorProjectTransaction } break; - case self::TYPE_SLUGS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed project hashtag(s), added %d: %s; removed %d: %s.', - $author_handle, - count($add), - $this->renderSlugList($add), - count($rem), - $this->renderSlugList($rem)); - } else if ($add) { - return pht( - '%s added %d project hashtag(s): %s.', - $author_handle, - count($add), - $this->renderSlugList($add)); - } else if ($rem) { - return pht( - '%s removed %d project hashtag(s): %s.', - $author_handle, - count($rem), - $this->renderSlugList($rem)); - } - break; - case self::TYPE_MEMBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -380,36 +350,6 @@ final class PhabricatorProjectTransaction $author_handle, $object_handle); } - - case self::TYPE_SLUGS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', - $author_handle, - $object_handle, - count($add), - $this->renderSlugList($add), - count($rem), - $this->renderSlugList($rem)); - } else if ($add) { - return pht( - '%s added %d %s hashtag(s): %s.', - $author_handle, - count($add), - $object_handle, - $this->renderSlugList($add)); - } else if ($rem) { - return pht( - '%s removed %d %s hashtag(s): %s.', - $author_handle, - count($rem), - $object_handle, - $this->renderSlugList($rem)); - } - } return parent::getTitleForFeed(); @@ -418,8 +358,8 @@ final class PhabricatorProjectTransaction public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_NAME: - case self::TYPE_SLUGS: + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: + case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: case self::TYPE_IMAGE: case self::TYPE_ICON: case self::TYPE_COLOR: @@ -447,8 +387,4 @@ final class PhabricatorProjectTransaction return $tags; } - private function renderSlugList($slugs) { - return implode(', ', $slugs); - } - } diff --git a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php new file mode 100644 index 0000000000..a9105e5ba3 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php @@ -0,0 +1,165 @@ +getSlugs(); + $slugs = mpull($slugs, 'getSlug', 'getSlug'); + unset($slugs[$object->getPrimarySlug()]); + return array_keys($slugs); + } + + public function generateNewValue($object, $value) { + return $this->getEditor()->normalizeSlugs($value); + } + + public function applyInternalEffects($object, $value) { + return; + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + $new = $value; + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + foreach ($add as $slug) { + $this->getEditor()->addSlug($object, $slug, true); + } + + $this->getEditor()->removeSlugs($object, $rem); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s changed project hashtag(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderSlugList($add), + count($rem), + $this->renderSlugList($rem)); + } else if ($add) { + return pht( + '%s added %d project hashtag(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderSlugList($add)); + } else if ($rem) { + return pht( + '%s removed %d project hashtag(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderSlugList($rem)); + } + break; + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + $this->renderObject(), + count($add), + $this->renderSlugList($add), + count($rem), + $this->renderSlugList($rem)); + } else if ($add) { + return pht( + '%s added %d %s hashtag(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderObject(), + $this->renderSlugList($add)); + } else if ($rem) { + return pht( + '%s removed %d %s hashtag(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderObject(), + $this->renderSlugList($rem)); + } + } + + public function getIcon() { + return 'fa-tag'; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $slug_xaction = last($xactions); + + $new = $slug_xaction->getNewValue(); + + $invalid = array(); + foreach ($new as $slug) { + if (!PhabricatorSlug::isValidProjectSlug($slug)) { + $invalid[] = $slug; + } + } + + if ($invalid) { + $errors[] = $this->newInvalidError( + pht( + 'Hashtags must contain at least one letter or number. %s '. + 'project hashtag(s) are invalid: %s.', + phutil_count($invalid), + implode(', ', $invalid))); + + return $errors; + } + + $new = $this->getEditor()->normalizeSlugs($new); + + if ($new) { + $slugs_used_already = id(new PhabricatorProjectSlug()) + ->loadAllWhere('slug IN (%Ls)', $new); + } else { + // The project doesn't have any extra slugs. + $slugs_used_already = array(); + } + + $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); + foreach ($slugs_used_already as $project_phid => $used_slugs) { + if ($project_phid == $object->getPHID()) { + continue; + } + + $used_slug_strs = mpull($used_slugs, 'getSlug'); + + $errors[] = $this->newInvalidError( + pht( + '%s project hashtag(s) are already used by other projects: %s.', + phutil_count($used_slug_strs), + implode(', ', $used_slug_strs))); + } + + return $errors; + } + + private function renderSlugList($slugs) { + return implode(', ', $slugs); + } + +} From f92059d84c6a37f4aed038f3fd237cb04ae10d8e Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 18 May 2017 11:24:59 -0700 Subject: [PATCH 122/543] Migrate Project status to modular transactions Test Plan: Unit tests pass. Archived/activated some projects a couple times; observed expected transactions on timeline. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17953 --- .../sql/patches/20131020.pxactionmig.php | 2 +- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectArchiveController.php | 3 +- ...PhabricatorProjectColumnHideController.php | 3 +- .../PhabricatorProjectTransactionEditor.php | 10 +-- .../PhabricatorProjectTestDataGenerator.php | 2 +- .../storage/PhabricatorProjectTransaction.php | 49 +------------- .../PhabricatorProjectStatusTransaction.php | 66 +++++++++++++++++++ 8 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectStatusTransaction.php diff --git a/resources/sql/patches/20131020.pxactionmig.php b/resources/sql/patches/20131020.pxactionmig.php index a6d305b77a..9a7b82f3e8 100644 --- a/resources/sql/patches/20131020.pxactionmig.php +++ b/resources/sql/patches/20131020.pxactionmig.php @@ -34,7 +34,7 @@ foreach ($rows as $row) { $type_map = array( 'name' => PhabricatorProjectNameTransaction::TRANSACTIONTYPE, 'members' => PhabricatorProjectTransaction::TYPE_MEMBERS, - 'status' => PhabricatorProjectTransaction::TYPE_STATUS, + 'status' => PhabricatorProjectStatusTransaction::TRANSACTIONTYPE, 'canview' => PhabricatorTransactions::TYPE_VIEW_POLICY, 'canedit' => PhabricatorTransactions::TYPE_EDIT_POLICY, 'canjoin' => PhabricatorTransactions::TYPE_JOIN_POLICY, diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 85120f9fe8..c3427d2f0a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3669,6 +3669,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', + 'PhabricatorProjectStatusTransaction' => 'applications/project/xaction/PhabricatorProjectStatusTransaction.php', 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php', @@ -9084,6 +9085,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', + 'PhabricatorProjectStatusTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', diff --git a/src/applications/project/controller/PhabricatorProjectArchiveController.php b/src/applications/project/controller/PhabricatorProjectArchiveController.php index ee974bf8e8..b97d5391ab 100644 --- a/src/applications/project/controller/PhabricatorProjectArchiveController.php +++ b/src/applications/project/controller/PhabricatorProjectArchiveController.php @@ -32,7 +32,8 @@ final class PhabricatorProjectArchiveController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectColumnHideController.php b/src/applications/project/controller/PhabricatorProjectColumnHideController.php index 27dbb17c47..1dd5e47ecb 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnHideController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnHideController.php @@ -65,7 +65,8 @@ final class PhabricatorProjectColumnHideController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index f5c4f9fab2..0a38867d71 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_STATUS; $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; $types[] = PhabricatorProjectTransaction::TYPE_ICON; $types[] = PhabricatorProjectTransaction::TYPE_COLOR; @@ -50,8 +49,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_STATUS: - return $object->getStatus(); case PhabricatorProjectTransaction::TYPE_IMAGE: return $object->getProfileImagePHID(); case PhabricatorProjectTransaction::TYPE_ICON: @@ -81,7 +78,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: @@ -109,9 +105,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_IMAGE: $object->setProfileImagePHID($xaction->getNewValue()); return; @@ -157,7 +150,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: @@ -343,7 +335,7 @@ final class PhabricatorProjectTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: - case PhabricatorProjectTransaction::TYPE_STATUS: + case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: diff --git a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php index 8032749f94..e29ee13f2d 100644 --- a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php +++ b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php @@ -20,7 +20,7 @@ final class PhabricatorProjectTestDataGenerator $this->newProjectTitle()); $xactions[] = $this->newTransaction( - PhabricatorProjectTransaction::TYPE_STATUS, + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE, $this->newProjectStatus()); // Almost always make the author a member. diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 2abd7a19ed..e61ff26277 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_STATUS = 'project:status'; const TYPE_IMAGE = 'project:image'; const TYPE_ICON = 'project:icon'; const TYPE_COLOR = 'project:color'; @@ -55,22 +54,6 @@ final class PhabricatorProjectTransaction return array_merge($req_phids, parent::getRequiredHandlePHIDs()); } - public function getColor() { - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return 'red'; - } else { - return 'green'; - } - } - return parent::getColor(); - } - public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_EDGE: @@ -115,12 +98,6 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return 'fa-ban'; - } else { - return 'fa-check'; - } case self::TYPE_LOCKED: if ($new) { return 'fa-lock'; @@ -149,18 +126,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_STATUS: - if ($old == 0) { - return pht( - '%s archived this project.', - $author_handle); - } else { - return pht( - '%s activated this project.', - $author_handle); - } - break; - case self::TYPE_IMAGE: // TODO: Some day, it would be nice to show the images. if (!$old) { @@ -288,18 +253,6 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return pht( - '%s archived %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s activated %s.', - $author_handle, - $object_handle); - } case self::TYPE_IMAGE: // TODO: Some day, it would be nice to show the images. if (!$old) { @@ -378,7 +331,7 @@ final class PhabricatorProjectTransaction $tags[] = self::MAILTAG_OTHER; } break; - case self::TYPE_STATUS: + case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: case self::TYPE_LOCKED: default: $tags[] = self::MAILTAG_OTHER; diff --git a/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php b/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php new file mode 100644 index 0000000000..e5c8ead4f4 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php @@ -0,0 +1,66 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if ($old == 0) { + return pht( + '%s archived this project.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this project.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old == 0) { + return pht( + '%s archived %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s activated %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getColor() { + $old = $this->getOldValue(); + + if ($old == 0) { + return 'red'; + } else { + return 'green'; + } + } + + public function getIcon() { + $old = $this->getOldValue(); + + if ($old == 0) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} From eb84bf98a413cd578f987aba2ddd2f08630932c6 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 18 May 2017 15:14:02 -0700 Subject: [PATCH 123/543] Migrate Project image to modular transactions Summary: I'm not sure you can actually remove a project's image (maybe via the API?), but I kept the code for rendering the relevant title/feed anyway. Test Plan: Unit tests + adding/changing project pictures. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D17954 --- src/__phutil_library_map__.php | 2 + .../PhabricatorFileComposeController.php | 3 +- ...habricatorProjectEditPictureController.php | 3 +- .../PhabricatorProjectTransactionEditor.php | 26 +--- .../storage/PhabricatorProjectTransaction.php | 50 +------ .../PhabricatorProjectImageTransaction.php | 136 ++++++++++++++++++ .../PhabricatorProjectSlugsTransaction.php | 1 - 7 files changed, 144 insertions(+), 77 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectImageTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c3427d2f0a..d43d756426 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3621,6 +3621,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php', + 'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', @@ -9035,6 +9036,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', 'PhabricatorProjectLockController' => 'PhabricatorProjectController', diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 930ec61483..7afd99330a 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -48,7 +48,8 @@ final class PhabricatorFileComposeController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setTransactionType( + PhabricatorProjectImageTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); $editor = id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 4352ec6ad8..9d0c16efb9 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -78,7 +78,8 @@ final class PhabricatorProjectEditPictureController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setTransactionType( + PhabricatorProjectImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 0a38867d71..101020970a 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; $types[] = PhabricatorProjectTransaction::TYPE_ICON; $types[] = PhabricatorProjectTransaction::TYPE_COLOR; $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; @@ -49,8 +48,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: - return $object->getProfileImagePHID(); case PhabricatorProjectTransaction::TYPE_ICON: return $object->getIcon(); case PhabricatorProjectTransaction::TYPE_COLOR: @@ -78,7 +75,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: @@ -105,9 +101,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: - $object->setProfileImagePHID($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_ICON: $object->setIcon($xaction->getNewValue()); return; @@ -150,7 +143,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: @@ -336,7 +328,7 @@ final class PhabricatorProjectTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: - case PhabricatorProjectTransaction::TYPE_IMAGE: + case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: PhabricatorPolicyFilter::requireCapability( @@ -475,22 +467,6 @@ final class PhabricatorProjectTransactionEditor return true; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: - $new = $xaction->getNewValue(); - if ($new) { - return array($new); - } - break; - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index e61ff26277..d99cf4c5ce 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_IMAGE = 'project:image'; const TYPE_ICON = 'project:icon'; const TYPE_COLOR = 'project:color'; const TYPE_LOCKED = 'project:locked'; @@ -45,10 +44,6 @@ final class PhabricatorProjectTransaction $rem = array_diff($old, $new); $req_phids = array_merge($add, $rem); break; - case self::TYPE_IMAGE: - $req_phids[] = $old; - $req_phids[] = $new; - break; } return array_merge($req_phids, parent::getRequiredHandlePHIDs()); @@ -106,8 +101,6 @@ final class PhabricatorProjectTransaction } case self::TYPE_ICON: return PhabricatorProjectIconSet::getIconIcon($new); - case self::TYPE_IMAGE: - return 'fa-photo'; case self::TYPE_MEMBERS: return 'fa-user'; } @@ -126,26 +119,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_IMAGE: - // TODO: Some day, it would be nice to show the images. - if (!$old) { - return pht( - "%s set this project's image to %s.", - $author_handle, - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this project's image.", - $author_handle); - } else { - return pht( - "%s updated this project's image from %s to %s.", - $author_handle, - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - case self::TYPE_ICON: $set = new PhabricatorProjectIconSet(); @@ -253,27 +226,6 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_IMAGE: - // TODO: Some day, it would be nice to show the images. - if (!$old) { - return pht( - '%s set the image for %s to %s.', - $author_handle, - $object_handle, - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - '%s removed the image for %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s updated the image for %s from %s to %s.', - $author_handle, - $object_handle, - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } case self::TYPE_ICON: $set = new PhabricatorProjectIconSet(); @@ -313,7 +265,7 @@ final class PhabricatorProjectTransaction switch ($this->getTransactionType()) { case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE: + case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: case self::TYPE_ICON: case self::TYPE_COLOR: $tags[] = self::MAILTAG_METADATA; diff --git a/src/applications/project/xaction/PhabricatorProjectImageTransaction.php b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php new file mode 100644 index 0000000000..f6d10f0961 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php @@ -0,0 +1,136 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + // TODO: Some day, it would be nice to show the images. + if (!$old) { + return pht( + "%s set this project's image to %s.", + $this->renderAuthor(), + $this->renderNewHandle()); + } else if (!$new) { + return pht( + "%s removed this project's image.", + $this->renderAuthor()); + } else { + return pht( + "%s updated this project's image from %s to %s.", + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + // TODO: Some day, it would be nice to show the images. + if (!$old) { + return pht( + '%s set the image for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewHandle()); + } else if (!$new) { + return pht( + '%s removed the image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the image for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function getIcon() { + return 'fa-photo'; + } + + public function extractFilePHIDs($object, $value) { + if ($value) { + return array($value); + } + return array(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + // Only validate if file was uploaded + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + } + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php index a9105e5ba3..d442c39a76 100644 --- a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php +++ b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php @@ -61,7 +61,6 @@ final class PhabricatorProjectSlugsTransaction count($rem), $this->renderSlugList($rem)); } - break; } public function getTitleForFeed() { From 1bff5309e667a8cf4a60f52492e68aa844b293b9 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 18 May 2017 16:15:24 -0700 Subject: [PATCH 124/543] Migrate Project icons to modular transactions Test Plan: Unit tests pass. Changed some icons, observed expected timeline entries. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17956 --- src/__phutil_library_map__.php | 2 + .../conduit/ProjectCreateConduitAPIMethod.php | 2 +- .../PhabricatorProjectTransactionEditor.php | 10 +---- .../engine/PhabricatorProjectEditEngine.php | 4 +- .../storage/PhabricatorProjectTransaction.php | 24 +---------- .../PhabricatorProjectIconTransaction.php | 42 +++++++++++++++++++ 6 files changed, 49 insertions(+), 35 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectIconTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d43d756426..2516fd72c6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3620,6 +3620,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php', 'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', + 'PhabricatorProjectIconTransaction' => 'applications/project/xaction/PhabricatorProjectIconTransaction.php', 'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php', 'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', @@ -9035,6 +9036,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', + 'PhabricatorProjectIconTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectListController' => 'PhabricatorProjectController', diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index 896cc9a07b..407512de9a 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -52,7 +52,7 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { if ($request->getValue('icon')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('icon')); } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 101020970a..915807445a 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_ICON; $types[] = PhabricatorProjectTransaction::TYPE_COLOR; $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; $types[] = PhabricatorProjectTransaction::TYPE_PARENT; @@ -48,8 +47,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_ICON: - return $object->getIcon(); case PhabricatorProjectTransaction::TYPE_COLOR: return $object->getColor(); case PhabricatorProjectTransaction::TYPE_LOCKED: @@ -75,7 +72,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: @@ -101,9 +97,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_ICON: - $object->setIcon($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_COLOR: $object->setColor($xaction->getNewValue()); return; @@ -143,7 +136,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: @@ -329,7 +321,7 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: - case PhabricatorProjectTransaction::TYPE_ICON: + case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: case PhabricatorProjectTransaction::TYPE_COLOR: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index b0e3384d4a..a4a97654cd 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -112,7 +112,7 @@ final class PhabricatorProjectEditEngine PhabricatorTransactions::TYPE_VIEW_POLICY, PhabricatorTransactions::TYPE_EDIT_POLICY, PhabricatorTransactions::TYPE_JOIN_POLICY, - PhabricatorProjectTransaction::TYPE_ICON, + PhabricatorProjectIconTransaction::TRANSACTIONTYPE, PhabricatorProjectTransaction::TYPE_COLOR, ); $unavailable = array_fuse($unavailable); @@ -244,7 +244,7 @@ final class PhabricatorProjectEditEngine id(new PhabricatorIconSetEditField()) ->setKey('icon') ->setLabel(pht('Icon')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setIconSet(new PhabricatorProjectIconSet()) ->setDescription(pht('Project icon.')) ->setConduitDescription(pht('Change the project icon.')) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index d99cf4c5ce..9f707c3e94 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_ICON = 'project:icon'; const TYPE_COLOR = 'project:color'; const TYPE_LOCKED = 'project:locked'; const TYPE_PARENT = 'project:parent'; @@ -99,8 +98,6 @@ final class PhabricatorProjectTransaction } else { return 'fa-unlock'; } - case self::TYPE_ICON: - return PhabricatorProjectIconSet::getIconIcon($new); case self::TYPE_MEMBERS: return 'fa-user'; } @@ -119,15 +116,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_ICON: - $set = new PhabricatorProjectIconSet(); - - return pht( - "%s set this project's icon to %s.", - $author_handle, - $set->getIconLabel($new)); - break; - case self::TYPE_COLOR: return pht( "%s set this project's color to %s.", @@ -226,16 +214,6 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - - case self::TYPE_ICON: - $set = new PhabricatorProjectIconSet(); - - return pht( - '%s set the icon for %s to %s.', - $author_handle, - $object_handle, - $set->getIconLabel($new)); - case self::TYPE_COLOR: return pht( '%s set the color for %s to %s.', @@ -266,7 +244,7 @@ final class PhabricatorProjectTransaction case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: - case self::TYPE_ICON: + case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: case self::TYPE_COLOR: $tags[] = self::MAILTAG_METADATA; break; diff --git a/src/applications/project/xaction/PhabricatorProjectIconTransaction.php b/src/applications/project/xaction/PhabricatorProjectIconTransaction.php new file mode 100644 index 0000000000..932ce4bdc4 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectIconTransaction.php @@ -0,0 +1,42 @@ +getIcon(); + } + + public function applyInternalEffects($object, $value) { + $object->setIcon($value); + } + + public function getTitle() { + $set = new PhabricatorProjectIconSet(); + $new = $this->getNewValue(); + + return pht( + "%s set this project's icon to %s.", + $this->renderAuthor(), + $this->renderValue($set->getIconLabel($new))); + } + + public function getTitleForFeed() { + $set = new PhabricatorProjectIconSet(); + $new = $this->getNewValue(); + + return pht( + '%s set the icon for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($set->getIconLabel($new))); + } + + public function getIcon() { + $new = $this->getNewValue(); + return PhabricatorProjectIconSet::getIconIcon($new); + } + +} From 7e46d7ab6a6be474b7dd90f9e71ee3bd6ae66fbc Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 18 May 2017 16:32:04 -0700 Subject: [PATCH 125/543] Migrate Project color to modular transactions Test Plan: Unit tests + changing project colors. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17958 --- src/__phutil_library_map__.php | 2 ++ .../conduit/ProjectCreateConduitAPIMethod.php | 6 ++-- .../PhabricatorProjectTransactionEditor.php | 10 +----- .../engine/PhabricatorProjectEditEngine.php | 5 +-- .../storage/PhabricatorProjectTransaction.php | 17 +--------- .../PhabricatorProjectColorTransaction.php | 33 +++++++++++++++++++ 6 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectColorTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2516fd72c6..cbf324c847 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3584,6 +3584,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', + 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', 'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', @@ -8987,6 +8988,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectCardView' => 'AphrontTagView', + 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index 407512de9a..cbaa006951 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -52,13 +52,15 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { if ($request->getValue('icon')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE) + ->setTransactionType( + PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('icon')); } if ($request->getValue('color')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setTransactionType( + PhabricatorProjectColorTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('color')); } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 915807445a..2f86afc1ac 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_COLOR; $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; $types[] = PhabricatorProjectTransaction::TYPE_PARENT; $types[] = PhabricatorProjectTransaction::TYPE_MILESTONE; @@ -47,8 +46,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_COLOR: - return $object->getColor(); case PhabricatorProjectTransaction::TYPE_LOCKED: return (int)$object->getIsMembershipLocked(); case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: @@ -72,7 +69,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: @@ -97,9 +93,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_COLOR: - $object->setColor($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_LOCKED: $object->setIsMembershipLocked($xaction->getNewValue()); return; @@ -136,7 +129,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: @@ -322,7 +314,7 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: - case PhabricatorProjectTransaction::TYPE_COLOR: + case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index a4a97654cd..cc377bed44 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -113,7 +113,7 @@ final class PhabricatorProjectEditEngine PhabricatorTransactions::TYPE_EDIT_POLICY, PhabricatorTransactions::TYPE_JOIN_POLICY, PhabricatorProjectIconTransaction::TRANSACTIONTYPE, - PhabricatorProjectTransaction::TYPE_COLOR, + PhabricatorProjectColorTransaction::TRANSACTIONTYPE, ); $unavailable = array_fuse($unavailable); @@ -253,7 +253,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorSelectEditField()) ->setKey('color') ->setLabel(pht('Color')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setTransactionType( + PhabricatorProjectColorTransaction::TRANSACTIONTYPE) ->setOptions(PhabricatorProjectIconSet::getColorMap()) ->setDescription(pht('Project tag color.')) ->setConduitDescription(pht('Change the project tag color.')) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 9f707c3e94..99686a91d1 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_COLOR = 'project:color'; const TYPE_LOCKED = 'project:locked'; const TYPE_PARENT = 'project:parent'; const TYPE_MILESTONE = 'project:milestone'; @@ -116,13 +115,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_COLOR: - return pht( - "%s set this project's color to %s.", - $author_handle, - PHUITagView::getShadeName($new)); - break; - case self::TYPE_LOCKED: if ($new) { return pht( @@ -214,13 +206,6 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_COLOR: - return pht( - '%s set the color for %s to %s.', - $author_handle, - $object_handle, - PHUITagView::getShadeName($new)); - case self::TYPE_LOCKED: if ($new) { return pht( @@ -245,7 +230,7 @@ final class PhabricatorProjectTransaction case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: - case self::TYPE_COLOR: + case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_METADATA; break; case PhabricatorTransactions::TYPE_EDGE: diff --git a/src/applications/project/xaction/PhabricatorProjectColorTransaction.php b/src/applications/project/xaction/PhabricatorProjectColorTransaction.php new file mode 100644 index 0000000000..9f36179b12 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectColorTransaction.php @@ -0,0 +1,33 @@ +getColor(); + } + + public function applyInternalEffects($object, $value) { + $object->setColor($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + return pht( + "%s set this project's color to %s.", + $this->renderAuthor(), + $this->renderValue(PHUITagView::getShadeName($new))); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + return pht( + '%s set the color for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue(PHUITagView::getShadeName($new))); + } + +} From 93e28da76eb2f9bdebb734e624545dc0a1b890ee Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 17:54:53 +0000 Subject: [PATCH 126/543] Add more "disabled" UI to PHUIObjectItemView Summary: Brings more UI tweaks to disabled objects, like projects/people. Also fixes a missing icon in projects. Test Plan: Application search with people and projects that have disabled results. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17962 --- resources/celerity/map.php | 6 +++--- .../people/query/PhabricatorPeopleSearchEngine.php | 1 + .../project/view/PhabricatorProjectListView.php | 2 +- .../rsrc/css/phui/object-item/phui-oi-list-view.css | 11 +++++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 614824d9c6..9cc404312d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '0ab752b2', + 'core.pkg.css' => 'd6dc3994', 'core.pkg.js' => 'a0c8fb20', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '52b014e7', @@ -131,7 +131,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '7c8ec27a', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '412bef1a', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -869,7 +869,7 @@ return array( 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '7c8ec27a', + 'phui-oi-list-view-css' => '412bef1a', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index e800a8ee1a..fa363c4428 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -265,6 +265,7 @@ final class PhabricatorPeopleSearchEngine if ($user->getIsDisabled()) { $item->addIcon('fa-ban', pht('Disabled')); + $item->setDisabled(true); } if (!$is_approval) { diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index d1d0792855..7496a401bb 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -68,7 +68,7 @@ final class PhabricatorProjectListView extends AphrontView { )); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { - $item->addIcon('delete-grey', pht('Archived')); + $item->addIcon('fa-ban', pht('Archived')); $item->setDisabled(true); } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 2f29001f9a..26b0781e8d 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -390,6 +390,17 @@ ul.phui-oi-icons { text-decoration: line-through; } +.phui-oi.phui-oi-disabled .phui-oi-image { + opacity: .8; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} + +.phui-oi.phui-oi-disabled .phui-oi-attribute, +.phui-oi.phui-oi-disabled .phui-oi-attribute .phui-icon-view { + color: {$lightgreytext}; +} + /* - Effects ------------------------------------------------------------------- From d783299a19c536d79bc4e73a5b1f6c1942ad2d05 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 17:57:38 +0000 Subject: [PATCH 127/543] Fix Phriction status not set property on new document Summary: I deleted too many lines of code here and TYPE_MOVE was always being applied when CONTENT was set. This should fix on next document save, but should I write some migration tool anyways? Test Plan: Create a new document, see document with correct status. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17960 --- .../phriction/editor/PhrictionTransactionEditor.php | 4 +++- .../phriction/xaction/PhrictionDocumentTitleTransaction.php | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index cbe6e426d0..1f97dd3c54 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -164,6 +164,8 @@ final class PhrictionTransactionEditor switch ($xaction->getTransactionType()) { case PhrictionTransaction::TYPE_CONTENT: + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + return; case PhrictionTransaction::TYPE_MOVE_AWAY: $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); return; @@ -241,10 +243,10 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: case PhrictionTransaction::TYPE_MOVE_AWAY: - case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $save_content = true; break; default: diff --git a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php index 730b062f34..4f1ba850a7 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php @@ -14,6 +14,9 @@ final class PhrictionDocumentTitleTransaction public function applyInternalEffects($object, $value) { $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { $this->getEditor()->getNewContent()->setTitle($value); } From 6c46f27d98c48e1f57109677acaea34dab243516 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 18 May 2017 11:54:58 -0700 Subject: [PATCH 128/543] Add quest objectives to the minimap Summary: Add important objectives (like waygates and quest markers) to the minimap. This also probably fixes @cspeckmim's bug with the {key @} keyboard shortcut. Test Plan: (This is probably easier to undestand if you `arc patch` + click around.) {F4966037} Reviewers: chad, amckinley Reviewed By: chad Subscribers: cspeckmim Differential Revision: https://secure.phabricator.com/D17955 --- resources/celerity/map.php | 116 +++++++++------ resources/celerity/packages.php | 3 + .../view/DifferentialChangesetDetailView.php | 2 + .../view/PHUIDiffInlineCommentDetailView.php | 11 ++ .../differential/changeset-view.css | 29 ++++ .../rsrc/js/application/diff/DiffChangeset.js | 68 ++++++++- .../js/application/diff/DiffChangesetList.js | 29 +++- .../rsrc/js/application/diff/DiffInline.js | 78 +++++++++- .../js/application/diff/ScrollObjective.js | 121 +++++++++++++++ .../application/diff/ScrollObjectiveList.js | 139 ++++++++++++++++++ .../differential/behavior-comment-preview.js | 2 + .../behavior-show-older-transactions.js | 1 + 12 files changed, 542 insertions(+), 57 deletions(-) create mode 100644 webroot/rsrc/js/application/diff/ScrollObjective.js create mode 100644 webroot/rsrc/js/application/diff/ScrollObjectiveList.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9cc404312d..5c1839de41 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => 'd6dc3994', - 'core.pkg.js' => 'a0c8fb20', + 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '52b014e7', - 'differential.pkg.js' => '1efe85bf', + 'differential.pkg.css' => 'deae6388', + 'differential.pkg.js' => 'dedee9c8', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'e7bd2a79', + 'rsrc/css/application/differential/changeset-view.css' => '6b79bdf3', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -390,11 +390,13 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '3268dd83', - 'rsrc/js/application/diff/DiffChangesetList.js' => '0a4f7809', - 'rsrc/js/application/diff/DiffInline.js' => '3337c065', + 'rsrc/js/application/diff/DiffChangeset.js' => '20580ec0', + 'rsrc/js/application/diff/DiffChangesetList.js' => '61086d73', + 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', + 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', + 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', + 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', @@ -446,7 +448,7 @@ return array( 'rsrc/js/application/transactions/behavior-comment-actions.js' => '9a6dd75c', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', - 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72', + 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'ae95d984', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', @@ -565,7 +567,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'e7bd2a79', + 'differential-changeset-view-css' => '6b79bdf3', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -617,7 +619,7 @@ return array( 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-feedback-preview' => 'b064af76', + 'javelin-behavior-differential-feedback-preview' => '51c5ad07', 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', @@ -665,7 +667,7 @@ return array( 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', - 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', + 'javelin-behavior-phabricator-show-older-transactions' => 'ae95d984', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', @@ -775,9 +777,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '3268dd83', - 'phabricator-diff-changeset-list' => '0a4f7809', - 'phabricator-diff-inline' => '3337c065', + 'phabricator-diff-changeset' => '20580ec0', + 'phabricator-diff-changeset-list' => '61086d73', + 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -797,6 +799,8 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', + 'phabricator-scroll-objective' => '0eee7a00', + 'phabricator-scroll-objective-list' => '1ca4d9db', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -973,8 +977,12 @@ return array( 'javelin-dom', 'javelin-router', ), - '0a4f7809' => array( + '0eee7a00' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', 'javelin-install', + 'javelin-workflow', ), '0f764c35' => array( 'javelin-install', @@ -1026,6 +1034,14 @@ return array( 'javelin-request', 'javelin-uri', ), + '1ca4d9db' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'phabricator-scroll-objective', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1054,6 +1070,17 @@ return array( 'javelin-install', 'javelin-dom', ), + '20580ec0' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '21df4ff5' => array( 'javelin-install', 'javelin-workboard-card', @@ -1112,26 +1139,12 @@ return array( 'javelin-dom', 'javelin-vector', ), - '3268dd83' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '327a00d1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', ), - '3337c065' => array( - 'javelin-dom', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1295,6 +1308,14 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + '51c5ad07' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-request', + 'javelin-util', + 'phabricator-shaped-request', + ), '522431f7' => array( 'javelin-behavior', 'javelin-util', @@ -1386,6 +1407,10 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '61086d73' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', @@ -1417,6 +1442,9 @@ return array( 'javelin-util', 'javelin-magical-init', ), + '6b79bdf3' => array( + 'phui-inline-comment-view-css', + ), '6b8ef10b' => array( 'javelin-install', ), @@ -1469,6 +1497,9 @@ return array( 'javelin-reactor', 'javelin-util', ), + '77e14b60' => array( + 'javelin-dom', + ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', @@ -1629,12 +1660,6 @@ return array( 'javelin-resource', 'javelin-routable', ), - '94c65b72' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-busy', - ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', @@ -1776,20 +1801,18 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), + 'ae95d984' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-busy', + ), 'b003d4fb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phuix-dropdown-menu', ), - 'b064af76' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-request', - 'javelin-util', - 'phabricator-shaped-request', - ), 'b1f0ccee' => array( 'javelin-install', 'javelin-dom', @@ -2124,9 +2147,6 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), - 'e7bd2a79' => array( - 'phui-inline-comment-view-css', - ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2412,6 +2432,8 @@ return array( 'javelin-behavior-load-blame', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', + 'phabricator-scroll-objective', + 'phabricator-scroll-objective-list', 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index e756e696cb..afa6c456a6 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -203,6 +203,9 @@ return array( 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', + 'phabricator-scroll-objective', + 'phabricator-scroll-objective-list', + 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index 86fba06c40..123fd71f99 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -184,6 +184,8 @@ final class DifferentialChangesetDetailView extends AphrontView { 'loaded' => $this->getLoaded(), 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), 'path' => $display_filename, + 'objectiveName' => basename($display_filename), + 'icon' => 'fa-file-text-o', ), 'class' => $class, 'id' => $id, diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 91f27f0e2b..9fc1e1bded 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -99,6 +99,14 @@ final class PHUIDiffInlineCommentDetailView 'differential-inline-comment', ); + $is_fixed = false; + switch ($inline->getFixedState()) { + case PhabricatorInlineCommentInterface::STATE_DONE: + case PhabricatorInlineCommentInterface::STATE_DRAFT: + $is_fixed = true; + break; + } + $metadata = array( 'id' => $inline->getID(), 'phid' => $inline->getPHID(), @@ -109,6 +117,9 @@ final class PHUIDiffInlineCommentDetailView 'on_right' => $this->getIsOnRight(), 'original' => $inline->getContent(), 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), + 'isDraft' => $inline->isDraft(), + 'isFixed' => $is_fixed, + 'isGhost' => $inline->getIsGhost(), ); $sigil = 'differential-inline-comment'; diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 1931ce354b..3d4ff2eb48 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -404,3 +404,32 @@ tr.differential-inline-loading { .diff-banner .phui-icon-view { margin-right: 4px; } + +.scroll-objective-list { + position: fixed; + right: 0; + width: 24px; + top: 48px; + bottom: 48px; + z-index: 6; + background: rgba(255, 255, 255, 0.50); + border-style: solid; + border-color: rgba(255, 255, 255, 0.95); + border-width: 1px 0 1px 1px; + box-shadow: -1px 0 2px rgba(255, 255, 255, 0.25); + overflow: hidden; +} + +.scroll-objective { + display: block; + position: absolute; + box-sizing: border-box; + cursor: pointer; + left: 8px; +} + +.scroll-objective .phui-icon-view { + text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.25); + display: block; + height: 14px; +} diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 148be860b9..28e19b6276 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -32,14 +32,12 @@ JX.install('DiffChangeset', { this._rightID = data.right; this._path = data.path; + this._objectiveName = data.objectiveName; + this._icon = data.icon; this._inlines = []; }, - properties: { - changesetList: null - }, - members: { _node: null, _loaded: false, @@ -63,6 +61,11 @@ JX.install('DiffChangeset', { _undoNode: null, _path: null, + _changesetList: null, + _objective: null, + _objectiveName: null, + _icon: null, + getLeftChangesetID: function() { return this._leftID; }, @@ -71,6 +74,49 @@ JX.install('DiffChangeset', { return this._rightID; }, + setChangesetList: function(list) { + this._changesetList = list; + + var objectives = list.getObjectives(); + this._objective = objectives.newObjective() + .setAnchor(this._node); + + this._updateObjective(); + + return this; + }, + + _updateObjective: function() { + this._objective + .setIcon(this.getIcon()) + .setColor(this.getColor()) + .setTooltip(this.getObjectiveName()); + }, + + getIcon: function() { + if (!this._visible) { + return 'fa-file-o'; + } + + return this._icon; + }, + + getColor: function() { + if (!this._visible) { + return 'grey'; + } + + return 'blue'; + }, + + getObjectiveName: function() { + return this._objectiveName; + }, + + getChangesetList: function() { + return this._changesetList; + }, + /** * Has the content of this changeset been loaded? * @@ -523,6 +569,11 @@ JX.install('DiffChangeset', { } JX.Stratcom.invoke('differential-inline-comment-refresh'); + + this._objective.show(); + this._rebuildAllInlines(); + + JX.Stratcom.invoke('resize'); }, _getContentFrame: function() { @@ -672,9 +723,18 @@ JX.install('DiffChangeset', { JX.DOM.appendContent(diff.parentNode, undo); } + this._updateObjective(); + for (var ii = 0; ii < this._inlines.length; ii++) { + this._inlines[ii].updateObjective(); + } + JX.Stratcom.invoke('resize'); }, + isVisible: function() { + return this._visible; + }, + _getUndoNode: function() { if (!this._undoNode) { var pht = this.getChangesetList().getTranslations(); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 2798d0d1c6..f8ca3b9a56 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1,6 +1,7 @@ /** * @provides phabricator-diff-changeset-list * @requires javelin-install + * phabricator-scroll-objective-list * @javelin */ @@ -8,6 +9,7 @@ JX.install('DiffChangesetList', { construct: function() { this._changesets = []; + this._objectives = new JX.ScrollObjectiveList(); var onload = JX.bind(this, this._ifawake, this._onload); JX.Stratcom.listen('click', 'differential-load', onload); @@ -100,6 +102,7 @@ JX.install('DiffChangesetList', { _initialized: false, _asleep: true, _changesets: null, + _objectives: null, _cursorItem: null, @@ -124,6 +127,8 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); this.resetHover(); + + this._objectives.hide(); }, wake: function() { @@ -132,6 +137,8 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); + this._objectives.show(); + if (this._initialized) { return; } @@ -192,6 +199,10 @@ JX.install('DiffChangesetList', { return this._asleep; }, + getObjectives: function() { + return this._objectives; + }, + newChangesetForNode: function(node) { var changeset = JX.DiffChangeset.getForNode(node); @@ -273,6 +284,18 @@ JX.install('DiffChangesetList', { manager.scrollTo(toc); }, + getSelectedInline: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + return cursor.target; + } + } + + return null; + }, + _onkeyreply: function(is_quote) { var cursor = this._cursorItem; @@ -772,7 +795,7 @@ JX.install('DiffChangesetList', { } else if (diffs.length == 1) { var diff = diffs[0]; visible_item.setDisabled(false); - if (JX.Stratcom.getData(diff).hidden) { + if (!changeset.isVisible()) { visible_item .setName(pht('Expand File')) .setIcon('fa-expand'); @@ -836,6 +859,10 @@ JX.install('DiffChangesetList', { // event. e.kill(); + this.selectInline(inline); + }, + + selectInline: function(inline) { var selection = this._getSelectionState(); var item; diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index b46bc00b4f..e66fb544c5 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -9,10 +9,6 @@ JX.install('DiffInline', { construct : function() { }, - properties: { - changeset: null - }, - members: { _id: null, _phid: null, @@ -31,12 +27,18 @@ JX.install('DiffInline', { _isInvisible: false, _isLoading: false, + _changeset: null, + _objective: null, + + _isDraft: null, + _isFixed: null, + bindToRow: function(row) { this._row = row; + this._objective.setAnchor(this._row); var row_data = JX.Stratcom.getData(row); row_data.inline = this; - this._hidden = row_data.hidden || false; // TODO: Get smarter about this once we do more editing, this is pretty @@ -65,6 +67,13 @@ JX.install('DiffInline', { this._replyToCommentPHID = data.replyToCommentPHID; + this._isDraft = data.isDraft; + this._isFixed = data.isFixed; + this._isGhost = data.isGhost; + + this._changesetID = data.changesetID; + + this.updateObjective(); this.setInvisible(false); return this; @@ -152,6 +161,60 @@ JX.install('DiffInline', { return this; }, + setChangeset: function(changeset) { + this._changeset = changeset; + + var objectives = changeset.getChangesetList().getObjectives(); + this._objective = objectives.newObjective() + .setCallback(JX.bind(this, this._onobjective)); + this.updateObjective(); + + return this; + }, + + getChangeset: function() { + return this._changeset; + }, + + _onobjective: function() { + this.getChangeset().getChangesetList().selectInline(this); + }, + + updateObjective: function() { + var objective = this._objective; + + if (this.isHidden() || this._isDeleted) { + objective.hide(); + return; + } + + var changeset = this.getChangeset(); + if (!changeset.isVisible()) { + objective.hide(); + return; + } + + var icon = 'fa-comment'; + var color = 'bluegrey'; + + if (this._isDraft) { + // This inline is an unsubmitted draft. + icon = 'fa-pencil'; + } else if (this._isFixed) { + // This inline has been marked done. + icon = 'fa-check'; + color = 'grey'; + } else if (this._isGhost) { + icon = 'fa-comment-o'; + color = 'grey'; + } + + objective + .setIcon(icon) + .setColor(color) + .show(); + }, + canReply: function() { if (!this._hasAction('reply')) { return false; @@ -202,6 +265,7 @@ JX.install('DiffInline', { JX.Stratcom.getData(row).inline = this; this._row = row; + this._objective.setAnchor(this._row); this._id = null; this._phid = null; @@ -272,6 +336,8 @@ JX.install('DiffInline', { // top-level "draft" state of unsubmitted comments. JX.DOM.alterClass(comment, 'inline-state-is-draft', response.draftState); + this._isFixed = response.isChecked; + this._didUpdate(); }, @@ -633,6 +699,8 @@ JX.install('DiffInline', { this.getChangeset().getChangesetList().redrawPreview(); } + this.updateObjective(); + this.getChangeset().getChangesetList().redrawCursor(); this.getChangeset().getChangesetList().resetHover(); diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js new file mode 100644 index 0000000000..4e6fa55d6a --- /dev/null +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -0,0 +1,121 @@ +/** + * @provides phabricator-scroll-objective + * @requires javelin-dom + * javelin-util + * javelin-stratcom + * javelin-install + * javelin-workflow + * @javelin + */ + + +JX.install('ScrollObjective', { + + construct : function() { + var node = this.getNode(); + + var onclick = JX.bind(this, this._onclick); + JX.DOM.listen(node, 'click', null, onclick); + }, + + members: { + _list: null, + + _node: null, + _anchor: null, + + _visible: false, + _callback: false, + + getNode: function() { + if (!this._node) { + var attributes = { + className: 'scroll-objective' + }; + + var content = this._getIconObject().getNode(); + + var node = JX.$N('div', attributes, content); + + this._node = node; + } + + return this._node; + }, + + setCallback: function(callback) { + this._callback = callback; + return this; + }, + + setObjectiveList: function(list) { + this._list = list; + return this; + }, + + _getIconObject: function() { + if (!this._iconObject) { + this._iconObject = new JX.PHUIXIconView(); + } + return this._iconObject; + }, + + _onclick: function(e) { + (this._callback && this._callback(e)); + + if (e.getPrevented()) { + return; + } + + e.kill(); + + // This is magic to account for the banner, and should probably be made + // less hard-coded. + var buffer = 48; + + JX.DOM.scrollToPosition(null, JX.$V(this.getAnchor()).y - buffer); + }, + + setAnchor: function(node) { + this._anchor = node; + return this; + }, + + getAnchor: function() { + return this._anchor; + }, + + setIcon: function(icon) { + this._getIconObject().setIcon(icon); + return this; + }, + + setColor: function(color) { + this._getIconObject().setColor(color); + return this; + }, + + setTooltip: function(tip) { + var node = this._getIconObject().getNode(); + JX.Stratcom.addSigil(node, 'has-tooltip'); + JX.Stratcom.getData(node).tip = tip; + JX.Stratcom.getData(node).align = 'W'; + return this; + }, + + show: function() { + this._visible = true; + return this; + }, + + hide: function() { + this._visible = false; + }, + + isVisible: function() { + return this._visible; + } + + } + +}); diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js new file mode 100644 index 0000000000..f5beb751e6 --- /dev/null +++ b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js @@ -0,0 +1,139 @@ +/** + * @provides phabricator-scroll-objective-list + * @requires javelin-dom + * javelin-util + * javelin-stratcom + * javelin-install + * javelin-workflow + * phabricator-scroll-objective + * @javelin + */ + + +JX.install('ScrollObjectiveList', { + + construct : function() { + this._objectives = []; + + var onresize = JX.bind(this, this._dirty); + JX.Stratcom.listen('resize', null, onresize); + }, + + members: { + _objectives: null, + _visible: false, + _trigger: null, + + newObjective: function() { + var objective = new JX.ScrollObjective() + .setObjectiveList(this); + + this._objectives.push(objective); + this._getNode().appendChild(objective.getNode()); + + this._dirty(); + + return objective; + }, + + show: function() { + this._visible = true; + this._dirty(); + return this; + }, + + hide: function() { + this._visible = false; + this._dirty(); + return this; + }, + + _getNode: function() { + if (!this._node) { + var node = new JX.$N('div', {className: 'scroll-objective-list'}); + this._node = node; + } + return this._node; + }, + + _dirty: function() { + if (this._trigger !== null) { + return; + } + + this._trigger = setTimeout(JX.bind(this, this._redraw), 0); + }, + + _redraw: function() { + this._trigger = null; + + var node = this._getNode(); + + var is_visible = + (this._visible) && + (JX.Device.getDevice() == 'desktop') && + (this._objectives.length); + + if (!is_visible) { + JX.DOM.remove(node); + return; + } + + document.body.appendChild(node); + + var d = JX.Vector.getDocument(); + + var list_dimensions = JX.Vector.getDim(node); + var icon_height = 16; + var list_y = (list_dimensions.y - icon_height); + + var ii; + var offset; + + // First, build a list of all the items we're going to show. + var items = []; + for (ii = 0; ii < this._objectives.length; ii++) { + var objective = this._objectives[ii]; + var objective_node = objective.getNode(); + + var anchor = objective.getAnchor(); + if (!anchor || !objective.isVisible()) { + JX.DOM.remove(objective_node); + continue; + } + + offset = (JX.$V(anchor).y / d.y) * (list_y); + + items.push({ + offset: offset, + node: objective_node + }); + } + + // Now, sort it from top to bottom. + items.sort(function(u, v) { + return u.offset - v.offset; + }); + + // Lay out the items in the objective list, leaving a minimum amount + // of space between them so they do not overlap. + var min = null; + for (ii = 0; ii < items.length; ii++) { + var item = items[ii]; + + offset = item.offset; + + if (min !== null) { + offset = Math.max(offset, min); + } + min = offset + 15; + + item.node.style.top = offset + 'px'; + node.appendChild(item.node); + } + + } + + } + +}); diff --git a/webroot/rsrc/js/application/differential/behavior-comment-preview.js b/webroot/rsrc/js/application/differential/behavior-comment-preview.js index 283e6ea12c..beb9f9a5d9 100644 --- a/webroot/rsrc/js/application/differential/behavior-comment-preview.js +++ b/webroot/rsrc/js/application/differential/behavior-comment-preview.js @@ -74,6 +74,8 @@ JX.behavior('differential-feedback-preview', function(config) { }); updateLinks(); + + JX.Stratcom.invoke('resize'); }) .setTimeout(5000) .send(); diff --git a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js index b4dcd2975b..ab4bf768d8 100644 --- a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js +++ b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js @@ -60,6 +60,7 @@ JX.behavior('phabricator-show-older-transactions', function(config) { var show_older = function(swap, r) { JX.DOM.replace(swap, JX.$H(r.timeline).getFragment()); + JX.Stratcom.invoke('resize'); }; var load_hidden_hash_callback = function(swap, r) { From fdf00f6df475ff33268ee245fc1517a138999df2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 18 May 2017 16:31:16 -0700 Subject: [PATCH 129/543] Clean up some minor UI behaviors in Differential Summary: Minor UI tweaks: - Use the dynamic icon for each file (e.g., image, text), not a hard-coded icon. - Render the path (less important) in grey and the filename (more important) in black. Test Plan: {F4966176} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17957 --- resources/celerity/map.php | 58 +++++++++---------- .../view/DifferentialChangesetDetailView.php | 24 +++++++- .../differential/changeset-view.css | 5 +- webroot/rsrc/css/core/z-index.css | 4 ++ .../rsrc/js/application/diff/DiffChangeset.js | 8 +-- .../js/application/diff/DiffChangesetList.js | 4 +- 6 files changed, 65 insertions(+), 38 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5c1839de41..e50d8bbd1a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,11 +9,11 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd6dc3994', + 'core.pkg.css' => '5ffe8b79', 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => 'deae6388', - 'differential.pkg.js' => 'dedee9c8', + 'differential.pkg.css' => '4d7dd14e', + 'differential.pkg.js' => '68a4fa60', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '6b79bdf3', + 'rsrc/css/application/differential/changeset-view.css' => '54774a28', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -116,7 +116,7 @@ return array( 'rsrc/css/core/core.css' => '9f4cb463', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', - 'rsrc/css/core/z-index.css' => '9d8f7c4b', + 'rsrc/css/core/z-index.css' => '998f3ce1', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', @@ -390,8 +390,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '20580ec0', - 'rsrc/js/application/diff/DiffChangesetList.js' => '61086d73', + 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', + 'rsrc/js/application/diff/DiffChangesetList.js' => '5c68c40c', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', @@ -567,7 +567,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '6b79bdf3', + 'differential-changeset-view-css' => '54774a28', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -777,8 +777,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '20580ec0', - 'phabricator-diff-changeset-list' => '61086d73', + 'phabricator-diff-changeset' => 'cf4e2140', + 'phabricator-diff-changeset-list' => '5c68c40c', 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -820,7 +820,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '9d8f7c4b', + 'phabricator-zindex-css' => '998f3ce1', 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -1070,17 +1070,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '20580ec0' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '21df4ff5' => array( 'javelin-install', 'javelin-workboard-card', @@ -1327,6 +1316,9 @@ return array( '5294060f' => array( 'phui-theme-css', ), + '54774a28' => array( + 'phui-inline-comment-view-css', + ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1376,6 +1368,10 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '5c68c40c' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '5e2634b9' => array( 'javelin-behavior', 'javelin-aphlict', @@ -1407,10 +1403,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '61086d73' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', @@ -1442,9 +1434,6 @@ return array( 'javelin-util', 'javelin-magical-init', ), - '6b79bdf3' => array( - 'phui-inline-comment-view-css', - ), '6b8ef10b' => array( 'javelin-install', ), @@ -2013,6 +2002,17 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'cf4e2140' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index 123fd71f99..e80dc20ec7 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -168,6 +168,26 @@ final class DifferentialChangesetDetailView extends AphrontView { $right_id = $changeset_id; } + // In the persistent banner, emphasize the current filename. + $path_part = dirname($display_filename); + $file_part = basename($display_filename); + $display_parts = array(); + if (strlen($path_part)) { + $path_part = $path_part.'/'; + $display_parts[] = phutil_tag( + 'span', + array( + 'class' => 'diff-banner-path', + ), + $path_part); + } + $display_parts[] = phutil_tag( + 'span', + array( + 'class' => 'diff-banner-file', + ), + $file_part); + return javelin_tag( 'div', array( @@ -183,9 +203,9 @@ final class DifferentialChangesetDetailView extends AphrontView { 'autoload' => $this->getAutoload(), 'loaded' => $this->getLoaded(), 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), - 'path' => $display_filename, + 'displayPath' => hsprintf('%s', $display_parts), 'objectiveName' => basename($display_filename), - 'icon' => 'fa-file-text-o', + 'icon' => $display_icon, ), 'class' => $class, 'id' => $id, diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 3d4ff2eb48..eee0e167f3 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -405,13 +405,16 @@ tr.differential-inline-loading { margin-right: 4px; } +.diff-banner-path { + color: {$greytext}; +} + .scroll-objective-list { position: fixed; right: 0; width: 24px; top: 48px; bottom: 48px; - z-index: 6; background: rgba(255, 255, 255, 0.50); border-style: solid; border-color: rgba(255, 255, 255, 0.95); diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 75cd394988..04b013386d 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -97,6 +97,10 @@ div.phui-calendar-day-event { z-index: 6; } +.scroll-objective-list { + z-index: 6; +} + .conpherence-durable-column { z-index: 7; } diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 28e19b6276..cd7f888a40 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -31,7 +31,7 @@ JX.install('DiffChangeset', { this._leftID = data.left; this._rightID = data.right; - this._path = data.path; + this._displayPath = JX.$H(data.displayPath); this._objectiveName = data.objectiveName; this._icon = data.icon; @@ -59,7 +59,7 @@ JX.install('DiffChangeset', { _visible: true, _undoNode: null, - _path: null, + _displayPath: null, _changesetList: null, _objective: null, @@ -277,8 +277,8 @@ JX.install('DiffChangeset', { JX.Router.getInstance().queue(routable); }, - getPath: function() { - return this._path; + getDisplayPath: function() { + return this._displayPath; }, /** diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index f8ca3b9a56..e256372852 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1322,9 +1322,9 @@ JX.install('DiffChangesetList', { } var icon = new JX.PHUIXIconView() - .setIcon('fa-file') + .setIcon(changeset.getIcon()) .getNode(); - JX.DOM.setContent(node, [icon, ' ', changeset.getPath()]); + JX.DOM.setContent(node, [icon, ' ', changeset.getDisplayPath()]); document.body.appendChild(node); }, From c9889e3d55b1ee5d5bf0cc401470fb5fa7771cc3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 19 May 2017 12:41:46 -0700 Subject: [PATCH 130/543] Fix an issue in Phriction where moving a document just copied it instead Summary: Ref T12732. See D17918. With modular transactions, `getCustomTransactionNewValue()` isn't actually called. Test Plan: Moved document `/x/` to `/y/`, saw document gone at `/x/` instead of copied. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17963 --- .../phriction/editor/PhrictionTransactionEditor.php | 10 +++++----- .../xaction/PhrictionDocumentMoveToTransaction.php | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 1f97dd3c54..9ab91d5db9 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -69,6 +69,11 @@ final class PhrictionTransactionEditor return $this->processContentVersionError; } + public function setMoveAwayDocument(PhrictionDocument $document) { + $this->moveAwayDocument = $document; + return $this; + } + public function getEditorApplicationClass() { return 'PhabricatorPhrictionApplication'; } @@ -116,11 +121,6 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: return $xaction->getNewValue(); - case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - $document = $xaction->getNewValue(); - // grab the real object now for the sub-editor to come - $this->moveAwayDocument = $document; - return; case PhrictionTransaction::TYPE_MOVE_AWAY: $document = $xaction->getNewValue(); $dict = array( diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php index a513b8fe36..a95e70ebdb 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php @@ -17,12 +17,15 @@ final class PhrictionDocumentMoveToTransaction 'content' => $document->getContent()->getContent(), 'title' => $document->getContent()->getTitle(), ); + + $editor = $this->getEditor(); + $editor->setMoveAwayDocument($document); + return $dict; } public function applyInternalEffects($object, $value) { $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - $this->getEditor()->getNewContent()->setTitle($value); } public function applyExternalEffects($object, $value) { From 601622013d371d489f72de76117efc24fc570169 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 13:29:54 -0700 Subject: [PATCH 131/543] Clarify milestone/subproject creation language Summary: Ref T12732 Test Plan: Read new language Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17961 --- .../controller/PhabricatorProjectSubprojectsController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index f516f87e32..269e80a080 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -183,7 +183,7 @@ final class PhabricatorProjectSubprojectsController 'have their own milestones.'); } else { if (!$milestones) { - $note = pht('You can create milestones for this project.'); + $note = pht('Milestones can be created for this project.'); } else { $note = pht('This project has milestones.'); } @@ -199,7 +199,7 @@ final class PhabricatorProjectSubprojectsController 'subprojects.'); } else { if (!$subprojects) { - $note = pht('You can create subprojects for this project.'); + $note = pht('Subprojects can be created for this project.'); } else { $note = pht('This project has subprojects.'); } From 5a34b299e4133f8e68acb762b5a04e1271e1c301 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 13:41:21 -0700 Subject: [PATCH 132/543] Update Maniphest title language Summary: Ref T12732 Test Plan: Read Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17964 --- .../maniphest/xaction/ManiphestTaskTitleTransaction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php index dcaf959a57..5a4be8faf8 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -36,7 +36,7 @@ final class ManiphestTaskTitleTransaction } return pht( - '%s changed the title from %s to %s.', + '%s renamed the title from %s to %s.', $this->renderAuthor(), $this->renderOldValue(), $this->renderNewValue()); @@ -53,7 +53,7 @@ final class ManiphestTaskTitleTransaction } return pht( - '%s changed %s title from %s to %s.', + '%s renamed %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), $this->renderOldValue(), From abd791889c3f62db7add11ebad9209179f5959ac Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 13:43:45 -0700 Subject: [PATCH 133/543] Update Maniphest title transaction again Summary: Ref T12732, third time is the charm? Test Plan: Read Reviewers: epriestley Subscribers: Korvin Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17965 --- .../maniphest/xaction/ManiphestTaskTitleTransaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php index 5a4be8faf8..5fade77d07 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -36,7 +36,7 @@ final class ManiphestTaskTitleTransaction } return pht( - '%s renamed the title from %s to %s.', + '%s renamed this task from %s to %s.', $this->renderAuthor(), $this->renderOldValue(), $this->renderNewValue()); From 10b38792320830e0b34f6fb59ad8054e2b154db1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 19 May 2017 13:45:06 -0700 Subject: [PATCH 134/543] Make Project slug/hashtag transactions render a little more nicely Summary: Ref T12732. Use `renderValue()` to build `renderValueList()` so we get nice fancy text for these. Test Plan: {F4967410} Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17966 --- .../PhabricatorProjectSlugsTransaction.php | 30 ++++++++++++------- .../PhabricatorModularTransactionType.php | 13 ++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php index d442c39a76..e1f22b3746 100644 --- a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php +++ b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php @@ -40,26 +40,29 @@ final class PhabricatorProjectSlugsTransaction $add = array_diff($new, $old); $rem = array_diff($old, $new); + $add = $this->renderHashtags($add); + $rem = $this->renderHashtags($rem); + if ($add && $rem) { return pht( '%s changed project hashtag(s), added %d: %s; removed %d: %s.', $this->renderAuthor(), count($add), - $this->renderSlugList($add), + $this->renderValueList($add), count($rem), - $this->renderSlugList($rem)); + $this->renderValueList($rem)); } else if ($add) { return pht( '%s added %d project hashtag(s): %s.', $this->renderAuthor(), count($add), - $this->renderSlugList($add)); + $this->renderValueList($add)); } else if ($rem) { return pht( '%s removed %d project hashtag(s): %s.', $this->renderAuthor(), count($rem), - $this->renderSlugList($rem)); + $this->renderValueList($rem)); } } @@ -70,29 +73,32 @@ final class PhabricatorProjectSlugsTransaction $add = array_diff($new, $old); $rem = array_diff($old, $new); + $add = $this->renderHashtags($add); + $rem = $this->renderHashtags($rem); + if ($add && $rem) { return pht( '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', $this->renderAuthor(), $this->renderObject(), count($add), - $this->renderSlugList($add), + $this->renderValueList($add), count($rem), - $this->renderSlugList($rem)); + $this->renderValueList($rem)); } else if ($add) { return pht( '%s added %d %s hashtag(s): %s.', $this->renderAuthor(), count($add), $this->renderObject(), - $this->renderSlugList($add)); + $this->renderValueList($add)); } else if ($rem) { return pht( '%s removed %d %s hashtag(s): %s.', $this->renderAuthor(), count($rem), $this->renderObject(), - $this->renderSlugList($rem)); + $this->renderValueList($rem)); } } @@ -157,8 +163,12 @@ final class PhabricatorProjectSlugsTransaction return $errors; } - private function renderSlugList($slugs) { - return implode(', ', $slugs); + private function renderHashtags(array $tags) { + $result = array(); + foreach ($tags as $tag) { + $result[] = '#'.$tag; + } + return $result; } } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 8a56e8e8ce..128b5c7c19 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -208,6 +208,19 @@ abstract class PhabricatorModularTransactionType $value); } + final protected function renderValueList(array $values) { + $result = array(); + foreach ($values as $value) { + $result[] = $this->renderValue($value); + } + + if ($this->isTextMode()) { + return implode(', ', $result); + } + + return phutil_implode_html(', ', $result); + } + final protected function renderOldValue() { return $this->renderValue($this->getOldValue()); } From 789d57522b039b1ca755af7e5dc4e591d92e7277 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 19 May 2017 13:52:26 -0700 Subject: [PATCH 135/543] Make editing project images redirect to "Manage" more consistently Summary: Ref T12732. Currently, different ways of setting a profile image can leave you in different places. Instead, always send the user back to the "Manage" page. Test Plan: Used "Current Picture", "use picture", "Build picture" and "upload picture", always ended up in the same spot. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17967 --- .../controller/PhabricatorProjectEditPictureController.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 9d0c16efb9..95d3bbd855 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -23,8 +23,7 @@ final class PhabricatorProjectEditPictureController $this->setProject($project); - $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); - $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); + $manage_uri = $this->getApplicationURI('manage/'.$project->getID().'/'); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; @@ -90,7 +89,7 @@ final class PhabricatorProjectEditPictureController $editor->applyTransactions($project, $xactions); - return id(new AphrontRedirectResponse())->setURI($edit_uri); + return id(new AphrontRedirectResponse())->setURI($manage_uri); } } @@ -243,7 +242,7 @@ final class PhabricatorProjectEditPictureController pht('Supported formats: %s', implode(', ', $supported_formats)))) ->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton($edit_uri) + ->addCancelButton($manage_uri) ->setValue(pht('Upload Picture'))); $form_box = id(new PHUIObjectBoxView()) From bbc5f792274ca98bc639cef662c45431c427eaa3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 19 May 2017 14:08:55 -0700 Subject: [PATCH 136/543] Make membership lock/unlock feed stories read more naturally Summary: Ref T12732. This is pre-existing. Test Plan: {F4967438} Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17969 --- .../project/storage/PhabricatorProjectTransaction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 99686a91d1..b0d442ae57 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -209,12 +209,12 @@ final class PhabricatorProjectTransaction case self::TYPE_LOCKED: if ($new) { return pht( - '%s locked %s membership.', + '%s locked membership for %s.', $author_handle, $object_handle); } else { return pht( - '%s unlocked %s membership.', + '%s unlocked membership for %s.', $author_handle, $object_handle); } From c6a7bcfe89287c2be2baa290def7c2f4e365396d Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 19 May 2017 14:11:54 -0700 Subject: [PATCH 137/543] Make Pholio description behave as a remarkup field (e.g., subscribe mentioned users) Summary: Ref T12732. This is pre-existing but fix it since I caught it while banging around. Test Plan: {F4967442} Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12732 Differential Revision: https://secure.phabricator.com/D17970 --- .../xaction/PholioMockDescriptionTransaction.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php index ed14d43c99..75293168e1 100644 --- a/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php +++ b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php @@ -44,4 +44,14 @@ final class PholioMockDescriptionTransaction ->setNewText($this->getNewValue()); } + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + } From 1644b45050579d323d5337f452e6f564b676b1cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 18 May 2017 21:50:27 -0700 Subject: [PATCH 138/543] Disperse task subpriorities in blocks Summary: Ref T7664. The current algorithm for moving task subpriorities can end up stuck in a real sticky swamp in some unusual situations. Instead, use an algorithm which works like this: - When we notice two tasks are too close together, look at the area around those tasks (just a few paces). - If things look pretty empty, we can just spread the tasks out a little bit. - But, if things are still real crowded, take another look further. - Keep doing that until we're looking at a real nice big spot which doesn't have too many tasks in it in total, even if they're all in one place right now. - Then, move 'em out! Also: - Just swallow our pride and do the gross `INSERT INTO ... "", "", "", "", "", "", ... ON DUPLICATE KEY UPDATE` to bulk update. - Fix an issue where a single move could cause two different subpriority recalculations. Test Plan: - Changed `ManiphesTaskTestCase->testTaskAdjacentBlocks()` to insert 1,000 tasks with identical subpriorities, saw them spread out in 11 queries instead of >1,000. - Dragged tons of tasks around on workboards. - Ran unit tests. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7664 Differential Revision: https://secure.phabricator.com/D17959 --- .../__tests__/ManiphestTaskTestCase.php | 28 +++ .../editor/ManiphestTransactionEditor.php | 230 +++++++++++++----- .../maniphest/query/ManiphestTaskQuery.php | 22 -- .../PhabricatorProjectMoveController.php | 3 + 4 files changed, 195 insertions(+), 88 deletions(-) diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php index 5f5c198a84..1e571190d7 100644 --- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php +++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php @@ -125,6 +125,34 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { 9, count($subpri), pht('Expected subpriorities to be distributed.')); + + // Move task 9 to the end. + $this->moveTask($viewer, $t[9], $t[1], true); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(8, 7, 6, 5, 4, 3, 2, 1, 9), + array_keys($tasks)); + + // Move task 3 to the beginning. + $this->moveTask($viewer, $t[3], $t[8], false); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(3, 8, 7, 6, 5, 4, 2, 1, 9), + array_keys($tasks)); + + // Move task 3 to the end. + $this->moveTask($viewer, $t[3], $t[9], true); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(8, 7, 6, 5, 4, 2, 1, 9, 3), + array_keys($tasks)); + + // Move task 5 to before task 4 (this is its current position). + $this->moveTask($viewer, $t[5], $t[4], false); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(8, 7, 6, 5, 4, 2, 1, 9, 3), + array_keys($tasks)); } private function newTask(PhabricatorUser $viewer, $title) { diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index c9e17cd4de..1b780549dc 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -384,8 +384,7 @@ final class ManiphestTransactionEditor */ public static function getAdjacentSubpriority( ManiphestTask $dst, - $is_after, - $allow_recursion = true) { + $is_after) { $query = id(new ManiphestTaskQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -407,76 +406,25 @@ final class ManiphestTransactionEditor // If we find an adjacent task, we average the two subpriorities and // return the result. if ($adjacent) { - $epsilon = 0.01; + $epsilon = 1.0; // If the adjacent task has a subpriority that is identical or very - // close to the task we're looking at, we're going to move it and all - // tasks with the same subpriority a little farther down the subpriority - // scale. - if ($allow_recursion && - (abs($adjacent->getSubpriority() - $base) < $epsilon)) { - $conn_w = $adjacent->establishConnection('w'); + // close to the task we're looking at, we're going to spread out all + // the nearby tasks. - $min = ($adjacent->getSubpriority() - ($epsilon)); - $max = ($adjacent->getSubpriority() + ($epsilon)); - - // Get all of the tasks with the similar subpriorities to the adjacent - // task, including the adjacent task itself. - $query = id(new ManiphestTaskQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPriorities(array($adjacent->getPriority())) - ->withSubpriorityBetween($min, $max); - - if (!$is_after) { - $query->setOrderVector(array('-priority', '-subpriority', '-id')); + $adjacent_sub = $adjacent->getSubpriority(); + if ((abs($adjacent_sub - $base) < $epsilon)) { + $base = self::disperseBlock( + $dst, + $epsilon * 2); + if ($is_after) { + $sub = $base - $epsilon; } else { - $query->setOrderVector(array('priority', 'subpriority', 'id')); - } - - $shift_all = $query->execute(); - $shift_last = last($shift_all); - - // Select the most extreme subpriority in the result set as the - // base value. - $shift_base = head($shift_all)->getSubpriority(); - - // Find the subpriority before or after the task at the end of the - // block. - list($shift_pri, $shift_sub) = self::getAdjacentSubpriority( - $shift_last, - $is_after, - $allow_recursion = false); - - $delta = ($shift_sub - $shift_base); - $count = count($shift_all); - - $shift = array(); - $cursor = 1; - foreach ($shift_all as $shift_task) { - $shift_target = $shift_base + (($cursor / $count) * $delta); - $cursor++; - - queryfx( - $conn_w, - 'UPDATE %T SET subpriority = %f WHERE id = %d', - $adjacent->getTableName(), - $shift_target, - $shift_task->getID()); - - // If we're shifting the adjacent task, update it. - if ($shift_task->getID() == $adjacent->getID()) { - $adjacent->setSubpriority($shift_target); - } - - // If we're shifting the original target task, update the base - // subpriority. - if ($shift_task->getID() == $dst->getID()) { - $base = $shift_target; - } + $sub = $base + $epsilon; } + } else { + $sub = ($adjacent_sub + $base) / 2; } - - $sub = ($adjacent->getSubpriority() + $base) / 2; } else { // Otherwise, we take a step away from the target's subpriority and // use that. @@ -490,6 +438,156 @@ final class ManiphestTransactionEditor return array($dst->getPriority(), $sub); } + /** + * Distribute a cluster of tasks with similar subpriorities. + */ + private static function disperseBlock( + ManiphestTask $task, + $spacing) { + + $conn = $task->establishConnection('w'); + + // Find a block of subpriority space which is, on average, sparse enough + // to hold all the tasks that are inside it with a reasonable level of + // separation between them. + + // We'll start by looking near the target task for a range of numbers + // which has more space available than tasks. For example, if the target + // task has subpriority 33 and we want to separate each task by at least 1, + // we might start by looking in the range [23, 43]. + + // If we find fewer than 20 tasks there, we have room to reassign them + // with the desired level of separation. We space them out, then we're + // done. + + // However: if we find more than 20 tasks, we don't have enough room to + // distribute them. We'll widen our search and look in a bigger range, + // maybe [13, 53]. This range has more space, so if we find fewer than + // 40 tasks in this range we can spread them out. If we still find too + // many tasks, we keep widening the search. + + $base = $task->getSubpriority(); + + $scale = 4.0; + while (true) { + $range = ($spacing * $scale) / 2.0; + $min = ($base - $range); + $max = ($base + $range); + + $result = queryfx_one( + $conn, + 'SELECT COUNT(*) N FROM %T WHERE priority = %d AND + subpriority BETWEEN %f AND %f', + $task->getTableName(), + $task->getPriority(), + $min, + $max); + + $count = $result['N']; + if ($count < $scale) { + // We have found a block which we can make sparse enough, so bail and + // continue below with our selection. + break; + } + + // This block had too many tasks for its size, so try again with a + // bigger block. + $scale *= 2.0; + } + + $rows = queryfx_all( + $conn, + 'SELECT id FROM %T WHERE priority = %d AND + subpriority BETWEEN %f AND %f + ORDER BY priority, subpriority, id', + $task->getTableName(), + $task->getPriority(), + $min, + $max); + + $task_id = $task->getID(); + $result = null; + + // NOTE: In strict mode (which we encourage enabling) we can't structure + // this bulk update as an "INSERT ... ON DUPLICATE KEY UPDATE" unless we + // provide default values for ALL of the columns that don't have defaults. + + // This is gross, but we may be moving enough rows that individual + // queries are unreasonably slow. An alternate construction which might + // be worth evaluating is to use "CASE". Another approach is to disable + // strict mode for this query. + + $extra_columns = array( + 'phid' => '""', + 'authorPHID' => '""', + 'status' => '""', + 'priority' => 0, + 'title' => '""', + 'originalTitle' => '""', + 'description' => '""', + 'dateCreated' => 0, + 'dateModified' => 0, + 'mailKey' => '""', + 'viewPolicy' => '""', + 'editPolicy' => '""', + 'ownerOrdering' => '""', + 'spacePHID' => '""', + 'bridgedObjectPHID' => '""', + 'properties' => '""', + 'points' => 0, + 'subtype' => '""', + ); + + $defaults = implode(', ', $extra_columns); + + $sql = array(); + $offset = 0; + + // Often, we'll have more room than we need in the range. Distribute the + // tasks evenly over the whole range so that we're less likely to end up + // with tasks spaced exactly the minimum distance apart, which may + // get shifted again later. We have one fewer space to distribute than we + // have tasks. + $divisor = (double)(count($rows) - 1.0); + if ($divisor > 0) { + $available_distance = (($max - $min) / $divisor); + } else { + $available_distance = 0.0; + } + + foreach ($rows as $row) { + $subpriority = $min + ($offset * $available_distance); + + // If this is the task that we're spreading out relative to, keep track + // of where it is ending up so we can return the new subpriority. + $id = $row['id']; + if ($id == $task_id) { + $result = $subpriority; + } + + $sql[] = qsprintf( + $conn, + '(%d, %Q, %f)', + $id, + $defaults, + $subpriority); + + $offset++; + } + + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { + queryfx( + $conn, + 'INSERT INTO %T (id, %Q, subpriority) VALUES %Q + ON DUPLICATE KEY UPDATE subpriority = VALUES(subpriority)', + $task->getTableName(), + implode(', ', array_keys($extra_columns)), + $chunk); + } + + return $result; + } + protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 4cf47f40cb..704a67f548 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -17,8 +17,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $dateCreatedBefore; private $dateModifiedAfter; private $dateModifiedBefore; - private $subpriorityMin; - private $subpriorityMax; private $bridgedObjectPHIDs; private $hasOpenParents; private $hasOpenSubtasks; @@ -112,12 +110,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } - public function withSubpriorityBetween($min, $max) { - $this->subpriorityMin = $min; - $this->subpriorityMax = $max; - return $this; - } - public function withSubscribers(array $subscribers) { $this->subscriberPHIDs = $subscribers; return $this; @@ -408,20 +400,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $this->subpriorities); } - if ($this->subpriorityMin !== null) { - $where[] = qsprintf( - $conn, - 'task.subpriority >= %f', - $this->subpriorityMin); - } - - if ($this->subpriorityMax !== null) { - $where[] = qsprintf( - $conn, - 'task.subpriority <= %f', - $this->subpriorityMax); - } - if ($this->bridgedObjectPHIDs !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index 68915664cb..801acbb90e 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -148,6 +148,9 @@ final class PhabricatorProjectMoveController list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority( $task, $is_after); + + // If we find a priority on the first try, don't keep going. + break; } $xactions = array(); From 5f49f9c793e370157559f0c5c78f716f01c8e58b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 21:40:08 -0700 Subject: [PATCH 139/543] Add sound to logged out Conpherence Summary: Fixes T12735. Adds a sound if the user is logged out, skips checking a setting. Test Plan: set participants to null and verify sound plays, no exceptions1111 Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12735 Differential Revision: https://secure.phabricator.com/D17973 --- .../controller/ConpherenceUpdateController.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index a814c64452..a792a5a4d4 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -369,9 +369,17 @@ final class ConpherenceUpdateController ->setViewer($user); $dropdown_query->execute(); - $sounds = $this->getSoundForParticipant($user, $participant); - $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; - $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; + $map = ConpherenceRoomSettings::getSoundMap(); + $default_receive = ConpherenceRoomSettings::DEFAULT_RECEIVE_SOUND; + $receive_sound = $map[$default_receive]['rsrc']; + $mention_sound = null; + + // Get the user's defaults if logged in + if ($participant) { + $sounds = $this->getSoundForParticipant($user, $participant); + $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; + $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; + } $content = array( 'non_update' => $non_update, From 6945e80feed50c4c584e383b17c0684a9840cf93 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:27:21 -0700 Subject: [PATCH 140/543] For the diff banner, detect the current changeset better Summary: Ref T12733. Currently, we detect the changeset which is in the middle of the screen as the current changeset. This doesn't always get us the most intuitive changeset, particularly after a navigation from the scroll objective list: when you jump to changeset "X", you'd tend to expect "X" to be shown in the header, but the //next// changeset may be shown if "X" is too short. Instead, select the changeset near the top of the screen (spanning an invisible line slightly below the banner). Test Plan: Scrolled and jumped through a document with long and short changesets, saw a more intuitive changeset selected by the banner. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17976 --- resources/celerity/map.php | 14 +++++++------- .../js/application/diff/DiffChangesetList.js | 16 ++++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e50d8bbd1a..019856296b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4d7dd14e', - 'differential.pkg.js' => '68a4fa60', + 'differential.pkg.js' => '6d05ad4c', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => '5c68c40c', + 'rsrc/js/application/diff/DiffChangesetList.js' => '541206ba', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => '5c68c40c', + 'phabricator-diff-changeset-list' => '541206ba', 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1316,6 +1316,10 @@ return array( '5294060f' => array( 'phui-theme-css', ), + '541206ba' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '54774a28' => array( 'phui-inline-comment-view-css', ), @@ -1368,10 +1372,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '5c68c40c' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '5e2634b9' => array( 'javelin-behavior', 'javelin-aphlict', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index e256372852..b615ca79eb 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1358,16 +1358,20 @@ JX.install('DiffChangesetList', { return null; } - var v = JX.Vector.getViewport(); + // We're going to find the changeset which spans an invisible line a + // little underneath the bottom of the banner. This makes the header + // tick over from "A.txt" to "B.txt" just as "A.txt" scrolls completely + // offscreen. + var detect_height = 64; + for (var ii = 0; ii < this._changesets.length; ii++) { var changeset = this._changesets[ii]; var c = changeset.getVectors(); - // If the changeset starts above the upper half of the screen... - if (c.pos.y < (s.y + (v.y / 2))) { - // ...and ends below the lower half of the screen, this is the - // current visible changeset. - if ((c.pos.y + c.dim.y) > (s.y + (v.y / 2))) { + // If the changeset starts above the line... + if (c.pos.y <= (s.y + detect_height)) { + // ...and ends below the line, this is the current visible changeset. + if ((c.pos.y + c.dim.y) >= (s.y + detect_height)) { return changeset; } } From bdecff7d67ce149c3dfe65629cdf8c90a919f95e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 07:18:34 -0700 Subject: [PATCH 141/543] Show "objectives" UI only if prototypes are enabled Summary: See D17955. Test Plan: Loaded a revision, no longer saw annotations with prototypes off. Still saw annotations with prototypes on. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17983 --- resources/celerity/map.php | 34 +++++++++---------- .../view/DifferentialChangesetListView.php | 4 +++ .../js/application/diff/DiffChangesetList.js | 10 +++++- .../differential/behavior-populate.js | 3 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 019856296b..5b6c54b2f1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4d7dd14e', - 'differential.pkg.js' => '6d05ad4c', + 'differential.pkg.js' => '0dfe037d', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,14 +391,14 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => '541206ba', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', + 'rsrc/js/application/differential/behavior-populate.js' => '1de8bf63', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -620,7 +620,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '5e41c819', + 'javelin-behavior-differential-populate' => '1de8bf63', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => '541206ba', + 'phabricator-diff-changeset-list' => 'a716ca27', 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1042,6 +1042,14 @@ return array( 'javelin-workflow', 'phabricator-scroll-objective', ), + '1de8bf63' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1316,10 +1324,6 @@ return array( '5294060f' => array( 'phui-theme-css', ), - '541206ba' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '54774a28' => array( 'phui-inline-comment-view-css', ), @@ -1378,14 +1382,6 @@ return array( 'phabricator-phtize', 'javelin-dom', ), - '5e41c819' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1730,6 +1726,10 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + 'a716ca27' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 4ac2612ecd..ea8a5e42f1 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -203,11 +203,15 @@ final class DifferentialChangesetListView extends AphrontView { $this->requireResource('aphront-tooltip-css'); + $show_objectives = + PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); + $this->initBehavior( 'differential-populate', array( 'changesetViewIDs' => $ids, 'inlineURI' => $this->inlineURI, + 'showObjectives' => $show_objectives, 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show All Context' => pht('Show All Context'), diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index b615ca79eb..8e35a92f80 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -120,6 +120,7 @@ JX.install('DiffChangesetList', { _rangeTarget: null, _bannerNode: null, + _showObjectives: false, sleep: function() { this._asleep = true; @@ -137,7 +138,9 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); - this._objectives.show(); + if (this._showObjectives) { + this._objectives.show(); + } if (this._initialized) { return; @@ -195,6 +198,11 @@ JX.install('DiffChangesetList', { this._installKey('q', label, this._onkeyhide); }, + setShowObjectives: function(show) { + this._showObjectives = show; + return this; + }, + isAsleep: function() { return this._asleep; }, diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index e5b0d5039d..b1b5bd415c 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -60,7 +60,8 @@ JX.behavior('differential-populate', function(config, statics) { var changeset_list = new JX.DiffChangesetList() .setTranslations(JX.phtize(config.pht)) - .setInlineURI(config.inlineURI); + .setInlineURI(config.inlineURI) + .setShowObjectives(config.showObjectives); // Install and activate the current page. var page_id = JX.Quicksand.getCurrentPageID(); From aba209e9990fe1c975c0786be1bfbc4a9a9d173c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:10:32 -0700 Subject: [PATCH 142/543] Hide the Differential scroll objective list on trackpad systems Summary: Ref T12733. In the longer run I'd like to just push this out from the edge, but that currently gets us into trouble since we start bumping into content. On my system, the trackpad scrollbar also expands in size when moused over, so the minimum number of pixels we need to push it out is approximatley 15px. This hits body content and the persistent chat. For now, just disable this element on trackpad systems. Test Plan: Disconnected all USB peripherals, quit and relaunched Safari, saw no objective list. Reconnected mouse, relaunched Safari, saw objective list. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17974 --- resources/celerity/map.php | 47 ++++++++++--------- .../differential/changeset-view.css | 5 ++ .../rsrc/externals/javelin/lib/Scrollbar.js | 8 ++-- .../application/diff/ScrollObjectiveList.js | 6 +++ 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5b6c54b2f1..bf2fa84a72 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => 'e822b496', + 'core.pkg.js' => '6b2da600', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '4d7dd14e', - 'differential.pkg.js' => '0dfe037d', + 'differential.pkg.css' => 'bf87589e', + 'differential.pkg.js' => 'ee4f14c5', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '54774a28', + 'rsrc/css/application/differential/changeset-view.css' => 'fa476ec0', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -247,7 +247,7 @@ return array( 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', - 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c', + 'rsrc/externals/javelin/lib/Scrollbar.js' => '9065f639', 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', @@ -394,7 +394,7 @@ return array( 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', - 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', + 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -567,7 +567,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '54774a28', + 'differential-changeset-view-css' => 'fa476ec0', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -732,7 +732,7 @@ return array( 'javelin-resource' => '44959b73', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', - 'javelin-scrollbar' => '087e919c', + 'javelin-scrollbar' => '9065f639', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6ad39b6f', 'javelin-tokenizer' => '8d3bc1b2', @@ -800,7 +800,7 @@ return array( 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', 'phabricator-scroll-objective' => '0eee7a00', - 'phabricator-scroll-objective-list' => '1ca4d9db', + 'phabricator-scroll-objective-list' => '085dd101', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -956,11 +956,14 @@ return array( 'javelin-stratcom', 'javelin-util', ), - '087e919c' => array( - 'javelin-install', + '085dd101' => array( 'javelin-dom', + 'javelin-util', 'javelin-stratcom', - 'javelin-vector', + 'javelin-install', + 'javelin-workflow', + 'javelin-scrollbar', + 'phabricator-scroll-objective', ), '08f4ccc3' => array( 'phui-oi-list-view-css', @@ -1034,14 +1037,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '1ca4d9db' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'phabricator-scroll-objective', - ), '1de8bf63' => array( 'javelin-behavior', 'javelin-dom', @@ -1324,9 +1319,6 @@ return array( '5294060f' => array( 'phui-theme-css', ), - '54774a28' => array( - 'phui-inline-comment-view-css', - ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1611,6 +1603,12 @@ return array( 'javelin-dom', 'javelin-request', ), + '9065f639' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '92b9ec77' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2208,6 +2206,9 @@ return array( 'javelin-install', 'javelin-dom', ), + 'fa476ec0' => array( + 'phui-inline-comment-view-css', + ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index eee0e167f3..6a2552fbd1 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -423,6 +423,11 @@ tr.differential-inline-loading { overflow: hidden; } +.scroll-objective-list.has-aesthetic-scrollbar { + /* For now, hide this element on systems with aesthetic scrollbars. */ + display: none; +} + .scroll-objective { display: block; position: absolute; diff --git a/webroot/rsrc/externals/javelin/lib/Scrollbar.js b/webroot/rsrc/externals/javelin/lib/Scrollbar.js index 7596939581..cd69eb4def 100644 --- a/webroot/rsrc/externals/javelin/lib/Scrollbar.js +++ b/webroot/rsrc/externals/javelin/lib/Scrollbar.js @@ -34,7 +34,7 @@ JX.install('Scrollbar', { // width. If it doesn't, we're already in an environment with an aesthetic // scrollbar (like Safari on OSX with no mouse connected, or an iPhone) // and we don't need to do anything. - if (JX.Scrollbar._getScrollbarControlWidth() === 0) { + if (JX.Scrollbar.getScrollbarControlWidth() === 0) { return; } @@ -104,7 +104,7 @@ JX.install('Scrollbar', { /** * Compute the width of the browser's scrollbar control, in pixels. */ - _getScrollbarControlWidth: function() { + getScrollbarControlWidth: function() { var self = JX.Scrollbar; if (self._controlWidth === null) { @@ -140,7 +140,7 @@ JX.install('Scrollbar', { // If this browser and OS don't render a real scrollbar control, we // need to leave a margin. Generally, this is OSX with no mouse attached. - if (self._getScrollbarControlWidth() === 0) { + if (self.getScrollbarControlWidth() === 0) { return 12; } @@ -357,7 +357,7 @@ JX.install('Scrollbar', { */ _resizeViewport: function() { var fdim = JX.Vector.getDim(this._frame); - fdim.x += JX.Scrollbar._getScrollbarControlWidth(); + fdim.x += JX.Scrollbar.getScrollbarControlWidth(); fdim.setDim(this._viewport); }, diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js index f5beb751e6..7aae1fb1b4 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js +++ b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js @@ -5,6 +5,7 @@ * javelin-stratcom * javelin-install * javelin-workflow + * javelin-scrollbar * phabricator-scroll-objective * @javelin */ @@ -81,6 +82,11 @@ JX.install('ScrollObjectiveList', { document.body.appendChild(node); + // If we're on OSX without a mouse or some other system with zero-width + // trackpad-style scrollbars, adjust the display appropriately. + var aesthetic = (JX.Scrollbar.getScrollbarControlWidth() === 0); + JX.DOM.alterClass(node, 'has-aesthetic-scrollbar', aesthetic); + var d = JX.Vector.getDocument(); var list_dimensions = JX.Vector.getDim(node); From 8b2a06387dbcb3d2e4621c6debf5234c46abbe0d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:19:30 -0700 Subject: [PATCH 143/543] Stop long filenames in objective list tooltips from being cut off Summary: Ref T12733. Currently, long filenames get cut off at 160px. Instead, don't cut them off. Test Plan: Before: {F4968401} After: {F4968402} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17975 --- resources/celerity/map.php | 38 +++++++++---------- .../js/application/diff/ScrollObjective.js | 1 + webroot/rsrc/js/core/ToolTip.js | 6 ++- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bf2fa84a72..bbd3c5e317 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => '6b2da600', + 'core.pkg.js' => 'a8eda64a', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'bf87589e', - 'differential.pkg.js' => 'ee4f14c5', + 'differential.pkg.js' => '24d1acf0', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -393,7 +393,7 @@ return array( 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', - 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', + 'rsrc/js/application/diff/ScrollObjective.js' => '2e069f79', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -480,7 +480,7 @@ return array( 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', - 'rsrc/js/core/ToolTip.js' => '8fadb715', + 'rsrc/js/core/ToolTip.js' => '74caa17f', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', @@ -799,7 +799,7 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '0eee7a00', + 'phabricator-scroll-objective' => '2e069f79', 'phabricator-scroll-objective-list' => '085dd101', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', @@ -808,7 +808,7 @@ return array( 'phabricator-standard-page-view' => 'eb5b80c5', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', - 'phabricator-tooltip' => '8fadb715', + 'phabricator-tooltip' => '74caa17f', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', @@ -980,13 +980,6 @@ return array( 'javelin-dom', 'javelin-router', ), - '0eee7a00' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1120,6 +1113,13 @@ return array( 'javelin-install', 'javelin-event', ), + '2e069f79' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '2ee659ce' => array( 'javelin-install', ), @@ -1462,6 +1462,12 @@ return array( 'javelin-vector', 'javelin-dom', ), + '74caa17f' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1587,12 +1593,6 @@ return array( 'javelin-stratcom', 'javelin-install', ), - '8fadb715' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - ), '8ff5e24c' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js index 4e6fa55d6a..c9fd4bb67e 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -100,6 +100,7 @@ JX.install('ScrollObjective', { JX.Stratcom.addSigil(node, 'has-tooltip'); JX.Stratcom.getData(node).tip = tip; JX.Stratcom.getData(node).align = 'W'; + JX.Stratcom.getData(node).size = 'auto'; return this; }, diff --git a/webroot/rsrc/js/core/ToolTip.js b/webroot/rsrc/js/core/ToolTip.js index 6fef44ab55..64a2deb301 100644 --- a/webroot/rsrc/js/core/ToolTip.js +++ b/webroot/rsrc/js/core/ToolTip.js @@ -50,7 +50,11 @@ JX.install('Tooltip', { { className: 'jx-tooltip-container' }, node_inner); - node.style.maxWidth = scale + 'px'; + if (scale == 'auto') { + node.style.maxWidth = ''; + } else { + node.style.maxWidth = scale + 'px'; + } JX.Tooltip.hide(); self._node = node; From af07600aaac7da1ca89f985f21dd72ed50bba085 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:58:09 -0700 Subject: [PATCH 144/543] Make Differential objective markers show a brighter "editing" state Summary: Ref T12733. - While editing a comment, show a pink star ({icon star, color=pink}) with a tooltip. - Slight UI tweaks, including draft comments getting an indigo pencil ({icon pencil, color=indigo}). Test Plan: {F4968470} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17977 --- resources/celerity/map.php | 40 +++++++++---------- .../view/DifferentialChangesetListView.php | 2 + .../differential/changeset-view.css | 5 ++- .../rsrc/js/application/diff/DiffInline.js | 29 +++++++++++++- webroot/rsrc/js/core/ToolTip.js | 4 ++ 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bbd3c5e317..97b4f15a35 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => 'a8eda64a', + 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => 'bf87589e', - 'differential.pkg.js' => '24d1acf0', + 'differential.pkg.css' => '7d4cfa59', + 'differential.pkg.js' => 'f94e941c', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'fa476ec0', + 'rsrc/css/application/differential/changeset-view.css' => 'acfd58f6', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', + 'rsrc/js/application/diff/DiffInline.js' => 'fa07d36e', 'rsrc/js/application/diff/ScrollObjective.js' => '2e069f79', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -480,7 +480,7 @@ return array( 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', - 'rsrc/js/core/ToolTip.js' => '74caa17f', + 'rsrc/js/core/ToolTip.js' => '358b8c04', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', @@ -567,7 +567,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'fa476ec0', + 'differential-changeset-view-css' => 'acfd58f6', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -779,7 +779,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '77e14b60', + 'phabricator-diff-inline' => 'fa07d36e', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -808,7 +808,7 @@ return array( 'phabricator-standard-page-view' => 'eb5b80c5', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', - 'phabricator-tooltip' => '74caa17f', + 'phabricator-tooltip' => '358b8c04', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', @@ -1137,6 +1137,12 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '358b8c04' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1462,12 +1468,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '74caa17f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1480,9 +1480,6 @@ return array( 'javelin-reactor', 'javelin-util', ), - '77e14b60' => array( - 'javelin-dom', - ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', @@ -1788,6 +1785,9 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), + 'acfd58f6' => array( + 'phui-inline-comment-view-css', + ), 'ae95d984' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2206,8 +2206,8 @@ return array( 'javelin-install', 'javelin-dom', ), - 'fa476ec0' => array( - 'phui-inline-comment-view-css', + 'fa07d36e' => array( + 'javelin-dom', ), 'fbe497e7' => array( 'javelin-behavior', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index ea8a5e42f1..880fcb872d 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -232,6 +232,8 @@ final class DifferentialChangesetListView extends AphrontView { 'Loading...' => pht('Loading...'), + 'Editing Comment' => pht('Editing Comment'), + 'Jump to next change.' => pht('Jump to next change.'), 'Jump to previous change.' => pht('Jump to previous change.'), 'Jump to next file.' => pht('Jump to next file.'), diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 6a2552fbd1..6a29e1269b 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -419,7 +419,7 @@ tr.differential-inline-loading { border-style: solid; border-color: rgba(255, 255, 255, 0.95); border-width: 1px 0 1px 1px; - box-shadow: -1px 0 2px rgba(255, 255, 255, 0.25); + box-shadow: -1px 0 2px rgba(255, 255, 255, 0.10); overflow: hidden; } @@ -433,7 +433,8 @@ tr.differential-inline-loading { position: absolute; box-sizing: border-box; cursor: pointer; - left: 8px; + text-align: middle; + left: 7px; } .scroll-objective .phui-icon-view { diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index e66fb544c5..32cf0ac5a1 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -32,6 +32,7 @@ JX.install('DiffInline', { _isDraft: null, _isFixed: null, + _isEditing: false, bindToRow: function(row) { this._row = row; @@ -176,6 +177,12 @@ JX.install('DiffInline', { return this._changeset; }, + setEditing: function(editing) { + this._isEditing = editing; + this.updateObjective(); + return this; + }, + _onobjective: function() { this.getChangeset().getChangesetList().selectInline(this); }, @@ -194,12 +201,20 @@ JX.install('DiffInline', { return; } + var pht = changeset.getChangesetList().getTranslations(); + var icon = 'fa-comment'; var color = 'bluegrey'; + var tooltip = null; - if (this._isDraft) { + if (this._isEditing) { + icon = 'fa-star'; + color = 'pink'; + tooltip = pht('Editing Comment'); + } else if (this._isDraft) { // This inline is an unsubmitted draft. icon = 'fa-pencil'; + color = 'indigo'; } else if (this._isFixed) { // This inline has been marked done. icon = 'fa-check'; @@ -212,6 +227,7 @@ JX.install('DiffInline', { objective .setIcon(icon) .setColor(color) + .setTooltip(tooltip) .show(); }, @@ -511,7 +527,12 @@ JX.install('DiffInline', { this._undoRow = this._drawRows(template, cursor, mode, text); }, + _drawContentRows: function(rows) { + return this._drawRows(rows, null, 'content'); + }, + _drawEditRows: function(rows) { + this.setEditing(true); return this._drawRows(rows, null, 'edit'); }, @@ -560,6 +581,8 @@ JX.install('DiffInline', { 'click', 'inline-edit-cancel', JX.bind(this, this._oncancel, row_meta))); + } else if (type == 'content') { + // No special listeners for these rows. } else { row_meta.listeners.push( JX.DOM.listen( @@ -645,6 +668,7 @@ JX.install('DiffInline', { } this._removeRow(row); + this.setEditing(false); this.setInvisible(false); @@ -670,6 +694,7 @@ JX.install('DiffInline', { this.setLoading(false); this.setInvisible(false); + this.setEditing(false); this._onupdate(response); }, @@ -677,7 +702,7 @@ JX.install('DiffInline', { _onupdate: function(response) { var new_row; if (response.markup) { - new_row = this._drawEditRows(JX.$H(response.markup).getNode()).node; + new_row = this._drawContentRows(JX.$H(response.markup).getNode()).node; } // TODO: Save the old row so the action it's undo-able if it was a diff --git a/webroot/rsrc/js/core/ToolTip.js b/webroot/rsrc/js/core/ToolTip.js index 64a2deb301..635c9466ad 100644 --- a/webroot/rsrc/js/core/ToolTip.js +++ b/webroot/rsrc/js/core/ToolTip.js @@ -21,6 +21,10 @@ JX.install('Tooltip', { return; } + if (content === null) { + return; + } + if (__DEV__) { switch (align) { case 'N': From c056ff56cc957135f78768a9ace60014dd731a64 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:26:51 -0700 Subject: [PATCH 145/543] Fix a diff objective issue where objectives could appear in the wrong place Summary: Ref T12733. When creating a new comment, the objective could appear to far up in the scrollbar because we were anchoring it to an invisible row. Instead, anchor to the "edit" row while editing. Test Plan: Created a new comment at the very top of a file, saw "File, Star" icons instead of "Star, File". Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17978 --- resources/celerity/map.php | 30 +++++++++---------- .../rsrc/js/application/diff/DiffInline.js | 16 ++++++++-- .../js/application/diff/ScrollObjective.js | 1 + 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 97b4f15a35..968862b535 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => 'f94e941c', + 'differential.pkg.js' => 'fc6a23eb', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,8 +392,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => 'fa07d36e', - 'rsrc/js/application/diff/ScrollObjective.js' => '2e069f79', + 'rsrc/js/application/diff/DiffInline.js' => '93cbb03f', + 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -779,7 +779,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => 'fa07d36e', + 'phabricator-diff-inline' => '93cbb03f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -799,7 +799,7 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '2e069f79', + 'phabricator-scroll-objective' => '9df4e4e2', 'phabricator-scroll-objective-list' => '085dd101', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', @@ -1113,13 +1113,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2e069f79' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '2ee659ce' => array( 'javelin-install', ), @@ -1611,6 +1604,9 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '93cbb03f' => array( + 'javelin-dom', + ), '93d0c9e3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1675,6 +1671,13 @@ return array( '9d9685d6' => array( 'phui-oi-list-view-css', ), + '9df4e4e2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2206,9 +2209,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'fa07d36e' => array( - 'javelin-dom', - ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 32cf0ac5a1..77a0da3ec7 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -74,9 +74,10 @@ JX.install('DiffInline', { this._changesetID = data.changesetID; - this.updateObjective(); this.setInvisible(false); + this.updateObjective(); + return this; }, @@ -166,9 +167,11 @@ JX.install('DiffInline', { this._changeset = changeset; var objectives = changeset.getChangesetList().getObjectives(); + + // Create this inline's objective, but don't show it yet. this._objective = objectives.newObjective() - .setCallback(JX.bind(this, this._onobjective)); - this.updateObjective(); + .setCallback(JX.bind(this, this._onobjective)) + .hide(); return this; }, @@ -206,11 +209,17 @@ JX.install('DiffInline', { var icon = 'fa-comment'; var color = 'bluegrey'; var tooltip = null; + var anchor = this._row; if (this._isEditing) { icon = 'fa-star'; color = 'pink'; tooltip = pht('Editing Comment'); + + // If we're editing, anchor to the row with the editor instead of the + // actual comment row (which is invisible and can have a misleading + // position). + anchor = this._row.nextSibling; } else if (this._isDraft) { // This inline is an unsubmitted draft. icon = 'fa-pencil'; @@ -225,6 +234,7 @@ JX.install('DiffInline', { } objective + .setAnchor(anchor) .setIcon(icon) .setColor(color) .setTooltip(tooltip) diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js index c9fd4bb67e..b8fb169914 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -111,6 +111,7 @@ JX.install('ScrollObjective', { hide: function() { this._visible = false; + return this; }, isVisible: function() { From 7a40dd380ee7514f802c292e1522245800c77272 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:33:59 -0700 Subject: [PATCH 146/543] When a user cancels a new inline, clear it from the objective list Summary: Ref T12733. Currently, creating a new inline and then canceling it leaves a marker in the objective list. Instead, remove the marker. Test Plan: - Created an empty inline, cancelled. Created a non-empty inline, cancelled. No objective marker in either case. - Created a new normal inline, objective marker. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17979 --- resources/celerity/map.php | 12 ++++++------ webroot/rsrc/js/application/diff/DiffInline.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 968862b535..402ae9013e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => 'fc6a23eb', + 'differential.pkg.js' => 'd7e3edd5', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '93cbb03f', + 'rsrc/js/application/diff/DiffInline.js' => 'ca0fafde', 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -779,7 +779,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '93cbb03f', + 'phabricator-diff-inline' => 'ca0fafde', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1604,9 +1604,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '93cbb03f' => array( - 'javelin-dom', - ), '93d0c9e3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1979,6 +1976,9 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), + 'ca0fafde' => array( + 'javelin-dom', + ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 77a0da3ec7..e7c18ef6ad 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -33,6 +33,7 @@ JX.install('DiffInline', { _isDraft: null, _isFixed: null, _isEditing: false, + _isNew: false, bindToRow: function(row) { this._row = row; @@ -73,6 +74,7 @@ JX.install('DiffInline', { this._isGhost = data.isGhost; this._changesetID = data.changesetID; + this._isNew = false; this.setInvisible(false); @@ -87,6 +89,7 @@ JX.install('DiffInline', { this._length = parseInt(data.length, 10); this._isNewFile = data.isNewFile; this._changesetID = data.changesetID; + this._isNew = true; // Insert the comment after any other comments which already appear on // the same row. @@ -110,6 +113,7 @@ JX.install('DiffInline', { this._length = inline._length; this._isNewFile = inline._isNewFile; this._changesetID = inline._changesetID; + this._isNew = true; this._replyToCommentPHID = inline._phid; @@ -198,6 +202,13 @@ JX.install('DiffInline', { return; } + // If this is a new comment which we aren't editing, don't show anything: + // the use started a comment or reply, then cancelled it. + if (this._isNew && !this._isEditing) { + objective.hide(); + return; + } + var changeset = this.getChangeset(); if (!changeset.isVisible()) { objective.hide(); From 4dff754502bdbf57b964eccc12aa774b6cecf171 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:39:07 -0700 Subject: [PATCH 147/543] Show a snippet when hovering inlines in the objective list Summary: Ref T12733. Shows a comment snippet when hovering inlines in the objective list. Test Plan: {F4968490} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17980 --- resources/celerity/map.php | 12 ++++++------ .../diff/view/PHUIDiffInlineCommentDetailView.php | 9 +++++---- webroot/rsrc/js/application/diff/DiffInline.js | 4 +++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 402ae9013e..46443e7ecc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => 'd7e3edd5', + 'differential.pkg.js' => '06cddcc0', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => 'ca0fafde', + 'rsrc/js/application/diff/DiffInline.js' => '4478f8ac', 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -779,7 +779,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => 'ca0fafde', + 'phabricator-diff-inline' => '4478f8ac', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1203,6 +1203,9 @@ return array( 'javelin-workflow', 'javelin-workboard-controller', ), + '4478f8ac' => array( + 'javelin-dom', + ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1976,9 +1979,6 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), - 'ca0fafde' => array( - 'javelin-dom', - ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 9fc1e1bded..821834431a 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -421,6 +421,11 @@ final class PHUIDiffInlineCommentDetailView $actions, )); + $snippet = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(96) + ->truncateString($inline->getContent()); + $metadata['snippet'] = pht('%s: %s', $author, $snippet); + $markup = javelin_tag( 'div', array( @@ -444,10 +449,6 @@ final class PHUIDiffInlineCommentDetailView phutil_tag_div('phabricator-remarkup', $content)), )); - $snippet = id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(96) - ->truncateString($inline->getContent()); - $summary = phutil_tag( 'div', array( diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index e7c18ef6ad..1f0d503b9f 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -22,6 +22,7 @@ JX.install('DiffInline', { _undoRow: null, _replyToCommentPHID: null, _originalText: null, + _snippet: null, _isDeleted: false, _isInvisible: false, @@ -75,6 +76,7 @@ JX.install('DiffInline', { this._changesetID = data.changesetID; this._isNew = false; + this._snippet = data.snippet; this.setInvisible(false); @@ -219,7 +221,7 @@ JX.install('DiffInline', { var icon = 'fa-comment'; var color = 'bluegrey'; - var tooltip = null; + var tooltip = this._snippet; var anchor = this._row; if (this._isEditing) { From e15009b76e1b085fabb0efe58a03869999ca202a Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:48:38 -0700 Subject: [PATCH 148/543] Show "reply" inlines as replies in the objective list Summary: Ref T12733. Show a "reply" icon for replies, and make them stack directly under their parent. Test Plan: {F4968500} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17981 --- resources/celerity/map.php | 52 +++++++++---------- .../rsrc/js/application/diff/DiffInline.js | 5 ++ .../js/application/diff/ScrollObjective.js | 18 +++++++ .../application/diff/ScrollObjectiveList.js | 9 +++- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 46443e7ecc..1d430926a6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => '06cddcc0', + 'differential.pkg.js' => '886eadff', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,9 +392,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '4478f8ac', - 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', - 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', + 'rsrc/js/application/diff/DiffInline.js' => '1478c3b2', + 'rsrc/js/application/diff/ScrollObjective.js' => '7e8877e7', + 'rsrc/js/application/diff/ScrollObjectiveList.js' => '6120e99a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -779,7 +779,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '4478f8ac', + 'phabricator-diff-inline' => '1478c3b2', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -799,8 +799,8 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '9df4e4e2', - 'phabricator-scroll-objective-list' => '085dd101', + 'phabricator-scroll-objective' => '7e8877e7', + 'phabricator-scroll-objective-list' => '6120e99a', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -956,15 +956,6 @@ return array( 'javelin-stratcom', 'javelin-util', ), - '085dd101' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-scrollbar', - 'phabricator-scroll-objective', - ), '08f4ccc3' => array( 'phui-oi-list-view-css', ), @@ -990,6 +981,9 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), + '1478c3b2' => array( + 'javelin-dom', + ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1203,9 +1197,6 @@ return array( 'javelin-workflow', 'javelin-workboard-controller', ), - '4478f8ac' => array( - 'javelin-dom', - ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1393,6 +1384,15 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '6120e99a' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-scrollbar', + 'phabricator-scroll-objective', + ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', @@ -1501,6 +1501,13 @@ return array( '7e41274a' => array( 'javelin-install', ), + '7e8877e7' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', @@ -1671,13 +1678,6 @@ return array( '9d9685d6' => array( 'phui-oi-list-view-css', ), - '9df4e4e2' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 1f0d503b9f..b0a8202e54 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -223,6 +223,7 @@ JX.install('DiffInline', { var color = 'bluegrey'; var tooltip = this._snippet; var anchor = this._row; + var should_stack = false; if (this._isEditing) { icon = 'fa-star'; @@ -244,6 +245,9 @@ JX.install('DiffInline', { } else if (this._isGhost) { icon = 'fa-comment-o'; color = 'grey'; + } else if (this._replyToCommentPHID) { + icon = 'fa-reply'; + should_stack = true; } objective @@ -251,6 +255,7 @@ JX.install('DiffInline', { .setIcon(icon) .setColor(color) .setTooltip(tooltip) + .setShouldStack(should_stack) .show(); }, diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js index b8fb169914..1e596bd816 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -26,6 +26,7 @@ JX.install('ScrollObjective', { _visible: false, _callback: false, + _stack: false, getNode: function() { if (!this._node) { @@ -104,6 +105,23 @@ JX.install('ScrollObjective', { return this; }, + + /** + * Should this objective always stack immediately under the previous + * objective? + * + * This allows related objectives (like "comment, reply, reply") to be + * rendered in a tight sequence. + */ + setShouldStack: function(stack) { + this._stack = stack; + return this; + }, + + shouldStack: function() { + return this._stack; + }, + show: function() { this._visible = true; return this; diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js index 7aae1fb1b4..ce47877d1a 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js +++ b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js @@ -112,7 +112,8 @@ JX.install('ScrollObjectiveList', { items.push({ offset: offset, - node: objective_node + node: objective_node, + objective: objective }); } @@ -130,7 +131,11 @@ JX.install('ScrollObjectiveList', { offset = item.offset; if (min !== null) { - offset = Math.max(offset, min); + if (item.objective.shouldStack()) { + offset = min; + } else { + offset = Math.max(offset, min); + } } min = offset + 15; From 7d44e7cb4d2f7f0fe5b58ce84c5fedf42dce512f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:59:47 -0700 Subject: [PATCH 149/543] Show the curent selected inline in the objective list Summary: Ref T12733. When an inline is selected, make it stand out so you can see where you are in the document more clearly. Test Plan: {F4968509} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17982 --- resources/celerity/map.php | 24 +++++++++---------- .../js/application/diff/DiffChangesetList.js | 16 ++++++++++++- .../rsrc/js/application/diff/DiffInline.js | 7 ++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1d430926a6..26ebc596f3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => '886eadff', + 'differential.pkg.js' => '1d120743', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,8 +391,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '1478c3b2', + 'rsrc/js/application/diff/DiffChangesetList.js' => '7a184082', + 'rsrc/js/application/diff/DiffInline.js' => '19582231', 'rsrc/js/application/diff/ScrollObjective.js' => '7e8877e7', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '6120e99a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -778,8 +778,8 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '1478c3b2', + 'phabricator-diff-changeset-list' => '7a184082', + 'phabricator-diff-inline' => '19582231', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -981,9 +981,6 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - '1478c3b2' => array( - 'javelin-dom', - ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1003,6 +1000,9 @@ return array( '185bbd53' => array( 'javelin-install', ), + 19582231 => array( + 'javelin-dom', + ), '19f9369b' => array( 'phui-oi-list-view-css', ), @@ -1488,6 +1488,10 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), + '7a184082' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1724,10 +1728,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'a716ca27' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 8e35a92f80..c344c96de8 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -538,10 +538,24 @@ JX.install('DiffChangesetList', { }, _setSelectionState: function(item, manager) { - this._cursorItem = item; + // If we had an inline selected before, we need to update it after + // changing our selection to clear the selected state. Then, update the + // new one to add the selected state. + var old_inline = this.getSelectedInline(); + this._cursorItem = item; this._redrawSelection(manager, true); + var new_inline = this.getSelectedInline(); + + if (old_inline) { + old_inline.updateObjective(); + } + + if (new_inline) { + new_inline.updateObjective(); + } + return this; }, diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index b0a8202e54..bfc2a3e258 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -250,6 +250,13 @@ JX.install('DiffInline', { should_stack = true; } + if (changeset.getChangesetList().getSelectedInline() === this) { + // TODO: Maybe add some other kind of effect here, since we're only + // using color to show this? + color = 'yellow'; + } + + objective .setAnchor(anchor) .setIcon(icon) From bc4ef0d22f3a71f1f6fb10ab4bc85ac954e13c9e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 20 May 2017 09:06:26 -0700 Subject: [PATCH 150/543] Reorder workboard menu items Summary: Adds a divider and better grouping Test Plan: Click on dropdown menu on a workboard Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17984 --- .../PhabricatorProjectBoardViewController.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index da1f0ccb95..51b907cac0 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -713,14 +713,6 @@ final class PhabricatorProjectBoardViewController ->setDisabled(!$can_edit) ->setWorkflow(true); - $background_uri = $this->getApplicationURI("board/{$id}/background/"); - $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-paint-brush') - ->setName(pht('Change Background Color')) - ->setHref($background_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(false); - if ($show_hidden) { $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', null); @@ -738,6 +730,17 @@ final class PhabricatorProjectBoardViewController ->setName($hidden_text) ->setHref($hidden_uri); + $manage_items[] = id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER); + + $background_uri = $this->getApplicationURI("board/{$id}/background/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-paint-brush') + ->setName(pht('Change Background Color')) + ->setHref($background_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(false); + $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-gear') From 11578bfc90090ec5ed96c3ed878695fe12a531a0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 21 May 2017 09:24:26 -0700 Subject: [PATCH 151/543] Add Revisions to User Profiles Summary: Ref T12423. Adds back revisions as a user profile page. I don't want to think about custom profiles for a while. Test Plan: Make some diffs, visit my profile, see diffs. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12423 Differential Revision: https://secure.phabricator.com/D17987 --- src/__phutil_library_map__.php | 4 + .../PhabricatorPeopleApplication.php | 2 + ...bricatorPeopleProfileCommitsController.php | 8 -- ...icatorPeopleProfileRevisionsController.php | 82 +++++++++++++++++++ ...habricatorPeopleProfileTasksController.php | 4 - .../PhabricatorPeopleProfileMenuEngine.php | 11 +++ ...bricatorPeopleRevisionsProfileMenuItem.php | 59 +++++++++++++ 7 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php create mode 100644 src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cbf324c847..f6b66d9ab8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3457,10 +3457,12 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileManageController' => 'applications/people/controller/PhabricatorPeopleProfileManageController.php', 'PhabricatorPeopleProfileMenuEngine' => 'applications/people/engine/PhabricatorPeopleProfileMenuEngine.php', 'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php', + 'PhabricatorPeopleProfileRevisionsController' => 'applications/people/controller/PhabricatorPeopleProfileRevisionsController.php', 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', + 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', 'PhabricatorPeopleTasksProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php', 'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php', @@ -8825,10 +8827,12 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileManageController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileMenuEngine' => 'PhabricatorProfileMenuEngine', 'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleProfileController', + 'PhabricatorPeopleProfileRevisionsController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', + 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleTasksProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator', diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index f2bb70e423..dde82f1d3a 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -70,6 +70,8 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { 'PhabricatorPeopleProfileTasksController', 'commits/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfileCommitsController', + 'revisions/(?P[1-9]\d*)/' => + 'PhabricatorPeopleProfileRevisionsController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfilePictureController', 'manage/(?P[1-9]\d*)/' => diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php index f2adf4b23d..3e1e6de1ee 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php @@ -13,10 +13,6 @@ final class PhabricatorPeopleProfileCommitsController ->needProfile(true) ->needProfileImage(true) ->needAvailability(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$user) { return new Aphront404Response(); @@ -63,10 +59,6 @@ final class PhabricatorPeopleProfileCommitsController ->needAuditRequests(true) ->needCommitData(true) ->needDrafts(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->setLimit(100) ->execute(); diff --git a/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php new file mode 100644 index 0000000000..adb2a60e5d --- /dev/null +++ b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php @@ -0,0 +1,82 @@ +getViewer(); + $id = $request->getURIData('id'); + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfile(true) + ->needProfileImage(true) + ->needAvailability(true) + ->executeOne(); + if (!$user) { + return new Aphront404Response(); + } + + $class = 'PhabricatorDifferentialApplication'; + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + return new Aphront404Response(); + } + + $this->setUser($user); + $title = array(pht('Recent Revisions'), $user->getUsername()); + $header = $this->buildProfileHeader(); + $commits = $this->buildRevisionsView($user); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Recent Revisions')); + $crumbs->setBorder(true); + + $nav = $this->getProfileMenu(); + $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_REVISIONS); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setFooter(array( + $commits, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($nav) + ->appendChild($view); + } + + private function buildRevisionsView(PhabricatorUser $user) { + $viewer = $this->getViewer(); + + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withAuthors(array($user->getPHID())) + ->needFlags(true) + ->needDrafts(true) + ->needReviewers(true) + ->setLimit(100) + ->execute(); + + $list = id(new DifferentialRevisionListView()) + ->setUser($viewer) + ->setNoBox(true) + ->setRevisions($revisions) + ->setNoDataString(pht('No recent revisions.')); + + $object_phids = $list->getRequiredHandlePHIDs(); + $handles = $this->loadViewerHandles($object_phids); + $list->setHandles($handles); + + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Revisions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($list); + + return $view; + } +} diff --git a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php index 52c88d89fa..47cc605bb8 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php @@ -13,10 +13,6 @@ final class PhabricatorPeopleProfileTasksController ->needProfile(true) ->needProfileImage(true) ->needAvailability(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$user) { return new Aphront404Response(); diff --git a/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php b/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php index e5f25eb4a0..6d6d239f5b 100644 --- a/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php +++ b/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php @@ -9,6 +9,7 @@ final class PhabricatorPeopleProfileMenuEngine const ITEM_BADGES = 'people.badges'; const ITEM_TASKS = 'people.tasks'; const ITEM_COMMITS = 'people.commits'; + const ITEM_REVISIONS = 'people.revisions'; protected function isMenuEngineConfigurable() { return false; @@ -52,6 +53,16 @@ final class PhabricatorPeopleProfileMenuEngine ->setMenuItemKey(PhabricatorPeopleTasksProfileMenuItem::MENUITEMKEY); } + $have_differential = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + if ($have_differential) { + $items[] = $this->newItem() + ->setBuiltinKey(self::ITEM_REVISIONS) + ->setMenuItemKey( + PhabricatorPeopleRevisionsProfileMenuItem::MENUITEMKEY); + } + $have_diffusion = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorDiffusionApplication', $viewer); diff --git a/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php new file mode 100644 index 0000000000..499fc1d7f4 --- /dev/null +++ b/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php @@ -0,0 +1,59 @@ +getMenuItemProperty('name'); + + if (strlen($name)) { + return $name; + } + + return $this->getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setPlaceholder($this->getDefaultName()) + ->setValue($config->getMenuItemProperty('name')), + ); + } + + protected function newNavigationMenuItems( + PhabricatorProfileMenuItemConfiguration $config) { + + $user = $config->getProfileObject(); + $id = $user->getID(); + + $item = $this->newItem() + ->setHref("/people/revisions/{$id}/") + ->setName($this->getDisplayName($config)) + ->setIcon('fa-gear'); + + return array( + $item, + ); + } + +} From 179d80dd57f89424bc26097b7deb913f32717960 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Sat, 20 May 2017 15:34:07 -0700 Subject: [PATCH 152/543] Migrate Project lock to modular transactions Summary: See T12673 Test Plan: Unit tests pass. Locked and unlocked a project and saw timeline changes. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17986 --- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectLockController.php | 3 +- .../PhabricatorProjectTransactionEditor.php | 10 +--- .../storage/PhabricatorProjectTransaction.php | 51 +---------------- .../PhabricatorProjectLockTransaction.php | 56 +++++++++++++++++++ 5 files changed, 62 insertions(+), 60 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectLockTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f6b66d9ab8..9b1841639c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3630,6 +3630,7 @@ phutil_register_library_map(array( 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', 'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php', + 'PhabricatorProjectLockTransaction' => 'applications/project/xaction/PhabricatorProjectLockTransaction.php', 'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', @@ -9048,6 +9049,7 @@ phutil_register_library_map(array( 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', 'PhabricatorProjectLockController' => 'PhabricatorProjectController', + 'PhabricatorProjectLockTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/project/controller/PhabricatorProjectLockController.php b/src/applications/project/controller/PhabricatorProjectLockController.php index b9b56bde10..a746911d45 100644 --- a/src/applications/project/controller/PhabricatorProjectLockController.php +++ b/src/applications/project/controller/PhabricatorProjectLockController.php @@ -49,7 +49,8 @@ final class PhabricatorProjectLockController } $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_LOCKED) + ->setTransactionType( + PhabricatorProjectLockTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 2f86afc1ac..47f451b037 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; $types[] = PhabricatorProjectTransaction::TYPE_PARENT; $types[] = PhabricatorProjectTransaction::TYPE_MILESTONE; $types[] = PhabricatorProjectTransaction::TYPE_HASWORKBOARD; @@ -46,8 +45,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_LOCKED: - return (int)$object->getIsMembershipLocked(); case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: return (int)$object->getHasWorkboard(); case PhabricatorProjectTransaction::TYPE_PARENT: @@ -69,7 +66,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: @@ -93,9 +89,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_LOCKED: - $object->setIsMembershipLocked($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_PARENT: $object->setParentProjectPHID($xaction->getNewValue()); return; @@ -129,7 +122,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: @@ -320,7 +312,7 @@ final class PhabricatorProjectTransactionEditor $object, PhabricatorPolicyCapability::CAN_EDIT); return; - case PhabricatorProjectTransaction::TYPE_LOCKED: + case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), newv($this->getEditorApplicationClass(), array()), diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index b0d442ae57..f0a9966646 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_LOCKED = 'project:locked'; const TYPE_PARENT = 'project:parent'; const TYPE_MILESTONE = 'project:milestone'; const TYPE_HASWORKBOARD = 'project:hasworkboard'; @@ -87,16 +86,7 @@ final class PhabricatorProjectTransaction } public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_LOCKED: - if ($new) { - return 'fa-lock'; - } else { - return 'fa-unlock'; - } case self::TYPE_MEMBERS: return 'fa-user'; } @@ -115,18 +105,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_LOCKED: - if ($new) { - return pht( - "%s locked this project's membership.", - $author_handle); - } else { - return pht( - "%s unlocked this project's membership.", - $author_handle); - } - break; - case self::TYPE_MEMBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -196,33 +174,6 @@ final class PhabricatorProjectTransaction return parent::getTitle(); } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - $author_handle = $this->renderHandleLink($author_phid); - $object_handle = $this->renderHandleLink($object_phid); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_LOCKED: - if ($new) { - return pht( - '%s locked membership for %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s unlocked membership for %s.', - $author_handle, - $object_handle); - } - } - - return parent::getTitleForFeed(); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { @@ -247,7 +198,7 @@ final class PhabricatorProjectTransaction } break; case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: - case self::TYPE_LOCKED: + case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: default: $tags[] = self::MAILTAG_OTHER; break; diff --git a/src/applications/project/xaction/PhabricatorProjectLockTransaction.php b/src/applications/project/xaction/PhabricatorProjectLockTransaction.php new file mode 100644 index 0000000000..42551dfb2c --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectLockTransaction.php @@ -0,0 +1,56 @@ +getIsMembershipLocked(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsMembershipLocked($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + "%s locked this project's membership.", + $this->renderAuthor()); + } else { + return pht( + "%s unlocked this project's membership.", + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s locked %s membership.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s unlocked %s membership.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new) { + return 'fa-lock'; + } else { + return 'fa-unlock'; + } + } + +} From 5d966897f14ae9593bf6d85174da565a84150c8b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 22 May 2017 10:03:30 -0700 Subject: [PATCH 153/543] Add Outline tag type to PHUITagView Summary: Adds a new tag type, starts to try to clean up the mess that are PHUITags Test Plan: Review UIExamples. {F4972323} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17991 --- resources/celerity/map.php | 6 +- .../phid/PhabricatorObjectHandle.php | 4 +- .../phid/view/PHUIHandleTagListView.php | 4 +- .../uiexample/examples/PHUITagExample.php | 34 +++- src/view/phui/PHUITagView.php | 53 +++++- webroot/rsrc/css/phui/phui-tag-view.css | 173 ++++++++++++++---- 6 files changed, 216 insertions(+), 58 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 26ebc596f3..0649f9dbc0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '5ffe8b79', + 'core.pkg.css' => '6e9cf0af', 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -172,7 +172,7 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', + 'rsrc/css/phui/phui-tag-view.css' => '3fa7765e', 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', @@ -882,7 +882,7 @@ return array( 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => 'cc4fd402', + 'phui-tag-view-css' => '3fa7765e', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => '313c7f22', 'phui-two-column-view-css' => 'ce9fa0b7', diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index 49255fd9e8..1e6812b53b 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -407,8 +407,8 @@ final class PhabricatorObjectHandle public function renderTag() { return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade($this->getTagColor()) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor($this->getTagColor()) ->setIcon($this->getIcon()) ->setHref($this->getURI()) ->setName($this->getLinkName()); diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index c4ef3762c3..abae359a16 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -121,8 +121,8 @@ final class PHUIHandleTagListView extends AphrontTagView { private function newPlaceholderTag() { return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade(PHUITagView::COLOR_DISABLED) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_DISABLED) ->setSlimShady($this->slim); } diff --git a/src/applications/uiexample/examples/PHUITagExample.php b/src/applications/uiexample/examples/PHUITagExample.php index 155fe4e9da..764caaf717 100644 --- a/src/applications/uiexample/examples/PHUITagExample.php +++ b/src/applications/uiexample/examples/PHUITagExample.php @@ -162,15 +162,15 @@ final class PHUITagExample extends PhabricatorUIExample { $tags = array(); foreach ($shades as $shade) { $tags[] = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade($shade) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor($shade) ->setIcon('fa-tags') ->setName(ucwords($shade)) ->setHref('#'); $tags[] = hsprintf(' '); $tags[] = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade($shade) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor($shade) ->setSlimShady(true) ->setIcon('fa-tags') ->setName(ucwords($shade)) @@ -182,6 +182,26 @@ final class PHUITagExample extends PhabricatorUIExample { ->appendChild($tags) ->addPadding(PHUI::PADDING_LARGE); + $outlines = PHUITagView::getOutlines(); + $tags = array(); + foreach ($outlines as $outline) { + $tags[] = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_OUTLINE) + ->setShade($outline) + ->setName($outline); + $tags[] = hsprintf(' '); + $tags[] = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_OUTLINE) + ->setShade($outline) + ->setSlimShady(true) + ->setName($outline); + $tags[] = hsprintf('

'); + } + + $content5 = id(new PHUIBoxView()) + ->appendChild($tags) + ->addPadding(PHUI::PADDING_LARGE); + $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Inline')) ->appendChild($intro); @@ -202,6 +222,10 @@ final class PHUITagExample extends PhabricatorUIExample { ->setHeaderText(pht('Shades')) ->appendChild($content4); - return array($box, $box1, $box2, $box3, $box4); + $box5 = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Outlines')) + ->appendChild($content5); + + return array($box, $box1, $box2, $box3, $box4, $box5); } } diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index ae80fc1731..e8d30e935e 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -6,6 +6,7 @@ final class PHUITagView extends AphrontTagView { const TYPE_OBJECT = 'object'; const TYPE_STATE = 'state'; const TYPE_SHADE = 'shade'; + const TYPE_OUTLINE = 'outline'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; @@ -15,6 +16,8 @@ final class PHUITagView extends AphrontTagView { const COLOR_VIOLET = 'violet'; const COLOR_GREEN = 'green'; const COLOR_BLACK = 'black'; + const COLOR_SKY = 'sky'; + const COLOR_FIRE = 'fire'; const COLOR_GREY = 'grey'; const COLOR_WHITE = 'white'; const COLOR_PINK = 'pink'; @@ -29,6 +32,7 @@ final class PHUITagView extends AphrontTagView { private $href; private $name; private $phid; + private $color; private $backgroundColor; private $dotColor; private $closed; @@ -41,6 +45,7 @@ final class PHUITagView extends AphrontTagView { $this->type = $type; switch ($type) { case self::TYPE_SHADE: + case self::TYPE_OUTLINE: break; case self::TYPE_OBJECT: $this->setBackgroundColor(self::COLOR_OBJECT); @@ -52,8 +57,14 @@ final class PHUITagView extends AphrontTagView { return $this; } + /* Deprecated, use setColor */ public function setShade($shade) { - $this->shade = $shade; + $this->color = $shade; + return $this; + } + + public function setColor($color) { + $this->color = $color; return $this; } @@ -109,12 +120,16 @@ final class PHUITagView extends AphrontTagView { 'phui-tag-type-'.$this->type, ); - if ($this->shade) { + if ($this->color) { + $classes[] = 'phui-tag-'.$this->color; + } + + if ($this->slimShady) { + $classes[] = 'phui-tag-slim'; + } + + if ($this->type == self::TYPE_SHADE) { $classes[] = 'phui-tag-shade'; - $classes[] = 'phui-tag-shade-'.$this->shade; - if ($this->slimShady) { - $classes[] = 'phui-tag-shade-slim'; - } } if ($this->icon) { @@ -240,6 +255,32 @@ final class PHUITagView extends AphrontTagView { return idx(self::getShadeMap(), $shade, $shade); } + public static function getOutlines() { + return array_keys(self::getOutlineMap()); + } + + public static function getOutlineMap() { + return array( + self::COLOR_RED => pht('Red'), + self::COLOR_ORANGE => pht('Orange'), + self::COLOR_YELLOW => pht('Yellow'), + self::COLOR_BLUE => pht('Blue'), + self::COLOR_INDIGO => pht('Indigo'), + self::COLOR_VIOLET => pht('Violet'), + self::COLOR_GREEN => pht('Green'), + self::COLOR_GREY => pht('Grey'), + self::COLOR_PINK => pht('Pink'), + self::COLOR_SKY => pht('Sky'), + self::COLOR_FIRE => pht('Fire'), + self::COLOR_BLACK => pht('Black'), + self::COLOR_DISABLED => pht('Disabled'), + ); + } + + public static function getOutlineName($outline) { + return idx(self::getOutlineMap(), $outline, $outline); + } + public function setExternal($external) { $this->external = $external; diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index d5b2710d7b..56b5badf8d 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -161,225 +161,233 @@ a.phui-tag-view:hover */ -.phui-tag-shade { +.phui-tag-view.phui-tag-type-shade { font-weight: normal; } -.phui-tag-shade .phui-icon-view { +.phui-tag-view.phui-tag-type-shade .phui-icon-view { font-size: 12px; } -.phui-tag-shade-slim .phui-icon-view { + +/* - Slim Tags ----------------------------------------------------------------- + + A thinner tag for object list, workboards. + +*/ + +.phui-tag-slim .phui-icon-view { font-size: 11px; } -.phui-tag-shade-slim .phui-tag-core { +.phui-tag-slim .phui-tag-core { font-size: {$smallerfontsize}; } + /* - Red -------------------------------------------------------------------- */ -.phui-tag-shade-red .phui-tag-core, +.phui-tag-red .phui-tag-core, .jx-tokenizer-token.red { background: {$sh-redbackground}; border-color: {$sh-lightredborder}; color: {$sh-redtext}; } -.phui-tag-shade-red .phui-icon-view, +.phui-tag-red .phui-icon-view, .jx-tokenizer-token.red .phui-icon-view, .jx-tokenizer-token.red .jx-tokenizer-x { color: {$sh-redicon}; } -a.phui-tag-view:hover.phui-tag-shade-red .phui-tag-core, +a.phui-tag-view:hover.phui-tag-red .phui-tag-core, .jx-tokenizer-token.red:hover { border-color: {$sh-redborder}; } /* - Orange ----------------------------------------------------------------- */ -.phui-tag-shade-orange .phui-tag-core, +.phui-tag-orange .phui-tag-core, .jx-tokenizer-token.orange { background: {$sh-orangebackground}; border-color: {$sh-lightorangeborder}; color: {$sh-orangetext}; } -.phui-tag-shade-orange .phui-icon-view, +.phui-tag-orange .phui-icon-view, .jx-tokenizer-token.orange .phui-icon-view, .jx-tokenizer-token.orange .jx-tokenizer-x { color: {$sh-orangeicon}; } -a.phui-tag-view:hover.phui-tag-shade-orange .phui-tag-core, +a.phui-tag-view:hover.phui-tag-orange .phui-tag-core, .jx-tokenizer-token.orange:hover { border-color: {$sh-orangeborder}; } /* - Yellow ----------------------------------------------------------------- */ -.phui-tag-shade-yellow .phui-tag-core, +.phui-tag-yellow .phui-tag-core, .jx-tokenizer-token.yellow { background: {$sh-yellowbackground}; border-color: {$sh-lightyellowborder}; color: {$sh-yellowtext}; } -.phui-tag-shade-yellow .phui-icon-view, +.phui-tag-yellow .phui-icon-view, .jx-tokenizer-token.yellow .phui-icon-view, .jx-tokenizer-token.yellow .jx-tokenizer-x { color: {$sh-yellowicon}; } -a.phui-tag-view:hover.phui-tag-shade-yellow .phui-tag-core, +a.phui-tag-view:hover.phui-tag-yellow .phui-tag-core, .jx-tokenizer-token.yellow:hover { border-color: {$sh-yellowborder}; } /* - Blue ------------------------------------------------------------------- */ -.phui-tag-shade-blue .phui-tag-core, +.phui-tag-blue .phui-tag-core, .jx-tokenizer-token.blue { background: {$sh-bluebackground}; border-color: {$sh-lightblueborder}; color: {$sh-bluetext}; } -.phui-tag-shade-blue .phui-icon-view, +.phui-tag-blue .phui-icon-view, .jx-tokenizer-token.blue .phui-icon-view, .jx-tokenizer-token.blue .jx-tokenizer-x { color: {$sh-blueicon}; } -a.phui-tag-view:hover.phui-tag-shade-blue .phui-tag-core, +a.phui-tag-view:hover.phui-tag-blue .phui-tag-core, .jx-tokenizer-token.blue:hover { border-color: {$sh-blueborder}; } /* - Sky ------------------------------------------------------------------- */ -.phui-tag-shade-sky .phui-tag-core, +.phui-tag-sky .phui-tag-core, .jx-tokenizer-token.sky { background: #E0F0FA; border-color: {$sh-lightblueborder}; color: {$sh-bluetext}; } -.phui-tag-shade-sky .phui-icon-view, +.phui-tag-sky .phui-icon-view, .jx-tokenizer-token.sky .phui-icon-view, .jx-tokenizer-token.sky .jx-tokenizer-x { color: {$sh-blueicon}; } -a.phui-tag-view:hover.phui-tag-shade-sky .phui-tag-core, +a.phui-tag-view:hover.phui-tag-sky .phui-tag-core, .jx-tokenizer-token.sky:hover { border-color: {$sh-blueborder}; } /* - Indigo ----------------------------------------------------------------- */ -.phui-tag-shade-indigo .phui-tag-core, +.phui-tag-indigo .phui-tag-core, .jx-tokenizer-token.indigo { background: {$sh-indigobackground}; border-color: {$sh-lightindigoborder}; color: {$sh-indigotext}; } -.phui-tag-shade-indigo .phui-icon-view, +.phui-tag-indigo .phui-icon-view, .jx-tokenizer-token.indigo .phui-icon-view, .jx-tokenizer-token.indigo .jx-tokenizer-x { color: {$sh-indigoicon}; } -a.phui-tag-view:hover.phui-tag-shade-indigo .phui-tag-core, +a.phui-tag-view:hover.phui-tag-indigo .phui-tag-core, .jx-tokenizer-token.indigo:hover { border-color: {$sh-indigoborder}; } /* - Green ------------------------------------------------------------------ */ -.phui-tag-shade-green .phui-tag-core, +.phui-tag-green .phui-tag-core, .jx-tokenizer-token.green { background: {$sh-greenbackground}; border-color: {$sh-lightgreenborder}; color: {$sh-greentext}; } -.phui-tag-shade-green .phui-icon-view, +.phui-tag-green .phui-icon-view, .jx-tokenizer-token.green .phui-icon-view, .jx-tokenizer-token.green .jx-tokenizer-x { color: {$sh-greenicon}; } -a.phui-tag-view:hover.phui-tag-shade-green .phui-tag-core, +a.phui-tag-view:hover.phui-tag-green .phui-tag-core, .jx-tokenizer-token.green:hover { border-color: {$sh-greenborder}; } /* - Violet ----------------------------------------------------------------- */ -.phui-tag-shade-violet .phui-tag-core, +.phui-tag-violet .phui-tag-core, .jx-tokenizer-token.violet { background: {$sh-violetbackground}; border-color: {$sh-lightvioletborder}; color: {$sh-violettext}; } -.phui-tag-shade-violet .phui-icon-view, +.phui-tag-violet .phui-icon-view, .jx-tokenizer-token.violet .phui-icon-view, .jx-tokenizer-token.violet .jx-tokenizer-x { color: {$sh-violeticon}; } -a.phui-tag-view:hover.phui-tag-shade-violet .phui-tag-core, +a.phui-tag-view:hover.phui-tag-violet .phui-tag-core, .jx-tokenizer-token.violet:hover { border-color: {$sh-violetborder}; } /* - Pink ------------------------------------------------------------------- */ -.phui-tag-shade-pink .phui-tag-core, +.phui-tag-pink .phui-tag-core, .jx-tokenizer-token.pink { background: {$sh-pinkbackground}; border-color: {$sh-lightpinkborder}; color: {$sh-pinktext}; } -.phui-tag-shade-pink .phui-icon-view, +.phui-tag-pink .phui-icon-view, .jx-tokenizer-token.pink .phui-icon-view, .jx-tokenizer-token.pink .jx-tokenizer-x { color: {$sh-pinkicon}; } -a.phui-tag-view:hover.phui-tag-shade-pink .phui-tag-core, +a.phui-tag-view:hover.phui-tag-pink .phui-tag-core, .jx-tokenizer-token.pink:hover { border-color: {$sh-pinkborder}; } /* - Grey ------------------------------------------------------------------- */ -.phui-tag-shade-grey .phui-tag-core, +.phui-tag-grey .phui-tag-core, .jx-tokenizer-token.grey { background: {$sh-greybackground}; border-color: {$sh-lightgreyborder}; color: {$sh-greytext}; } -.phui-tag-shade-grey .phui-icon-view, +.phui-tag-grey .phui-icon-view, .jx-tokenizer-token.grey .phui-icon-view, .jx-tokenizer-token.grey .jx-tokenizer-x { color: {$sh-greyicon}; } -a.phui-tag-view:hover.phui-tag-shade-grey .phui-tag-core, +a.phui-tag-view:hover.phui-tag-grey .phui-tag-core, .jx-tokenizer-token.grey:hover { border-color: {$sh-greyborder}; } /* - Checkered -------------------------------------------------------------- */ -.phui-tag-shade-checkered .phui-tag-core, +.phui-tag-checkered .phui-tag-core, .jx-tokenizer-token.checkered { background: url(/rsrc/image/checker_lighter.png); border-style: dashed; @@ -388,13 +396,13 @@ a.phui-tag-view:hover.phui-tag-shade-grey .phui-tag-core, text-shadow: 1px 1px #fff; } -.phui-tag-shade-checkered .phui-icon-view, +.phui-tag-checkered .phui-icon-view, .jx-tokenizer-token.checkered .phui-icon-view, .jx-tokenizer-token.checkered .jx-tokenizer-x { color: {$sh-greyicon}; } -a.phui-tag-view:hover.phui-tag-shade-checkered .phui-tag-core, +a.phui-tag-view:hover.phui-tag-checkered .phui-tag-core, .jx-tokenizer-token.checkered:hover { border-style: solid; border-color: {$sh-greyborder}; @@ -402,16 +410,101 @@ a.phui-tag-view:hover.phui-tag-shade-checkered .phui-tag-core, /* - Disabled --------------------------------------------------------------- */ -.phui-tag-shade-disabled .phui-tag-core { +.phui-tag-disabled .phui-tag-core { background-color: {$sh-disabledbackground}; border-color: {$sh-lightdisabledborder}; color: {$sh-disabledtext}; } -.phui-tag-shade-disabled .phui-icon-view { +.phui-tag-disabled .phui-icon-view { color: {$sh-disabledicon}; } -a.phui-tag-view:hover.phui-tag-shade-disabled .phui-tag-core { +a.phui-tag-view:hover.phui-tag-disabled .phui-tag-core { border-color: {$sh-disabledborder}; } + +/* - Outline Tags -------------------------------------------------------------- + + Basic Tag with a bold border and white background + +*/ + +.phui-tag-type-outline { + text-transform: uppercase; + font-weight: normal; +} + +.phui-tag-view.phui-tag-type-outline .phui-tag-core { + background: #fff; + padding: 0 6px 1px 6px; +} + +.phui-tag-slim.phui-tag-type-outline .phui-tag-core { + font-size: {$smallestfontsize}; +} + +.phui-tag-type-outline.phui-tag-red .phui-tag-core { + color: {$red}; + border-color: {$red}; +} + +.phui-tag-type-outline.phui-tag-orange .phui-tag-core { + color: {$orange}; + border-color: {$orange}; +} + +.phui-tag-type-outline.phui-tag-yellow .phui-tag-core { + color: {$yellow}; + border-color: {$yellow}; +} + +.phui-tag-type-outline.phui-tag-green .phui-tag-core { + color: {$green}; + border-color: {$green}; +} + +.phui-tag-type-outline.phui-tag-blue .phui-tag-core { + color: {$blue}; + border-color: {$blue}; +} + +.phui-tag-type-outline.phui-tag-indigo .phui-tag-core { + color: {$indigo}; + border-color: {$indigo}; +} + +.phui-tag-type-outline.phui-tag-violet .phui-tag-core { + color: {$violet}; + border-color: {$violet}; +} + +.phui-tag-type-outline.phui-tag-grey .phui-tag-core { + color: {$bluetext}; + border-color: {$bluetext}; +} + +.phui-tag-type-outline.phui-tag-disabled .phui-tag-core { + color: {$lightgreytext}; + border-color: {$lightgreytext}; +} + +.phui-tag-type-outline.phui-tag-pink .phui-tag-core { + color: {$pink}; + border-color: {$pink}; +} + +.phui-tag-type-outline.phui-tag-sky .phui-tag-core { + color: {$sky}; + border-color: {$sky}; +} + +.phui-tag-type-outline.phui-tag-fire .phui-tag-core { + color: {$fire}; + border-color: {$fire}; +} + +.phui-tag-type-outline.phui-tag-black .phui-tag-core { + color: #000; + border-color: #000; +} From 03d4d674f8033300be2c55feb1bf415f3cd7b25f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 22 May 2017 10:47:19 -0700 Subject: [PATCH 154/543] Clean up some colors missing from PHUITagView type shade Summary: Grep for phui-tag-shade and verify we're no longer calling shade-color directly. Test Plan: Search, workboard, story points, etc. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17993 --- resources/celerity/map.php | 22 +++++++++---------- ...PhabricatorCalendarEventViewController.php | 2 +- .../controller/DiffusionController.php | 2 +- .../ManiphestTaskDetailController.php | 2 +- .../css/application/search/search-results.css | 2 +- webroot/rsrc/css/phui/phui-header-view.css | 2 +- .../application/projects/WorkboardColumn.js | 6 ++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0649f9dbc0..2415f90ff1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '6e9cf0af', + 'core.pkg.css' => '5387f8b6', 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -109,7 +109,7 @@ return array( 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', - 'rsrc/css/application/search/search-results.css' => 'f87d23ad', + 'rsrc/css/application/search/search-results.css' => '8f8e08ed', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', @@ -154,7 +154,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'e082678d', + 'rsrc/css/phui/phui-header-view.css' => 'a3d1aecd', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '12b387a1', @@ -433,7 +433,7 @@ return array( 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/WorkboardBoard.js' => '8935deef', 'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f', - 'rsrc/js/application/projects/WorkboardColumn.js' => '21df4ff5', + 'rsrc/js/application/projects/WorkboardColumn.js' => '758b4758', 'rsrc/js/application/projects/WorkboardController.js' => '26167537', 'rsrc/js/application/projects/behavior-project-boards.js' => '4250a34e', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', @@ -754,7 +754,7 @@ return array( 'javelin-websocket' => '3ffe32d6', 'javelin-workboard-board' => '8935deef', 'javelin-workboard-card' => 'c587b80f', - 'javelin-workboard-column' => '21df4ff5', + 'javelin-workboard-column' => '758b4758', 'javelin-workboard-controller' => '26167537', 'javelin-workflow' => '1e911d0f', 'maniphest-batch-editor' => 'b0f0b6d5', @@ -801,7 +801,7 @@ return array( 'phabricator-remarkup-css' => 'd1a5e11e', 'phabricator-scroll-objective' => '7e8877e7', 'phabricator-scroll-objective-list' => '6120e99a', - 'phabricator-search-results-css' => 'f87d23ad', + 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', @@ -856,7 +856,7 @@ return array( 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'e082678d', + 'phui-header-view-css' => 'a3d1aecd', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', @@ -1060,10 +1060,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '21df4ff5' => array( - 'javelin-install', - 'javelin-workboard-card', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1464,6 +1460,10 @@ return array( 'javelin-vector', 'javelin-dom', ), + '758b4758' => array( + 'javelin-install', + 'javelin-workboard-card', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 91d3a9e336..3ba3aa1e70 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -125,7 +125,7 @@ final class PhabricatorCalendarEventViewController ->setName(pht('Imported')) ->setIcon('fa-download') ->setHref($event->getImportSource()->getURI()) - ->setShade('orange')); + ->setColor(PHUITagView::COLOR_ORANGE)); } foreach ($this->buildRSVPActions($event) as $action) { diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index a018eb3dbb..a296761bdf 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -332,7 +332,7 @@ abstract class DiffusionController extends PhabricatorController { $tag = id(new PHUITagView()) ->setName($commit) - ->setShade('indigo') + ->setColor(PHUITagView::COLOR_INDIGO) ->setType(PHUITagView::TYPE_SHADE); return $tag; diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 5384825de8..7859599a2f 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -233,7 +233,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ManiphestTaskPoints::getPointsLabel()); $tag = id(new PHUITagView()) ->setName($points_name) - ->setShade('blue') + ->setColor(PHUITagView::COLOR_BLUE) ->setType(PHUITagView::TYPE_SHADE); $view->addTag($tag); diff --git a/webroot/rsrc/css/application/search/search-results.css b/webroot/rsrc/css/application/search/search-results.css index a1837fa391..0f0a31a1b9 100644 --- a/webroot/rsrc/css/application/search/search-results.css +++ b/webroot/rsrc/css/application/search/search-results.css @@ -26,6 +26,6 @@ margin: 0 2px; } -.phui-fulltext-tokens .phui-tag-view.phui-tag-shade-grey { +.phui-fulltext-tokens .phui-tag-view.phui-tag-grey { opacity: 0.5; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 5c24d5575e..470e821544 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -353,7 +353,7 @@ body .phui-header-shell.phui-bleed-header vertical-align: top; } -.phui-header-view .phui-tag-shade-indigo a { +.phui-header-view .phui-tag-indigo a { color: {$sh-indigotext}; } diff --git a/webroot/rsrc/js/application/projects/WorkboardColumn.js b/webroot/rsrc/js/application/projects/WorkboardColumn.js index 81fba84077..9973648593 100644 --- a/webroot/rsrc/js/application/projects/WorkboardColumn.js +++ b/webroot/rsrc/js/application/projects/WorkboardColumn.js @@ -285,9 +285,9 @@ JX.install('WorkboardColumn', { JX.DOM.alterClass(panel, 'project-panel-over-limit', over_limit); var color_map = { - 'phui-tag-shade-disabled': (total_points === 0), - 'phui-tag-shade-blue': (total_points > 0 && !over_limit), - 'phui-tag-shade-red': (over_limit) + 'phui-tag-disabled': (total_points === 0), + 'phui-tag-blue': (total_points > 0 && !over_limit), + 'phui-tag-red': (over_limit) }; for (var c in color_map) { From 1069c2bff9d3686465fd2802b6165bcbc2b2d8c0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 22 May 2017 08:45:52 -0700 Subject: [PATCH 155/543] Convert Nuance Items to Modular Transactions Summary: Ref T12738. Moves existing non-modular transactions to modular transactions. Some of these are pretty flimsy, but a lot of them don't actually work or do anything in Nuance yet anyway. Test Plan: Gently poked Nuance, nothing fell over. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D17990 --- src/__phutil_library_map__.php | 20 +++- .../nuance/editor/NuanceItemEditor.php | 94 ------------------- .../nuance/item/NuanceItemType.php | 2 +- .../nuance/source/NuanceSourceDefinition.php | 7 +- .../nuance/storage/NuanceItemTransaction.php | 64 +------------ .../nuance/storage/NuanceQueueTransaction.php | 4 + .../storage/NuanceSourceTransaction.php | 4 + .../nuance/storage/NuanceTransaction.php | 2 +- .../xaction/NuanceItemCommandTransaction.php | 22 +++++ .../xaction/NuanceItemOwnerTransaction.php | 27 ++++++ .../xaction/NuanceItemPropertyTransaction.php | 27 ++++++ .../xaction/NuanceItemQueueTransaction.php | 25 +++++ .../NuanceItemRequestorTransaction.php | 16 ++++ .../xaction/NuanceItemSourceTransaction.php | 16 ++++ .../xaction/NuanceItemTransactionType.php | 4 + .../xaction/NuanceQueueTransactionType.php | 4 + .../xaction/NuanceSourceTransactionType.php | 4 + 17 files changed, 180 insertions(+), 162 deletions(-) create mode 100644 src/applications/nuance/xaction/NuanceItemCommandTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceItemOwnerTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceItemPropertyTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceItemQueueTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceItemRequestorTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceItemSourceTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceItemTransactionType.php create mode 100644 src/applications/nuance/xaction/NuanceQueueTransactionType.php create mode 100644 src/applications/nuance/xaction/NuanceSourceTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9b1841639c..9831b24686 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1620,16 +1620,23 @@ phutil_register_library_map(array( 'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php', 'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php', 'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php', + 'NuanceItemCommandTransaction' => 'applications/nuance/xaction/NuanceItemCommandTransaction.php', 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', 'NuanceItemManageController' => 'applications/nuance/controller/NuanceItemManageController.php', + 'NuanceItemOwnerTransaction' => 'applications/nuance/xaction/NuanceItemOwnerTransaction.php', 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', + 'NuanceItemPropertyTransaction' => 'applications/nuance/xaction/NuanceItemPropertyTransaction.php', 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', + 'NuanceItemQueueTransaction' => 'applications/nuance/xaction/NuanceItemQueueTransaction.php', + 'NuanceItemRequestorTransaction' => 'applications/nuance/xaction/NuanceItemRequestorTransaction.php', 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', + 'NuanceItemSourceTransaction' => 'applications/nuance/xaction/NuanceItemSourceTransaction.php', 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', + 'NuanceItemTransactionType' => 'applications/nuance/xaction/NuanceItemTransactionType.php', 'NuanceItemType' => 'applications/nuance/item/NuanceItemType.php', 'NuanceItemUpdateWorker' => 'applications/nuance/worker/NuanceItemUpdateWorker.php', 'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php', @@ -1651,6 +1658,7 @@ phutil_register_library_map(array( 'NuanceQueueTransaction' => 'applications/nuance/storage/NuanceQueueTransaction.php', 'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php', 'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php', + 'NuanceQueueTransactionType' => 'applications/nuance/xaction/NuanceQueueTransactionType.php', 'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php', 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', @@ -1672,6 +1680,7 @@ phutil_register_library_map(array( 'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php', 'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php', 'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php', + 'NuanceSourceTransactionType' => 'applications/nuance/xaction/NuanceSourceTransactionType.php', 'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php', 'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php', 'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php', @@ -6720,16 +6729,23 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'NuanceItemCommandQuery' => 'NuanceQuery', + 'NuanceItemCommandTransaction' => 'NuanceItemTransactionType', 'NuanceItemController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceItemListController' => 'NuanceItemController', 'NuanceItemManageController' => 'NuanceController', + 'NuanceItemOwnerTransaction' => 'NuanceItemTransactionType', 'NuanceItemPHIDType' => 'PhabricatorPHIDType', + 'NuanceItemPropertyTransaction' => 'NuanceItemTransactionType', 'NuanceItemQuery' => 'NuanceQuery', + 'NuanceItemQueueTransaction' => 'NuanceItemTransactionType', + 'NuanceItemRequestorTransaction' => 'NuanceItemTransactionType', 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'NuanceItemSourceTransaction' => 'NuanceItemTransactionType', 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceItemTransactionType' => 'PhabricatorModularTransactionType', 'NuanceItemType' => 'Phobject', 'NuanceItemUpdateWorker' => 'NuanceWorker', 'NuanceItemViewController' => 'NuanceController', @@ -6755,6 +6771,7 @@ phutil_register_library_map(array( 'NuanceQueueTransaction' => 'NuanceTransaction', 'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceQueueTransactionType' => 'PhabricatorModularTransactionType', 'NuanceQueueViewController' => 'NuanceQueueController', 'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'NuanceSource' => array( @@ -6781,8 +6798,9 @@ phutil_register_library_map(array( 'NuanceSourceTransaction' => 'NuanceTransaction', 'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceSourceTransactionType' => 'PhabricatorModularTransactionType', 'NuanceSourceViewController' => 'NuanceSourceController', - 'NuanceTransaction' => 'PhabricatorApplicationTransaction', + 'NuanceTransaction' => 'PhabricatorModularTransaction', 'NuanceWorker' => 'PhabricatorWorker', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', diff --git a/src/applications/nuance/editor/NuanceItemEditor.php b/src/applications/nuance/editor/NuanceItemEditor.php index 45288052c2..b41ca77563 100644 --- a/src/applications/nuance/editor/NuanceItemEditor.php +++ b/src/applications/nuance/editor/NuanceItemEditor.php @@ -14,104 +14,10 @@ final class NuanceItemEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = NuanceItemTransaction::TYPE_OWNER; - $types[] = NuanceItemTransaction::TYPE_SOURCE; - $types[] = NuanceItemTransaction::TYPE_REQUESTOR; - $types[] = NuanceItemTransaction::TYPE_PROPERTY; - $types[] = NuanceItemTransaction::TYPE_QUEUE; - $types[] = NuanceItemTransaction::TYPE_COMMAND; - - $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - return $object->getRequestorPHID(); - case NuanceItemTransaction::TYPE_SOURCE: - return $object->getSourcePHID(); - case NuanceItemTransaction::TYPE_OWNER: - return $object->getOwnerPHID(); - case NuanceItemTransaction::TYPE_QUEUE: - return $object->getQueuePHID(); - case NuanceItemTransaction::TYPE_PROPERTY: - $key = $xaction->getMetadataValue( - NuanceItemTransaction::PROPERTY_KEY); - return $object->getNuanceProperty($key); - case NuanceItemTransaction::TYPE_COMMAND: - return null; - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - case NuanceItemTransaction::TYPE_SOURCE: - case NuanceItemTransaction::TYPE_OWNER: - case NuanceItemTransaction::TYPE_PROPERTY: - case NuanceItemTransaction::TYPE_QUEUE: - case NuanceItemTransaction::TYPE_COMMAND: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - $object->setRequestorPHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_SOURCE: - $object->setSourcePHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_OWNER: - $object->setOwnerPHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_QUEUE: - $object->setQueuePHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_PROPERTY: - $key = $xaction->getMetadataValue( - NuanceItemTransaction::PROPERTY_KEY); - $object->setNuanceProperty($key, $xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_COMMAND: - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - case NuanceItemTransaction::TYPE_SOURCE: - case NuanceItemTransaction::TYPE_OWNER: - case NuanceItemTransaction::TYPE_PROPERTY: - case NuanceItemTransaction::TYPE_QUEUE: - case NuanceItemTransaction::TYPE_COMMAND: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - } diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index 74555494d0..ff89108f4a 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -115,7 +115,7 @@ abstract class NuanceItemType } $xaction = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_COMMAND) + ->setTransactionType(NuanceItemCommandTransaction::TRANSACTIONTYPE) ->setNewValue( array( 'command' => $command->getCommand(), diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 99d79f8f31..0330aa5c7e 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -162,18 +162,19 @@ abstract class NuanceSourceDefinition extends Phobject { $xactions = array(); $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) + ->setTransactionType(NuanceItemSourceTransaction::TRANSACTIONTYPE) ->setNewValue($source->getPHID()); // TODO: Eventually, apply real routing rules. For now, just put everything // in the default queue for the source. $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_QUEUE) + ->setTransactionType(NuanceItemQueueTransaction::TRANSACTIONTYPE) ->setNewValue($source->getDefaultQueuePHID()); + // TODO: Maybe this should all be modular transactions now? foreach ($properties as $key => $property) { $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_PROPERTY) + ->setTransactionType(NuanceItemPropertyTransaction::TRANSACTIONTYPE) ->setMetadataValue(NuanceItemTransaction::PROPERTY_KEY, $key) ->setNewValue($property); } diff --git a/src/applications/nuance/storage/NuanceItemTransaction.php b/src/applications/nuance/storage/NuanceItemTransaction.php index 77471fcdb9..5579183a66 100644 --- a/src/applications/nuance/storage/NuanceItemTransaction.php +++ b/src/applications/nuance/storage/NuanceItemTransaction.php @@ -5,13 +5,6 @@ final class NuanceItemTransaction const PROPERTY_KEY = 'property.key'; - const TYPE_OWNER = 'nuance.item.owner'; - const TYPE_REQUESTOR = 'nuance.item.requestor'; - const TYPE_SOURCE = 'nuance.item.source'; - const TYPE_PROPERTY = 'nuance.item.property'; - const TYPE_QUEUE = 'nuance.item.queue'; - const TYPE_COMMAND = 'nuance.item.command'; - public function getApplicationTransactionType() { return NuanceItemPHIDType::TYPECONST; } @@ -20,61 +13,8 @@ final class NuanceItemTransaction return new NuanceItemTransactionComment(); } - public function shouldHide() { - $old = $this->getOldValue(); - $type = $this->getTransactionType(); - - switch ($type) { - case self::TYPE_REQUESTOR: - case self::TYPE_SOURCE: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $phids = parent::getRequiredHandlePHIDs(); - switch ($type) { - case self::TYPE_QUEUE: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $author_phid = $this->getAuthorPHID(); - - switch ($type) { - case self::TYPE_QUEUE: - return pht( - '%s routed this item to the %s queue.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - case self::TYPE_COMMAND: - // TODO: Give item types a chance to render this properly. - return pht( - '%s applied command "%s" to this item.', - $this->renderHandleLink($author_phid), - idx($new, 'command')); - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'NuanceItemTransactionType'; } } diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php index 44309c2ae6..801ce62d20 100644 --- a/src/applications/nuance/storage/NuanceQueueTransaction.php +++ b/src/applications/nuance/storage/NuanceQueueTransaction.php @@ -12,6 +12,10 @@ final class NuanceQueueTransaction extends NuanceTransaction { return new NuanceQueueTransactionComment(); } + public function getBaseTransactionClass() { + return 'NuanceSourceTransactionType'; + } + public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); diff --git a/src/applications/nuance/storage/NuanceSourceTransaction.php b/src/applications/nuance/storage/NuanceSourceTransaction.php index 0b18c81184..5d70376ce3 100644 --- a/src/applications/nuance/storage/NuanceSourceTransaction.php +++ b/src/applications/nuance/storage/NuanceSourceTransaction.php @@ -14,6 +14,10 @@ final class NuanceSourceTransaction return new NuanceSourceTransactionComment(); } + public function getBaseTransactionClass() { + return 'NuanceSourceTransactionType'; + } + public function shouldHide() { $old = $this->getOldValue(); $new = $this->getNewValue(); diff --git a/src/applications/nuance/storage/NuanceTransaction.php b/src/applications/nuance/storage/NuanceTransaction.php index 5e9ae656ef..0cdb98ff27 100644 --- a/src/applications/nuance/storage/NuanceTransaction.php +++ b/src/applications/nuance/storage/NuanceTransaction.php @@ -1,7 +1,7 @@ renderAuthor()); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemOwnerTransaction.php b/src/applications/nuance/xaction/NuanceItemOwnerTransaction.php new file mode 100644 index 0000000000..381371c8f3 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemOwnerTransaction.php @@ -0,0 +1,27 @@ +getOwnerPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setOwnerPHID($value); + } + + public function getTitle() { + + // TODO: Assign, unassign strings probably need variants. + + return pht( + '%s reassigned this item from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($this->getOldValue()), + $this->renderHandle($this->getNewValue())); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php b/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php new file mode 100644 index 0000000000..76724843de --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php @@ -0,0 +1,27 @@ +getMetadataValue($property_key); + return $object->getNuanceProperty($key); + } + + public function applyInternalEffects($object, $value) { + $property_key = NuanceItemTransaction::PROPERTY_KEY; + $key = $this->getMetadataValue($property_key); + + $object->setNuanceProperty($key, $value); + } + + public function getTitle() { + return pht( + '%s set a property on this item.', + $this->renderAuthor()); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemQueueTransaction.php b/src/applications/nuance/xaction/NuanceItemQueueTransaction.php new file mode 100644 index 0000000000..8ccbdb7797 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemQueueTransaction.php @@ -0,0 +1,25 @@ +getQueuePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setQueuePHID($value); + } + + public function getTitle() { + return pht( + '%s rerouted this item from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($this->getOldValue()), + $this->renderHandle($this->getNewValue())); + } + + +} diff --git a/src/applications/nuance/xaction/NuanceItemRequestorTransaction.php b/src/applications/nuance/xaction/NuanceItemRequestorTransaction.php new file mode 100644 index 0000000000..a19ef45197 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemRequestorTransaction.php @@ -0,0 +1,16 @@ +getRequestorPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setRequestorPHID($value); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemSourceTransaction.php b/src/applications/nuance/xaction/NuanceItemSourceTransaction.php new file mode 100644 index 0000000000..e637485990 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemSourceTransaction.php @@ -0,0 +1,16 @@ +getSourcePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setSourcePHID($value); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemTransactionType.php b/src/applications/nuance/xaction/NuanceItemTransactionType.php new file mode 100644 index 0000000000..cc03b31ac4 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemTransactionType.php @@ -0,0 +1,4 @@ + Date: Mon, 22 May 2017 10:29:14 -0700 Subject: [PATCH 156/543] Convert Nuance queues to modular transactions Summary: Ref T12738. Swaps queues over. Also fixes a typo from D17990. Test Plan: Renamed a queue. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D17992 --- src/__phutil_library_map__.php | 2 + .../nuance/editor/NuanceQueueEditEngine.php | 2 +- .../nuance/editor/NuanceQueueEditor.php | 79 ------------------- .../nuance/storage/NuanceQueueTransaction.php | 26 +----- .../xaction/NuanceQueueNameTransaction.php | 47 +++++++++++ 5 files changed, 51 insertions(+), 105 deletions(-) create mode 100644 src/applications/nuance/xaction/NuanceQueueNameTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9831b24686..7cbf6f7216 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1652,6 +1652,7 @@ phutil_register_library_map(array( 'NuanceQueueEditEngine' => 'applications/nuance/editor/NuanceQueueEditEngine.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', 'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php', + 'NuanceQueueNameTransaction' => 'applications/nuance/xaction/NuanceQueueNameTransaction.php', 'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php', 'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php', 'NuanceQueueSearchEngine' => 'applications/nuance/query/NuanceQueueSearchEngine.php', @@ -6765,6 +6766,7 @@ phutil_register_library_map(array( 'NuanceQueueEditEngine' => 'PhabricatorEditEngine', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceQueueListController' => 'NuanceQueueController', + 'NuanceQueueNameTransaction' => 'NuanceQueueTransactionType', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', 'NuanceQueueQuery' => 'NuanceQuery', 'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/nuance/editor/NuanceQueueEditEngine.php b/src/applications/nuance/editor/NuanceQueueEditEngine.php index 049916adbe..12f5c7b517 100644 --- a/src/applications/nuance/editor/NuanceQueueEditEngine.php +++ b/src/applications/nuance/editor/NuanceQueueEditEngine.php @@ -75,7 +75,7 @@ final class NuanceQueueEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the queue.')) - ->setTransactionType(NuanceQueueTransaction::TYPE_NAME) + ->setTransactionType(NuanceQueueNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); diff --git a/src/applications/nuance/editor/NuanceQueueEditor.php b/src/applications/nuance/editor/NuanceQueueEditor.php index cb3ead2417..2a18188f98 100644 --- a/src/applications/nuance/editor/NuanceQueueEditor.php +++ b/src/applications/nuance/editor/NuanceQueueEditor.php @@ -14,89 +14,10 @@ final class NuanceQueueEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = NuanceQueueTransaction::TYPE_NAME; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - return $object->getName(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case NuanceQueueTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('A queue must have a name.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - } diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php index 801ce62d20..1561db80e5 100644 --- a/src/applications/nuance/storage/NuanceQueueTransaction.php +++ b/src/applications/nuance/storage/NuanceQueueTransaction.php @@ -2,8 +2,6 @@ final class NuanceQueueTransaction extends NuanceTransaction { - const TYPE_NAME = 'nuance.queue.name'; - public function getApplicationTransactionType() { return NuanceQueuePHIDType::TYPECONST; } @@ -13,29 +11,7 @@ final class NuanceQueueTransaction extends NuanceTransaction { } public function getBaseTransactionClass() { - return 'NuanceSourceTransactionType'; + return 'NuanceQueueTransactionType'; } - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $author_phid = $this->getAuthorPHID(); - - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this queue.', - $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - return pht( - '%s renamed this queue from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); - } } diff --git a/src/applications/nuance/xaction/NuanceQueueNameTransaction.php b/src/applications/nuance/xaction/NuanceQueueNameTransaction.php new file mode 100644 index 0000000000..e9a9efcd93 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceQueueNameTransaction.php @@ -0,0 +1,47 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this queue from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Queues must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Queue names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} From 3a0a086a094c75be0bd3347be2c51e5f9a456533 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 22 May 2017 10:52:44 -0700 Subject: [PATCH 157/543] Convert Nuance sources to modular transactions Summary: Ref T12738. Update sources to modular transactions. Test Plan: Created and edited a source. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D17994 --- src/__phutil_library_map__.php | 4 + .../nuance/editor/NuanceSourceEditEngine.php | 5 +- .../nuance/editor/NuanceSourceEditor.php | 101 ------------------ .../storage/NuanceSourceTransaction.php | 62 ----------- .../NuanceSourceDefaultQueueTransaction.php | 42 ++++++++ .../xaction/NuanceSourceNameTransaction.php | 47 ++++++++ 6 files changed, 96 insertions(+), 165 deletions(-) create mode 100644 src/applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php create mode 100644 src/applications/nuance/xaction/NuanceSourceNameTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7cbf6f7216..7088012879 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1666,6 +1666,7 @@ phutil_register_library_map(array( 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', 'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', + 'NuanceSourceDefaultQueueTransaction' => 'applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php', 'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php', @@ -1675,6 +1676,7 @@ phutil_register_library_map(array( 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', 'NuanceSourceNameNgrams' => 'applications/nuance/storage/NuanceSourceNameNgrams.php', + 'NuanceSourceNameTransaction' => 'applications/nuance/xaction/NuanceSourceNameTransaction.php', 'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php', 'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php', 'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php', @@ -6785,6 +6787,7 @@ phutil_register_library_map(array( 'NuanceSourceActionController' => 'NuanceController', 'NuanceSourceController' => 'NuanceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'NuanceSourceDefaultQueueTransaction' => 'NuanceSourceTransactionType', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase', @@ -6794,6 +6797,7 @@ phutil_register_library_map(array( 'NuanceSourceListController' => 'NuanceSourceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceNameNgrams' => 'PhabricatorSearchNgrams', + 'NuanceSourceNameTransaction' => 'NuanceSourceTransactionType', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', 'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/nuance/editor/NuanceSourceEditEngine.php b/src/applications/nuance/editor/NuanceSourceEditEngine.php index 18d27863ac..eac751c3a5 100644 --- a/src/applications/nuance/editor/NuanceSourceEditEngine.php +++ b/src/applications/nuance/editor/NuanceSourceEditEngine.php @@ -96,14 +96,15 @@ final class NuanceSourceEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the source.')) - ->setTransactionType(NuanceSourceTransaction::TYPE_NAME) + ->setTransactionType(NuanceSourceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorDatasourceEditField()) ->setKey('defaultQueue') ->setLabel(pht('Default Queue')) ->setDescription(pht('Default queue.')) - ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE) + ->setTransactionType( + NuanceSourceDefaultQueueTransaction::TRANSACTIONTYPE) ->setDatasource(new NuanceQueueDatasource()) ->setSingleValue($object->getDefaultQueuePHID()), ); diff --git a/src/applications/nuance/editor/NuanceSourceEditor.php b/src/applications/nuance/editor/NuanceSourceEditor.php index 5fbc02b962..b56b183f9e 100644 --- a/src/applications/nuance/editor/NuanceSourceEditor.php +++ b/src/applications/nuance/editor/NuanceSourceEditor.php @@ -18,111 +18,10 @@ final class NuanceSourceEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = NuanceSourceTransaction::TYPE_NAME; - $types[] = NuanceSourceTransaction::TYPE_DEFAULT_QUEUE; - - $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - return $object->getName(); - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - return $object->getDefaultQueuePHID(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - $object->setDefaultQueuePHID($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case NuanceSourceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Source name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - foreach ($xactions as $xaction) { - if (!$xaction->getNewValue()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Sources must have a default queue.'), - $xaction); - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - } - break; - } - - return $errors; - } - } diff --git a/src/applications/nuance/storage/NuanceSourceTransaction.php b/src/applications/nuance/storage/NuanceSourceTransaction.php index 5d70376ce3..0e264876dd 100644 --- a/src/applications/nuance/storage/NuanceSourceTransaction.php +++ b/src/applications/nuance/storage/NuanceSourceTransaction.php @@ -3,9 +3,6 @@ final class NuanceSourceTransaction extends NuanceTransaction { - const TYPE_NAME = 'source.name'; - const TYPE_DEFAULT_QUEUE = 'source.queue.default'; - public function getApplicationTransactionType() { return NuanceSourcePHIDType::TYPECONST; } @@ -18,63 +15,4 @@ final class NuanceSourceTransaction return 'NuanceSourceTransactionType'; } - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - switch ($type) { - case self::TYPE_DEFAULT_QUEUE: - return !$old; - case self::TYPE_NAME: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $phids = parent::getRequiredHandlePHIDs(); - switch ($type) { - case self::TYPE_DEFAULT_QUEUE: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - $author_phid = $this->getAuthorPHID(); - - switch ($type) { - case self::TYPE_DEFAULT_QUEUE: - return pht( - '%s changed the default queue from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - case self::TYPE_NAME: - return pht( - '%s renamed this source from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); - } - } diff --git a/src/applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php b/src/applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php new file mode 100644 index 0000000000..f5a7cc4302 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php @@ -0,0 +1,42 @@ +getDefaultQueuePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultQueuePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the default queue for this source from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$object->getDefaultQueuePHID() && !$xactions) { + $errors[] = $this->newRequiredError( + pht('Sources must have a default queue.')); + } + + foreach ($xactions as $xaction) { + if (!$xaction->getNewValue()) { + $errors[] = $this->newRequiredError( + pht('Sources must have a default queue.')); + } + } + + return $errors; + } + +} diff --git a/src/applications/nuance/xaction/NuanceSourceNameTransaction.php b/src/applications/nuance/xaction/NuanceSourceNameTransaction.php new file mode 100644 index 0000000000..e33b60551c --- /dev/null +++ b/src/applications/nuance/xaction/NuanceSourceNameTransaction.php @@ -0,0 +1,47 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this source from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Sources must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Source names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} From 00400ae6f96583ef150c39acd53dc35f377d05c5 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 22 May 2017 18:59:53 +0000 Subject: [PATCH 158/543] Search and Replace calls to setShade Summary: grep for setShade and update to setColor. Add deprecated warning. Test Plan: Diffusion, Workboards, Maniphest, Project tags, tokenizer, uiexamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, O14 ATC Monitoring Differential Revision: https://secure.phabricator.com/D17995 --- .../calendar/view/PHUIUserAvailabilityView.php | 2 +- .../conpherence/controller/ConpherenceController.php | 2 +- .../differential/constants/DifferentialRevisionStatus.php | 2 +- .../files/controller/PhabricatorFileInfoController.php | 4 ++-- .../harbormaster/view/HarbormasterUnitSummaryView.php | 2 +- .../maniphest/constants/ManiphestTaskStatus.php | 2 +- .../PhabricatorOwnersHovercardEngineExtension.php | 2 +- src/applications/people/view/PhabricatorUserCardView.php | 2 +- .../controller/PhabricatorProjectBoardViewController.php | 2 +- .../project/query/PhabricatorProjectSearchEngine.php | 2 +- src/applications/project/view/ProjectBoardTaskCard.php | 2 +- .../search/query/PhabricatorFulltextToken.php | 2 +- src/applications/uiexample/examples/PHUITagExample.php | 4 ++-- .../diff/view/PHUIDiffInlineCommentDetailView.php | 4 ++-- .../markup/rule/PhabricatorNavigationRemarkupRule.php | 2 +- src/view/phui/PHUIHeaderView.php | 2 +- src/view/phui/PHUITagView.php | 8 +++++++- 17 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/applications/calendar/view/PHUIUserAvailabilityView.php b/src/applications/calendar/view/PHUIUserAvailabilityView.php index 7ab0ace77a..f4f9696ba9 100644 --- a/src/applications/calendar/view/PHUIUserAvailabilityView.php +++ b/src/applications/calendar/view/PHUIUserAvailabilityView.php @@ -29,7 +29,7 @@ final class PHUIUserAvailabilityView $away_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) + ->setColor($color) ->setName($name) ->setDotColor($color); diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 90328cca04..9d7473e159 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -71,7 +71,7 @@ abstract class ConpherenceController extends PhabricatorController { if (strlen($data['topic'])) { $topic = id(new PHUITagView()) ->setName($data['topic']) - ->setShade(PHUITagView::COLOR_VIOLET) + ->setColor(PHUITagView::COLOR_VIOLET) ->setType(PHUITagView::TYPE_SHADE) ->addClass('conpherence-header-topic'); $header->addTag($topic); diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index 4f332838ac..38e6a2dd49 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -65,7 +65,7 @@ final class DifferentialRevisionStatus extends Phobject { $tag = id(new PHUITagView()) ->setName($status_name) ->setIcon(self::getRevisionStatusIcon($status)) - ->setShade(self::getRevisionStatusColor($status)) + ->setColor(self::getRevisionStatusColor($status)) ->setType(PHUITagView::TYPE_SHADE); return $tag; diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 061790aa31..f30421c132 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -44,7 +44,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { if ($ttl !== null) { $ttl_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_YELLOW) + ->setColor(PHUITagView::COLOR_YELLOW) ->setName(pht('Temporary')); $header->addTag($ttl_tag); } @@ -53,7 +53,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { if ($partial) { $partial_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_ORANGE) + ->setColor(PHUITagView::COLOR_ORANGE) ->setName(pht('Partial Upload')); $header->addTag($partial_tag); } diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index 28f79ee181..e5a66a3eb8 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -56,7 +56,7 @@ final class HarbormasterUnitSummaryView extends AphrontView { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($tag_color) + ->setColor($tag_color) ->setIcon($tag_icon) ->setName($tag_text); diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index 5734892f0a..6781fb7724 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -97,7 +97,7 @@ final class ManiphestTaskStatus extends ManiphestConstants { ->setName($name) ->setIcon($icon) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color); + ->setColor($color); return $tag; } diff --git a/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php b/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php index 744d3fbea2..39fe30ad28 100644 --- a/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php +++ b/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php @@ -64,7 +64,7 @@ final class PhabricatorOwnersHovercardEngineExtension if ($package->isArchived()) { $tag = id(new PHUITagView()) ->setName(pht('Archived')) - ->setShade(PHUITagView::COLOR_INDIGO) + ->setColor(PHUITagView::COLOR_INDIGO) ->setType(PHUITagView::TYPE_OBJECT); $hovercard->addTag($tag); } diff --git a/src/applications/people/view/PhabricatorUserCardView.php b/src/applications/people/view/PhabricatorUserCardView.php index 4f4f15a33d..f1fc515f88 100644 --- a/src/applications/people/view/PhabricatorUserCardView.php +++ b/src/applications/people/view/PhabricatorUserCardView.php @@ -85,7 +85,7 @@ final class PhabricatorUserCardView extends AphrontTagView { ->setType(PHUITagView::TYPE_SHADE); if ($tag_shade !== null) { - $tag->setShade($tag_shade); + $tag->setColor($tag_shade); } $body = array(); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 51b907cac0..ad08df1692 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -331,7 +331,7 @@ final class PhabricatorProjectBoardViewController $count_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_BLUE) + ->setColor(PHUITagView::COLOR_BLUE) ->addSigil('column-points') ->setName( javelin_tag( diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index df13278812..452085fa9f 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -211,7 +211,7 @@ final class PhabricatorProjectSearchEngine $options[$color] = array( id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) + ->setColor($color) ->setName($name), ); } diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index ba5213a623..100fee20b3 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -100,7 +100,7 @@ final class ProjectBoardTaskCard extends Phobject { if ($points !== null) { $points_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_GREY) + ->setColor(PHUITagView::COLOR_GREY) ->setSlimShady(true) ->setName($points) ->addClass('phui-workcard-points'); diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php index d42c15e9e6..4edeb098a9 100644 --- a/src/applications/search/query/PhabricatorFulltextToken.php +++ b/src/applications/search/query/PhabricatorFulltextToken.php @@ -64,7 +64,7 @@ final class PhabricatorFulltextToken extends Phobject { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($shade) + ->setColor($shade) ->setName($token->getValue()); if ($tip !== null) { diff --git a/src/applications/uiexample/examples/PHUITagExample.php b/src/applications/uiexample/examples/PHUITagExample.php index 764caaf717..027be2f9d8 100644 --- a/src/applications/uiexample/examples/PHUITagExample.php +++ b/src/applications/uiexample/examples/PHUITagExample.php @@ -187,12 +187,12 @@ final class PHUITagExample extends PhabricatorUIExample { foreach ($outlines as $outline) { $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OUTLINE) - ->setShade($outline) + ->setColor($outline) ->setName($outline); $tags[] = hsprintf(' '); $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OUTLINE) - ->setShade($outline) + ->setColor($outline) ->setSlimShady(true) ->setName($outline); $tags[] = hsprintf('

'); diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 821834431a..a42efa932a 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -148,7 +148,7 @@ final class PHUIDiffInlineCommentDetailView ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Unsubmitted')) ->setSlimShady(true) - ->setShade(PHUITagView::COLOR_RED) + ->setColor(PHUITagView::COLOR_RED) ->addClass('mml inline-draft-text'); } @@ -383,7 +383,7 @@ final class PHUIDiffInlineCommentDetailView ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Author')) ->setSlimShady(true) - ->setShade(PHUITagView::COLOR_YELLOW) + ->setColor(PHUITagView::COLOR_YELLOW) ->addClass('mml'); } } diff --git a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php index 458ee5b834..3fc3373001 100644 --- a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php @@ -60,7 +60,7 @@ final class PhabricatorNavigationRemarkupRule extends PhutilRemarkupRule { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($item_color) + ->setColor($item_color) ->setName($item_name); if ($item['icon']) { diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0cd8379b42..ab576c64d5 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -131,7 +131,7 @@ final class PHUIHeaderView extends AphrontTagView { $tag = id(new PHUITagView()) ->setName($name) ->setIcon($icon) - ->setShade($color) + ->setColor($color) ->setType(PHUITagView::TYPE_SHADE); return $this->addProperty(self::PROPERTY_STATUS, $tag); diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index e8d30e935e..1811f8f1a9 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -57,8 +57,14 @@ final class PHUITagView extends AphrontTagView { return $this; } - /* Deprecated, use setColor */ + /** + * This method has been deprecated, use @{method:setColor} instead. + * + * @deprecated + */ public function setShade($shade) { + phlog( + pht('Deprecated call to setShade(), use setColor() instead.')); $this->color = $shade; return $this; } From a1b10824649fb7c950c91a9bc556c658ad9a67a0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 22 May 2017 14:27:22 -0700 Subject: [PATCH 159/543] Update Phriction Delete to modular transactions Summary: Ref T12625. Updates to modular transactions Test Plan: Delete a document, restore a document, see feed story. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12625 Differential Revision: https://secure.phabricator.com/D17996 --- src/__phutil_library_map__.php | 2 + .../controller/PhrictionDeleteController.php | 3 +- .../editor/PhrictionTransactionEditor.php | 47 +--------- .../storage/PhrictionTransaction.php | 20 +---- .../PhrictionDocumentDeleteTransaction.php | 86 +++++++++++++++++++ 5 files changed, 95 insertions(+), 63 deletions(-) create mode 100644 src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7088012879..bceb28a334 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4635,6 +4635,7 @@ phutil_register_library_map(array( 'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php', 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', + 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', @@ -10297,6 +10298,7 @@ phutil_register_library_map(array( 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentController' => 'PhrictionController', + 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', diff --git a/src/applications/phriction/controller/PhrictionDeleteController.php b/src/applications/phriction/controller/PhrictionDeleteController.php index 58dc06a65b..b061e89756 100644 --- a/src/applications/phriction/controller/PhrictionDeleteController.php +++ b/src/applications/phriction/controller/PhrictionDeleteController.php @@ -26,7 +26,8 @@ final class PhrictionDeleteController extends PhrictionController { if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_DELETE) + ->setTransactionType( + PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE) ->setNewValue(true); $editor = id(new PhrictionTransactionEditor()) diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 9ab91d5db9..e29bd50152 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -86,7 +86,6 @@ final class PhrictionTransactionEditor $types = parent::getTransactionTypes(); $types[] = PhrictionTransaction::TYPE_CONTENT; - $types[] = PhrictionTransaction::TYPE_DELETE; $types[] = PhrictionTransaction::TYPE_MOVE_AWAY; $types[] = PhabricatorTransactions::TYPE_EDGE; @@ -107,7 +106,6 @@ final class PhrictionTransactionEditor return null; } return $this->getOldContent()->getContent(); - case PhrictionTransaction::TYPE_DELETE: case PhrictionTransaction::TYPE_MOVE_AWAY: return null; } @@ -119,7 +117,6 @@ final class PhrictionTransactionEditor switch ($xaction->getTransactionType()) { case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_DELETE: return $xaction->getNewValue(); case PhrictionTransaction::TYPE_MOVE_AWAY: $document = $xaction->getNewValue(); @@ -141,7 +138,7 @@ final class PhrictionTransactionEditor switch ($xaction->getTransactionType()) { case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_DELETE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_MOVE_AWAY: return true; @@ -169,9 +166,6 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_MOVE_AWAY: $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); return; - case PhrictionTransaction::TYPE_DELETE: - $object->setStatus(PhrictionDocumentStatus::STATUS_DELETED); - return; } } @@ -188,7 +182,8 @@ final class PhrictionTransactionEditor $content = $xaction->getNewValue(); if ($content === '') { $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_DELETE) + ->setTransactionType( + PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE) ->setNewValue(true) ->setMetadataValue('contentDelete', true); } @@ -218,11 +213,6 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: $this->getNewContent()->setContent($xaction->getNewValue()); break; - case PhrictionTransaction::TYPE_DELETE: - $this->getNewContent()->setContent(''); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_DELETE); - break; case PhrictionTransaction::TYPE_MOVE_AWAY: $dict = $xaction->getNewValue(); $this->getNewContent()->setContent(''); @@ -245,7 +235,7 @@ final class PhrictionTransactionEditor case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_DELETE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_MOVE_AWAY: $save_content = true; break; @@ -536,35 +526,6 @@ final class PhrictionTransactionEditor } break; - case PhrictionTransaction::TYPE_DELETE: - switch ($object->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - if ($xaction->getMetadataValue('contentDelete')) { - $e_text = pht( - 'This document is already deleted. You must specify '. - 'content to re-create the document and make further edits.'); - } else { - $e_text = pht( - 'An already deleted document can not be deleted.'); - } - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $e_text = pht('A moved document can not be deleted.'); - break; - case PhrictionDocumentStatus::STATUS_STUB: - $e_text = pht('A stub document can not be deleted.'); - break; - default: - break 2; - } - - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Can not delete document.'), - $e_text, - $xaction); - $errors[] = $error; - break; } } diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php index d3d78ff665..e447bf0bb4 100644 --- a/src/applications/phriction/storage/PhrictionTransaction.php +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -4,7 +4,6 @@ final class PhrictionTransaction extends PhabricatorModularTransaction { const TYPE_CONTENT = 'content'; - const TYPE_DELETE = 'delete'; const TYPE_MOVE_AWAY = 'move-away'; const MAILTAG_TITLE = 'phriction-title'; @@ -99,8 +98,6 @@ final class PhrictionTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return 1.3; - case self::TYPE_DELETE: - return 1.5; case self::TYPE_MOVE_AWAY: return 1.0; } @@ -115,8 +112,6 @@ final class PhrictionTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return pht('Edited'); - case self::TYPE_DELETE: - return pht('Deleted'); case self::TYPE_MOVE_AWAY: return pht('Moved Away'); } @@ -131,8 +126,6 @@ final class PhrictionTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return 'fa-pencil'; - case self::TYPE_DELETE: - return 'fa-times'; case self::TYPE_MOVE_AWAY: return 'fa-arrows'; } @@ -153,11 +146,6 @@ final class PhrictionTransaction '%s edited the document content.', $this->renderHandleLink($author_phid)); - case self::TYPE_DELETE: - return pht( - '%s deleted this document.', - $this->renderHandleLink($author_phid)); - case self::TYPE_MOVE_AWAY: return pht( '%s moved this document to %s', @@ -184,12 +172,6 @@ final class PhrictionTransaction $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); - case self::TYPE_DELETE: - return pht( - '%s deleted %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } return parent::getTitleForFeed(); } @@ -218,7 +200,7 @@ final class PhrictionTransaction case self::TYPE_CONTENT: $tags[] = self::MAILTAG_CONTENT; break; - case self::TYPE_DELETE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DELETE; break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: diff --git a/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php new file mode 100644 index 0000000000..25cc7c2b28 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php @@ -0,0 +1,86 @@ +setStatus(PhrictionDocumentStatus::STATUS_DELETED); + } + + public function applyExternalEffects($object, $value) { + $this->getEditor()->getNewContent()->setContent(''); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_DELETE); + } + + public function getActionStrength() { + return 1.5; + } + + public function getActionName() { + return pht('Deleted'); + } + + public function getTitle() { + return pht( + '%s deleted this document.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s deleted %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $e_text = null; + foreach ($xactions as $xaction) { + switch ($object->getStatus()) { + case PhrictionDocumentStatus::STATUS_DELETED: + if ($xaction->getMetadataValue('contentDelete')) { + $e_text = pht( + 'This document is already deleted. You must specify '. + 'content to re-create the document and make further edits.'); + } else { + $e_text = pht( + 'An already deleted document can not be deleted.'); + } + break; + case PhrictionDocumentStatus::STATUS_MOVED: + $e_text = pht('A moved document can not be deleted.'); + break; + case PhrictionDocumentStatus::STATUS_STUB: + $e_text = pht('A stub document can not be deleted.'); + break; + default: + break; + } + + if ($e_text !== null) { + $errors[] = $this->newInvalidError($e_text); + } + + } + + return $errors; + } + + public function getIcon() { + return 'fa-trash-o'; + } + + public function getColor() { + return 'red'; + } + +} From 2325c61ad77536e75322936d5a0505ee2bbb616d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 22 May 2017 22:26:58 +0000 Subject: [PATCH 160/543] Make the Install Dashboard button green Summary: GREEN Test Plan: View a dashboard page, see green button Reviewers: amckinley, epriestley Reviewed By: epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17997 --- .../dashboard/controller/PhabricatorDashboardViewController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php index 07faee8f05..41441f09f4 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php @@ -47,6 +47,7 @@ final class PhabricatorDashboardViewController ->setTag('a') ->setText('Install Dashboard') ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREEN) ->setWorkflow(true) ->setHref($this->getApplicationURI("/install/{$id}/")); $header->addActionLink($install_button); From b164d2d04bcb5314ac25930ece089561a75f8dfd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 22 May 2017 15:40:16 -0700 Subject: [PATCH 161/543] Update Phriction Move Away transaction to modular transactions Summary: Ref T12625 Test Plan: Move a document to a new location, verify the old and new document. Edit both. Grep for MOVE_AWAY Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12625 Differential Revision: https://secure.phabricator.com/D17988 --- src/__phutil_library_map__.php | 2 + .../editor/PhrictionTransactionEditor.php | 31 ++-------- .../storage/PhrictionTransaction.php | 20 +----- .../PhrictionDocumentMoveAwayTransaction.php | 62 +++++++++++++++++++ 4 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bceb28a334..bfc054b88b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4640,6 +4640,7 @@ phutil_register_library_map(array( 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', 'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php', + 'PhrictionDocumentMoveAwayTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php', 'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', @@ -10303,6 +10304,7 @@ phutil_register_library_map(array( 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', + 'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index e29bd50152..eaa6ea93f1 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -86,7 +86,6 @@ final class PhrictionTransactionEditor $types = parent::getTransactionTypes(); $types[] = PhrictionTransaction::TYPE_CONTENT; - $types[] = PhrictionTransaction::TYPE_MOVE_AWAY; $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; @@ -106,8 +105,6 @@ final class PhrictionTransactionEditor return null; } return $this->getOldContent()->getContent(); - case PhrictionTransaction::TYPE_MOVE_AWAY: - return null; } } @@ -118,15 +115,6 @@ final class PhrictionTransactionEditor switch ($xaction->getTransactionType()) { case PhrictionTransaction::TYPE_CONTENT: return $xaction->getNewValue(); - case PhrictionTransaction::TYPE_MOVE_AWAY: - $document = $xaction->getNewValue(); - $dict = array( - 'id' => $document->getID(), - 'phid' => $document->getPHID(), - 'content' => $document->getContent()->getContent(), - 'title' => $document->getContent()->getTitle(), - ); - return $dict; } } @@ -140,7 +128,7 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case PhrictionTransaction::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: return true; } } @@ -163,9 +151,6 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); return; - case PhrictionTransaction::TYPE_MOVE_AWAY: - $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); - return; } } @@ -213,13 +198,6 @@ final class PhrictionTransactionEditor case PhrictionTransaction::TYPE_CONTENT: $this->getNewContent()->setContent($xaction->getNewValue()); break; - case PhrictionTransaction::TYPE_MOVE_AWAY: - $dict = $xaction->getNewValue(); - $this->getNewContent()->setContent(''); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_AWAY); - $this->getNewContent()->setChangeRef($dict['id']); - break; default: break; } @@ -234,9 +212,9 @@ final class PhrictionTransactionEditor switch ($xaction->getTransactionType()) { case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: - case PhrictionTransaction::TYPE_MOVE_AWAY: + case PhrictionTransaction::TYPE_CONTENT: $save_content = true; break; default: @@ -303,7 +281,8 @@ final class PhrictionTransactionEditor if ($this->moveAwayDocument !== null) { $move_away_xactions = array(); $move_away_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_MOVE_AWAY) + ->setTransactionType( + PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE) ->setNewValue($object); $sub_editor = id(new PhrictionTransactionEditor()) ->setActor($this->getActor()) diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php index e447bf0bb4..542e9b3e58 100644 --- a/src/applications/phriction/storage/PhrictionTransaction.php +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -4,7 +4,6 @@ final class PhrictionTransaction extends PhabricatorModularTransaction { const TYPE_CONTENT = 'content'; - const TYPE_MOVE_AWAY = 'move-away'; const MAILTAG_TITLE = 'phriction-title'; const MAILTAG_CONTENT = 'phriction-content'; @@ -33,7 +32,7 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case self::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: $phids[] = $new['phid']; break; case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: @@ -75,7 +74,7 @@ final class PhrictionTransaction public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case self::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: return true; case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); @@ -86,7 +85,7 @@ final class PhrictionTransaction public function shouldHideForFeed() { switch ($this->getTransactionType()) { case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case self::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: return true; case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); @@ -98,8 +97,6 @@ final class PhrictionTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return 1.3; - case self::TYPE_MOVE_AWAY: - return 1.0; } return parent::getActionStrength(); @@ -112,8 +109,6 @@ final class PhrictionTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return pht('Edited'); - case self::TYPE_MOVE_AWAY: - return pht('Moved Away'); } return parent::getActionName(); @@ -126,8 +121,6 @@ final class PhrictionTransaction switch ($this->getTransactionType()) { case self::TYPE_CONTENT: return 'fa-pencil'; - case self::TYPE_MOVE_AWAY: - return 'fa-arrows'; } return parent::getIcon(); @@ -145,13 +138,6 @@ final class PhrictionTransaction return pht( '%s edited the document content.', $this->renderHandleLink($author_phid)); - - case self::TYPE_MOVE_AWAY: - return pht( - '%s moved this document to %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new['phid'])); - } return parent::getTitle(); diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php new file mode 100644 index 0000000000..c827f7337d --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php @@ -0,0 +1,62 @@ + $document->getID(), + 'phid' => $document->getPHID(), + 'content' => $document->getContent()->getContent(), + 'title' => $document->getContent()->getTitle(), + ); + return $dict; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); + } + + public function applyExternalEffects($object, $value) { + $dict = $value; + $this->getEditor()->getNewContent()->setContent(''); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_MOVE_AWAY); + $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + } + + public function getActionName() { + return pht('Moved Away'); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s moved this document to %s', + $this->renderAuthor(), + $this->renderHandleLink($new['phid'])); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s moved %s to %s', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandleLink($new['phid'])); + } + + public function getIcon() { + return 'fa-arrows'; + } + +} From 20e7f7d0e2900fe878a830796a088204e4ba0a6d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 23 May 2017 16:28:05 +0000 Subject: [PATCH 162/543] Bump markup engine version to clear old "Navigation Sequence" elements Summary: The tag/shade stuff changed, so purge older markup (like Diviner documents). Test Plan: {F4972666} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17998 --- src/infrastructure/markup/PhabricatorMarkupEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index ad380414f6..0c56d9ed41 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -42,7 +42,7 @@ final class PhabricatorMarkupEngine extends Phobject { private $objects = array(); private $viewer; private $contextObject; - private $version = 16; + private $version = 17; private $engineCaches = array(); private $auxiliaryConfig = array(); From 93d8b33ccade9b70e600e7f8c7ba7a92e5b5a217 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 23 May 2017 16:59:15 +0000 Subject: [PATCH 163/543] Check for strlen instead of null for Maniphest title Summary: Fixes T12744. Unclear why `null` doesn't work here but does for the title, but `!strlen` seems to work fine in both cases. Test Plan: Create a new task, check mail folder, see [Created] Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12744 Differential Revision: https://secure.phabricator.com/D18002 --- .../maniphest/xaction/ManiphestTaskTitleTransaction.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php index 5fade77d07..506817b0fc 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -19,8 +19,8 @@ final class ManiphestTaskTitleTransaction public function getActionName() { $old = $this->getOldValue(); - $new = $this->getNewValue(); - if ($old === null) { + + if (!strlen($old)) { return pht('Created'); } @@ -29,7 +29,8 @@ final class ManiphestTaskTitleTransaction public function getTitle() { $old = $this->getOldValue(); - if ($old === null) { + + if (!strlen($old)) { return pht( '%s created this task.', $this->renderAuthor()); From cd136a6af86a80fb1df5db777e43f2f08c251148 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 23 May 2017 11:12:30 -0700 Subject: [PATCH 164/543] Migrate Project parent and milestone to modular transactions Test Plan: Unit tests pass. Went through the UI for creating new subprojects and milestones, but didn't setup some API calls to check that all the validation errors were still caught. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D17999 --- src/__phutil_library_map__.php | 6 + .../PhabricatorProjectCoreTestCase.php | 6 +- .../PhabricatorProjectTransactionEditor.php | 116 +----------------- .../engine/PhabricatorProjectEditEngine.php | 9 +- .../storage/PhabricatorProjectTransaction.php | 2 - ...PhabricatorProjectMilestoneTransaction.php | 31 +++++ .../PhabricatorProjectParentTransaction.php | 29 +++++ .../PhabricatorProjectTypeTransaction.php | 71 +++++++++++ 8 files changed, 152 insertions(+), 118 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php create mode 100644 src/applications/project/xaction/PhabricatorProjectParentTransaction.php create mode 100644 src/applications/project/xaction/PhabricatorProjectTypeTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bfc054b88b..ada16cc079 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3660,6 +3660,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', 'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php', 'PhabricatorProjectMenuItemController' => 'applications/project/controller/PhabricatorProjectMenuItemController.php', + 'PhabricatorProjectMilestoneTransaction' => 'applications/project/xaction/PhabricatorProjectMilestoneTransaction.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', 'PhabricatorProjectNameTransaction' => 'applications/project/xaction/PhabricatorProjectNameTransaction.php', @@ -3668,6 +3669,7 @@ phutil_register_library_map(array( 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', + 'PhabricatorProjectParentTransaction' => 'applications/project/xaction/PhabricatorProjectParentTransaction.php', 'PhabricatorProjectPictureProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php', 'PhabricatorProjectPointsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', @@ -3696,6 +3698,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', + 'PhabricatorProjectTypeTransaction' => 'applications/project/xaction/PhabricatorProjectTypeTransaction.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -9093,6 +9096,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController', 'PhabricatorProjectMenuItemController' => 'PhabricatorProjectController', + 'PhabricatorProjectMilestoneTransaction' => 'PhabricatorProjectTypeTransaction', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhabricatorProjectNameTransaction' => 'PhabricatorProjectTransactionType', @@ -9101,6 +9105,7 @@ phutil_register_library_map(array( 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', + 'PhabricatorProjectParentTransaction' => 'PhabricatorProjectTypeTransaction', 'PhabricatorProjectPictureProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectPointsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', @@ -9132,6 +9137,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorProjectTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorProjectTypeTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 0bec2fe53c..d9356357b3 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -1447,11 +1447,13 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { if ($parent) { if ($is_milestone) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setTransactionType( + PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE) ->setNewValue($parent->getPHID()); } else { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setTransactionType( + PhabricatorProjectParentTransaction::TRANSACTIONTYPE) ->setNewValue($parent->getPHID()); } } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 47f451b037..8b77f71505 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,8 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_PARENT; - $types[] = PhabricatorProjectTransaction::TYPE_MILESTONE; $types[] = PhabricatorProjectTransaction::TYPE_HASWORKBOARD; $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT; $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER; @@ -47,9 +45,6 @@ final class PhabricatorProjectTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: return (int)$object->getHasWorkboard(); - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: - return null; case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: return $object->getDefaultWorkboardSort(); case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: @@ -66,8 +61,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: return $xaction->getNewValue(); @@ -89,14 +82,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_PARENT: - $object->setParentProjectPHID($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_MILESTONE: - $number = $object->getParentProject()->loadNextMilestoneNumber(); - $object->setMilestoneNumber($number); - $object->setParentProjectPHID($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: $object->setHasWorkboard($xaction->getNewValue()); return; @@ -122,8 +107,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: @@ -145,8 +128,8 @@ final class PhabricatorProjectTransactionEditor $parent_xaction = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: + case PhabricatorProjectParentTransaction::TRANSACTIONTYPE: + case PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE: if ($xaction->getNewValue() === null) { continue; } @@ -208,95 +191,6 @@ final class PhabricatorProjectTransactionEditor return $errors; } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: - if (!$xactions) { - break; - } - - $xaction = last($xactions); - - $parent_phid = $xaction->getNewValue(); - if (!$parent_phid) { - continue; - } - - if (!$this->getIsNewObject()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can only set a parent or milestone project when creating a '. - 'project for the first time.'), - $xaction); - break; - } - - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($parent_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - if (!$projects) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Parent or milestone project PHID ("%s") must be the PHID of a '. - 'valid, visible project which you have permission to edit.', - $parent_phid), - $xaction); - break; - } - - $project = head($projects); - - if ($project->isMilestone()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Parent or milestone project PHID ("%s") must not be a '. - 'milestone. Milestones may not have subprojects or milestones.', - $parent_phid), - $xaction); - break; - } - - $limit = PhabricatorProject::getProjectDepthLimit(); - if ($project->getProjectDepth() >= ($limit - 1)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can not create a subproject or mielstone under this parent '. - 'because it would nest projects too deeply. The maximum '. - 'nesting depth of projects is %s.', - new PhutilNumber($limit)), - $xaction); - break; - } - - $object->attachParentProject($project); - break; - } - - return $errors; - } - - protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -458,8 +352,8 @@ final class PhabricatorProjectTransactionEditor break; } break; - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: + case PhabricatorProjectParentTransaction::TRANSACTIONTYPE: + case PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE: $materialize = true; $new_parent = $object->getParentProject(); break; @@ -634,7 +528,7 @@ final class PhabricatorProjectTransactionEditor $is_milestone = $object->isMilestone(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_MILESTONE: + case PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE: if ($xaction->getNewValue() !== null) { $is_milestone = true; } diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index cc377bed44..a7702c345f 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -202,7 +202,8 @@ final class PhabricatorProjectEditEngine pht('Choose a parent project to create a subproject beneath.')) ->setConduitTypeDescription(pht('PHID of the parent project.')) ->setAliases(array('parentPHID')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setTransactionType( + PhabricatorProjectParentTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) ->setSingleValue($parent_phid) ->setIsReorderable(false) @@ -217,7 +218,8 @@ final class PhabricatorProjectEditEngine pht('Choose a parent project to create a new milestone for.')) ->setConduitTypeDescription(pht('PHID of the parent project.')) ->setAliases(array('milestonePHID')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setTransactionType( + PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) ->setSingleValue($milestone_phid) ->setIsReorderable(false) @@ -244,7 +246,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorIconSetEditField()) ->setKey('icon') ->setLabel(pht('Icon')) - ->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE) + ->setTransactionType( + PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setIconSet(new PhabricatorProjectIconSet()) ->setDescription(pht('Project icon.')) ->setConduitDescription(pht('Change the project icon.')) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index f0a9966646..b6c77808bc 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,8 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_PARENT = 'project:parent'; - const TYPE_MILESTONE = 'project:milestone'; const TYPE_HASWORKBOARD = 'project:hasworkboard'; const TYPE_DEFAULT_SORT = 'project:sort'; const TYPE_DEFAULT_FILTER = 'project:filter'; diff --git a/src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php b/src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php new file mode 100644 index 0000000000..74ac77b55d --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php @@ -0,0 +1,31 @@ +setViewer($this->getActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $object->attachParentProject($project); + + $number = $object->getParentProject()->loadNextMilestoneNumber(); + $object->setMilestoneNumber($number); + $object->setParentProjectPHID($value); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectParentTransaction.php b/src/applications/project/xaction/PhabricatorProjectParentTransaction.php new file mode 100644 index 0000000000..6fc850ddde --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectParentTransaction.php @@ -0,0 +1,29 @@ +setViewer($this->getActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $object->attachParentProject($project); + + $object->setParentProjectPHID($value); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectTypeTransaction.php b/src/applications/project/xaction/PhabricatorProjectTypeTransaction.php new file mode 100644 index 0000000000..7bba2c5289 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectTypeTransaction.php @@ -0,0 +1,71 @@ +getNewValue(); + if (!$parent_phid) { + return $errors; + } + + if (!$this->getEditor()->getIsNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'You can only set a parent or milestone project when creating a '. + 'project for the first time.')); + return $errors; + } + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + if (!$projects) { + $errors[] = $this->newInvalidError( + pht( + 'Parent or milestone project PHID ("%s") must be the PHID of a '. + 'valid, visible project which you have permission to edit.', + $parent_phid)); + return $errors; + } + + $project = head($projects); + + if ($project->isMilestone()) { + $errors[] = $this->newInvalidError( + pht( + 'Parent or milestone project PHID ("%s") must not be a '. + 'milestone. Milestones may not have subprojects or milestones.', + $parent_phid)); + return $errors; + } + + $limit = PhabricatorProject::getProjectDepthLimit(); + if ($project->getProjectDepth() >= ($limit - 1)) { + $errors[] = $this->newInvalidError( + pht( + 'You can not create a subproject or milestone under this parent '. + 'because it would nest projects too deeply. The maximum '. + 'nesting depth of projects is %s.', + new PhutilNumber($limit))); + return $errors; + } + + return $errors; + } + +} From 1a75ae24056b76ce80d320c37bd28360dece3c23 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 23 May 2017 11:27:21 -0700 Subject: [PATCH 165/543] Modernize Diffusion Create with sidenav, curtain Summary: This moves the navigation to a standard sidebar, and moves all actions to the curtain. Also pulled out info view when available for cleaner UI. Test Plan: Create a git, svn, hg test repository and verify each page in the sidebar renders as expected. {F4973792} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18003 --- ...fusionRepositoryManagePanelsController.php | 11 ++- ...fusionRepositoryActionsManagementPanel.php | 13 ++-- ...ionRepositoryAutomationManagementPanel.php | 17 +++-- ...ffusionRepositoryBasicsManagementPanel.php | 34 ++++++---- ...usionRepositoryBranchesManagementPanel.php | 13 ++-- ...RepositoryDocumentationManagementPanel.php | 4 ++ ...fusionRepositoryHistoryManagementPanel.php | 3 + .../DiffusionRepositoryManagementPanel.php | 38 +++-------- ...usionRepositoryPoliciesManagementPanel.php | 13 ++-- ...fusionRepositoryStagingManagementPanel.php | 13 ++-- ...ffusionRepositoryStatusManagementPanel.php | 13 ++-- ...fusionRepositoryStorageManagementPanel.php | 44 ++++++------ ...ionRepositorySubversionManagementPanel.php | 13 ++-- ...fusionRepositorySymbolsManagementPanel.php | 13 ++-- ...DiffusionRepositoryURIsManagementPanel.php | 68 ++++++++++--------- 15 files changed, 162 insertions(+), 148 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php index 22c03145e0..12faa6799d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php @@ -83,11 +83,11 @@ final class DiffusionRepositoryManagePanelsController ->setTag('a') ->setText(pht('View Repository')) ->setHref($repository->getURI()) - ->setIcon('fa-code')); + ->setIcon('fa-code') + ->setColor(PHUIButtonView::GREEN)); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) ->setMainColumn($content); $curtain = $panel->buildManagementPanelCurtain(); @@ -98,6 +98,7 @@ final class DiffusionRepositoryManagePanelsController return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($view); } @@ -112,6 +113,12 @@ final class DiffusionRepositoryManagePanelsController $nav = id(new AphrontSideNavFilterView()) ->setBaseURI($base_uri); + $item = id(new PHUIListItemView()) + ->setName(pht('manage')) + ->setType(PHUIListItemView::TYPE_LABEL); + + $nav->addMenuItem($item); + foreach ($panels as $panel) { $key = $panel->getManagementPanelKey(); $label = $panel->getManagementPanelLabel(); diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php index 1f47a68de3..83697e52e3 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php @@ -37,9 +37,10 @@ final class DiffusionRepositoryActionsManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -48,14 +49,15 @@ final class DiffusionRepositoryActionsManagementPanel $actions_uri = $this->getEditPageURI(); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Actions')) ->setHref($actions_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); + ->setWorkflow(!$can_edit)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -63,8 +65,7 @@ final class DiffusionRepositoryActionsManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $notify = $repository->getDetail('herald-disabled') ? pht('Off') diff --git a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php index 14182a0444..b37aa05ecf 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php @@ -46,9 +46,10 @@ final class DiffusionRepositoryAutomationManagementPanel return 'fa-truck'; } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -60,20 +61,23 @@ final class DiffusionRepositoryAutomationManagementPanel $automation_uri = $this->getEditPageURI(); $test_uri = $repository->getPathURI('edit/testautomation/'); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Automation')) ->setHref($automation_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), + ->setWorkflow(!$can_edit)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-gamepad') ->setName(pht('Test Configuration')) ->setWorkflow(true) ->setDisabled(!$can_test) - ->setHref($test_uri), - ); + ->setHref($test_uri)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -81,8 +85,7 @@ final class DiffusionRepositoryAutomationManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); if (!$blueprint_phids) { diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 45c2ca142a..a5e09e40d3 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -33,9 +33,10 @@ final class DiffusionRepositoryBasicsManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -67,38 +68,47 @@ final class DiffusionRepositoryBasicsManagementPanel $can_dangerous = ($can_edit && $repository->canAllowDangerousChanges()); } - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Basic Information')) ->setHref($edit_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), + ->setWorkflow(!$can_edit)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-text-width') ->setName(pht('Edit Text Encoding')) ->setHref($encoding_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), + ->setWorkflow(!$can_edit)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon($dangerous_icon) ->setName($dangerous_name) ->setHref($dangerous_uri) ->setDisabled(!$can_dangerous) - ->setWorkflow(true), + ->setWorkflow(true)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setHref($activate_uri) ->setIcon($activate_icon) ->setName($activate_label) ->setDisabled(!$can_edit) - ->setWorkflow(true), + ->setWorkflow(true)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setIcon('fa-times') ->setHref($delete_uri) ->setDisabled(true) - ->setWorkflow(true), - ); + ->setWorkflow(true)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -108,6 +118,7 @@ final class DiffusionRepositoryBasicsManagementPanel $repository = $this->getRepository(); $is_new = $repository->isNewlyInitialized(); + $info_view = null; if ($is_new) { $messages = array(); @@ -131,8 +142,6 @@ final class DiffusionRepositoryBasicsManagementPanel $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors($messages); - - $basics->setInfoView($info_view); } $result[] = $basics; @@ -142,7 +151,7 @@ final class DiffusionRepositoryBasicsManagementPanel $result[] = $this->newBox(pht('Description'), $description); } - return $result; + return array($info_view, $result); } private function buildBasics() { @@ -150,8 +159,7 @@ final class DiffusionRepositoryBasicsManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $name = $repository->getName(); $view->addProperty(pht('Name'), $name); diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index b3e7926cfc..aa2bb49ccb 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -41,9 +41,10 @@ final class DiffusionRepositoryBranchesManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -52,14 +53,15 @@ final class DiffusionRepositoryBranchesManagementPanel $branches_uri = $this->getEditPageURI(); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Branches')) ->setHref($branches_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); + ->setWorkflow(!$can_edit)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -67,8 +69,7 @@ final class DiffusionRepositoryBranchesManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $default_branch = nonempty( $repository->getHumanReadableDetail('default-branch'), diff --git a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php index ca2c577b38..9d067cd90b 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php @@ -21,6 +21,10 @@ final class DiffusionRepositoryDocumentationManagementPanel return null; } + public function buildManagementPanelCurtain() { + return null; + } + public function getPanelNavigationURI() { return PhabricatorEnv::getDoclink( 'Diffusion User Guide: Managing Repositories'); diff --git a/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php index c1a812dca0..4ce2367f20 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php @@ -21,5 +21,8 @@ final class DiffusionRepositoryHistoryManagementPanel return $this->newTimeline(); } + public function buildManagementPanelCurtain() { + return null; + } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index 34047cdb89..0098fe1df9 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -37,6 +37,7 @@ abstract class DiffusionRepositoryManagementPanel abstract public function getManagementPanelLabel(); abstract public function getManagementPanelOrder(); abstract public function buildManagementPanelContent(); + abstract public function buildManagementPanelCurtain(); public function getManagementPanelIcon() { return 'fa-pencil'; @@ -51,41 +52,20 @@ abstract class DiffusionRepositoryManagementPanel return true; } - final protected function newActions() { - $actions = $this->buildManagementPanelActions(); - if (!$actions) { - return null; - } - + public function getNewActionList() { $viewer = $this->getViewer(); + $action_id = celerity_generate_unique_node_id(); - $action_list = id(new PhabricatorActionListView()) - ->setViewer($viewer); - - foreach ($actions as $action) { - $action_list->addAction($action); - } - - return $action_list; + return id(new PhabricatorActionListView()) + ->setViewer($viewer) + ->setID($action_id); } - public function buildManagementPanelCurtain() { - // TODO: Delete or fix this, curtains always render in the left gutter - // at the moment. - return null; - - $actions = $this->newActions(); - if (!$actions) { - return null; - } - + public function getNewCurtainView(PhabricatorActionListView $action_list) { $viewer = $this->getViewer(); - - $curtain = id(new PHUICurtainView()) + return id(new PHUICurtainView()) ->setViewer($viewer) - ->setActionList($actions); - - return $curtain; + ->setActionList($action_list); } public static function getAllPanels() { diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php index 081869fbaa..6b2972e30c 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php @@ -54,9 +54,10 @@ final class DiffusionRepositoryPoliciesManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -65,14 +66,15 @@ final class DiffusionRepositoryPoliciesManagementPanel $edit_uri = $this->getEditPageURI(); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Policies')) ->setHref($edit_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); + ->setWorkflow(!$can_edit)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -80,8 +82,7 @@ final class DiffusionRepositoryPoliciesManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, diff --git a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php index 79b716e82a..bab7e72857 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php @@ -37,9 +37,10 @@ final class DiffusionRepositoryStagingManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -48,14 +49,15 @@ final class DiffusionRepositoryStagingManagementPanel $staging_uri = $this->getEditPageURI(); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Staging')) ->setHref($staging_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); + ->setWorkflow(!$can_edit)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -63,8 +65,7 @@ final class DiffusionRepositoryStagingManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $staging_uri = $repository->getStagingURI(); if (!$staging_uri) { diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php index 324338443d..23b2cd1684 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php @@ -28,9 +28,10 @@ final class DiffusionRepositoryStatusManagementPanel return 'fa-check grey'; } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -39,14 +40,15 @@ final class DiffusionRepositoryStatusManagementPanel $update_uri = $repository->getPathURI('edit/update/'); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-refresh') ->setName(pht('Update Now')) ->setWorkflow(true) ->setDisabled(!$can_edit) - ->setHref($update_uri), - ); + ->setHref($update_uri)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -54,8 +56,7 @@ final class DiffusionRepositoryStatusManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $view->addProperty( pht('Update Frequency'), diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php index 9cf210632d..3d6733d5d3 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php @@ -25,6 +25,22 @@ final class DiffusionRepositoryStorageManagementPanel } } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); + + $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setName(pht('Cluster Documentation'))); + + return $this->getNewCurtainView($action_list); + } + public function buildManagementPanelContent() { return array( $this->buildStorageStatusPanel(), @@ -55,13 +71,9 @@ final class DiffusionRepositoryStorageManagementPanel $view->addProperty(pht('Storage Path'), $storage_path); $view->addProperty(pht('Storage Cluster'), $storage_service); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Storage')); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($view); + $box = $this->newBox(pht('Storage'), null); + $box->addPropertyList($view); + return $box; } private function buildClusterStatusPanel() { @@ -231,21 +243,9 @@ final class DiffusionRepositoryStorageManagementPanel 'date', )); - $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Cluster Status')) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + $box = $this->newBox(pht('Cluster Status'), null); + $box->setTable($table); + return $box; } } diff --git a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php index c6b236d5b9..5b9384d195 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php @@ -36,9 +36,10 @@ final class DiffusionRepositorySubversionManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -47,14 +48,15 @@ final class DiffusionRepositorySubversionManagementPanel $subversion_uri = $this->getEditPageURI(); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Properties')) ->setHref($subversion_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); + ->setWorkflow(!$can_edit)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -62,8 +64,7 @@ final class DiffusionRepositorySubversionManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $default_branch = nonempty( $repository->getHumanReadableDetail('svn-subpath'), diff --git a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php index 996473f4f1..22d8780077 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php @@ -34,9 +34,10 @@ final class DiffusionRepositorySymbolsManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelCurtain() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -45,14 +46,15 @@ final class DiffusionRepositorySymbolsManagementPanel $symbols_uri = $this->getEditPageURI(); - return array( + $action_list->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Symbols')) ->setHref($symbols_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); + ->setWorkflow(!$can_edit)); + + return $this->getNewCurtainView($action_list); } public function buildManagementPanelContent() { @@ -60,8 +62,7 @@ final class DiffusionRepositorySymbolsManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $languages = $repository->getSymbolLanguages(); if ($languages) { diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php index 333c2f03c8..4fe9e00475 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php @@ -17,6 +17,35 @@ final class DiffusionRepositoryURIsManagementPanel return 400; } + public function buildManagementPanelCurtain() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $action_list = $this->getNewActionList(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs'); + $add_href = $repository->getPathURI('uri/edit/'); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-plus') + ->setHref($add_href) + ->setDisabled(!$can_edit) + ->setName(pht('Add New URI'))); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setName(pht('URI Documentation'))); + + return $this->getNewCurtainView($action_list); + } + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); @@ -95,24 +124,6 @@ final class DiffusionRepositoryURIsManagementPanel null, )); - $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs'); - $add_href = $repository->getPathURI('uri/edit/'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Repository URIs')) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-plus') - ->setHref($add_href) - ->setTag('a') - ->setText(pht('Add New URI'))) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); - $is_new = $repository->isNewlyInitialized(); $messages = array(); @@ -123,11 +134,7 @@ final class DiffusionRepositoryURIsManagementPanel $host_message = pht('Phabricator is hosting this repository.'); } - $messages[] = array( - id(new PHUIIconView())->setIcon('fa-folder'), - ' ', - $host_message, - ); + $messages[] = $host_message; } else { if ($is_new) { $observe_message = pht( @@ -137,22 +144,17 @@ final class DiffusionRepositoryURIsManagementPanel 'This repository is hosted remotely. Phabricator is observing it.'); } - $messages[] = array( - id(new PHUIIconView())->setIcon('fa-download'), - ' ', - $observe_message, - ); + $messages[] = $observe_message; } $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors($messages); - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setInfoView($info_view) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + $box = $this->newBox(pht('Repository URIs'), null); + $box->setTable($table); + + return array($info_view, $box); } } From 7e822fc94fa373cecaf55e702f03fd91be33ae96 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 23 May 2017 11:31:27 -0700 Subject: [PATCH 166/543] Update PhrictionContent for modular transactions Summary: Fixes T12625. Moves TYPE_CONTENT in Phriction over to modular transactions. Test Plan: Edit some documents. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12625 Differential Revision: https://secure.phabricator.com/D18000 --- src/__phutil_library_map__.php | 2 + .../PhrictionCreateConduitAPIMethod.php | 2 +- .../conduit/PhrictionEditConduitAPIMethod.php | 2 +- .../controller/PhrictionEditController.php | 6 +- .../editor/PhrictionTransactionEditor.php | 92 ++------------ .../storage/PhrictionTransaction.php | 113 +----------------- .../PhrictionDocumentContentTransaction.php | 95 +++++++++++++++ 7 files changed, 117 insertions(+), 195 deletions(-) create mode 100644 src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ada16cc079..1aef433927 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4637,6 +4637,7 @@ phutil_register_library_map(array( 'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php', 'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php', 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', + 'PhrictionDocumentContentTransaction' => 'applications/phriction/xaction/PhrictionDocumentContentTransaction.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', @@ -10304,6 +10305,7 @@ phutil_register_library_map(array( ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', + 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index 4b05874410..fc28b53b66 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -50,7 +50,7 @@ final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType(PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('content')); $editor = id(new PhrictionTransactionEditor()) diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index d4c2b63b5a..70c02d376a 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -45,7 +45,7 @@ final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType(PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('content')); $editor = id(new PhrictionTransactionEditor()) diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 1917348c24..17d11fdf3c 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -136,7 +136,8 @@ final class PhrictionEditController ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType( + PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($content_text); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -178,7 +179,8 @@ final class PhrictionEditController PhrictionDocumentTitleTransaction::TRANSACTIONTYPE), true); $e_content = nonempty( - $ex->getShortMessage(PhrictionTransaction::TYPE_CONTENT), + $ex->getShortMessage( + PhrictionDocumentContentTransaction::TRANSACTIONTYPE), true); // if we're not supposed to process the content version error, then diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index eaa6ea93f1..1920b4c718 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -85,8 +85,6 @@ final class PhrictionTransactionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhrictionTransaction::TYPE_CONTENT; - $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -95,41 +93,18 @@ final class PhrictionTransactionEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: - if ($this->getIsNewObject()) { - return null; - } - return $this->getOldContent()->getContent(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: - return $xaction->getNewValue(); - } - } - protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: - case PhrictionTransaction::TYPE_CONTENT: - case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: - case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: - return true; + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: + return true; } } return parent::shouldApplyInitialEffects($object, $xactions); @@ -143,24 +118,13 @@ final class PhrictionTransactionEditor $this->setNewContent($this->buildNewContentTemplate($object)); } - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: - $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - return; - } - } - protected function expandTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $xactions = parent::expandTransaction($object, $xaction); switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: if ($this->getIsNewObject()) { break; } @@ -190,19 +154,6 @@ final class PhrictionTransactionEditor return $xactions; } - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: - $this->getNewContent()->setContent($xaction->getNewValue()); - break; - default: - break; - } - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -214,7 +165,7 @@ final class PhrictionTransactionEditor case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $save_content = true; break; default: @@ -257,7 +208,8 @@ final class PhrictionTransactionEditor ->setNewValue(PhabricatorSlug::getDefaultTitle($slug)) ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType( + PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue('') ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) @@ -295,7 +247,7 @@ final class PhrictionTransactionEditor // Compute the content diff URI for the publishing phase. foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $uri = id(new PhutilURI('/phriction/diff/'.$object->getID().'/')) ->alter('l', $this->getOldContent()->getVersion()) ->alter('r', $this->getNewContent()->getVersion()); @@ -419,29 +371,12 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($type) { - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: if ($xaction->getMetadataValue('stub:create:phid')) { continue; } - $missing = false; - if ($this->getIsNewObject()) { - $content = $object->getContent()->getContent(); - $missing = $this->validateIsEmptyTextField( - $content, - $xactions); - } - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Document content is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($this->getProcessContentVersionError()) { + if ($this->getProcessContentVersionError()) { $error = $this->validateContentVersion($object, $type, $xaction); if ($error) { $this->setProcessContentVersionError(false); @@ -459,7 +394,6 @@ final class PhrictionTransactionEditor $errors = array_merge($errors, $ancestry_errors); } } - break; case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php index 542e9b3e58..2ce9d38a08 100644 --- a/src/applications/phriction/storage/PhrictionTransaction.php +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -3,8 +3,6 @@ final class PhrictionTransaction extends PhabricatorModularTransaction { - const TYPE_CONTENT = 'content'; - const MAILTAG_TITLE = 'phriction-title'; const MAILTAG_CONTENT = 'phriction-content'; const MAILTAG_DELETE = 'phriction-delete'; @@ -45,32 +43,6 @@ final class PhrictionTransaction return $phids; } - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function shouldHide() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - if ($this->getOldValue() === null) { - return true; - } else { - return false; - } - break; - } - - return parent::shouldHide(); - } - public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: @@ -93,89 +65,6 @@ final class PhrictionTransaction return parent::shouldHideForFeed(); } - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return 1.3; - } - - return parent::getActionStrength(); - } - - public function getActionName() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return pht('Edited'); - } - - return parent::getActionName(); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return 'fa-pencil'; - } - - return parent::getIcon(); - } - - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return pht( - '%s edited the document content.', - $this->renderHandleLink($author_phid)); - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - - case self::TYPE_CONTENT: - return pht( - '%s edited the content of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } - return parent::getTitleForFeed(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } public function getMailTags() { $tags = array(); @@ -183,7 +72,7 @@ final class PhrictionTransaction case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_TITLE; break; - case self::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_CONTENT; break; case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: diff --git a/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php new file mode 100644 index 0000000000..a051546824 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php @@ -0,0 +1,95 @@ +getEditor()->getIsNewObject()) { + return null; + } + return $object->getContent()->getContent(); + } + + public function generateNewValue($object, $value) { + return $value; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { + $this->getEditor()->getNewContent()->setContent($value); + } + + public function shouldHide() { + if ($this->getOldValue() === null) { + return true; + } else { + return false; + } + } + + public function getActionStrength() { + return 1.3; + } + + public function getActionName() { + return pht('Edited'); + } + + public function getTitle() { + return pht( + '%s edited the content of this document.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s edited the content of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT CONTENT'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $content = $object->getContent()->getContent(); + if ($this->isEmptyTextTransaction($content, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have content.')); + } + + return $errors; + } + +} From 774fba3ce925af1500bcece04368932483e9cdf8 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 23 May 2017 11:54:37 -0700 Subject: [PATCH 167/543] Migrate Project workboard to modular transactions Summary: This was interesting, because there were a mix of callsites using transactions and others that just set the property on the `Project` object. I made everything consistent in using transactions to change this property. I also found an implementation of `getTitle()` that I don't think is ever being invoked since `shouldHide()` is returning `true`, but I migrated it anyway. Test Plan: Unit tests pass + enabling/disabling workboards (and importing). Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D18004 --- src/__phutil_library_map__.php | 2 + ...abricatorProjectBoardDisableController.php | 3 +- ...habricatorProjectBoardImportController.php | 12 +++++- .../PhabricatorProjectBoardViewController.php | 16 +++++++- .../PhabricatorProjectTransactionEditor.php | 9 ----- .../storage/PhabricatorProjectTransaction.php | 15 +------- ...PhabricatorProjectWorkboardTransaction.php | 38 +++++++++++++++++++ 7 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1aef433927..21daf85177 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3708,6 +3708,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php', 'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php', 'PhabricatorProjectWorkboardProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php', + 'PhabricatorProjectWorkboardTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardTransaction.php', 'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsAncestorsSearchEngineAttachment.php', 'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', @@ -9148,6 +9149,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView', 'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject', 'PhabricatorProjectWorkboardProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorProjectWorkboardTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', diff --git a/src/applications/project/controller/PhabricatorProjectBoardDisableController.php b/src/applications/project/controller/PhabricatorProjectBoardDisableController.php index 0440ff9eff..fc850640f1 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardDisableController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardDisableController.php @@ -33,7 +33,8 @@ final class PhabricatorProjectBoardDisableController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) ->setNewValue(0); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardImportController.php b/src/applications/project/controller/PhabricatorProjectBoardImportController.php index 988084d3ee..c344bc0af0 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardImportController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardImportController.php @@ -61,8 +61,18 @@ final class PhabricatorProjectBoardImportController ->setProperties($import_column->getProperties()) ->save(); } + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) + ->setNewValue(1); - $project->setHasWorkboard(1)->save(); + id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($project, $xactions); $table->saveTransaction(); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index ad08df1692..8405ec677f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -966,7 +966,18 @@ final class PhabricatorProjectBoardViewController ->setProjectPHID($project->getPHID()) ->save(); - $project->setHasWorkboard(1)->save(); + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) + ->setNewValue(1); + + id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($project, $xactions); return id(new AphrontRedirectResponse()) ->setURI($board_uri); @@ -1050,7 +1061,8 @@ final class PhabricatorProjectBoardViewController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) ->setNewValue(1); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 8b77f71505..69d16c3786 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_HASWORKBOARD; $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT; $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER; $types[] = PhabricatorProjectTransaction::TYPE_BACKGROUND; @@ -43,8 +42,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - return (int)$object->getHasWorkboard(); case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: return $object->getDefaultWorkboardSort(); case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: @@ -64,8 +61,6 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: return $xaction->getNewValue(); - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - return (int)$xaction->getNewValue(); case PhabricatorProjectTransaction::TYPE_BACKGROUND: $value = $xaction->getNewValue(); if (!strlen($value)) { @@ -82,9 +77,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - $object->setHasWorkboard($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: $object->setDefaultWorkboardSort($xaction->getNewValue()); return; @@ -107,7 +99,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: case PhabricatorProjectTransaction::TYPE_BACKGROUND: diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index b6c77808bc..cfb99d7469 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,7 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_HASWORKBOARD = 'project:hasworkboard'; const TYPE_DEFAULT_SORT = 'project:sort'; const TYPE_DEFAULT_FILTER = 'project:filter'; const TYPE_BACKGROUND = 'project:background'; @@ -61,7 +60,6 @@ final class PhabricatorProjectTransaction public function shouldHideForFeed() { switch ($this->getTransactionType()) { - case self::TYPE_HASWORKBOARD: case self::TYPE_DEFAULT_SORT: case self::TYPE_DEFAULT_FILTER: case self::TYPE_BACKGROUND: @@ -73,7 +71,7 @@ final class PhabricatorProjectTransaction public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { - case self::TYPE_HASWORKBOARD: + case PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE: case self::TYPE_DEFAULT_SORT: case self::TYPE_DEFAULT_FILTER: case self::TYPE_BACKGROUND: @@ -142,17 +140,6 @@ final class PhabricatorProjectTransaction } break; - case self::TYPE_HASWORKBOARD: - if ($new) { - return pht( - '%s enabled the workboard for this project.', - $author_handle); - } else { - return pht( - '%s disabled the workboard for this project.', - $author_handle); - } - case self::TYPE_DEFAULT_SORT: return pht( '%s changed the default sort order for the project workboard.', diff --git a/src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php b/src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php new file mode 100644 index 0000000000..b16a1921e0 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php @@ -0,0 +1,38 @@ +getHasWorkboard(); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setHasWorkboard($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s enabled the workboard for this project.', + $this->renderAuthor()); + } else { + return pht( + '%s disabled the workboard for this project.', + $this->renderAuthor()); + } + } + + public function shouldHide() { + return true; + } + +} From 1b36252ef39bfa2f58dbc7b1d54a00d34d489fd7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 23 May 2017 14:01:32 -0700 Subject: [PATCH 168/543] Add a dedicated HistoryListView for Diffusion Summary: Going to play a bit with this layout (diffusion sans audit) and see how it feels on profile. Uses a user image, moves the commit hash (easily selectible) and separates commits by date. Test Plan: Review profiles with and without commits. {F4973987} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18005 --- resources/celerity/map.php | 2 + src/__phutil_library_map__.php | 2 + .../view/DiffusionHistoryListView.php | 148 ++++++++++++++++++ ...bricatorPeopleProfileCommitsController.php | 11 +- .../diffusion/diffusion-history.css | 12 ++ 5 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 src/applications/diffusion/view/DiffusionHistoryListView.php create mode 100644 webroot/rsrc/css/application/diffusion/diffusion-history.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2415f90ff1..8329159685 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -71,6 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', + 'rsrc/css/application/diffusion/diffusion-history.css' => 'b4ac65b3', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -574,6 +575,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', + 'diffusion-history-css' => 'b4ac65b3', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 21daf85177..ff287cbb01 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -728,6 +728,7 @@ phutil_register_library_map(array( 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', + 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', @@ -5694,6 +5695,7 @@ phutil_register_library_map(array( ), 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionHistoryController' => 'DiffusionController', + 'DiffusionHistoryListView' => 'AphrontView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php new file mode 100644 index 0000000000..d3223f70db --- /dev/null +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -0,0 +1,148 @@ +noDataString = $no_data_string; + return $this; + } + + public function setHeader($header) { + $this->header = $header; + return $this; + } + + public function setCommits(array $commits) { + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); + $this->commits = mpull($commits, null, 'getPHID'); + return $this; + } + + public function getCommits() { + return $this->commits; + } + + private function getCommitDescription($phid) { + if ($this->commits === null) { + return pht('(Unknown Commit)'); + } + + $commit = idx($this->commits, $phid); + if (!$commit) { + return pht('(Unknown Commit)'); + } + + $summary = $commit->getCommitData()->getSummary(); + if (strlen($summary)) { + return $summary; + } + + // No summary, so either this is still importing or just has an empty + // commit message. + + if (!$commit->isImported()) { + return pht('(Importing Commit...)'); + } else { + return pht('(Untitled Commit)'); + } + } + + public function render() { + require_celerity_resource('diffusion-history-css'); + return $this->buildList(); + } + + public function buildList() { + $viewer = $this->getViewer(); + $rowc = array(); + + $phids = array(); + foreach ($this->getCommits() as $commit) { + $phids[] = $commit->getPHID(); + + $author_phid = $commit->getAuthorPHID(); + if ($author_phid) { + $phids[] = $author_phid; + } + } + + $handles = $viewer->loadHandles($phids); + + $cur_date = 0; + $list = null; + $header = null; + $view = array(); + foreach ($this->commits as $commit) { + $new_date = date('Ymd', $commit->getEpoch()); + if ($cur_date != $new_date) { + if ($list) { + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + $date = ucfirst( + phabricator_relative_date($commit->getEpoch(), $viewer)); + $header = id(new PHUIHeaderView()) + ->setHeader($date); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list'); + } + + $commit_phid = $commit->getPHID(); + $commit_handle = $handles[$commit_phid]; + $committed = null; + + $commit_name = $commit_handle->getName(); + $commit_link = $commit_handle->getURI(); + $commit_desc = $this->getCommitDescription($commit_phid); + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + + $author_phid = $commit->getAuthorPHID(); + if ($author_phid) { + $author_name = $handles[$author_phid]->renderLink(); + $author_image_uri = $handles[$author_phid]->getImageURI(); + } else { + $author_name = $commit->getCommitData()->getAuthorName(); + $author_image_uri = + celerity_get_resource_uri('/rsrc/image/people/user0.png'); + } + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setSlimShady(true); + + $item = id(new PHUIObjectItemView()) + ->setHeader($commit_desc) + ->setHref($commit_link) + ->setDisabled($commit->isUnreachable()) + ->setImageURI($author_image_uri) + ->addByline(pht('Author: %s', $author_name)) + ->addIcon('none', $committed) + ->addAttribute($commit_tag); + + $list->addItem($item); + $cur_date = $new_date; + } + + if (!$view) { + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->setNoDataString($this->noDataString); + + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Commits')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + + return $view; + } + +} diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php index 3e1e6de1ee..81e4fa304e 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php @@ -56,22 +56,15 @@ final class PhabricatorPeopleProfileCommitsController $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withAuthorPHIDs(array($user->getPHID())) - ->needAuditRequests(true) ->needCommitData(true) - ->needDrafts(true) ->setLimit(100) ->execute(); - $list = id(new PhabricatorAuditListView()) + $list = id(new DiffusionHistoryListView()) ->setViewer($viewer) ->setCommits($commits) ->setNoDataString(pht('No recent commits.')); - $view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Commits')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($list); - - return $view; + return $list; } } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css new file mode 100644 index 0000000000..796b4b6059 --- /dev/null +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -0,0 +1,12 @@ +/** + * @provides diffusion-history-css + */ + +.diffusion-history-list .phui-oi-link { + color: {$darkbluetext}; + font-size: {$biggerfontsize}; +} + +.diffusion-history-list .phui-oi-attribute .phui-tag-core { + border-color: transparent; +} From 684ce701fb03746c6669c02ad3285f637c252cc2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 23 May 2017 19:21:05 -0700 Subject: [PATCH 169/543] Add a description/toggle to PHUIObjectItemView Summary: Gives the ability to hide a big long block of text in an ObjectListItem without cluttering the UI. Test Plan: Added a test case to UIExamples. Click on icon, see content. Click again, content go away. {F4974153} {F4974311} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18006 --- resources/celerity/map.php | 10 +++--- .../view/DiffusionHistoryListView.php | 9 +++++ .../examples/PHUIObjectItemListExample.php | 2 +- src/view/phui/PHUIObjectItemView.php | 34 +++++++++++++++++++ .../diffusion/diffusion-history.css | 8 +++++ .../phui/object-item/phui-oi-list-view.css | 25 ++++++++++++++ 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8329159685..a71d9f524e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '5387f8b6', + 'core.pkg.css' => '71865bdf', 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => 'b4ac65b3', + 'rsrc/css/application/diffusion/diffusion-history.css' => '0c596546', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -132,7 +132,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '412bef1a', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '78fdc98e', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -575,7 +575,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => 'b4ac65b3', + 'diffusion-history-css' => '0c596546', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', @@ -875,7 +875,7 @@ return array( 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '412bef1a', + 'phui-oi-list-view-css' => '78fdc98e', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index d3223f70db..00a6eb0741 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -102,6 +102,14 @@ final class DiffusionHistoryListView extends AphrontView { $commit_desc = $this->getCommitDescription($commit_phid); $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); + $engine->setConfig('viewer', $viewer); + $commit_data = $commit->getCommitData(); + $message = $commit_data->getCommitMessage(); + $message = $engine->markupText($message); + $message = phutil_tag_div( + 'diffusion-history-message phabricator-remarkup', $message); + $author_phid = $commit->getAuthorPHID(); if ($author_phid) { $author_name = $handles[$author_phid]->renderLink(); @@ -122,6 +130,7 @@ final class DiffusionHistoryListView extends AphrontView { ->setHeader($commit_desc) ->setHref($commit_link) ->setDisabled($commit->isUnreachable()) + ->setDescription($message) ->setImageURI($author_image_uri) ->addByline(pht('Author: %s', $author_name)) ->addIcon('none', $committed) diff --git a/src/applications/uiexample/examples/PHUIObjectItemListExample.php b/src/applications/uiexample/examples/PHUIObjectItemListExample.php index 4a75ceede1..691c84554c 100644 --- a/src/applications/uiexample/examples/PHUIObjectItemListExample.php +++ b/src/applications/uiexample/examples/PHUIObjectItemListExample.php @@ -202,7 +202,7 @@ final class PHUIObjectItemListExample extends PhabricatorUIExample { $list->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Ace of Hearts')) - ->setSubHead( + ->setDescription( pht('This is a powerful card in the game "Hearts".')) ->setHref('#') ->addAttribute(pht('Suit: Hearts')) diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 6d193081d2..59547c4442 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -26,6 +26,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $countdownNoun; private $launchButton; private $coverImage; + private $description; public function setDisabled($disabled) { $this->disabled = $disabled; @@ -148,6 +149,11 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function setDescription($description) { + $this->description = $description; + return $this; + } + public function setEpoch($epoch) { $date = phabricator_datetime($epoch, $this->getUser()); $this->addIcon('none', $date); @@ -334,6 +340,23 @@ final class PHUIObjectItemView extends AphrontTagView { ), $this->header); + $description_tag = null; + if ($this->description) { + $decription_id = celerity_generate_unique_node_id(); + $description_tag = id(new PHUITagView()) + ->setIcon('fa-ellipsis-h') + ->addClass('phui-oi-description-tag') + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_GREY) + ->addSigil('jx-toggle-class') + ->setSlimShady(true) + ->setMetaData(array( + 'map' => array( + $decription_id => 'phui-oi-description-reveal', + ), + )); + } + // Wrap the header content in a with the "slippery" sigil. This // prevents us from beginning a drag if you click the text (like "T123"), // but not if you click the white space after the header. @@ -351,6 +374,7 @@ final class PHUIObjectItemView extends AphrontTagView { $this->headIcons, $header_name, $header_link, + $description_tag, ))); $icons = array(); @@ -453,6 +477,16 @@ final class PHUIObjectItemView extends AphrontTagView { $this->subhead); } + if ($this->description) { + $subhead = phutil_tag( + 'div', + array( + 'class' => 'phui-oi-subhead phui-oi-description', + 'id' => $decription_id, + ), + $this->description); + } + if ($icons) { $icons = phutil_tag( 'div', diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index 796b4b6059..b340cd913d 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -10,3 +10,11 @@ .diffusion-history-list .phui-oi-attribute .phui-tag-core { border-color: transparent; } + +.diffusion-history-message { + background-color: {$bluebackground}; + padding: 16px; + margin: 4px 0; + border-radius: 5px; + color: {$darkbluetext}; +} diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 26b0781e8d..cbc2d43238 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -285,6 +285,31 @@ ul.phui-oi-list-view { padding: 0 8px 6px; } +.phui-oi-description { + display: none; +} + +.phui-oi-description.phui-oi-description-reveal { + display: block; +} + +.phui-oi-description-tag { + margin-left: 4px; +} + +.phui-oi-description-tag:hover .phui-tag-core { + cursor: pointer; + background: {$darkgreybackground}; +} + +.phui-oi-description-tag .phui-tag-core { + border: none; +} + +.phui-oi-description-tag.phui-tag-view .phui-icon-view { + margin: 2px; +} + /* - Attribute List ------------------------------------------------------------ From c4392ba067575c5c5a6523df25dd5b341bcc1be4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 24 May 2017 10:18:21 -0700 Subject: [PATCH 170/543] Add slugs to project manage page Summary: Minor, just shows the slugs on the manage project page, also normalized language to "details" Test Plan: review a project with slugs, description. Reviewers: amckinley Reviewed By: amckinley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D17985 --- .../project/controller/PhabricatorProjectController.php | 8 ++++++++ .../controller/PhabricatorProjectManageController.php | 7 ++++++- .../controller/PhabricatorProjectProfileController.php | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 45e2f193af..7c344b0b8e 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -166,4 +166,12 @@ abstract class PhabricatorProjectController extends PhabricatorController { ->buildResponse(); } + public function renderHashtags(array $tags) { + $result = array(); + foreach ($tags as $key => $tag) { + $result[] = '#'.$tag; + } + return implode(', ', $result); + } + } diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 17b9030658..2c76c63606 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -138,6 +138,12 @@ final class PhabricatorProjectManageController pht('Looks Like'), $viewer->renderHandle($project->getPHID())->setAsTag(true)); + $slugs = $project->getSlugs(); + $tags = mpull($slugs, 'getSlug'); + + $view->addProperty( + pht('Hashtags'), + $this->renderHashtags($tags)); $field_list = PhabricatorCustomField::getObjectFields( $project, @@ -147,5 +153,4 @@ final class PhabricatorProjectManageController return $view; } - } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 32dbec3c0f..e47cc678bc 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -135,7 +135,7 @@ final class PhabricatorProjectProfileController } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Properties')); + ->setHeader(pht('Details')); $view = id(new PHUIObjectBoxView()) ->setHeader($header) From 272a5d668f9e67b1ad61f6b13877974007ccf9ec Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 22 May 2017 12:24:58 -0700 Subject: [PATCH 171/543] Fix a handful of Nuance fatals Summary: Ref T12738. Some of the Nuance "form" workflows currently fatal after work on the GitHub stuff. Try to make everything stop fataling, at least. Test Plan: Using "Complaints Form" no longer fatals, and now lodges a complaint instead. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D18007 --- .../autopatches/20170522.nuance.01.itemkey.sql | 2 ++ src/__phutil_library_map__.php | 2 ++ .../cursor/NuanceGitHubIssuesImportCursor.php | 3 +-- .../NuanceGitHubRepositoryImportCursor.php | 3 +-- .../nuance/item/NuanceFormItemType.php | 16 ++++++++++++++++ .../nuance/phid/NuanceItemPHIDType.php | 2 +- .../NuancePhabricatorFormSourceDefinition.php | 4 +++- .../nuance/source/NuanceSourceDefinition.php | 5 ++++- src/applications/nuance/storage/NuanceItem.php | 8 ++++++-- .../xaction/NuanceItemPropertyTransaction.php | 4 ++-- 10 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 resources/sql/autopatches/20170522.nuance.01.itemkey.sql create mode 100644 src/applications/nuance/item/NuanceFormItemType.php diff --git a/resources/sql/autopatches/20170522.nuance.01.itemkey.sql b/resources/sql/autopatches/20170522.nuance.01.itemkey.sql new file mode 100644 index 0000000000..75118205ce --- /dev/null +++ b/resources/sql/autopatches/20170522.nuance.01.itemkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + MODIFY itemKey VARCHAR(64) COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ff287cbb01..ac24aa3cb6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1606,6 +1606,7 @@ phutil_register_library_map(array( 'NuanceContentSource' => 'applications/nuance/contentsource/NuanceContentSource.php', 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', + 'NuanceFormItemType' => 'applications/nuance/item/NuanceFormItemType.php', 'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php', 'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php', 'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php', @@ -6716,6 +6717,7 @@ phutil_register_library_map(array( 'NuanceContentSource' => 'PhabricatorContentSource', 'NuanceController' => 'PhabricatorController', 'NuanceDAO' => 'PhabricatorLiskDAO', + 'NuanceFormItemType' => 'NuanceItemType', 'NuanceGitHubEventItemType' => 'NuanceItemType', 'NuanceGitHubImportCursor' => 'NuanceImportCursor', 'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor', diff --git a/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php index d250b1d02d..151a5876ba 100644 --- a/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php @@ -17,10 +17,9 @@ final class NuanceGitHubIssuesImportCursor $container_key = null; - return NuanceItem::initializeNewItem() + return NuanceItem::initializeNewItem(NuanceGitHubEventItemType::ITEMTYPE) ->setStatus(NuanceItem::STATUS_IMPORTING) ->setSourcePHID($source->getPHID()) - ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) ->setItemKey($item_key) ->setItemContainerKey($container_key) ->setItemProperty('api.type', 'issue') diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php index 72aca276d2..018b3aa8c1 100644 --- a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -36,10 +36,9 @@ final class NuanceGitHubRepositoryImportCursor $container_key = "github.issue.{$issue_id}"; } - return NuanceItem::initializeNewItem() + return NuanceItem::initializeNewItem(NuanceGitHubEventItemType::ITEMTYPE) ->setStatus(NuanceItem::STATUS_IMPORTING) ->setSourcePHID($source->getPHID()) - ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) ->setItemKey($item_key) ->setItemContainerKey($container_key) ->setItemProperty('api.type', 'repository') diff --git a/src/applications/nuance/item/NuanceFormItemType.php b/src/applications/nuance/item/NuanceFormItemType.php new file mode 100644 index 0000000000..ee2f923d08 --- /dev/null +++ b/src/applications/nuance/item/NuanceFormItemType.php @@ -0,0 +1,16 @@ + $handle) { $item = $objects[$phid]; - $handle->setName($item->getItemDisplayName()); + $handle->setName($item->getDisplayName()); $handle->setURI($item->getURI()); } } diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index e7c7212590..2eaf5781dc 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -39,6 +39,8 @@ final class NuancePhabricatorFormSourceDefinition $content_source = PhabricatorContentSource::newFromRequest($request); $item = $this->newItemFromProperties( + NuanceFormItemType::ITEMTYPE, + $viewer->getPHID(), $properties, $content_source); @@ -79,7 +81,7 @@ final class NuancePhabricatorFormSourceDefinition NuanceItem $item, PHUIPropertyListView $view) { - $complaint = $item->getNuanceProperty('complaint'); + $complaint = $item->getItemProperty('complaint'); $complaint = new PHUIRemarkupView($viewer, $complaint); $view->addSectionHeader( pht('Complaint'), 'fa-exclamation-circle'); diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 0330aa5c7e..2c4ebc65e5 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -149,6 +149,8 @@ abstract class NuanceSourceDefinition extends Phobject { } protected function newItemFromProperties( + $item_type, + $author_phid, array $properties, PhabricatorContentSource $content_source) { @@ -157,7 +159,7 @@ abstract class NuanceSourceDefinition extends Phobject { $actor = PhabricatorUser::getOmnipotentUser(); $source = $this->getSource(); - $item = NuanceItem::initializeNewItem(); + $item = NuanceItem::initializeNewItem($item_type); $xactions = array(); @@ -181,6 +183,7 @@ abstract class NuanceSourceDefinition extends Phobject { $editor = id(new NuanceItemEditor()) ->setActor($actor) + ->setActingAsPHID($author_phid) ->setContentSource($content_source); $editor->applyTransactions($item, $xactions); diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 2eadcdb2d9..9fbfea8dae 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -26,8 +26,12 @@ final class NuanceItem private $source = self::ATTACHABLE; private $implementation = self::ATTACHABLE; - public static function initializeNewItem() { + public static function initializeNewItem($item_type) { + + // TODO: Validate that the type is valid, and construct and attach it. + return id(new NuanceItem()) + ->setItemType($item_type) ->setStatus(self::STATUS_OPEN); } @@ -42,7 +46,7 @@ final class NuanceItem 'requestorPHID' => 'phid?', 'queuePHID' => 'phid?', 'itemType' => 'text64', - 'itemKey' => 'text64', + 'itemKey' => 'text64?', 'itemContainerKey' => 'text64?', 'status' => 'text32', 'mailKey' => 'bytes20', diff --git a/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php b/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php index 76724843de..3367a6a0cf 100644 --- a/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php +++ b/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php @@ -8,14 +8,14 @@ final class NuanceItemPropertyTransaction public function generateOldValue($object) { $property_key = NuanceItemTransaction::PROPERTY_KEY; $key = $this->getMetadataValue($property_key); - return $object->getNuanceProperty($key); + return $object->getItemProperty($key); } public function applyInternalEffects($object, $value) { $property_key = NuanceItemTransaction::PROPERTY_KEY; $key = $this->getMetadataValue($property_key); - $object->setNuanceProperty($key, $value); + $object->setItemProperty($key, $value); } public function getTitle() { From cbf9008d158fedb42a2957f9d541877467bbafe9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 24 May 2017 06:51:59 -0700 Subject: [PATCH 172/543] Rough in a Nuance "work" controller Summary: Ref T12738. This is mostly just laying in groundwork and prerequisites, like the ability to query items by queue. Eventually, this will become the main UI which staff use to process a queue of items. For now, it does nothing and renders nonsense. This and probably the next big chunk of changes are all going to be made-up, nonfinal things that just make basic operations work until we have fundamental flows -- like "assign", "comment", "close" -- working at a basic level and can think more about UI/workflow. Test Plan: Visited the page, it loaded a mostly-reasonable item and then rendered nonsense: {F4975050} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D18008 --- src/__phutil_library_map__.php | 2 + .../PhabricatorNuanceApplication.php | 1 + .../controller/NuanceQueueViewController.php | 8 ++ .../controller/NuanceQueueWorkController.php | 98 +++++++++++++++++++ .../nuance/query/NuanceItemQuery.php | 69 ++++++++++++- .../nuance/query/NuanceItemSearchEngine.php | 13 +++ .../nuance/storage/NuanceItem.php | 10 ++ 7 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 src/applications/nuance/controller/NuanceQueueWorkController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ac24aa3cb6..cc16cb4e88 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1663,6 +1663,7 @@ phutil_register_library_map(array( 'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php', 'NuanceQueueTransactionType' => 'applications/nuance/xaction/NuanceQueueTransactionType.php', 'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php', + 'NuanceQueueWorkController' => 'applications/nuance/controller/NuanceQueueWorkController.php', 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', @@ -6788,6 +6789,7 @@ phutil_register_library_map(array( 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceQueueTransactionType' => 'PhabricatorModularTransactionType', 'NuanceQueueViewController' => 'NuanceQueueController', + 'NuanceQueueWorkController' => 'NuanceQueueController', 'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'NuanceSource' => array( 'NuanceDAO', diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index cf18bc389d..9f99c9e5bd 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -51,6 +51,7 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { $this->getQueryRoutePattern() => 'NuanceQueueListController', $this->getEditRoutePattern('edit/') => 'NuanceQueueEditController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', + 'work/(?P[1-9]\d*)/' => 'NuanceQueueWorkController', ), ), '/action/' => array( diff --git a/src/applications/nuance/controller/NuanceQueueViewController.php b/src/applications/nuance/controller/NuanceQueueViewController.php index 8f4e85565a..f5284f6bfa 100644 --- a/src/applications/nuance/controller/NuanceQueueViewController.php +++ b/src/applications/nuance/controller/NuanceQueueViewController.php @@ -70,6 +70,14 @@ final class NuanceQueueViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Begin Work')) + ->setIcon('fa-play-circle-o') + ->setHref($this->getApplicationURI("queue/work/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + return $curtain; } diff --git a/src/applications/nuance/controller/NuanceQueueWorkController.php b/src/applications/nuance/controller/NuanceQueueWorkController.php new file mode 100644 index 0000000000..3d6308664d --- /dev/null +++ b/src/applications/nuance/controller/NuanceQueueWorkController.php @@ -0,0 +1,98 @@ +getViewer(); + + $queue = id(new NuanceQueueQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$queue) { + return new Aphront404Response(); + } + + $title = $queue->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Queues'), $this->getApplicationURI('queue/')); + $crumbs->addTextCrumb($queue->getName(), $queue->getURI()); + $crumbs->addTextCrumb(pht('Work')); + $crumbs->setBorder(true); + + // For now, just pick the first open item. + + $items = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withQueuePHIDs( + array( + $queue->getPHID(), + )) + ->withStatuses( + array( + NuanceItem::STATUS_OPEN, + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->setLimit(5) + ->execute(); + + if (!$items) { + return $this->newDialog() + ->setTitle(pht('Queue Empty')) + ->appendParagraph( + pht( + 'This queue has no open items which you have permission to '. + 'work on.')) + ->addCancelButton($queue->getURI()); + } + + $item = head($items); + + $curtain = $this->buildCurtain($queue); + + $timeline = $this->buildTransactionTimeline( + $item, + new NuanceItemTransactionQuery()); + $timeline->setShouldTerminate(true); + + $view = id(new PHUITwoColumnView()) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildCurtain(NuanceQueue $queue) { + $viewer = $this->getViewer(); + $id = $queue->getID(); + + $curtain = $this->newCurtainView(); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_LABEL) + ->setName(pht('Queue Actions'))); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage Queue')) + ->setIcon('fa-cog') + ->setHref($this->getApplicationURI("queue/view/{$id}/"))); + + return $curtain; + } + +} diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php index 5bb0ec70ec..834e81ca72 100644 --- a/src/applications/nuance/query/NuanceItemQuery.php +++ b/src/applications/nuance/query/NuanceItemQuery.php @@ -6,9 +6,11 @@ final class NuanceItemQuery private $ids; private $phids; private $sourcePHIDs; + private $queuePHIDs; private $itemTypes; private $itemKeys; private $containerKeys; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -25,6 +27,11 @@ final class NuanceItemQuery return $this; } + public function withQueuePHIDs(array $queue_phids) { + $this->queuePHIDs = $queue_phids; + return $this; + } + public function withItemTypes(array $item_types) { $this->itemTypes = $item_types; return $this; @@ -35,6 +42,11 @@ final class NuanceItemQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function withItemContainerKeys(array $container_keys) { $this->containerKeys = $container_keys; return $this; @@ -49,13 +61,11 @@ final class NuanceItemQuery } protected function willFilterPage(array $items) { + $viewer = $this->getViewer(); $source_phids = mpull($items, 'getSourcePHID'); - // NOTE: We always load sources, even if the viewer can't formally see - // them. If they can see the item, they're allowed to be aware of the - // source in some sense. $sources = id(new NuanceSourceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withPHIDs($source_phids) ->execute(); $sources = mpull($sources, null, 'getPHID'); @@ -81,6 +91,43 @@ final class NuanceItemQuery $item->attachImplementation($type); } + $queue_phids = array(); + foreach ($items as $item) { + $queue_phid = $item->getQueuePHID(); + if ($queue_phid) { + $queue_phids[$queue_phid] = $queue_phid; + } + } + + if ($queue_phids) { + $queues = id(new NuanceQueueQuery()) + ->setViewer($viewer) + ->withPHIDs($queue_phids) + ->execute(); + $queues = mpull($queues, null, 'getPHID'); + } else { + $queues = array(); + } + + foreach ($items as $key => $item) { + $queue_phid = $item->getQueuePHID(); + + if (!$queue_phid) { + $item->attachQueue(null); + continue; + } + + $queue = idx($queues, $queue_phid); + + if (!$queue) { + unset($items[$key]); + $this->didRejectResult($item); + continue; + } + + $item->attachQueue($queue); + } + return $items; } @@ -94,6 +141,13 @@ final class NuanceItemQuery $this->sourcePHIDs); } + if ($this->queuePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'queuePHID IN (%Ls)', + $this->queuePHIDs); + } + if ($this->ids !== null) { $where[] = qsprintf( $conn, @@ -108,6 +162,13 @@ final class NuanceItemQuery $this->phids); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'status IN (%Ls)', + $this->statuses); + } + if ($this->itemTypes !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/nuance/query/NuanceItemSearchEngine.php b/src/applications/nuance/query/NuanceItemSearchEngine.php index 2f0951b4e0..0868d7551a 100644 --- a/src/applications/nuance/query/NuanceItemSearchEngine.php +++ b/src/applications/nuance/query/NuanceItemSearchEngine.php @@ -72,6 +72,19 @@ final class NuanceItemSearchEngine $impl->getItemTypeDisplayIcon(), $impl->getItemTypeDisplayName()); + $queue = $item->getQueue(); + if ($queue) { + $view->addAttribute( + phutil_tag( + 'a', + array( + 'href' => $queue->getURI(), + ), + $queue->getName())); + } else { + $view->addAttribute(phutil_tag('em', array(), pht('Not in Queue'))); + } + $list->addItem($view); } diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 9fbfea8dae..b1deab3af2 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -23,6 +23,7 @@ final class NuanceItem protected $data = array(); protected $mailKey; + private $queue = self::ATTACHABLE; private $source = self::ATTACHABLE; private $implementation = self::ATTACHABLE; @@ -176,6 +177,15 @@ final class NuanceItem return $this; } + public function getQueue() { + return $this->assertAttached($this->queue); + } + + public function attachQueue(NuanceQueue $queue = null) { + $this->queue = $queue; + return $this; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From e1b8532e2478395bd1f36baecb998adeef5bea3d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 24 May 2017 07:36:46 -0700 Subject: [PATCH 173/543] Allow Nuance items to put commands actions into the work UI Summary: Ref T12738. This doesn't actually do anything yet, but allows items to define commands that show up in the UI. Adds a "Throw in Trash" item for complaints. This construction will allow future changes to add an `EngineExtension` which can provide generic/default commands across item types. Test Plan: {F4975086} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D18009 --- src/__phutil_library_map__.php | 2 + .../nuance/command/NuanceItemCommandSpec.php | 37 +++++++++++++++++++ .../controller/NuanceQueueWorkController.php | 31 ++++++++++++++-- .../nuance/item/NuanceFormItemType.php | 34 +++++++++++++++++ .../nuance/item/NuanceItemType.php | 12 ++++++ 5 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/applications/nuance/command/NuanceItemCommandSpec.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cc16cb4e88..45a9aa2b96 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1622,6 +1622,7 @@ phutil_register_library_map(array( 'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php', 'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php', 'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php', + 'NuanceItemCommandSpec' => 'applications/nuance/command/NuanceItemCommandSpec.php', 'NuanceItemCommandTransaction' => 'applications/nuance/xaction/NuanceItemCommandTransaction.php', 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', @@ -6744,6 +6745,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'NuanceItemCommandQuery' => 'NuanceQuery', + 'NuanceItemCommandSpec' => 'Phobject', 'NuanceItemCommandTransaction' => 'NuanceItemTransactionType', 'NuanceItemController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/nuance/command/NuanceItemCommandSpec.php b/src/applications/nuance/command/NuanceItemCommandSpec.php new file mode 100644 index 0000000000..d449e54833 --- /dev/null +++ b/src/applications/nuance/command/NuanceItemCommandSpec.php @@ -0,0 +1,37 @@ +commandKey = $command_key; + return $this; + } + + public function getCommandKey() { + return $this->commandKey; + } + + public function setName($name) { + $this->name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + +} diff --git a/src/applications/nuance/controller/NuanceQueueWorkController.php b/src/applications/nuance/controller/NuanceQueueWorkController.php index 3d6308664d..fb979c4e02 100644 --- a/src/applications/nuance/controller/NuanceQueueWorkController.php +++ b/src/applications/nuance/controller/NuanceQueueWorkController.php @@ -54,16 +54,25 @@ final class NuanceQueueWorkController $item = head($items); - $curtain = $this->buildCurtain($queue); + $curtain = $this->buildCurtain($queue, $item); $timeline = $this->buildTransactionTimeline( $item, new NuanceItemTransactionQuery()); $timeline->setShouldTerminate(true); + $impl = $item->getImplementation() + ->setViewer($viewer); + + $work_content = $impl->buildItemWorkView($item); + $view = id(new PHUITwoColumnView()) ->setCurtain($curtain) - ->setMainColumn($timeline); + ->setMainColumn( + array( + $work_content, + $timeline, + )); return $this->newPage() ->setTitle($title) @@ -71,12 +80,28 @@ final class NuanceQueueWorkController ->appendChild($view); } - private function buildCurtain(NuanceQueue $queue) { + private function buildCurtain(NuanceQueue $queue, NuanceItem $item) { $viewer = $this->getViewer(); $id = $queue->getID(); $curtain = $this->newCurtainView(); + $impl = $item->getImplementation(); + $commands = $impl->buildWorkCommands($item); + + foreach ($commands as $command) { + $command_key = $command->getCommandKey(); + + $item_id = $item->getID(); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($command->getName()) + ->setIcon($command->getIcon()) + ->setHref("queue/command/{$id}/{$command_key}/{$item_id}/")) + ->setWorkflow(true); + } + $curtain->addAction( id(new PhabricatorActionView()) ->setType(PhabricatorActionView::TYPE_DIVIDER)); diff --git a/src/applications/nuance/item/NuanceFormItemType.php b/src/applications/nuance/item/NuanceFormItemType.php index ee2f923d08..401d20fd87 100644 --- a/src/applications/nuance/item/NuanceFormItemType.php +++ b/src/applications/nuance/item/NuanceFormItemType.php @@ -13,4 +13,38 @@ final class NuanceFormItemType return pht('Complaint'); } + protected function newWorkCommands(NuanceItem $item) { + return array( + $this->newCommand('trash') + ->setIcon('fa-trash') + ->setName(pht('Throw In Trash')), + ); + } + + protected function newItemView(NuanceItem $item) { + $viewer = $this->getViewer(); + + $content = $item->getItemProperty('complaint'); + $content_view = id(new PHUIRemarkupView($viewer, $content)) + ->setContextObject($item); + + $content_section = id(new PHUIPropertyListView()) + ->addTextContent( + phutil_tag( + 'div', + array( + 'class' => 'phabricator-remarkup', + ), + $content_view)); + + $content_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Complaint')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content_section); + + return array( + $content_box, + ); + } + } diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index ff89108f4a..c0c78c9efa 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -32,6 +32,10 @@ abstract class NuanceItemType return $this->newItemView($item); } + final public function buildItemWorkView(NuanceItem $item) { + return $this->newItemView($item); + } + protected function newItemView(NuanceItem $item) { return null; } @@ -104,6 +108,10 @@ abstract class NuanceItemType return null; } + final public function buildWorkCommands(NuanceItem $item) { + return $this->newWorkCommands($item); + } + final public function applyCommand( NuanceItem $item, NuanceItemCommand $command) { @@ -159,4 +167,8 @@ abstract class NuanceItemType return id(new PhabricatorNuanceApplication())->getPHID(); } + protected function newCommand($command_key) { + return id(new NuanceItemCommandSpec()) + ->setCommandKey($command_key); + } } From 7e91f42b02b0a8e91d77f038fd0c9ac4ac47ff19 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 24 May 2017 09:28:36 -0700 Subject: [PATCH 174/543] Issue commands to Nuance items, at least roughly Summary: Ref T12738. This makes clicking "Throw In Trash" technically do something, sort of. In Nuance, the default mode of operation for actions is asynchronous -- so you don't have to wait for a response from Twitter or GitHub after you mash the "send default reply tweet" / "close this pull request with a nice response" button and can move directly to the next item instead. In the future, some operations will attempt to apply synchronously (e.g., local actions like "ignore this item forever"). This fakes our way through that for now. There's also no connection to the action actually doing anything yet, but I'll probably rig that up next. Test Plan: {F4975227} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D18010 --- .../20170524.nuance.01.command.sql | 8 +++ .../20170524.nuance.02.commandstatus.sql | 5 ++ .../PhabricatorNuanceApplication.php | 2 + .../controller/NuanceItemActionController.php | 66 +++++++++++++++++- .../controller/NuanceItemViewController.php | 34 ---------- .../controller/NuanceQueueWorkController.php | 67 ++++++++++++++++++- .../nuance/item/NuanceFormItemType.php | 4 ++ .../nuance/item/NuanceItemType.php | 8 +-- .../nuance/query/NuanceItemCommandQuery.php | 13 ++++ .../nuance/storage/NuanceItemCommand.php | 64 ++++++++++++++++-- .../nuance/storage/NuanceQueue.php | 4 ++ .../nuance/worker/NuanceItemUpdateWorker.php | 23 ++++++- 12 files changed, 247 insertions(+), 51 deletions(-) create mode 100644 resources/sql/autopatches/20170524.nuance.01.command.sql create mode 100644 resources/sql/autopatches/20170524.nuance.02.commandstatus.sql diff --git a/resources/sql/autopatches/20170524.nuance.01.command.sql b/resources/sql/autopatches/20170524.nuance.01.command.sql new file mode 100644 index 0000000000..529756e748 --- /dev/null +++ b/resources/sql/autopatches/20170524.nuance.01.command.sql @@ -0,0 +1,8 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD dateCreated INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD dateModified INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD queuePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20170524.nuance.02.commandstatus.sql b/resources/sql/autopatches/20170524.nuance.02.commandstatus.sql new file mode 100644 index 0000000000..14f57af053 --- /dev/null +++ b/resources/sql/autopatches/20170524.nuance.02.commandstatus.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD status VARCHAR(64) NOT NULL; + +UPDATE {$NAMESPACE}_nuance.nuance_itemcommand + SET status = 'done' WHERE status = ''; diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index 9f99c9e5bd..cd268dd95e 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -52,6 +52,8 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { $this->getEditRoutePattern('edit/') => 'NuanceQueueEditController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', 'work/(?P[1-9]\d*)/' => 'NuanceQueueWorkController', + 'action/(?P[1-9]\d*)/(?P[^/]+)/(?P[1-9]\d*)/' + => 'NuanceItemActionController', ), ), '/action/' => array( diff --git a/src/applications/nuance/controller/NuanceItemActionController.php b/src/applications/nuance/controller/NuanceItemActionController.php index c64ac5f6ac..c6dc139b11 100644 --- a/src/applications/nuance/controller/NuanceItemActionController.php +++ b/src/applications/nuance/controller/NuanceItemActionController.php @@ -6,6 +6,14 @@ final class NuanceItemActionController extends NuanceController { $viewer = $this->getViewer(); $id = $request->getURIData('id'); + if (!$request->validateCSRF()) { + return new Aphront400Response(); + } + + // NOTE: This controller can be reached from an individual item (usually + // by a user) or while working through a queue (usually by staff). When + // a command originates from a queue, the URI will have a queue ID. + $item = id(new NuanceItemQuery()) ->setViewer($viewer) ->withIDs(array($id)) @@ -14,13 +22,69 @@ final class NuanceItemActionController extends NuanceController { return new Aphront404Response(); } + $cancel_uri = $item->getURI(); + + $queue_id = $request->getURIData('queueID'); + $queue = null; + if ($queue_id) { + $queue = id(new NuanceQueueQuery()) + ->setViewer($viewer) + ->withIDs(array($queue_id)) + ->executeOne(); + if (!$queue) { + return new Aphront404Response(); + } + + $item_queue = $item->getQueue(); + if (!$item_queue || ($item_queue->getPHID() != $queue->getPHID())) { + return $this->newDialog() + ->setTitle(pht('Wrong Queue')) + ->appendParagraph( + pht( + 'You are trying to act on this item from the wrong queue: it '. + 'is currently in a different queue.')) + ->addCancelButton($cancel_uri); + } + } + $action = $request->getURIData('action'); $impl = $item->getImplementation(); $impl->setViewer($viewer); $impl->setController($this); - return $impl->buildActionResponse($item, $action); + $command = NuanceItemCommand::initializeNewCommand() + ->setItemPHID($item->getPHID()) + ->setAuthorPHID($viewer->getPHID()) + ->setCommand($action); + + if ($queue) { + $command->setQueuePHID($queue->getPHID()); + } + + $command->save(); + + // TODO: Here, we should check if the command should be tried immediately, + // and just defer it to the daemons if not. If we're going to try to apply + // the command directly, we should first acquire the worker lock. If we + // can not, we should defer the command even if it's an immediate command. + // For the moment, skip all this stuff by deferring unconditionally. + + $should_defer = true; + if ($should_defer) { + $item->scheduleUpdate(); + } else { + // ... + } + + if ($queue) { + $done_uri = $queue->getWorkURI(); + } else { + $done_uri = $item->getURI(); + } + + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); } } diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index 7ef5d06682..a902dc3b06 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -26,14 +26,12 @@ final class NuanceItemViewController extends NuanceController { $curtain = $this->buildCurtain($item); $content = $this->buildContent($item); - $commands = $this->buildCommands($item); $timeline = $this->buildTransactionTimeline( $item, new NuanceItemTransactionQuery()); $main = array( - $commands, $content, $timeline, ); @@ -91,36 +89,4 @@ final class NuanceItemViewController extends NuanceController { return $impl->buildItemView($item); } - private function buildCommands(NuanceItem $item) { - $viewer = $this->getViewer(); - - $commands = id(new NuanceItemCommandQuery()) - ->setViewer($viewer) - ->withItemPHIDs(array($item->getPHID())) - ->execute(); - $commands = msort($commands, 'getID'); - - if (!$commands) { - return null; - } - - $rows = array(); - foreach ($commands as $command) { - $rows[] = array( - $command->getCommand(), - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders( - array( - pht('Command'), - )); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Pending Commands')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - } diff --git a/src/applications/nuance/controller/NuanceQueueWorkController.php b/src/applications/nuance/controller/NuanceQueueWorkController.php index fb979c4e02..8703a69334 100644 --- a/src/applications/nuance/controller/NuanceQueueWorkController.php +++ b/src/applications/nuance/controller/NuanceQueueWorkController.php @@ -64,12 +64,14 @@ final class NuanceQueueWorkController $impl = $item->getImplementation() ->setViewer($viewer); + $commands = $this->buildCommands($item); $work_content = $impl->buildItemWorkView($item); $view = id(new PHUITwoColumnView()) ->setCurtain($curtain) ->setMainColumn( array( + $commands, $work_content, $timeline, )); @@ -94,12 +96,15 @@ final class NuanceQueueWorkController $item_id = $item->getID(); + $action_uri = "queue/action/{$id}/{$command_key}/{$item_id}/"; + $action_uri = $this->getApplicationURI($action_uri); + $curtain->addAction( id(new PhabricatorActionView()) ->setName($command->getName()) ->setIcon($command->getIcon()) - ->setHref("queue/command/{$id}/{$command_key}/{$item_id}/")) - ->setWorkflow(true); + ->setHref($action_uri) + ->setWorkflow(true)); } $curtain->addAction( @@ -120,4 +125,62 @@ final class NuanceQueueWorkController return $curtain; } + private function buildCommands(NuanceItem $item) { + $viewer = $this->getViewer(); + + $commands = id(new NuanceItemCommandQuery()) + ->setViewer($viewer) + ->withItemPHIDs(array($item->getPHID())) + ->withStatuses( + array( + NuanceItemCommand::STATUS_ISSUED, + NuanceItemCommand::STATUS_EXECUTING, + NuanceItemCommand::STATUS_FAILED, + )) + ->execute(); + $commands = msort($commands, 'getID'); + + if (!$commands) { + return null; + } + + $rows = array(); + foreach ($commands as $command) { + $icon = $command->getStatusIcon(); + $color = $command->getStatusColor(); + + $rows[] = array( + $command->getID(), + id(new PHUIIconView()) + ->setIcon($icon, $color), + $viewer->renderHandle($command->getAuthorPHID()), + $command->getCommand(), + phabricator_datetime($command->getDateCreated(), $viewer), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + null, + pht('Actor'), + pht('Command'), + pht('Date'), + )) + ->setColumnClasses( + array( + null, + 'icon', + null, + 'pri', + 'wide right', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Pending Commands')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + } diff --git a/src/applications/nuance/item/NuanceFormItemType.php b/src/applications/nuance/item/NuanceFormItemType.php index 401d20fd87..cbdde0e89c 100644 --- a/src/applications/nuance/item/NuanceFormItemType.php +++ b/src/applications/nuance/item/NuanceFormItemType.php @@ -47,4 +47,8 @@ final class NuanceFormItemType ); } + protected function handleAction(NuanceItem $item, $action) { + return null; + } + } diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index c0c78c9efa..de64977cb3 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -95,13 +95,7 @@ abstract class NuanceItemType } final public function buildActionResponse(NuanceItem $item, $action) { - $response = $this->handleAction($item, $action); - - if ($response === null) { - return new Aphront404Response(); - } - - return $response; + return $this->handleAction($item, $action); } protected function handleAction(NuanceItem $item, $action) { diff --git a/src/applications/nuance/query/NuanceItemCommandQuery.php b/src/applications/nuance/query/NuanceItemCommandQuery.php index cb20610187..27137cf8f6 100644 --- a/src/applications/nuance/query/NuanceItemCommandQuery.php +++ b/src/applications/nuance/query/NuanceItemCommandQuery.php @@ -5,6 +5,7 @@ final class NuanceItemCommandQuery private $ids; private $itemPHIDs; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -16,6 +17,11 @@ final class NuanceItemCommandQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function newResultObject() { return new NuanceItemCommand(); } @@ -41,6 +47,13 @@ final class NuanceItemCommandQuery $this->itemPHIDs); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'status IN (%Ls)', + $this->statuses); + } + return $where; } diff --git a/src/applications/nuance/storage/NuanceItemCommand.php b/src/applications/nuance/storage/NuanceItemCommand.php index bda6860ff5..21d3792ff2 100644 --- a/src/applications/nuance/storage/NuanceItemCommand.php +++ b/src/applications/nuance/storage/NuanceItemCommand.php @@ -4,32 +4,86 @@ final class NuanceItemCommand extends NuanceDAO implements PhabricatorPolicyInterface { + const STATUS_ISSUED = 'issued'; + const STATUS_EXECUTING = 'executing'; + const STATUS_DONE = 'done'; + const STATUS_FAILED = 'failed'; + protected $itemPHID; protected $authorPHID; + protected $queuePHID; protected $command; - protected $parameters; + protected $status; + protected $parameters = array(); public static function initializeNewCommand() { - return new self(); + return id(new self()) + ->setStatus(self::STATUS_ISSUED); } protected function getConfiguration() { return array( - self::CONFIG_TIMESTAMPS => false, self::CONFIG_SERIALIZATION => array( 'parameters' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'command' => 'text64', + 'status' => 'text64', + 'queuePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( - 'key_item' => array( - 'columns' => array('itemPHID'), + 'key_pending' => array( + 'columns' => array('itemPHID', 'status'), ), ), ) + parent::getConfiguration(); } + public static function getStatusMap() { + return array( + self::STATUS_ISSUED => array( + 'name' => pht('Issued'), + 'icon' => 'fa-clock-o', + 'color' => 'bluegrey', + ), + self::STATUS_EXECUTING => array( + 'name' => pht('Executing'), + 'icon' => 'fa-play', + 'color' => 'green', + ), + self::STATUS_DONE => array( + 'name' => pht('Done'), + 'icon' => 'fa-check', + 'color' => 'blue', + ), + self::STATUS_FAILED => array( + 'name' => pht('Failed'), + 'icon' => 'fa-times', + 'color' => 'red', + ), + ); + } + + private function getStatusSpec() { + $map = self::getStatusMap(); + return idx($map, $this->getStatus(), array()); + } + + public function getStatusIcon() { + $spec = $this->getStatusSpec(); + return idx($spec, 'icon', 'fa-question'); + } + + public function getStatusColor() { + $spec = $this->getStatusSpec(); + return idx($spec, 'color', 'indigo'); + } + + public function getStatusName() { + $spec = $this->getStatusSpec(); + return idx($spec, 'name', $this->getStatus()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/nuance/storage/NuanceQueue.php b/src/applications/nuance/storage/NuanceQueue.php index 9691a42e2f..f0ba5bb45c 100644 --- a/src/applications/nuance/storage/NuanceQueue.php +++ b/src/applications/nuance/storage/NuanceQueue.php @@ -43,6 +43,10 @@ final class NuanceQueue return '/nuance/queue/view/'.$this->getID().'/'; } + public function getWorkURI() { + return '/nuance/queue/work/'.$this->getID().'/'; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php index 57be20edae..5f33f885f9 100644 --- a/src/applications/nuance/worker/NuanceItemUpdateWorker.php +++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php @@ -61,12 +61,31 @@ final class NuanceItemUpdateWorker $commands = id(new NuanceItemCommandQuery()) ->setViewer($viewer) ->withItemPHIDs(array($item->getPHID())) + ->withStatuses( + array( + NuanceItemCommand::STATUS_ISSUED, + )) ->execute(); $commands = msort($commands, 'getID'); foreach ($commands as $command) { - $impl->applyCommand($item, $command); - $command->delete(); + $command + ->setStatus(NuanceItemCommand::STATUS_EXECUTING) + ->save(); + + try { + $impl->applyCommand($item, $command); + + $command + ->setStatus(NuanceItemCommand::STATUS_DONE) + ->save(); + } catch (Exception $ex) { + $command + ->setStatus(NuanceItemCommand::STATUS_FAILED) + ->save(); + + throw $ex; + } } } From 8374ec46fdabf4184734d2748395a90342eb98d6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 24 May 2017 10:16:53 -0700 Subject: [PATCH 175/543] Make throwing things into the trash actually work in Nuance Summary: Ref T12738. Implements some modular behavior for Nuance commands. Test Plan: {F4975322} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D18011 --- src/__phutil_library_map__.php | 6 + .../command/NuanceCommandImplementation.php | 107 ++++++++++++++++++ .../nuance/command/NuanceTrashCommand.php | 23 ++++ .../nuance/item/NuanceGitHubEventItemType.php | 3 + .../nuance/item/NuanceItemType.php | 40 ------- .../nuance/storage/NuanceItem.php | 1 - .../nuance/worker/NuanceItemUpdateWorker.php | 18 ++- .../xaction/NuanceItemCommandTransaction.php | 12 +- .../xaction/NuanceItemStatusTransaction.php | 25 ++++ 9 files changed, 187 insertions(+), 48 deletions(-) create mode 100644 src/applications/nuance/command/NuanceCommandImplementation.php create mode 100644 src/applications/nuance/command/NuanceTrashCommand.php create mode 100644 src/applications/nuance/xaction/NuanceItemStatusTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 45a9aa2b96..a01125ec38 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1601,6 +1601,7 @@ phutil_register_library_map(array( 'MultimeterLabel' => 'applications/multimeter/storage/MultimeterLabel.php', 'MultimeterSampleController' => 'applications/multimeter/controller/MultimeterSampleController.php', 'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php', + 'NuanceCommandImplementation' => 'applications/nuance/command/NuanceCommandImplementation.php', 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', 'NuanceContentSource' => 'applications/nuance/contentsource/NuanceContentSource.php', @@ -1636,6 +1637,7 @@ phutil_register_library_map(array( 'NuanceItemRequestorTransaction' => 'applications/nuance/xaction/NuanceItemRequestorTransaction.php', 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', 'NuanceItemSourceTransaction' => 'applications/nuance/xaction/NuanceItemSourceTransaction.php', + 'NuanceItemStatusTransaction' => 'applications/nuance/xaction/NuanceItemStatusTransaction.php', 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', @@ -1690,6 +1692,7 @@ phutil_register_library_map(array( 'NuanceSourceTransactionType' => 'applications/nuance/xaction/NuanceSourceTransactionType.php', 'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php', 'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php', + 'NuanceTrashCommand' => 'applications/nuance/command/NuanceTrashCommand.php', 'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php', 'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php', 'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php', @@ -6714,6 +6717,7 @@ phutil_register_library_map(array( 'MultimeterLabel' => 'MultimeterDimension', 'MultimeterSampleController' => 'MultimeterController', 'MultimeterViewer' => 'MultimeterDimension', + 'NuanceCommandImplementation' => 'Phobject', 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 'NuanceConsoleController' => 'NuanceController', 'NuanceContentSource' => 'PhabricatorContentSource', @@ -6759,6 +6763,7 @@ phutil_register_library_map(array( 'NuanceItemRequestorTransaction' => 'NuanceItemTransactionType', 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceItemSourceTransaction' => 'NuanceItemTransactionType', + 'NuanceItemStatusTransaction' => 'NuanceItemTransactionType', 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -6822,6 +6827,7 @@ phutil_register_library_map(array( 'NuanceSourceTransactionType' => 'PhabricatorModularTransactionType', 'NuanceSourceViewController' => 'NuanceSourceController', 'NuanceTransaction' => 'PhabricatorModularTransaction', + 'NuanceTrashCommand' => 'NuanceCommandImplementation', 'NuanceWorker' => 'PhabricatorWorker', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', diff --git a/src/applications/nuance/command/NuanceCommandImplementation.php b/src/applications/nuance/command/NuanceCommandImplementation.php new file mode 100644 index 0000000000..46220266ff --- /dev/null +++ b/src/applications/nuance/command/NuanceCommandImplementation.php @@ -0,0 +1,107 @@ +actor = $actor; + return $this; + } + + final public function getActor() { + return $this->actor; + } + + abstract public function getCommandName(); + abstract public function canApplyToItem(NuanceItem $item); + + abstract protected function executeCommand( + NuanceItem $item, + NuanceItemCommand $command); + + final public function applyCommand( + NuanceItem $item, + NuanceItemCommand $command) { + + $command_key = $command->getCommand(); + $implementation_key = $this->getCommandKey(); + if ($command_key !== $implementation_key) { + throw new Exception( + pht( + 'This command implementation("%s") can not apply a command of a '. + 'different type ("%s").', + $implementation_key, + $command_key)); + } + + if (!$this->canApplyToItem($item)) { + throw new Exception( + pht( + 'This command implementation ("%s") can not be applied to an '. + 'item of type "%s".', + $implementation_key, + $item->getItemType())); + } + + $this->transactionQueue = array(); + + $command_type = NuanceItemCommandTransaction::TRANSACTIONTYPE; + $command_xaction = $this->newTransaction($command_type); + + $result = $this->executeCommand($item, $command); + + $xactions = $this->transactionQueue; + $this->transactionQueue = array(); + + $command_xaction->setNewValue( + array( + 'command' => $command->getCommand(), + 'parameters' => $command->getParameters(), + 'result' => $result, + )); + + // TODO: Maybe preserve the actor's original content source? + $source = PhabricatorContentSource::newForSource( + PhabricatorDaemonContentSource::SOURCECONST); + + $actor = $this->getActor(); + + id(new NuanceItemEditor()) + ->setActor($actor) + ->setActingAsPHID($command->getAuthorPHID()) + ->setContentSource($source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($item, $xactions); + } + + final public function getCommandKey() { + return $this->getPhobjectClassConstant('COMMANDKEY'); + } + + final public static function getAllCommands() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getCommandKey') + ->execute(); + } + + protected function newTransaction($type) { + $xaction = id(new NuanceItemTransaction()) + ->setTransactionType($type); + + $this->transactionQueue[] = $xaction; + + return $xaction; + } + + protected function newStatusTransaction($status) { + return $this->newTransaction(NuanceItemStatusTransaction::TRANSACTIONTYPE) + ->setNewValue($status); + } + +} diff --git a/src/applications/nuance/command/NuanceTrashCommand.php b/src/applications/nuance/command/NuanceTrashCommand.php new file mode 100644 index 0000000000..1f8a729277 --- /dev/null +++ b/src/applications/nuance/command/NuanceTrashCommand.php @@ -0,0 +1,23 @@ +getImplementation(); + return ($type instanceof NuanceFormItemType); + } + + protected function executeCommand( + NuanceItem $item, + NuanceItemCommand $command) { + $this->newStatusTransaction(NuanceItem::STATUS_CLOSED); + } + +} diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index b7b90690b7..b8bfb3ccab 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -329,6 +329,9 @@ final class NuanceGitHubEventItemType NuanceItem $item, NuanceItemCommand $command) { + // TODO: This code is no longer reachable, and has moved to + // CommandImplementation subclasses. + $action = $command->getCommand(); switch ($action) { case 'sync': diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index de64977cb3..3a65ad0195 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -106,46 +106,6 @@ abstract class NuanceItemType return $this->newWorkCommands($item); } - final public function applyCommand( - NuanceItem $item, - NuanceItemCommand $command) { - - $result = $this->handleCommand($item, $command); - - if ($result === null) { - return; - } - - $xaction = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemCommandTransaction::TRANSACTIONTYPE) - ->setNewValue( - array( - 'command' => $command->getCommand(), - 'parameters' => $command->getParameters(), - 'result' => $result, - )); - - $viewer = $this->getViewer(); - - // TODO: Maybe preserve the actor's original content source? - $source = PhabricatorContentSource::newForSource( - PhabricatorDaemonContentSource::SOURCECONST); - - $editor = id(new NuanceItemEditor()) - ->setActor($viewer) - ->setActingAsPHID($command->getAuthorPHID()) - ->setContentSource($source) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($item, array($xaction)); - } - - protected function handleCommand( - NuanceItem $item, - NuanceItemCommand $command) { - return null; - } - final protected function newContentSource( NuanceItem $item, $agent_phid) { diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index b1deab3af2..09a106ca7a 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -9,7 +9,6 @@ final class NuanceItem const STATUS_IMPORTING = 'importing'; const STATUS_ROUTING = 'routing'; const STATUS_OPEN = 'open'; - const STATUS_ASSIGNED = 'assigned'; const STATUS_CLOSED = 'closed'; protected $status; diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php index 5f33f885f9..30d5621872 100644 --- a/src/applications/nuance/worker/NuanceItemUpdateWorker.php +++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php @@ -68,13 +68,29 @@ final class NuanceItemUpdateWorker ->execute(); $commands = msort($commands, 'getID'); + $executors = NuanceCommandImplementation::getAllCommands(); foreach ($commands as $command) { $command ->setStatus(NuanceItemCommand::STATUS_EXECUTING) ->save(); try { - $impl->applyCommand($item, $command); + $command_key = $command->getCommand(); + + $executor = idx($executors, $command_key); + if (!$executor) { + throw new Exception( + pht( + 'Unable to execute command "%s": this command does not have '. + 'a recognized command implementation.', + $command_key)); + } + + $executor = clone $executor; + + $executor + ->setActor($viewer) + ->applyCommand($item, $command); $command ->setStatus(NuanceItemCommand::STATUS_DONE) diff --git a/src/applications/nuance/xaction/NuanceItemCommandTransaction.php b/src/applications/nuance/xaction/NuanceItemCommandTransaction.php index bf1cbdf8d6..6aa1d6473f 100644 --- a/src/applications/nuance/xaction/NuanceItemCommandTransaction.php +++ b/src/applications/nuance/xaction/NuanceItemCommandTransaction.php @@ -9,14 +9,14 @@ final class NuanceItemCommandTransaction return null; } - public function applyInternalEffects($object, $value) { - // TODO: Probably implement this. - } - public function getTitle() { + $spec = $this->getNewValue(); + $command_key = idx($spec, 'command', '???'); + return pht( - '%s applied a command to this item.', - $this->renderAuthor()); + '%s applied a "%s" command to this item.', + $this->renderAuthor(), + $command_key); } } diff --git a/src/applications/nuance/xaction/NuanceItemStatusTransaction.php b/src/applications/nuance/xaction/NuanceItemStatusTransaction.php new file mode 100644 index 0000000000..48c5a16e8c --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemStatusTransaction.php @@ -0,0 +1,25 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + return pht( + '%s changed the status of this item from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + +} From eb296796aaa579613437e21f911b45ef31be9e32 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 24 May 2017 12:07:04 -0700 Subject: [PATCH 176/543] Migrate Project sort and filter defaults to modular transactions Test Plan: Unit tests pass, manually changed the default sort and filter on a workboard and observed expected transactions in the DB. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D18013 --- src/__phutil_library_map__.php | 4 +++ .../PhabricatorProjectDefaultController.php | 4 +-- .../PhabricatorProjectTransactionEditor.php | 17 ------------ .../storage/PhabricatorProjectTransaction.php | 19 +++----------- .../PhabricatorProjectFilterTransaction.php | 26 +++++++++++++++++++ .../PhabricatorProjectSortTransaction.php | 26 +++++++++++++++++++ 6 files changed, 61 insertions(+), 35 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectFilterTransaction.php create mode 100644 src/applications/project/xaction/PhabricatorProjectSortTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a01125ec38..582315661a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3636,6 +3636,7 @@ phutil_register_library_map(array( 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', + 'PhabricatorProjectFilterTransaction' => 'applications/project/xaction/PhabricatorProjectFilterTransaction.php', 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', 'PhabricatorProjectHeraldAdapter' => 'applications/project/herald/PhabricatorProjectHeraldAdapter.php', @@ -3694,6 +3695,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php', 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php', + 'PhabricatorProjectSortTransaction' => 'applications/project/xaction/PhabricatorProjectSortTransaction.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', 'PhabricatorProjectStatusTransaction' => 'applications/project/xaction/PhabricatorProjectStatusTransaction.php', @@ -9082,6 +9084,7 @@ phutil_register_library_map(array( 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', + 'PhabricatorProjectFilterTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectHeraldAdapter' => 'HeraldAdapter', @@ -9139,6 +9142,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', 'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType', + 'PhabricatorProjectSortTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectStandardCustomField' => array( 'PhabricatorProjectCustomField', 'PhabricatorStandardCustomFieldInterface', diff --git a/src/applications/project/controller/PhabricatorProjectDefaultController.php b/src/applications/project/controller/PhabricatorProjectDefaultController.php index 0246f33f43..8f42ff9736 100644 --- a/src/applications/project/controller/PhabricatorProjectDefaultController.php +++ b/src/applications/project/controller/PhabricatorProjectDefaultController.php @@ -32,7 +32,7 @@ final class PhabricatorProjectDefaultController $button = pht('Save Default Filter'); $xaction_value = $request->getStr('filter'); - $xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER; + $xaction_type = PhabricatorProjectFilterTransaction::TRANSACTIONTYPE; break; case 'sort': $title = pht('Set Board Default Order'); @@ -43,7 +43,7 @@ final class PhabricatorProjectDefaultController $button = pht('Save Default Order'); $xaction_value = $request->getStr('order'); - $xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT; + $xaction_type = PhabricatorProjectSortTransaction::TRANSACTIONTYPE; break; default: return new Aphront404Response(); diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 69d16c3786..e73db3b026 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,8 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT; - $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER; $types[] = PhabricatorProjectTransaction::TYPE_BACKGROUND; return $types; @@ -42,10 +40,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - return $object->getDefaultWorkboardSort(); - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - return $object->getDefaultWorkboardFilter(); case PhabricatorProjectTransaction::TYPE_BACKGROUND: return $object->getWorkboardBackgroundColor(); } @@ -58,9 +52,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - return $xaction->getNewValue(); case PhabricatorProjectTransaction::TYPE_BACKGROUND: $value = $xaction->getNewValue(); if (!strlen($value)) { @@ -77,12 +68,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - $object->setDefaultWorkboardSort($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - $object->setDefaultWorkboardFilter($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_BACKGROUND: $object->setWorkboardBackgroundColor($xaction->getNewValue()); return; @@ -99,8 +84,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: case PhabricatorProjectTransaction::TYPE_BACKGROUND: return; } diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index cfb99d7469..8e026df045 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,8 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_DEFAULT_SORT = 'project:sort'; - const TYPE_DEFAULT_FILTER = 'project:filter'; const TYPE_BACKGROUND = 'project:background'; // NOTE: This is deprecated, members are just a normal edge now. @@ -60,8 +58,6 @@ final class PhabricatorProjectTransaction public function shouldHideForFeed() { switch ($this->getTransactionType()) { - case self::TYPE_DEFAULT_SORT: - case self::TYPE_DEFAULT_FILTER: case self::TYPE_BACKGROUND: return true; } @@ -69,11 +65,12 @@ final class PhabricatorProjectTransaction return parent::shouldHideForFeed(); } + public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { case PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE: - case self::TYPE_DEFAULT_SORT: - case self::TYPE_DEFAULT_FILTER: + case PhabricatorProjectSortTransaction::TRANSACTIONTYPE: + case PhabricatorProjectFilterTransaction::TRANSACTIONTYPE: case self::TYPE_BACKGROUND: return true; } @@ -140,16 +137,6 @@ final class PhabricatorProjectTransaction } break; - case self::TYPE_DEFAULT_SORT: - return pht( - '%s changed the default sort order for the project workboard.', - $author_handle); - - case self::TYPE_DEFAULT_FILTER: - return pht( - '%s changed the default filter for the project workboard.', - $author_handle); - case self::TYPE_BACKGROUND: return pht( '%s changed the background color of the project workboard.', diff --git a/src/applications/project/xaction/PhabricatorProjectFilterTransaction.php b/src/applications/project/xaction/PhabricatorProjectFilterTransaction.php new file mode 100644 index 0000000000..d3d94fbb74 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectFilterTransaction.php @@ -0,0 +1,26 @@ +getDefaultWorkboardFilter(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultWorkboardFilter($value); + } + + public function getTitle() { + return pht( + '%s changed the default filter for the project workboard.', + $this->renderAuthor()); + } + + public function shouldHide() { + return true; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectSortTransaction.php b/src/applications/project/xaction/PhabricatorProjectSortTransaction.php new file mode 100644 index 0000000000..f070f6dd19 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectSortTransaction.php @@ -0,0 +1,26 @@ +getDefaultWorkboardSort(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultWorkboardSort($value); + } + + public function getTitle() { + return pht( + '%s changed the default sort order for the project workboard.', + $this->renderAuthor()); + } + + public function shouldHide() { + return true; + } + +} From 88466addee2624d80771fec4fe6204e7b50f61b7 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 24 May 2017 12:46:49 -0700 Subject: [PATCH 177/543] Migrate Project workboard background color to modular transactions Summary: Removes now-unused method as well. Fixes T12673. Test Plan: UI fiddling. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T12673 Differential Revision: https://secure.phabricator.com/D18014 --- src/__phutil_library_map__.php | 2 + ...icatorProjectBoardBackgroundController.php | 3 +- .../PhabricatorProjectTransactionEditor.php | 58 ------------------- .../storage/PhabricatorProjectTransaction.php | 19 +----- ...rProjectWorkboardBackgroundTransaction.php | 26 +++++++++ 5 files changed, 31 insertions(+), 77 deletions(-) create mode 100644 src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 582315661a..7510f2e42c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3716,6 +3716,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php', 'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php', + 'PhabricatorProjectWorkboardBackgroundTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php', 'PhabricatorProjectWorkboardProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php', 'PhabricatorProjectWorkboardTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardTransaction.php', 'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsAncestorsSearchEngineAttachment.php', @@ -9166,6 +9167,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView', 'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject', + 'PhabricatorProjectWorkboardBackgroundTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectWorkboardProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectWorkboardTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index 99260d1770..be4049bb73 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -37,7 +37,8 @@ final class PhabricatorProjectBoardBackgroundController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_BACKGROUND) + ->setTransactionType( + PhabricatorProjectWorkboardBackgroundTransaction::TRANSACTIONTYPE) ->setNewValue($background_key); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index e73db3b026..393c4569f6 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -30,67 +30,9 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_BACKGROUND; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - return $object->getWorkboardBackgroundColor(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - $value = $xaction->getNewValue(); - if (!strlen($value)) { - return null; - } - return $value; - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - $object->setWorkboardBackgroundColor($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 8e026df045..158c2480c0 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -3,8 +3,6 @@ final class PhabricatorProjectTransaction extends PhabricatorModularTransaction { - const TYPE_BACKGROUND = 'project:background'; - // NOTE: This is deprecated, members are just a normal edge now. const TYPE_MEMBERS = 'project:members'; @@ -56,22 +54,12 @@ final class PhabricatorProjectTransaction return parent::shouldHide(); } - public function shouldHideForFeed() { - switch ($this->getTransactionType()) { - case self::TYPE_BACKGROUND: - return true; - } - - return parent::shouldHideForFeed(); - } - - public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { case PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE: case PhabricatorProjectSortTransaction::TRANSACTIONTYPE: case PhabricatorProjectFilterTransaction::TRANSACTIONTYPE: - case self::TYPE_BACKGROUND: + case PhabricatorProjectWorkboardBackgroundTransaction::TRANSACTIONTYPE: return true; } @@ -136,11 +124,6 @@ final class PhabricatorProjectTransaction } } break; - - case self::TYPE_BACKGROUND: - return pht( - '%s changed the background color of the project workboard.', - $author_handle); } return parent::getTitle(); diff --git a/src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php b/src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php new file mode 100644 index 0000000000..620959b0a2 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php @@ -0,0 +1,26 @@ +getWorkboardBackgroundColor(); + } + + public function applyInternalEffects($object, $value) { + $object->setWorkboardBackgroundColor($value); + } + + public function getTitle() { + return pht( + '%s changed the background color of the project workboard.', + $this->renderAuthor()); + } + + public function shouldHide() { + return true; + } + +} From 709c304d76d16e3d5585dd31f291a41b46883494 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 24 May 2017 11:47:40 -0700 Subject: [PATCH 178/543] Group query results under the "ANCESTOR" operator unconditionally Summary: Fixes T12753. See that task for reproduction instructions. We add a `GROUP BY` clause to queries with an "ANCESTOR" edge constraint only if the constaint has more than one PHID, but this is incorrect: the same row can be found twice by an ANCESTOR query if task T is tagged with both "B" and "C", children of "A", and the user queries for "tasks in A". Instead, always add GROUP BY for ANCESTOR queries. Test Plan: - Followed test plan in T12753. - Saw proper paging controls after change. - Saw `GROUP BY` in DarkConsole. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12753 Differential Revision: https://secure.phabricator.com/D18012 --- .../policy/PhabricatorCursorPagedPolicyAwareQuery.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 2bd374d5db..3ef2a72e6f 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1811,11 +1811,16 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery case PhabricatorQueryConstraint::OPERATOR_NOT: case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: - case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: if (count($list) > 1) { return true; } break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + // NOTE: We must always group query results rows when using an + // "ANCESTOR" operator because a single task may be related to + // two different descendants of a particular ancestor. For + // discussion, see T12753. + return true; case PhabricatorQueryConstraint::OPERATOR_NULL: return true; } From 7cfa8a831535c4f51b5aa8b72d60c0b74f8614f4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 25 May 2017 10:55:39 -0700 Subject: [PATCH 179/543] Add ImageHref attribute for PHUIObjectItemListView Summary: In some cases we may want a different URI for the image on an item than the header/title of the item (like user / title). This prioritizes ImageHref over Href. Test Plan: uiexamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18016 --- .../uiexample/examples/PHUIObjectItemListExample.php | 2 ++ src/view/phui/PHUIObjectItemView.php | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/applications/uiexample/examples/PHUIObjectItemListExample.php b/src/applications/uiexample/examples/PHUIObjectItemListExample.php index 691c84554c..6bc521dca2 100644 --- a/src/applications/uiexample/examples/PHUIObjectItemListExample.php +++ b/src/applications/uiexample/examples/PHUIObjectItemListExample.php @@ -330,6 +330,8 @@ final class PHUIObjectItemListExample extends PhabricatorUIExample { $list->addItem( id(new PHUIObjectItemView()) ->setImageURI($default_project->getViewURI()) + ->setImageHref('#') + ->setHref('$$$') ->setHeader(pht('Default Project Profile Image')) ->setGrippable(true) ->addAttribute(pht('This is the default project profile image.'))); diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 59547c4442..1182d9be92 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -19,6 +19,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $headIcons = array(); private $disabled; private $imageURI; + private $imageHref; private $imageIcon; private $titleText; private $badge; @@ -127,6 +128,11 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function setImageHref($image_href) { + $this->imageHref = $image_href; + return $this; + } + public function getImageURI() { return $this->imageURI; } @@ -575,11 +581,12 @@ final class PHUIObjectItemView extends AphrontTagView { $this->getImageIcon()); } - if ($image && $this->href) { + if ($image && (strlen($this->href) || strlen($this->imageHref))) { + $image_href = ($this->imageHref) ? $this->imageHref : $this->href; $image = phutil_tag( 'a', array( - 'href' => $this->href, + 'href' => $image_href, ), $image); } From f29b54944f9cfbc20d9e3c0e05ef9141599d34c3 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 25 May 2017 13:41:21 -0700 Subject: [PATCH 180/543] Make closed objects in global typeahead look closed Summary: Fixes T6906. I found the code in `behavior-search-typeahead.js` that was throwing away the closedness-detction work done in `Prejab.js::transformDatasourceResults`. Modified it to re-add the correct class name to the `phabricator-main-search-typeahead-result` elements. Then I found some CSS in `typeahead-browse.css` and completely flailed around until realizing that particular CSS only gets loaded when hitting the typeahead endpoint directly. Copied the relevant bit of CSS over to `main-menu-view.css` (but maybe it should be removed from `typeahead-browse.css`?). This is my first JS/CSS change, so please don't assume I did anything right. Test Plan: {F4975800} Reviewers: #blessed_reviewers, chad Reviewed By: #blessed_reviewers, chad Subscribers: epriestley Maniphest Tasks: T6906 Differential Revision: https://secure.phabricator.com/D18017 --- resources/celerity/map.php | 44 +++++++++---------- webroot/rsrc/css/aphront/typeahead-browse.css | 16 ------- .../css/application/base/main-menu-view.css | 16 +++++++ .../rsrc/js/core/behavior-search-typeahead.js | 4 ++ 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a71d9f524e..bcfae6b7e6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '71865bdf', - 'core.pkg.js' => '599698a7', + 'core.pkg.css' => 'd556e3e2', + 'core.pkg.js' => '21d34805', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', 'differential.pkg.js' => '1d120743', @@ -35,11 +35,11 @@ return array( 'rsrc/css/aphront/table-view.css' => '34cf86b4', 'rsrc/css/aphront/tokenizer.css' => '9a8cb501', 'rsrc/css/aphront/tooltip.css' => '173b9431', - 'rsrc/css/aphront/typeahead-browse.css' => '8904346a', + 'rsrc/css/aphront/typeahead-browse.css' => '4f82e510', 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => '5294060f', + 'rsrc/css/application/base/main-menu-view.css' => 'de9fe8c4', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', @@ -514,7 +514,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => 'eded9ee8', + 'rsrc/js/core/behavior-search-typeahead.js' => 'd0a99ab4', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -668,7 +668,7 @@ return array( 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', + 'javelin-behavior-phabricator-search-typeahead' => 'd0a99ab4', 'javelin-behavior-phabricator-show-older-transactions' => 'ae95d984', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -792,7 +792,7 @@ return array( 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', - 'phabricator-main-menu-view' => '5294060f', + 'phabricator-main-menu-view' => 'de9fe8c4', 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', @@ -914,7 +914,7 @@ return array( 'syntax-default-css' => '9923583c', 'syntax-highlighting-css' => 'cae95e89', 'tokens-css' => '3d0f239e', - 'typeahead-browse-css' => '8904346a', + 'typeahead-browse-css' => '4f82e510', 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( @@ -1307,9 +1307,6 @@ return array( 'javelin-vector', 'javelin-typeahead-static-source', ), - '5294060f' => array( - 'phui-theme-css', - ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -2016,6 +2013,17 @@ return array( 'javelin-vector', 'phabricator-diff-inline', ), + 'd0a99ab4' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2088,6 +2096,9 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), + 'de9fe8c4' => array( + 'phui-theme-css', + ), 'e0ec7f2f' => array( 'javelin-behavior', 'javelin-dom', @@ -2157,17 +2168,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'eded9ee8' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', diff --git a/webroot/rsrc/css/aphront/typeahead-browse.css b/webroot/rsrc/css/aphront/typeahead-browse.css index 625354bb71..7439d299e7 100644 --- a/webroot/rsrc/css/aphront/typeahead-browse.css +++ b/webroot/rsrc/css/aphront/typeahead-browse.css @@ -64,19 +64,3 @@ input.typeahead-browse-input { margin-top: 1px; margin-left: 6px; } - -.typeahead-browse-item .phabricator-main-search-typeahead-result { - margin: 2px 0; - padding: 0 8px; -} - -.typeahead-browse-item .phabricator-main-search-typeahead-result.has-image { - padding-left: 48px; -} - -.typeahead-browse-item - .phabricator-main-search-typeahead-result.result-closed - .result-name { - text-decoration: line-through; - color: {$lightgreytext}; -} diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 28148ae71a..eb5cfad8c7 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -309,6 +309,22 @@ a.phabricator-core-user-menu .caret:before { color: {$darkgreytext}; } +.phabricator-main-search-typeahead-result.result-closed { + opacity: .8; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} + +.phabricator-main-search-typeahead-result.result-closed + .result-name { + text-decoration: line-through; + color: {$lightgreytext}; +} + +.phabricator-main-search-typeahead-result.has-image { + padding-left: 48px; +} + .phabricator-main-search-typeahead-result .result-type { color: {$lightgreytext}; font-size: {$smallestfontsize}; diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index fc752a6079..ddcc2e6aaa 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -45,6 +45,10 @@ JX.behavior('phabricator-search-typeahead', function(config) { JX.$N('span', {className: 'result-type'}, object.type) ]); + if (object.closed) { + JX.DOM.alterClass(render, 'result-closed', true); + } + object.display = render; return object; From 84742a94db1301a80534fa0d90abb3464cb57aae Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 25 May 2017 13:03:41 -0700 Subject: [PATCH 181/543] Restore missing feed rendering for Maniphest points transactions Summary: See downstream . These got dropped in refactoring, or maybe never existed. Test Plan: {F4977212} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18018 --- .../ManiphestTaskPointsTransaction.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index 6d8548fdb4..b1c20af9e6 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -50,6 +50,32 @@ final class ManiphestTaskPointsTransaction } } + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the point value for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else if ($new === null) { + return pht( + '%s removed the point value for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s changed the point value for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { $errors = array(); From 19572f53fdfedd13e5c9267a52868d0fe9eb0ae0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 25 May 2017 13:19:30 -0700 Subject: [PATCH 182/543] Don't consider accepting on behalf of valid-but-accepted reviewers to be a validation error Summary: Fixes T12757. Here's a simple repro for this: - Add a package you own as a reviewer to a revision you're reviewing. - Open two windows, select "Accept", don't submit the form. - Submit the form in window A. - Submit the fomr in window B. Previously, window B would show an error, because we considered accepting on behalf of the package invalid, as the package had already accepted. Instead, let repeat-accepts through without complaint. Some product stuff: - We could roadblock users with a more narrow validation error message here instead, like "Package X has already been accepted.", but I think this would be more annoying than helpful. - If your accept has no effect (i.e., everything you're accepting for has already accepted) we currently just let it through. I think this is fine -- and a bit tricky to tailor -- but the ideal/consistent beavior is to do a "no effect" warning like "All the reviewers you're accepting for have already accepted.". This is sufficiently finnicky/rare (and probably not terribly useful/desiable in this specific case)that I'm just punting. Test Plan: Did the flow above, got an "Accept" instead of a validation error. Reviewers: chad, lvital Reviewed By: chad, lvital Subscribers: lvital Maniphest Tasks: T12757 Differential Revision: https://secure.phabricator.com/D18019 --- .../DifferentialRevisionAcceptTransaction.php | 21 +++++++++++++------ .../DifferentialRevisionReviewTransaction.php | 10 +++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index 7d1f1a92ef..9b6aad3ad1 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -50,7 +50,8 @@ final class DifferentialRevisionAcceptTransaction protected function getActionOptions( PhabricatorUser $viewer, - DifferentialRevision $revision) { + DifferentialRevision $revision, + $include_accepted = false) { $reviewers = $revision->getReviewers(); @@ -98,10 +99,13 @@ final class DifferentialRevisionAcceptTransaction } } - if ($reviewer->isAccepted($diff_phid)) { - // If a reviewer is already in a full "accepted" state, don't - // include that reviewer as an option. - continue; + if (!$include_accepted) { + if ($reviewer->isAccepted($diff_phid)) { + // If a reviewer is already in a full "accepted" state, don't + // include that reviewer as an option unless we're listing all + // reviwers, including reviewers who have already accepted. + continue; + } } $reviewer_phids[$reviewer_phid] = $reviewer_phid; @@ -185,7 +189,12 @@ final class DifferentialRevisionAcceptTransaction 'least one reviewer.')); } - list($options) = $this->getActionOptions($actor, $object); + // NOTE: We're including reviewers who have already been accepted in this + // check. Legitimate users may race one another to accept on behalf of + // packages. If we get a form submission which includes a reviewer which + // someone has already accepted, that's fine. See T12757. + + list($options) = $this->getActionOptions($actor, $object, true); foreach ($value as $phid) { if (!isset($options[$phid])) { throw new Exception( diff --git a/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php index d44de8706c..c30514b9ab 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php @@ -17,6 +17,16 @@ abstract class DifferentialRevisionReviewTransaction $viewer = $this->getActor(); list($options, $default) = $this->getActionOptions($viewer, $object); + // Remove reviewers which aren't actionable. In the case of "Accept", we + // may allow the transaction to proceed with some reviewers who have + // already accepted, to avoid race conditions where two reviewers fill + // out the form at the same time and accept on behalf of the same package. + // It's okay for these reviewers to survive validation, but they should + // not survive beyond this point. + $value = array_fuse($value); + $value = array_intersect($value, array_keys($options)); + $value = array_values($value); + sort($default); sort($value); From 6b3d04683d8796053cd9ead3cb30e71548c155ff Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 25 May 2017 14:43:58 -0700 Subject: [PATCH 183/543] Clean up SIMPLE button styles Summary: Some of these are unused, defaults to a lighter color naturally. Test Plan: uiexamples, grep, phriction Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18020 --- resources/celerity/map.php | 10 +++++----- .../uiexample/examples/PHUIButtonExample.php | 3 --- src/view/phui/PHUIButtonView.php | 4 ---- webroot/rsrc/css/phui/phui-button.css | 10 +++++----- webroot/rsrc/css/phui/phui-document-pro.css | 4 +++- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bcfae6b7e6..1497f7ce7f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd556e3e2', + 'core.pkg.css' => '2c6a5ba4', 'core.pkg.js' => '21d34805', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -140,14 +140,14 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '8d23596a', + 'rsrc/css/phui/phui-button.css' => 'ccd8c6c5', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', - 'rsrc/css/phui/phui-document-pro.css' => '62c4dcbf', + 'rsrc/css/phui/phui-document-pro.css' => '58199f99', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', @@ -838,7 +838,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '8d23596a', + 'phui-button-css' => 'ccd8c6c5', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -851,7 +851,7 @@ return array( 'phui-curtain-view-css' => '55dd0e59', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', - 'phui-document-view-pro-css' => '62c4dcbf', + 'phui-document-view-pro-css' => '58199f99', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index ae699eb7a3..900125cbc2 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -131,9 +131,6 @@ final class PHUIButtonExample extends PhabricatorUIExample { ); $colors = array( PHUIButtonView::SIMPLE, - PHUIButtonView::SIMPLE_YELLOW, - PHUIButtonView::SIMPLE_GREY, - PHUIButtonView::SIMPLE_BLUE, ); $column = array(); foreach ($colors as $color) { diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index ee6975357c..ac3f8aabe0 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -5,11 +5,7 @@ final class PHUIButtonView extends AphrontTagView { const GREEN = 'green'; const GREY = 'grey'; const DISABLED = 'disabled'; - const SIMPLE = 'simple'; - const SIMPLE_YELLOW = 'simple simple-yellow'; - const SIMPLE_GREY = 'simple simple-grey'; - const SIMPLE_BLUE = 'simple simple-blue'; const SMALL = 'small'; const BIG = 'big'; diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 09f2233a6c..e9d446a0ac 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -91,8 +91,8 @@ input[type="submit"].simple, a.simple, a.simple:visited { background: #fff; - color: {$blue}; - border: 1px solid {$blue}; + color: {$bluetext}; + border: 1px solid {$lightblueborder}; } a.simple.current { @@ -103,7 +103,7 @@ button.simple .phui-icon-view, input[type="submit"].simple .phui-icon-view, a.simple .phui-icon-view, a.simple:visited .phui-icon-view { - color: {$blue}; + color: {$lightbluetext}; } a.disabled, @@ -145,10 +145,10 @@ button.green:hover { a.button.simple:hover, button.simple:hover { - background-color: {$blue}; + background-color: {$lightblue}; background-image: linear-gradient(to bottom, {$blue}, {$blue}); color: #fff; - transition: 0.1s; + transition: 0s; } a.button.simple:hover .phui-icon-view, diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 0aef43ed90..8ece71db64 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -75,7 +75,8 @@ a.button.phui-document-toc { display: inline-block; height: 16px; width: 20px; - padding: 3px 8px 4px 8px; + padding-left: 8px; + padding-right: 8px; } .phui-document-view-pro .phui-document-toc-list { @@ -105,6 +106,7 @@ a.button.phui-document-toc { .phui-document-toc-open .phui-document-toc { background-color: {$blue}; + border-color: {$blue}; } .phui-document-toc-open .phui-document-toc .phui-icon-view { From 9bbea869b3dd904253c44b678e3c1625a72db31d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 25 May 2017 15:24:41 -0700 Subject: [PATCH 184/543] Move setLaunchButton to setSideColumn for ObjectItem Summary: Makes this a bit more flexible and allow UI to take over `col-2` completely. Also cleaned up application search a little with tags Test Plan: Review various pages, grep for callsites. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18021 --- resources/celerity/map.php | 6 ++-- .../guides/view/PhabricatorGuideListView.php | 4 +-- .../meta/query/PhabricatorAppSearchEngine.php | 33 ++++++++++++++----- .../phame/query/PhameBlogSearchEngine.php | 5 +-- src/view/phui/PHUIObjectItemView.php | 14 ++++---- .../phui/object-item/phui-oi-list-view.css | 4 +-- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1497f7ce7f..3011b17643 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '2c6a5ba4', + 'core.pkg.css' => 'b666574e', 'core.pkg.js' => '21d34805', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -132,7 +132,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '78fdc98e', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'ed19241b', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -875,7 +875,7 @@ return array( 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '78fdc98e', + 'phui-oi-list-view-css' => 'ed19241b', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', diff --git a/src/applications/guides/view/PhabricatorGuideListView.php b/src/applications/guides/view/PhabricatorGuideListView.php index 384caf15ee..04ab7c3058 100644 --- a/src/applications/guides/view/PhabricatorGuideListView.php +++ b/src/applications/guides/view/PhabricatorGuideListView.php @@ -30,8 +30,8 @@ final class PhabricatorGuideListView extends AphrontView { ->setText(pht('Skip')) ->setTag('a') ->setHref($skip_href) - ->setColor(PHUIButtonView::GREY); - $list_item->setLaunchButton($skip); + ->setColor(PHUIButtonView::SIMPLE); + $list_item->setSideColumn($skip); } $list->addItem($list_item); } diff --git a/src/applications/meta/query/PhabricatorAppSearchEngine.php b/src/applications/meta/query/PhabricatorAppSearchEngine.php index b609a8b137..ee938abbbb 100644 --- a/src/applications/meta/query/PhabricatorAppSearchEngine.php +++ b/src/applications/meta/query/PhabricatorAppSearchEngine.php @@ -218,20 +218,39 @@ final class PhabricatorAppSearchEngine $configure = id(new PHUIButtonView()) ->setTag('a') + ->setIcon('fa-gears') ->setHref('/applications/view/'.get_class($application).'/') ->setText(pht('Configure')) ->setColor(PHUIButtonView::GREY); $name = $application->getName(); - if ($application->isPrototype()) { - $name = $name.' '.pht('(Prototype)'); - } $item = id(new PHUIObjectItemView()) ->setHeader($name) ->setImageIcon($icon) - ->setSubhead($description) - ->setLaunchButton($configure); + ->setSideColumn($configure); + + if (!$application->isFirstParty()) { + $tag = id(new PHUITagView()) + ->setName(pht('Extension')) + ->setIcon('fa-puzzle-piece') + ->setColor(PHUITagView::COLOR_BLUE) + ->setType(PHUITagView::TYPE_SHADE) + ->setSlimShady(true); + $item->addAttribute($tag); + } + + if ($application->isPrototype()) { + $prototype_tag = id(new PHUITagView()) + ->setName(pht('Prototype')) + ->setIcon('fa-exclamation-circle') + ->setColor(PHUITagView::COLOR_ORANGE) + ->setType(PHUITagView::TYPE_SHADE) + ->setSlimShady(true); + $item->addAttribute($prototype_tag); + } + + $item->addAttribute($description); if ($application->getBaseURI() && $application->isInstalled()) { $item->setHref($application->getBaseURI()); @@ -242,10 +261,6 @@ final class PhabricatorAppSearchEngine $item->setDisabled(true); } - if (!$application->isFirstParty()) { - $item->addAttribute(pht('Extension')); - } - $list->addItem($item); } diff --git a/src/applications/phame/query/PhameBlogSearchEngine.php b/src/applications/phame/query/PhameBlogSearchEngine.php index d006745780..3d23a9763d 100644 --- a/src/applications/phame/query/PhameBlogSearchEngine.php +++ b/src/applications/phame/query/PhameBlogSearchEngine.php @@ -97,8 +97,9 @@ final class PhameBlogSearchEngine $button = id(new PHUIButtonView()) ->setTag('a') ->setText('New Post') - ->setHref($this->getApplicationURI('/post/edit/?blog='.$id)); - $item->setLaunchButton($button); + ->setHref($this->getApplicationURI('/post/edit/?blog='.$id)) + ->setColor(PHUIButtonView::SIMPLE); + $item->setSideColumn($button); } $list->addItem($item); diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 1182d9be92..070d436cf6 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -25,7 +25,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $badge; private $countdownNum; private $countdownNoun; - private $launchButton; + private $sideColumn; private $coverImage; private $description; @@ -229,9 +229,8 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } - public function setLaunchButton(PHUIButtonView $button) { - $button->setSize(PHUIButtonView::SMALL); - $this->launchButton = $button; + public function setSideColumn($column) { + $this->sideColumn = $column; return $this; } @@ -652,14 +651,15 @@ final class PHUIObjectItemView extends AphrontTagView { )); } - if ($this->launchButton) { + /* Fixed width, right column container. */ + if ($this->sideColumn) { $column2 = phutil_tag( 'div', array( - 'class' => 'phui-oi-col2 phui-oi-launch-button', + 'class' => 'phui-oi-col2 phui-oi-side-column', ), array( - $this->launchButton, + $this->sideColumn, )); } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index cbc2d43238..788b0e73ec 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -650,13 +650,13 @@ ul.phui-oi-list-view .phui-oi-selected /* - Launcher Button -------------------------------------------------------- */ -.phui-oi-col2.phui-oi-launch-button { +.phui-oi-col2.phui-oi-side-column { text-align: right; vertical-align: middle; padding-right: 4px; } -.device-phone .phui-oi-col2.phui-oi-launch-button { +.device-phone .phui-oi-col2.phui-oi-side-column { padding: 0 8px 8px; text-align: left; } From 66de16fbc48fb782002e3cc777e3a7c8dd5e006a Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 25 May 2017 17:27:20 -0700 Subject: [PATCH 185/543] Diffusion import documentation update Summary: Fixes T12761. Test Plan: doitlive Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T12761 Differential Revision: https://secure.phabricator.com/D18023 --- src/docs/user/userguide/diffusion_existing.diviner | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/user/userguide/diffusion_existing.diviner b/src/docs/user/userguide/diffusion_existing.diviner index b5b1fbdf76..bf09d9ef1f 100644 --- a/src/docs/user/userguide/diffusion_existing.diviner +++ b/src/docs/user/userguide/diffusion_existing.diviner @@ -44,8 +44,8 @@ Importing Repositories There are two primary ways to import an existing repository: **Observe First**: In Git or Mercurial, you can observe the repository first. -Once the import completes, disable the **Observe** URI to automatically convert -it into a hosted repository. +Once the import completes, change the "I/O Type" on the **Observe** URI to +"No I/O" mode to automatically convert it into a hosted repository. **Push to Empty Repository**: Create an activate an empty repository, then push all of your changes to the empty repository. From 5b43d5c89ccc58d2a907dd8b7b45e94a99e4000a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 24 May 2017 13:18:43 -0700 Subject: [PATCH 186/543] Allow Nuance commands to try to apply immediately Summary: Ref T12738. By default, we process Nuance commands in the background. The intent is to let the user continue working at full speed if Twitter or GitHub (or whatever) is being a little slow. Some commands don't do anything heavy and can be processed in the foreground. Let commands choose to try foreground execution. Test Plan: Threw complaints in the trash, saw them immediately go into the trash. Reviewers: chad Reviewed By: chad Subscribers: avivey Maniphest Tasks: T12738 Differential Revision: https://secure.phabricator.com/D18015 --- .../command/NuanceCommandImplementation.php | 6 ++ .../nuance/command/NuanceTrashCommand.php | 6 ++ .../controller/NuanceItemActionController.php | 49 +++++++++++--- .../nuance/worker/NuanceItemUpdateWorker.php | 65 +++++++++++++++++-- 4 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/applications/nuance/command/NuanceCommandImplementation.php b/src/applications/nuance/command/NuanceCommandImplementation.php index 46220266ff..2b47e40654 100644 --- a/src/applications/nuance/command/NuanceCommandImplementation.php +++ b/src/applications/nuance/command/NuanceCommandImplementation.php @@ -19,6 +19,12 @@ abstract class NuanceCommandImplementation abstract public function getCommandName(); abstract public function canApplyToItem(NuanceItem $item); + public function canApplyImmediately( + NuanceItem $item, + NuanceItemCommand $command) { + return false; + } + abstract protected function executeCommand( NuanceItem $item, NuanceItemCommand $command); diff --git a/src/applications/nuance/command/NuanceTrashCommand.php b/src/applications/nuance/command/NuanceTrashCommand.php index 1f8a729277..b5ac851539 100644 --- a/src/applications/nuance/command/NuanceTrashCommand.php +++ b/src/applications/nuance/command/NuanceTrashCommand.php @@ -14,6 +14,12 @@ final class NuanceTrashCommand return ($type instanceof NuanceFormItemType); } + public function canApplyImmediately( + NuanceItem $item, + NuanceItemCommand $command) { + return true; + } + protected function executeCommand( NuanceItem $item, NuanceItemCommand $command) { diff --git a/src/applications/nuance/controller/NuanceItemActionController.php b/src/applications/nuance/controller/NuanceItemActionController.php index c6dc139b11..39ae8fd995 100644 --- a/src/applications/nuance/controller/NuanceItemActionController.php +++ b/src/applications/nuance/controller/NuanceItemActionController.php @@ -53,6 +53,25 @@ final class NuanceItemActionController extends NuanceController { $impl->setViewer($viewer); $impl->setController($this); + $executors = NuanceCommandImplementation::getAllCommands(); + $executor = idx($executors, $action); + if (!$executor) { + return new Aphront404Response(); + } + + $executor = id(clone $executor) + ->setActor($viewer); + + if (!$executor->canApplyToItem($item)) { + return $this->newDialog() + ->setTitle(pht('Command Not Supported')) + ->appendParagraph( + pht( + 'This item does not support the specified command ("%s").', + $action)) + ->addCancelButton($cancel_uri); + } + $command = NuanceItemCommand::initializeNewCommand() ->setItemPHID($item->getPHID()) ->setAuthorPHID($viewer->getPHID()) @@ -64,17 +83,29 @@ final class NuanceItemActionController extends NuanceController { $command->save(); - // TODO: Here, we should check if the command should be tried immediately, - // and just defer it to the daemons if not. If we're going to try to apply - // the command directly, we should first acquire the worker lock. If we - // can not, we should defer the command even if it's an immediate command. - // For the moment, skip all this stuff by deferring unconditionally. + // If this command can be applied immediately, try to apply it now. - $should_defer = true; - if ($should_defer) { + // In most cases, local commands (like closing an item) can be applied + // immediately. + + // Commands that require making a call to a remote system (for example, + // to reply to a tweet or close a remote object) are usually done in the + // background so the user doesn't have to wait for the operation to + // complete before they can continue work. + + $did_apply = false; + $immediate = $executor->canApplyImmediately($item, $command); + if ($immediate) { + // TODO: Move this stuff to a new Engine, and have the controller and + // worker both call into the Engine. + $worker = new NuanceItemUpdateWorker(array()); + $did_apply = $worker->executeCommands($item, array($command)); + } + + // If this can't be applied immediately or we were unable to get a lock + // fast enough, do the update in the background instead. + if (!$did_apply) { $item->scheduleUpdate(); - } else { - // ... } if ($queue) { diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php index 30d5621872..cd6637187c 100644 --- a/src/applications/nuance/worker/NuanceItemUpdateWorker.php +++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php @@ -6,9 +6,7 @@ final class NuanceItemUpdateWorker protected function doWork() { $item_phid = $this->getTaskDataValue('itemPHID'); - $hash = PhabricatorHash::digestForIndex($item_phid); - $lock_key = "nuance.item.{$hash}"; - $lock = PhabricatorGlobalLock::newLock($lock_key); + $lock = $this->newLock($item_phid); $lock->lock(1); try { @@ -55,9 +53,6 @@ final class NuanceItemUpdateWorker private function applyCommands(NuanceItem $item) { $viewer = $this->getViewer(); - $impl = $item->getImplementation(); - $impl->setViewer($viewer); - $commands = id(new NuanceItemCommandQuery()) ->setViewer($viewer) ->withItemPHIDs(array($item->getPHID())) @@ -68,8 +63,60 @@ final class NuanceItemUpdateWorker ->execute(); $commands = msort($commands, 'getID'); + $this->executeCommandList($item, $commands); + } + + public function executeCommands(NuanceItem $item, array $commands) { + if (!$commands) { + return true; + } + + $item_phid = $item->getPHID(); + $viewer = $this->getViewer(); + + $lock = $this->newLock($item_phid); + try { + $lock->lock(1); + } catch (PhutilLockException $ex) { + return false; + } + + try { + $item = $this->loadItem($item_phid); + + // Reload commands now that we have a lock, to make sure we don't + // execute any commands twice by mistake. + $commands = id(new NuanceItemCommandQuery()) + ->setViewer($viewer) + ->withIDs(mpull($commands, 'getID')) + ->execute(); + + $this->executeCommandList($item, $commands); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return true; + } + + private function executeCommandList(NuanceItem $item, array $commands) { + $viewer = $this->getViewer(); + $executors = NuanceCommandImplementation::getAllCommands(); foreach ($commands as $command) { + if ($command->getItemPHID() !== $item->getPHID()) { + throw new Exception( + pht('Trying to apply a command to the wrong item!')); + } + + if ($command->getStatus() !== NuanceItemCommand::STATUS_ISSUED) { + // Never execute commands which have already been issued. + continue; + } + $command ->setStatus(NuanceItemCommand::STATUS_EXECUTING) ->save(); @@ -105,4 +152,10 @@ final class NuanceItemUpdateWorker } } + private function newLock($item_phid) { + $hash = PhabricatorHash::digestForIndex($item_phid); + $lock_key = "nuance.item.{$hash}"; + return PhabricatorGlobalLock::newLock($lock_key); + } + } From 718c39131da809cbcc40276193ae201d60e1e4d2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 08:58:35 -0700 Subject: [PATCH 187/543] Add a little style to Phriction ToC menu Summary: Adds some indentation and color. Ref T9868. Test Plan: A long page with multiple indentation levels. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9868 Differential Revision: https://secure.phabricator.com/D18025 --- resources/celerity/map.php | 4 +-- webroot/rsrc/css/phui/phui-document-pro.css | 27 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3011b17643..8f5dee69e8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -147,7 +147,7 @@ return array( 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', - 'rsrc/css/phui/phui-document-pro.css' => '58199f99', + 'rsrc/css/phui/phui-document-pro.css' => 'bb18da6b', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', @@ -851,7 +851,7 @@ return array( 'phui-curtain-view-css' => '55dd0e59', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', - 'phui-document-view-pro-css' => '58199f99', + 'phui-document-view-pro-css' => 'bb18da6b', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 8ece71db64..a4c05a0ecd 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -84,7 +84,7 @@ a.button.phui-document-toc { border: 1px solid {$lightgreyborder}; border-radius: 3px; box-shadow: {$dropshadow}; - width: 200px; + width: 260px; position: absolute; z-index: 30; background-color: #fff; @@ -114,17 +114,38 @@ a.button.phui-document-toc { } .phui-document-view-pro .phui-document-toc-content { - margin: 4px 12px; + margin: 8px 16px; } .phui-document-view-pro .phui-document-toc-header { font-weight: bold; color: {$bluetext}; margin-bottom: 8px; + text-transform: uppercase; + font-size: {$smallerfontsize}; } .phui-document-view-pro .phui-document-toc-content li { - margin: 4px 8px; + margin: 4px 8px 4px 0; +} + +.phui-document-view-pro .phui-document-toc-content a { + padding: 2px 0; + display: block; + text-decoration: none; + color: {$darkbluetext}; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.phui-document-view-pro .phui-document-toc-content a:hover { + color: {$anchor}; + text-decoration: underline; +} + +.phui-document-view-pro .phui-document-toc-content li + ul { + margin: 4px 0 4px 8px; } .phui-document-view-pro .phui-document-content .phabricator-remarkup { From 69538274c1aceb40374124063bb902cf32da76e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 May 2017 08:46:48 -0700 Subject: [PATCH 188/543] Garbage collect old daemon records based on modification date, not creation date Summary: Fixes T12720. Currently, old daemon records are collected based on creation date. By default, the GC collects them after 7 days. After T12298, this can incorrectly collect hibernating daemons which are in state "wait". In all cases, this could fail to collect daemons which are stuck in "running" for a long time for some reason. This doesn't seem to be causing any problems right now, but it makes me hesitant to do "dateCreated + not running or waiting" since that might make this become a problem, or make an existing problem with this that we just haven't bumped into worse. Daemons always heartbeat periodically and update their rows, so `dateModified` is always fresh, so collect rows based only on modification date. Test Plan: - Ran daemons (`bin/phd start`). - Waited a few minutes. - Verified that hibernating daemons in the "wait" state had fresh timestamps. - Verified that very old daemons still got GC'd properly. ``` mysql> select id, daemon, status, FROM_UNIXTIME(dateCreated), FROM_UNIXTIME(dateModified) from daemon_log; +-------+--------------------------------------+--------+----------------------------+-----------------------------+ | id | daemon | status | FROM_UNIXTIME(dateCreated) | FROM_UNIXTIME(dateModified) | +-------+--------------------------------------+--------+----------------------------+-----------------------------+ | 73377 | PhabricatorTaskmasterDaemon | exit | 2017-05-19 10:53:03 | 2017-05-19 12:38:54 | ... | 73388 | PhabricatorRepositoryPullLocalDaemon | run | 2017-05-26 08:43:29 | 2017-05-26 08:45:30 | | 73389 | PhabricatorTriggerDaemon | run | 2017-05-26 08:43:29 | 2017-05-26 08:46:35 | | 73390 | PhabricatorTaskmasterDaemon | wait | 2017-05-26 08:43:29 | 2017-05-26 08:46:35 | | 73391 | PhabricatorTaskmasterDaemon | wait | 2017-05-26 08:43:33 | 2017-05-26 08:46:33 | | 73392 | PhabricatorTaskmasterDaemon | wait | 2017-05-26 08:43:37 | 2017-05-26 08:46:31 | | 73393 | PhabricatorTaskmasterDaemon | wait | 2017-05-26 08:43:40 | 2017-05-26 08:46:33 | +-------+--------------------------------------+--------+----------------------------+-----------------------------+ 17 rows in set (0.00 sec) ``` Note that: - The oldest daemon is <7 days old -- I had some other older rows but they got GC'd properly. - The hibernating taskmasters (at the bottom, in state "wait") have recent/more-current `dateModified` dates than their `dateCreated` dates. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12720 Differential Revision: https://secure.phabricator.com/D18024 --- .../PhabricatorDaemonLogGarbageCollector.php | 5 ++--- src/applications/daemon/storage/PhabricatorDaemonLog.php | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php index 3ff26e3db7..fd7de9dd33 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php @@ -19,10 +19,9 @@ final class PhabricatorDaemonLogGarbageCollector queryfx( $conn_w, - 'DELETE FROM %T WHERE dateCreated < %d AND status != %s LIMIT 100', + 'DELETE FROM %T WHERE dateModified < %d LIMIT 100', $table->getTableName(), - $this->getGarbageEpoch(), - PhabricatorDaemonLog::STATUS_RUNNING); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/daemon/storage/PhabricatorDaemonLog.php b/src/applications/daemon/storage/PhabricatorDaemonLog.php index 2e61da3872..d3ac485ea2 100644 --- a/src/applications/daemon/storage/PhabricatorDaemonLog.php +++ b/src/applications/daemon/storage/PhabricatorDaemonLog.php @@ -37,13 +37,13 @@ final class PhabricatorDaemonLog extends PhabricatorDaemonDAO 'status' => array( 'columns' => array('status'), ), - 'dateCreated' => array( - 'columns' => array('dateCreated'), - ), 'key_daemonID' => array( 'columns' => array('daemonID'), 'unique' => true, ), + 'key_modified' => array( + 'columns' => array('dateModified'), + ), ), ) + parent::getConfiguration(); } From 2d79229083437a10bb16c4eb8bff393506f9c887 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 May 2017 09:43:40 -0700 Subject: [PATCH 189/543] Modernize FeedQuery a little bit Summary: Ref T12762. Updates some conventions and methods. This has no (meaningful) behavioral changes. Test Plan: - Grepped for `setFilterPHIDs()`. - Viewed main feed, user feed, project feed. - Called `feed.query`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12762 Differential Revision: https://secure.phabricator.com/D18027 --- .../conduit/FeedQueryConduitAPIMethod.php | 10 ++-- .../feed/query/PhabricatorFeedQuery.php | 60 +++++++++---------- .../query/PhabricatorFeedSearchEngine.php | 2 +- ...PhabricatorPeopleProfileViewController.php | 2 +- .../PhabricatorProjectProfileController.php | 2 +- 5 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php b/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php index 661b70a7ab..bddc4f5921 100644 --- a/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php +++ b/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php @@ -67,16 +67,16 @@ final class FeedQueryConduitAPIMethod extends FeedConduitAPIMethod { if (!$limit) { $limit = $this->getDefaultLimit(); } - $filter_phids = $request->getValue('filterPHIDs'); - if (!$filter_phids) { - $filter_phids = array(); - } $query = id(new PhabricatorFeedQuery()) ->setLimit($limit) - ->setFilterPHIDs($filter_phids) ->setViewer($user); + $filter_phids = $request->getValue('filterPHIDs'); + if ($filter_phids) { + $query->withFilterPHIDs($filter_phids); + } + $after = $request->getValue('after'); if (strlen($after)) { $query->setAfterID($after); diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index 13cfb266ed..7335a6d5b1 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -6,7 +6,7 @@ final class PhabricatorFeedQuery private $filterPHIDs; private $chronologicalKeys; - public function setFilterPHIDs(array $phids) { + public function withFilterPHIDs(array $phids) { $this->filterPHIDs = $phids; return $this; } @@ -16,50 +16,46 @@ final class PhabricatorFeedQuery return $this; } + public function newResultObject() { + return new PhabricatorFeedStoryData(); + } + protected function loadPage() { - $story_table = new PhabricatorFeedStoryData(); - $conn = $story_table->establishConnection('r'); - - $data = queryfx_all( - $conn, - 'SELECT story.* FROM %T story %Q %Q %Q %Q %Q', - $story_table->getTableName(), - $this->buildJoinClause($conn), - $this->buildWhereClause($conn), - $this->buildGroupClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $data; + // NOTE: We return raw rows from this method, which is a little unusual. + return $this->loadStandardPageRows($this->newResultObject()); } protected function willFilterPage(array $data) { return PhabricatorFeedStory::loadAllFromRows($data, $this->getViewer()); } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + // NOTE: We perform this join unconditionally (even if we have no filter // PHIDs) to omit rows which have no story references. These story data // rows are notifications or realtime alerts. $ref_table = new PhabricatorFeedStoryReference(); - return qsprintf( - $conn_r, + $joins[] = qsprintf( + $conn, 'JOIN %T ref ON ref.chronologicalKey = story.chronologicalKey', $ref_table->getTableName()); + + return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->filterPHIDs) { + if ($this->filterPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'ref.objectPHID IN (%Ls)', $this->filterPHIDs); } - if ($this->chronologicalKeys) { + if ($this->chronologicalKeys !== null) { // NOTE: We want to use integers in the query so we can take advantage // of keys, but can't use %d on 32-bit systems. Make sure all the keys // are integers and then format them raw. @@ -73,21 +69,19 @@ final class PhabricatorFeedQuery } $where[] = qsprintf( - $conn_r, + $conn, 'ref.chronologicalKey IN (%Q)', implode(', ', $keys)); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - if ($this->filterPHIDs) { - return qsprintf($conn_r, 'GROUP BY ref.chronologicalKey'); + protected function buildGroupClause(AphrontDatabaseConnection $conn) { + if ($this->filterPHIDs !== null) { + return qsprintf($conn, 'GROUP BY ref.chronologicalKey'); } else { - return qsprintf($conn_r, 'GROUP BY story.chronologicalKey'); + return qsprintf($conn, 'GROUP BY story.chronologicalKey'); } } @@ -120,6 +114,10 @@ final class PhabricatorFeedQuery return $item['chronologicalKey']; } + protected function getPrimaryTableAlias() { + return 'story'; + } + public function getQueryApplicationClass() { return 'PhabricatorFeedApplication'; } diff --git a/src/applications/feed/query/PhabricatorFeedSearchEngine.php b/src/applications/feed/query/PhabricatorFeedSearchEngine.php index b8caf60ae7..43a0b716ca 100644 --- a/src/applications/feed/query/PhabricatorFeedSearchEngine.php +++ b/src/applications/feed/query/PhabricatorFeedSearchEngine.php @@ -56,7 +56,7 @@ final class PhabricatorFeedSearchEngine $phids = array_mergev($phids); if ($phids) { - $query->setFilterPHIDs($phids); + $query->withFilterPHIDs($phids); } return $query; diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 9db106081d..7bcab34b17 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -229,7 +229,7 @@ final class PhabricatorPeopleProfileViewController $viewer) { $query = new PhabricatorFeedQuery(); - $query->setFilterPHIDs( + $query->withFilterPHIDs( array( $user->getPHID(), )); diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index e47cc678bc..d9fe019751 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -73,7 +73,7 @@ final class PhabricatorProjectProfileController $stories = id(new PhabricatorFeedQuery()) ->setViewer($viewer) - ->setFilterPHIDs( + ->withFilterPHIDs( array( $project->getPHID(), )) From 6fed08a98e042c883fcd482e4ec876f11d02de1e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 May 2017 10:08:33 -0700 Subject: [PATCH 190/543] Modernize FeedSearchEngine a little bit Summary: Ref T12762. This updates FeedSeachEngine to user modern construction. I've tried to retain behavior exactly, although the "Include stories about projects I'm a member of" checkbox is now nonstandard/obsolete. We could likely fold that into "Include Projects" in a future change which does a backward compatibility break. Test Plan: - Queried feed without constraints. - Queried feed by user, project, "stuff I'm a member of", prebuilt "Tags" query. - Viewed user profile / project profile feeds. - Used function tokens (`viewer()`, etc). Reviewers: chad Reviewed By: chad Maniphest Tasks: T12762 Differential Revision: https://secure.phabricator.com/D18028 --- .../feed/query/PhabricatorFeedQuery.php | 14 +++ .../query/PhabricatorFeedSearchEngine.php | 100 +++++++----------- 2 files changed, 55 insertions(+), 59 deletions(-) diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index 7335a6d5b1..f8f67f7a3e 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -89,6 +89,20 @@ final class PhabricatorFeedQuery return array('key'); } + public function getBuiltinOrders() { + return array( + 'newest' => array( + 'vector' => array('key'), + 'name' => pht('Creation (Newest First)'), + 'aliases' => array('created'), + ), + 'oldest' => array( + 'vector' => array('-key'), + 'name' => pht('Creation (Oldest First)'), + ), + ); + } + public function getOrderableColumns() { $table = ($this->filterPHIDs ? 'ref' : 'story'); return array( diff --git a/src/applications/feed/query/PhabricatorFeedSearchEngine.php b/src/applications/feed/query/PhabricatorFeedSearchEngine.php index 43a0b716ca..6dae9f9c37 100644 --- a/src/applications/feed/query/PhabricatorFeedSearchEngine.php +++ b/src/applications/feed/query/PhabricatorFeedSearchEngine.php @@ -11,50 +11,62 @@ final class PhabricatorFeedSearchEngine return 'PhabricatorFeedApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'userPHIDs', - $this->readUsersFromRequest($request, 'users')); - - $saved->setParameter( - 'projectPHIDs', - array_values($request->getArr('projectPHIDs'))); - - $saved->setParameter( - 'viewerProjects', - $request->getBool('viewerProjects')); - - return $saved; + public function newQuery() { + return new PhabricatorFeedQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorFeedQuery()); + protected function shouldShowOrderField() { + return false; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Include Users')) + ->setKey('userPHIDs'), + // NOTE: This query is not executed with EdgeLogic, so we can't use + // a fancy logical datasource. + id(new PhabricatorSearchDatasourceField()) + ->setDatasource(new PhabricatorProjectDatasource()) + ->setLabel(pht('Include Projects')) + ->setKey('projectPHIDs'), + + // NOTE: This is a legacy field retained only for backward + // compatibility. If the projects field used EdgeLogic, we could use + // `viewerprojects()` to execute an equivalent query. + id(new PhabricatorSearchCheckboxesField()) + ->setKey('viewerProjects') + ->setOptions( + array( + 'self' => pht('Include stories about projects I am a member of.'), + )), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); $phids = array(); - - $user_phids = $saved->getParameter('userPHIDs'); - if ($user_phids) { - $phids[] = $user_phids; + if ($map['userPHIDs']) { + $phids += array_fuse($map['userPHIDs']); } - $proj_phids = $saved->getParameter('projectPHIDs'); - if ($proj_phids) { - $phids[] = $proj_phids; + if ($map['projectPHIDs']) { + $phids += array_fuse($map['projectPHIDs']); } - $viewer_projects = $saved->getParameter('viewerProjects'); + // NOTE: This value may be `true` for older saved queries, or + // `array('self')` for newer ones. + $viewer_projects = $map['viewerProjects']; if ($viewer_projects) { $viewer = $this->requireViewer(); $projects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); - $phids[] = mpull($projects, 'getPHID'); + $phids += array_fuse(mpull($projects, 'getPHID')); } - $phids = array_mergev($phids); if ($phids) { $query->withFilterPHIDs($phids); } @@ -62,36 +74,6 @@ final class PhabricatorFeedSearchEngine return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $user_phids = $saved_query->getParameter('userPHIDs', array()); - $proj_phids = $saved_query->getParameter('projectPHIDs', array()); - $viewer_projects = $saved_query->getParameter('viewerProjects'); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('users') - ->setLabel(pht('Include Users')) - ->setValue($user_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectDatasource()) - ->setName('projectPHIDs') - ->setLabel(pht('Include Projects')) - ->setValue($proj_phids)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'viewerProjects', - 1, - pht('Include stories about projects I am a member of.'), - $viewer_projects)); - } - protected function getURI($path) { return '/feed/'.$path; } @@ -117,7 +99,7 @@ final class PhabricatorFeedSearchEngine case 'all': return $query; case 'projects': - return $query->setParameter('viewerProjects', true); + return $query->setParameter('viewerProjects', array('self')); } return parent::buildSavedQueryFromBuiltin($query_key); From 46a33c07dc0c5aa89022dafcc0c253d1340a6071 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 May 2017 10:22:29 -0700 Subject: [PATCH 191/543] Allow users to query feed by a date range Summary: Ref T12762. Test Plan: - Ran queries with start date, end date, both, neither. - Used EXPLAIN to try to make sure doing the bitshift isn't going to be a performance issue. {F4978842} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12762 Differential Revision: https://secure.phabricator.com/D18029 --- .../feed/query/PhabricatorFeedQuery.php | 26 ++++++++++++++++ .../query/PhabricatorFeedSearchEngine.php | 30 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index f8f67f7a3e..ae1da3aba0 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -5,6 +5,8 @@ final class PhabricatorFeedQuery private $filterPHIDs; private $chronologicalKeys; + private $rangeMin; + private $rangeMax; public function withFilterPHIDs(array $phids) { $this->filterPHIDs = $phids; @@ -16,6 +18,12 @@ final class PhabricatorFeedQuery return $this; } + public function withEpochInRange($range_min, $range_max) { + $this->rangeMin = $range_min; + $this->rangeMax = $range_max; + return $this; + } + public function newResultObject() { return new PhabricatorFeedStoryData(); } @@ -74,6 +82,24 @@ final class PhabricatorFeedQuery implode(', ', $keys)); } + // NOTE: We may not have 64-bit PHP, so do the shifts in MySQL instead. + // From EXPLAIN, it appears like MySQL is smart enough to compute the + // result and make use of keys to execute the query. + + if ($this->rangeMin !== null) { + $where[] = qsprintf( + $conn, + 'ref.chronologicalKey >= (%d << 32)', + $this->rangeMin); + } + + if ($this->rangeMax !== null) { + $where[] = qsprintf( + $conn, + 'ref.chronologicalKey < (%d << 32)', + $this->rangeMax); + } + return $where; } diff --git a/src/applications/feed/query/PhabricatorFeedSearchEngine.php b/src/applications/feed/query/PhabricatorFeedSearchEngine.php index 6dae9f9c37..d17c756524 100644 --- a/src/applications/feed/query/PhabricatorFeedSearchEngine.php +++ b/src/applications/feed/query/PhabricatorFeedSearchEngine.php @@ -30,6 +30,12 @@ final class PhabricatorFeedSearchEngine ->setDatasource(new PhabricatorProjectDatasource()) ->setLabel(pht('Include Projects')) ->setKey('projectPHIDs'), + id(new PhabricatorSearchDateControlField()) + ->setLabel(pht('Occurs After')) + ->setKey('rangeStart'), + id(new PhabricatorSearchDateControlField()) + ->setLabel(pht('Occurs Before')) + ->setKey('rangeEnd'), // NOTE: This is a legacy field retained only for backward // compatibility. If the projects field used EdgeLogic, we could use @@ -71,6 +77,30 @@ final class PhabricatorFeedSearchEngine $query->withFilterPHIDs($phids); } + $range_min = $map['rangeStart']; + if ($range_min) { + $range_min = $range_min->getEpoch(); + } + + $range_max = $map['rangeEnd']; + if ($range_max) { + $range_max = $range_max->getEpoch(); + } + + if ($range_min && $range_max) { + if ($range_min > $range_max) { + throw new PhabricatorSearchConstraintException( + pht( + 'The specified "Occurs Before" date is earlier in time than the '. + 'specified "Occurs After" date, so this query can never match '. + 'any results.')); + } + } + + if ($range_min || $range_max) { + $query->withEpochInRange($range_min, $range_max); + } + return $query; } From fc8465252fb1fa737671e34b4085d922770daa36 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 May 2017 10:34:42 -0700 Subject: [PATCH 192/543] Add "View All" header buttons to user and project feed boxes Summary: Fixes T12762. Currently, there's no way to get from these boxes into generaly history in Feed, and it isn't clear that the operation is possible. For now, add some simple links. See T12762 for future work. Test Plan: - Viewed user profles, saw "View All". - Viewed project profiles, saw "View All". {F4978858} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12762 Differential Revision: https://secure.phabricator.com/D18030 --- .../PhabricatorPeopleProfileViewController.php | 15 ++++++++++++++- .../PhabricatorProjectProfileController.php | 14 +++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 7bcab34b17..5dce872c4b 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -28,8 +28,21 @@ final class PhabricatorPeopleProfileViewController $name = $user->getUsername(); $feed = $this->buildPeopleFeed($user, $viewer); + + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIcon('fa-list-ul')) + ->setText(pht('View All')) + ->setHref('/feed/?userPHIDs='.$user->getPHID()); + + $feed_header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Activity')) + ->addActionLink($view_all); + $feed = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Activity')) + ->setHeader($feed_header) ->addClass('project-view-feed') ->appendChild($feed); diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index d9fe019751..1f59398930 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -80,9 +80,21 @@ final class PhabricatorProjectProfileController ->setLimit(50) ->execute(); + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIcon('fa-list-ul')) + ->setText(pht('View All')) + ->setHref('/feed/?projectPHIDs='.$project->getPHID()); + + $feed_header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Activity')) + ->addActionLink($view_all); + $feed = $this->renderStories($stories); $feed = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Activity')) + ->setHeader($feed_header) ->addClass('project-view-feed') ->appendChild($feed); From b27c2ed6d1e68e94f36db661f856c99d94fc161e Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Fri, 26 May 2017 13:01:28 -0700 Subject: [PATCH 193/543] Index Project milestones to accurately reflect milestone membership Summary: Fixes T12505. `PhabricatorProjectsMembershipIndexEngineExtension->materializeProject()` was incorrectly bailing early for milestone objects, which prevented milestone members from being calculated correctly. This was causing problems where (for example) an Owners package owned by a milestone wasn't being satisfied when a member of the milestone approved a revision. Test Plan: Invoked migration, observed that a user's milestones correctly showed up when searched for. Also observed that accepting a revision on behalf of a milestone now satisfies Owners rules. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12505 Differential Revision: https://secure.phabricator.com/D18033 --- .../sql/autopatches/20170526.milestones.php | 11 ++++++ ...ProjectsMembershipIndexEngineExtension.php | 35 ++++++++++--------- 2 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 resources/sql/autopatches/20170526.milestones.php diff --git a/resources/sql/autopatches/20170526.milestones.php b/resources/sql/autopatches/20170526.milestones.php new file mode 100644 index 0000000000..2e30ac4775 --- /dev/null +++ b/resources/sql/autopatches/20170526.milestones.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php index 567f5b749e..5acfaf913a 100644 --- a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php +++ b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php @@ -34,29 +34,30 @@ final class PhabricatorProjectsMembershipIndexEngineExtension } private function materializeProject(PhabricatorProject $project) { - if ($project->isMilestone()) { - return; - } - $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST; $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; $project_phid = $project->getPHID(); - $descendants = id(new PhabricatorProjectQuery()) - ->setViewer($this->getViewer()) - ->withAncestorProjectPHIDs(array($project->getPHID())) - ->withIsMilestone(false) - ->withHasSubprojects(false) - ->execute(); - $descendant_phids = mpull($descendants, 'getPHID'); - - if ($descendant_phids) { - $source_phids = $descendant_phids; - $has_subprojects = true; - } else { - $source_phids = array($project->getPHID()); + if ($project->isMilestone()) { + $source_phids = array($project->getParentProjectPHID()); $has_subprojects = false; + } else { + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($this->getViewer()) + ->withAncestorProjectPHIDs(array($project->getPHID())) + ->withIsMilestone(false) + ->withHasSubprojects(false) + ->execute(); + $descendant_phids = mpull($descendants, 'getPHID'); + + if ($descendant_phids) { + $source_phids = $descendant_phids; + $has_subprojects = true; + } else { + $source_phids = array($project->getPHID()); + $has_subprojects = false; + } } $conn_w = $project->establishConnection('w'); From aefc006ba503eb47113372e7adf2888fdf79d8a0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 09:26:06 -0700 Subject: [PATCH 194/543] Move DiffusionHistoryListView to DiffusionCommitListView Summary: I think this name is more accurate, also add proper links to author image. Test Plan: Review commits in sandbox, see new URL on image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18026 --- src/__phutil_library_map__.php | 4 ++-- ...fusionHistoryListView.php => DiffusionCommitListView.php} | 5 ++++- .../controller/PhabricatorPeopleProfileCommitsController.php | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) rename src/applications/diffusion/view/{DiffusionHistoryListView.php => DiffusionCommitListView.php} (95%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7510f2e42c..9605ebfc14 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -661,6 +661,7 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php', 'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php', 'DiffusionCommitListController' => 'applications/diffusion/controller/DiffusionCommitListController.php', + 'DiffusionCommitListView' => 'applications/diffusion/view/DiffusionCommitListView.php', 'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php', 'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php', 'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php', @@ -728,7 +729,6 @@ phutil_register_library_map(array( 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', - 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', @@ -5634,6 +5634,7 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitHookRejectException' => 'Exception', 'DiffusionCommitListController' => 'DiffusionController', + 'DiffusionCommitListView' => 'AphrontView', 'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField', @@ -5704,7 +5705,6 @@ phutil_register_library_map(array( ), 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionHistoryController' => 'DiffusionController', - 'DiffusionHistoryListView' => 'AphrontView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionCommitListView.php similarity index 95% rename from src/applications/diffusion/view/DiffusionHistoryListView.php rename to src/applications/diffusion/view/DiffusionCommitListView.php index 00a6eb0741..b3f2a9fb0c 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionCommitListView.php @@ -1,6 +1,6 @@ renderLink(); $author_image_uri = $handles[$author_phid]->getImageURI(); + $author_image_href = $handles[$author_phid]->getURI(); } else { $author_name = $commit->getCommitData()->getAuthorName(); $author_image_uri = celerity_get_resource_uri('/rsrc/image/people/user0.png'); + $author_image_href = null; } $commit_tag = id(new PHUITagView()) @@ -132,6 +134,7 @@ final class DiffusionHistoryListView extends AphrontView { ->setDisabled($commit->isUnreachable()) ->setDescription($message) ->setImageURI($author_image_uri) + ->setImageHref($author_image_href) ->addByline(pht('Author: %s', $author_name)) ->addIcon('none', $committed) ->addAttribute($commit_tag); diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php index 81e4fa304e..c18c5f4d96 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php @@ -60,7 +60,7 @@ final class PhabricatorPeopleProfileCommitsController ->setLimit(100) ->execute(); - $list = id(new DiffusionHistoryListView()) + $list = id(new DiffusionCommitListView()) ->setViewer($viewer) ->setCommits($commits) ->setNoDataString(pht('No recent commits.')); From 81809713e0780aeb135cd4f8b32131a0adf7e5dd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 11:42:31 -0700 Subject: [PATCH 195/543] Try layering state icons on PHUICircleIconView Summary: I think this is reasonable for my current use case, but stacking icons overally is pretty clunky. Test Plan: UIExamples {F4978899} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18032 --- resources/celerity/map.php | 6 +-- .../uiexample/examples/PHUIIconExample.php | 17 +++++++ src/view/phui/PHUIIconCircleView.php | 47 ++++++++++++++++++- webroot/rsrc/css/phui/phui-icon.css | 34 +++++++++++++- 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8f5dee69e8..a2119c98a4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'b666574e', + 'core.pkg.css' => '19f6f61f', 'core.pkg.js' => '21d34805', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -158,7 +158,7 @@ return array( 'rsrc/css/phui/phui-header-view.css' => 'a3d1aecd', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', - 'rsrc/css/phui/phui-icon.css' => '12b387a1', + 'rsrc/css/phui/phui-icon.css' => '4c6d624c', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6e217679', @@ -862,7 +862,7 @@ return array( 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', - 'phui-icon-view-css' => '12b387a1', + 'phui-icon-view-css' => '4c6d624c', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', diff --git a/src/applications/uiexample/examples/PHUIIconExample.php b/src/applications/uiexample/examples/PHUIIconExample.php index 8e01d6e16a..1bf96ce8e5 100644 --- a/src/applications/uiexample/examples/PHUIIconExample.php +++ b/src/applications/uiexample/examples/PHUIIconExample.php @@ -130,6 +130,23 @@ final class PHUIIconExample extends PhabricatorUIExample { ->addClass('mmr'); } + $circles = array('fa-gear', 'fa-recycle'); + $colors = array('green', 'pink', 'red', 'sky', 'violet'); + foreach ($circles as $circle) { + $states = PHUIIconCircleView::getStateMap(); + foreach ($states as $state => $name) { + $i = array_rand($colors); + $circleview[] = + id(new PHUIIconCircleView()) + ->setIcon($circle) + ->setSize(PHUIIconCircleView::SMALL) + ->setState($state) + ->setColor($colors[$i]) + ->setHref('#') + ->addClass('mmr'); + } + } + $squares = array('fa-briefcase', 'fa-code', 'fa-globe', 'fa-home'); $squareview = array(); foreach ($squares as $icon) { diff --git a/src/view/phui/PHUIIconCircleView.php b/src/view/phui/PHUIIconCircleView.php index 083818969f..11e2a2ac3b 100644 --- a/src/view/phui/PHUIIconCircleView.php +++ b/src/view/phui/PHUIIconCircleView.php @@ -6,10 +6,22 @@ final class PHUIIconCircleView extends AphrontTagView { private $icon; private $color; private $size; + private $state; const SMALL = 'circle-small'; const MEDIUM = 'circle-medium'; + const STATE_FAIL = 'fa-times-circle'; + const STATE_INFO = 'fa-info-circle'; + const STATE_STOP = 'fa-stop-circle'; + const STATE_START = 'fa-play-circle'; + const STATE_PAUSE = 'fa-pause-circle'; + const STATE_SUCCESS = 'fa-check-circle'; + const STATE_WARNING = 'fa-exclamation-circle'; + const STATE_PLUS = 'fa-plus-circle'; + const STATE_MINUS = 'fa-minus-circle'; + const STATE_UNKNOWN = 'fa-question-circle'; + public function setHref($href) { $this->href = $href; return $this; @@ -30,6 +42,11 @@ final class PHUIIconCircleView extends AphrontTagView { return $this; } + public function setState($state) { + $this->state = $state; + return $this; + } + protected function getTagName() { $tag = 'span'; if ($this->href) { @@ -54,6 +71,10 @@ final class PHUIIconCircleView extends AphrontTagView { $classes[] = $this->size; } + if ($this->state) { + $classes[] = 'phui-icon-circle-state'; + } + return array( 'href' => $this->href, 'class' => $classes, @@ -61,8 +82,32 @@ final class PHUIIconCircleView extends AphrontTagView { } protected function getTagContent() { + $state = null; + if ($this->state) { + $state = id(new PHUIIconView()) + ->setIcon($this->state.' '.$this->color) + ->addClass('phui-icon-circle-state-icon'); + } + return id(new PHUIIconView()) - ->setIcon($this->icon); + ->setIcon($this->icon) + ->addClass('phui-icon-circle-icon') + ->appendChild($state); + } + + public static function getStateMap() { + return array( + self::STATE_FAIL => pht('Failure'), + self::STATE_INFO => pht('Information'), + self::STATE_STOP => pht('Stop'), + self::STATE_START => pht('Start'), + self::STATE_PAUSE => pht('Pause'), + self::STATE_SUCCESS => pht('Success'), + self::STATE_WARNING => pht('Warning'), + self::STATE_PLUS => pht('Plus'), + self::STATE_MINUS => pht('Minus'), + self::STATE_UNKNOWN => pht('Unknown'), + ); } } diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css index 22b33bca11..627b7632fe 100644 --- a/webroot/rsrc/css/phui/phui-icon.css +++ b/webroot/rsrc/css/phui/phui-icon.css @@ -57,6 +57,7 @@ img.phui-image-disabled { cursor: pointer; background: transparent; padding: 0; + position: relative; } .phui-icon-circle.circle-medium { @@ -65,7 +66,21 @@ img.phui-image-disabled { border-radius: 36px; } -.phui-icon-circle .phui-icon-view { +.phui-icon-circle.phui-icon-circle-state { + border-color: transparent; + background-color: {$bluebackground}; +} + +.phui-icon-circle.phui-icon-circle-state .phui-icon-circle-icon { + color: {$bluetext}; + font-size: 16px; +} + +a.phui-icon-circle.phui-icon-circle-state:hover { + border-color: transparent !important; +} + +.phui-icon-circle .phui-icon-circle-icon { height: 24px; width: 24px; font-size: 11px; @@ -74,7 +89,7 @@ img.phui-image-disabled { cursor: pointer; } -.phui-icon-circle.circle-medium .phui-icon-view { +.phui-icon-circle.circle-medium .phui-icon-circle-icon { font-size: 18px; line-height: 36px; } @@ -129,6 +144,21 @@ a.phui-icon-circle.hover-red:hover .phui-icon-view { color: {$red}; } +a.phui-icon-circle .phui-icon-view.phui-icon-circle-state-icon { + position: absolute; + width: 14px; + height: 14px; + display: inline-block; + font-size: 12px; + right: -3px; + top: -4px; + text-shadow: + -1px -1px 0 #fff, + 1px -1px 0 #fff, + -1px 1px 0 #fff, + 1px 1px 0 #fff; +} + /* - Icon in a Square ------------------------------------------------------- */ .phui-icon-view.phui-icon-square { From 742c3a834fc5cd19a4b53e9ff31f7b0efd67e239 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 2 Mar 2017 05:02:53 -0800 Subject: [PATCH 196/543] Provide UI hints about task subtypes Summary: Ref T12314. Open to counterdiffs / iterating / suggestions / skipping most or all of this, mostly just throwing this out there as a maybe-reasonable first pass. When a task has a subtype (like "Plant" or "Animal"), provide some hints on the task list, workboards, and task detail. To make these hints more useful, allow subtypes to have icons and colors. Also use these icons and colors in the typeahead tokens. The current rule is that we show the subtype if it's not the default subtype. Another rule we could use is "show the subtype if there's more than one subtype defined", but my guess is that most installs will mostly have something like "normal task" as the default subtype. Test Plan: The interfaces this affects are: task detail view, task list view, workboard cards, subtype typeahead. {F3539128} {F3539144} {F3539167} {F3539185} Reviewers: chad Reviewed By: chad Subscribers: johnny-bit, bbrdaric, benwick, fooishbar Maniphest Tasks: T12314 Differential Revision: https://secure.phabricator.com/D17451 --- .../PhabricatorManiphestConfigOptions.php | 7 ++ .../ManiphestTaskDetailController.php | 6 ++ .../maniphest/storage/ManiphestTask.php | 5 ++ .../storage/ManiphestTransaction.php | 10 +++ .../ManiphestTaskSubtypeDatasource.php | 1 + .../maniphest/view/ManiphestTaskListView.php | 10 +++ .../xaction/ManiphestTaskTransactionType.php | 10 --- .../project/view/ProjectBoardTaskCard.php | 7 ++ .../PhabricatorEditEngineSubtype.php | 70 ++++++++++++++++++- 9 files changed, 113 insertions(+), 13 deletions(-) diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php index 4a1927c5a8..9d927ffca1 100644 --- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php +++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php @@ -335,9 +335,16 @@ dictionary with these keys: "task", "feature", or "bug". - `name` //Required string.// Human-readable name for this subtype, like "Task", "Feature Request" or "Bug Report". + - `tag` //Optional string.// Tag text for this subtype. + - `color` //Optional string.// Display color for this subtype. + - `icon` //Optional string.// Icon for the subtype. Each subtype must have a unique key, and you must define a subtype with the key "%s", which is used as a default subtype. + +The tag text (`tag`) is used to set the text shown in the subtype tag on list +views and workboards. If you do not configure it, the default subtype will have +no subtype tag and other subtypes will use their name as tag text. EOTEXT , $subtype_default_key)); diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 7859599a2f..92fe703f91 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -240,6 +240,12 @@ final class ManiphestTaskDetailController extends ManiphestController { } } + $subtype = $task->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView(); + $view->addTag($subtype_tag); + } + return $view; } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 16937da2f2..7cf9d3353f 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -540,6 +540,11 @@ final class ManiphestTask extends ManiphestDAO ); } + public function newSubtypeObject() { + $subtype_key = $this->getEditEngineSubtype(); + $subtype_map = $this->newEditEngineSubtypeMap(); + return idx($subtype_map, $subtype_key); + } /* -( PhabricatorFulltextInterface )--------------------------------------- */ diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index 0d27ddc5da..e2739c1d09 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -211,4 +211,14 @@ final class ManiphestTransaction return parent::getNoEffectDescription(); } + public function renderSubtypeName($value) { + $object = $this->getObject(); + $map = $object->newEditEngineSubtypeMap(); + if (!isset($map[$value])) { + return $value; + } + + return $map[$value]->getName(); + } + } diff --git a/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php b/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php index 2678e946b4..c061c694e4 100644 --- a/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php +++ b/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php @@ -32,6 +32,7 @@ final class ManiphestTaskSubtypeDatasource $result = id(new PhabricatorTypeaheadResult()) ->setIcon($subtype->getIcon()) + ->setColor($subtype->getColor()) ->setPHID($key) ->setName($subtype->getName()); diff --git a/src/applications/maniphest/view/ManiphestTaskListView.php b/src/applications/maniphest/view/ManiphestTaskListView.php index 6f714d0c5c..de6b386ac8 100644 --- a/src/applications/maniphest/view/ManiphestTaskListView.php +++ b/src/applications/maniphest/view/ManiphestTaskListView.php @@ -56,6 +56,9 @@ final class ManiphestTaskListView extends ManiphestView { Javelin::initBehavior('maniphest-list-editor'); } + $subtype_map = id(new ManiphestTask()) + ->newEditEngineSubtypeMap(); + foreach ($this->tasks as $task) { $item = id(new PHUIObjectItemView()) ->setUser($this->getUser()) @@ -94,6 +97,13 @@ final class ManiphestTaskListView extends ManiphestView { $item->addSigil('maniphest-task'); } + $subtype = $task->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView() + ->setSlimShady(true); + $item->addAttribute($subtype_tag); + } + $project_handles = array_select_keys( $handles, array_reverse($task->getProjectPHIDs())); diff --git a/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php index 699ef11631..c59de163c6 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php @@ -3,14 +3,4 @@ abstract class ManiphestTaskTransactionType extends PhabricatorModularTransactionType { - public function renderSubtypeName($value) { - $object = $this->getObject(); - $map = $object->newEditEngineSubtypeMap(); - if (!isset($map[$value])) { - return $value; - } - - return $map[$value]->getName(); - } - } diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index 100fee20b3..1a6807ec45 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -108,6 +108,13 @@ final class ProjectBoardTaskCard extends Phobject { } } + $subtype = $task->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView() + ->setSlimShady(true); + $card->addAttribute($subtype_tag); + } + if ($task->isClosed()) { $icon = ManiphestTaskStatus::getStatusIcon($task->getStatus()); $icon = id(new PHUIIconView()) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php b/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php index ff14ae239b..df367955af 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php @@ -8,6 +8,9 @@ final class PhabricatorEditEngineSubtype private $key; private $name; + private $icon; + private $tagText; + private $color; public function setKey($key) { $this->key = $key; @@ -27,8 +30,48 @@ final class PhabricatorEditEngineSubtype return $this->name; } + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + public function getIcon() { - return 'fa-drivers-license-o'; + return $this->icon; + } + + public function setTagText($text) { + $this->tagText = $text; + return $this; + } + + public function getTagText() { + return $this->tagText; + } + + public function setColor($color) { + $this->color = $color; + return $this; + } + + public function getColor() { + return $this->color; + } + + public function hasTagView() { + return (bool)strlen($this->getTagText()); + } + + public function newTagView() { + $view = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_OUTLINE) + ->setName($this->getTagText()); + + $color = $this->getColor(); + if ($color) { + $view->setColor($color); + } + + return $view; } public static function validateSubtypeKey($subtype) { @@ -72,6 +115,9 @@ final class PhabricatorEditEngineSubtype array( 'key' => 'string', 'name' => 'string', + 'tag' => 'optional string', + 'color' => 'optional string', + 'icon' => 'optional string', )); $key = $value['key']; @@ -113,9 +159,27 @@ final class PhabricatorEditEngineSubtype $key = $entry['key']; $name = $entry['name']; - $map[$key] = id(new self()) + $tag_text = idx($entry, 'tag'); + if ($tag_text === null) { + if ($key != self::SUBTYPE_DEFAULT) { + $tag_text = phutil_utf8_strtoupper($name); + } + } + + $color = idx($entry, 'color', 'blue'); + $icon = idx($entry, 'icon', 'fa-drivers-license-o'); + + $subtype = id(new self()) ->setKey($key) - ->setName($name); + ->setName($name) + ->setTagText($tag_text) + ->setIcon($icon); + + if ($color) { + $subtype->setColor($color); + } + + $map[$key] = $subtype; } return $map; From 04fd93e51e0368543773b72ed2dfef09d94080aa Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Fri, 26 May 2017 13:47:02 -0700 Subject: [PATCH 197/543] Drop DifferentialDraft storage Summary: Fixes T12104. Test Plan: Ran `bin/storage upgrade` and observed table dun got dropped. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T12104 Differential Revision: https://secure.phabricator.com/D18034 --- .../sql/autopatches/20141106.uniqdrafts.php | 29 +------------------ .../20170526.dropdifferentialdrafts.sql | 1 + src/__phutil_library_map__.php | 2 -- .../storage/DifferentialDraft.php | 23 --------------- 4 files changed, 2 insertions(+), 53 deletions(-) create mode 100644 resources/sql/autopatches/20170526.dropdifferentialdrafts.sql delete mode 100644 src/applications/differential/storage/DifferentialDraft.php diff --git a/resources/sql/autopatches/20141106.uniqdrafts.php b/resources/sql/autopatches/20141106.uniqdrafts.php index 31d6a53cb7..85b05f9ced 100644 --- a/resources/sql/autopatches/20141106.uniqdrafts.php +++ b/resources/sql/autopatches/20141106.uniqdrafts.php @@ -1,30 +1,3 @@ establishConnection('w'); - -$duplicates = queryfx_all( - $conn_w, - 'SELECT DISTINCT u.id id FROM %T u - JOIN %T v - ON u.objectPHID = v.objectPHID - AND u.authorPHID = v.authorPHID - AND u.draftKey = v.draftKey - AND u.id < v.id', - $table->getTableName(), - $table->getTableName()); - -$duplicates = ipull($duplicates, 'id'); -foreach (PhabricatorLiskDAO::chunkSQL($duplicates) as $chunk) { - queryfx( - $conn_w, - 'DELETE FROM %T WHERE id IN (%Q)', - $table->getTableName(), - $chunk); -} +// This table has been removed; see T12104 for details. diff --git a/resources/sql/autopatches/20170526.dropdifferentialdrafts.sql b/resources/sql/autopatches/20170526.dropdifferentialdrafts.sql new file mode 100644 index 0000000000..057bcb0d90 --- /dev/null +++ b/resources/sql/autopatches/20170526.dropdifferentialdrafts.sql @@ -0,0 +1 @@ +DROP TABLE {$NAMESPACE}_differential.differential_draft; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9605ebfc14..1b742b5730 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -441,7 +441,6 @@ phutil_register_library_map(array( 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', - 'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php', 'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php', 'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php', 'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php', @@ -5391,7 +5390,6 @@ phutil_register_library_map(array( 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', - 'DifferentialDraft' => 'DifferentialDAO', 'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialFieldParseException' => 'Exception', 'DifferentialFieldValidationException' => 'Exception', diff --git a/src/applications/differential/storage/DifferentialDraft.php b/src/applications/differential/storage/DifferentialDraft.php deleted file mode 100644 index 7dbe2f68bd..0000000000 --- a/src/applications/differential/storage/DifferentialDraft.php +++ /dev/null @@ -1,23 +0,0 @@ - array( - 'draftKey' => 'text64', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_unique' => array( - 'columns' => array('objectPHID', 'authorPHID', 'draftKey'), - 'unique' => true, - ), - ), - ) + parent::getConfiguration(); - } - -} From 9d37ad3022bfff8a2900b9d728e6abbb6a1e27f0 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Fri, 26 May 2017 15:39:18 -0700 Subject: [PATCH 198/543] Add maniphest.priority.search method Summary: Start on plan outlined in T12124. Adds a new Conduit method for querying information about task priorities. Test Plan: Ran locally; observed expected output: {F4979109} Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D18035 --- src/__phutil_library_map__.php | 2 + ...aniphestPrioritySearchConduitAPIMethod.php | 44 +++++++++++++++++++ .../constants/ManiphestTaskPriority.php | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1b742b5730..ce3cc27784 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1494,6 +1494,7 @@ phutil_register_library_map(array( 'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php', 'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php', 'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php', + 'ManiphestPrioritySearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php', 'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php', 'ManiphestQueryConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php', 'ManiphestQueryStatusesConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php', @@ -6593,6 +6594,7 @@ phutil_register_library_map(array( 'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand', + 'ManiphestPrioritySearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'ManiphestQueryConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestQueryStatusesConduitAPIMethod' => 'ManiphestConduitAPIMethod', diff --git a/src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php new file mode 100644 index 0000000000..eab06fb780 --- /dev/null +++ b/src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php @@ -0,0 +1,44 @@ +'; + } + + public function getRequiredScope() { + return self::SCOPE_ALWAYS; + } + + protected function execute(ConduitAPIRequest $request) { + $config = ManiphestTaskPriority::getConfig(); + + $results = array(); + foreach ($config as $code => $priority) { + $priority['value'] = $code; + $results[] = $priority; + } + + return array('data' => $results); + } + +} diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index 16a35a9e83..dd2ab69c69 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -110,7 +110,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { return idx($config, 'disabled', false); } - private static function getConfig() { + public static function getConfig() { $config = PhabricatorEnv::getEnvConfig('maniphest.priorities'); krsort($config); return $config; From 3fb4ca2429b78210f667d99fa3886429e42137f6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 18:54:21 -0700 Subject: [PATCH 199/543] Add needActiveDiffs to differential.createcomment method Summary: Ref T12766. Adds missing attachment for stacking actions in differential Test Plan: Asked end user to verify patch. Reviewers: epriestley, amckinley Reviewed By: epriestley, amckinley Subscribers: reed, Korvin Maniphest Tasks: T12766 Differential Revision: https://secure.phabricator.com/D18038 --- .../conduit/DifferentialCreateCommentConduitAPIMethod.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php index 52736d6f3b..943c3e7702 100644 --- a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php @@ -49,6 +49,7 @@ final class DifferentialCreateCommentConduitAPIMethod ->withIDs(array($request->getValue('revision_id'))) ->needReviewers(true) ->needReviewerAuthority(true) + ->needActiveDiffs(true) ->executeOne(); if (!$revision) { throw new ConduitException('ERR_BAD_REVISION'); From e5b3d0331974e26523e2c4077886abf62dfc3557 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 17:23:25 -0700 Subject: [PATCH 200/543] Fix lightbox circle icons Summary: These are unfortunatly manually built so I missed them in testing circle view changes. Test Plan: Test lightbox, conpherence, uiexamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18036 --- resources/celerity/map.php | 24 +++++++++---------- src/view/page/PhabricatorStandardPageView.php | 3 ++- .../js/core/behavior-lightbox-attachments.js | 4 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a2119c98a4..241f9acf08 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '19f6f61f', - 'core.pkg.js' => '21d34805', + 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', 'differential.pkg.js' => '1d120743', @@ -501,7 +501,7 @@ return array( 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', - 'rsrc/js/core/behavior-lightbox-attachments.js' => 'a5c57c24', + 'rsrc/js/core/behavior-lightbox-attachments.js' => '560f41da', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', @@ -644,7 +644,7 @@ return array( 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8499b6ab', 'javelin-behavior-launch-icon-composer' => '48086888', - 'javelin-behavior-lightbox-attachments' => 'a5c57c24', + 'javelin-behavior-lightbox-attachments' => '560f41da', 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', @@ -1332,6 +1332,15 @@ return array( 'javelin-vector', 'javelin-dom', ), + '560f41da' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-mask', + 'javelin-util', + 'phuix-icon-view', + 'phabricator-busy', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1708,15 +1717,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'a5c57c24' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-mask', - 'javelin-util', - 'phuix-icon-view', - 'phabricator-busy', - ), 'a6b98425' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index ffa82ab1b9..a138d077b5 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -268,7 +268,8 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } $icon = id(new PHUIIconView()) - ->setIcon('fa-download'); + ->setIcon('fa-download') + ->addClass('phui-icon-circle-icon'); $lightbox_id = celerity_generate_unique_node_id(); $download_form = phabricator_form( $user, diff --git a/webroot/rsrc/js/core/behavior-lightbox-attachments.js b/webroot/rsrc/js/core/behavior-lightbox-attachments.js index 929222f2f9..b9ea0dd8db 100644 --- a/webroot/rsrc/js/core/behavior-lightbox-attachments.js +++ b/webroot/rsrc/js/core/behavior-lightbox-attachments.js @@ -169,7 +169,7 @@ JX.behavior('lightbox-attachments', function (config) { ); var commentIcon = new JX.PHUIXIconView() - .setIcon('fa-comments') + .setIcon('fa-comments phui-icon-circle-icon') .getNode(); var commentButton = JX.$N('a', @@ -181,7 +181,7 @@ JX.behavior('lightbox-attachments', function (config) { commentIcon ); var closeIcon = new JX.PHUIXIconView() - .setIcon('fa-times') + .setIcon('fa-times phui-icon-circle-icon') .getNode(); var closeButton = JX.$N('a', From c4e45c6c8c2e5e1423d0f288214cfb70537df9c3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 09:27:48 -0700 Subject: [PATCH 201/543] Detect and prevent invalid configuation of "ui.footer-items" Summary: Fixes T12775. Currently, we do not validate this option and it's possible to configure it in an invalid way. Test Plan: Tried to misconfigure things, was helpfully pointed toward errors. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12775 Differential Revision: https://secure.phabricator.com/D18041 --- src/__phutil_library_map__.php | 2 + .../PhabricatorCustomUIFooterConfigType.php | 41 +++++++++++++++++++ .../option/PhabricatorUIConfigOptions.php | 3 +- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ce3cc27784..74ebbfe742 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2522,6 +2522,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldStorageQuery' => 'infrastructure/customfield/query/PhabricatorCustomFieldStorageQuery.php', 'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php', 'PhabricatorCustomLogoConfigType' => 'applications/config/custom/PhabricatorCustomLogoConfigType.php', + 'PhabricatorCustomUIFooterConfigType' => 'applications/config/custom/PhabricatorCustomUIFooterConfigType.php', 'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php', 'PhabricatorDaemonBulkJobController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobController.php', 'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php', @@ -7779,6 +7780,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldStorageQuery' => 'Phobject', 'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage', 'PhabricatorCustomLogoConfigType' => 'PhabricatorConfigOptionType', + 'PhabricatorCustomUIFooterConfigType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonBulkJobController' => 'PhabricatorDaemonController', 'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonBulkJobController', diff --git a/src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php b/src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php new file mode 100644 index 0000000000..d9961e7454 --- /dev/null +++ b/src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php @@ -0,0 +1,41 @@ + $item) { + if (!is_array($item)) { + throw new Exception( + pht( + 'Footer item with index "%s" is invalid: each item must be a '. + 'dictionary describing a footer item.', + $idx)); + } + + try { + PhutilTypeSpec::checkMap( + $item, + array( + 'name' => 'string', + 'href' => 'optional string', + )); + } catch (Exception $ex) { + throw new Exception( + pht( + 'Footer item with index "%s" is invalid: %s', + $idx, + $ex->getMessage())); + } + } + } + + +} diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php index 4f93946ce2..cef3fbd342 100644 --- a/src/applications/config/option/PhabricatorUIConfigOptions.php +++ b/src/applications/config/option/PhabricatorUIConfigOptions.php @@ -46,6 +46,7 @@ final class PhabricatorUIConfigOptions EOJSON; $logo_type = 'custom:PhabricatorCustomLogoConfigType'; + $footer_type = 'custom:PhabricatorCustomUIFooterConfigType'; return array( $this->newOption('ui.header-color', 'enum', 'blindigo') @@ -63,7 +64,7 @@ EOJSON; "Phabricator logo in the site header.\n\n". " - **Wordmark**: Choose new text to display next to the logo. ". "By default, the header displays //Phabricator//.\n\n")), - $this->newOption('ui.footer-items', 'list', array()) + $this->newOption('ui.footer-items', $footer_type, array()) ->setSummary( pht( 'Allows you to add footer links on most pages.')) From 7b290b94a726f7c9eb3efd9e065499fde1b0ad00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 11:49:51 -0700 Subject: [PATCH 202/543] Correct file PHID extraction on image update in Pholio Summary: Ref T12776. This extraction of file PHIDs extracted "PholioImage" object PHIDs (`PHID-PIMG-...`), not "File" PHIDs (`PHID-FILE-...`). Instead, dig into the Pholio images and actually extract the file PHIDs. This method is now similar to the `PholioImageFileTransaction` method, which works already. Test Plan: - Create a mock. - Update one of the images. - In Files, view the "Attached" tab of the updated image. - Before patch: not attached to mock. - After patch: properly attached to mock. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12776 Differential Revision: https://secure.phabricator.com/D18042 --- .../xaction/PholioImageReplaceTransaction.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/applications/pholio/xaction/PholioImageReplaceTransaction.php b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php index e6d45dfc7a..4978fa9768 100644 --- a/src/applications/pholio/xaction/PholioImageReplaceTransaction.php +++ b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php @@ -62,7 +62,19 @@ final class PholioImageReplaceTransaction } public function extractFilePHIDs($object, $value) { - return array($value); + $file_phids = array(); + + $editor = $this->getEditor(); + $images = $editor->getNewImages(); + foreach ($images as $image) { + if ($image->getPHID() !== $value) { + continue; + } + + $file_phids[] = $image->getFilePHID(); + } + + return $file_phids; } } From 88c5c02e723315a4bf4047c2a58294f1c265d94b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 30 May 2017 14:11:56 -0700 Subject: [PATCH 203/543] Group Maniphest Tasks by Priority on Profiles Summary: Ref T12423. Set the grouping by priority. Note this doesn't render headers but I don't want to spend a lot of time on this. Test Plan: Review tasks in my sandbox, see them ordered by priority. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12423 Differential Revision: https://secure.phabricator.com/D18046 --- .../controller/PhabricatorPeopleProfileTasksController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php index 47cc605bb8..b843af8fc7 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php @@ -61,6 +61,7 @@ final class PhabricatorPeopleProfileTasksController ->withStatuses($open) ->needProjectPHIDs(true) ->setLimit(100) + ->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY) ->execute(); $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); From 87c59c08674192c81cdc14166b276ff923ba9570 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 30 May 2017 14:27:04 -0700 Subject: [PATCH 204/543] Fix design atrocity Summary: So bad. Test Plan: Reload notification search page. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18047 --- .../query/PhabricatorNotificationSearchEngine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/notification/query/PhabricatorNotificationSearchEngine.php b/src/applications/notification/query/PhabricatorNotificationSearchEngine.php index 11b935eb50..0ee7327bfc 100644 --- a/src/applications/notification/query/PhabricatorNotificationSearchEngine.php +++ b/src/applications/notification/query/PhabricatorNotificationSearchEngine.php @@ -85,12 +85,12 @@ final class PhabricatorNotificationSearchEngine $viewer = $this->requireViewer(); $image = id(new PHUIIconView()) - ->setIcon('fa-eye-slash'); + ->setIcon('fa-bell-o'); $button = id(new PHUIButtonView()) ->setTag('a') ->addSigil('workflow') - ->setColor(PHUIButtonView::SIMPLE) + ->setColor(PHUIButtonView::GREY) ->setIcon($image) ->setText(pht('Mark All Read')); From c5bb69fd7d7902a9b24a7f0c4a8260ba42b436e5 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 27 May 2017 17:55:44 -0700 Subject: [PATCH 205/543] Use a list view for DiffusionHistory Summary: This moves Diffusion History to use an easier to parse list view for commits and their (diff, audit, build) status. I left TableView around, which is used on a repositories home, and we can maybe add a "graph view" history back as another controller. Not sure what the real use is for that kind of feature though. I don't have Harbormaster set up locally so I could use another install to give this a run. I also expect to maybe not live with this UI as final, I like the UX, but the icons for indicating status don't really feel great to me, just OK. Test Plan: pull various repositories, check various history displays. {F4980356} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18039 --- resources/celerity/map.php | 14 +- src/__phutil_library_map__.php | 6 +- .../controller/DiffusionHistoryController.php | 98 ++------ .../view/DiffusionCommitListView.php | 25 +- .../view/DiffusionHistoryListView.php | 225 ++++++++++++++++++ .../view/DiffusionHistoryTableView.php | 93 +------- .../diffusion/view/DiffusionHistoryView.php | 101 ++++++++ .../diffusion/view/DiffusionView.php | 30 +-- .../diffusion/diffusion-history.css | 15 +- .../phui/object-item/phui-oi-list-view.css | 4 + webroot/rsrc/css/phui/phui-icon.css | 2 +- 11 files changed, 425 insertions(+), 188 deletions(-) create mode 100644 src/applications/diffusion/view/DiffusionHistoryListView.php create mode 100644 src/applications/diffusion/view/DiffusionHistoryView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 241f9acf08..677e781ccd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '19f6f61f', + 'core.pkg.css' => '525ecd1c', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '0c596546', + 'rsrc/css/application/diffusion/diffusion-history.css' => '4faf40cd', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -132,7 +132,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'ed19241b', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '43752968', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -158,7 +158,7 @@ return array( 'rsrc/css/phui/phui-header-view.css' => 'a3d1aecd', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', - 'rsrc/css/phui/phui-icon.css' => '4c6d624c', + 'rsrc/css/phui/phui-icon.css' => '4c46b6ba', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6e217679', @@ -575,7 +575,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '0c596546', + 'diffusion-history-css' => '4faf40cd', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', @@ -862,7 +862,7 @@ return array( 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', - 'phui-icon-view-css' => '4c6d624c', + 'phui-icon-view-css' => '4c46b6ba', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', @@ -875,7 +875,7 @@ return array( 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => 'ed19241b', + 'phui-oi-list-view-css' => '43752968', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 74ebbfe742..aa11ace40c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -728,8 +728,10 @@ phutil_register_library_map(array( 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', + 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', + 'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php', 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', @@ -5705,8 +5707,10 @@ phutil_register_library_map(array( ), 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionHistoryController' => 'DiffusionController', + 'DiffusionHistoryListView' => 'DiffusionHistoryView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionHistoryTableView' => 'DiffusionView', + 'DiffusionHistoryTableView' => 'DiffusionHistoryView', + 'DiffusionHistoryView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 1a29a4263a..8ee6c848a7 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -26,11 +26,6 @@ final class DiffusionHistoryController extends DiffusionController { 'limit' => $pager->getPageSize() + 1, ); - if (!$request->getBool('copies')) { - $params['needDirectChanges'] = true; - $params['needChildChanges'] = true; - } - $history_results = $this->callConduitWithDiffusionRequest( 'diffusion.historyquery', $params); @@ -39,27 +34,12 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); - $show_graph = !strlen($drequest->getPath()); - $history_table = id(new DiffusionHistoryTableView()) - ->setUser($request->getUser()) + $history_list = id(new DiffusionHistoryListView()) + ->setViewer($viewer) ->setDiffusionRequest($drequest) ->setHistory($history); - $history_table->loadRevisions(); - - if ($show_graph) { - $history_table->setParents($history_results['parents']); - $history_table->setIsHead(!$pager->getOffset()); - $history_table->setIsTail(!$pager->getHasMorePages()); - } - - $history_header = $this->buildHistoryHeader($drequest); - $history_panel = id(new PHUIObjectBoxView()) - ->setHeader($history_header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($history_table) - ->setPager($pager); - + $history_list->loadRevisions(); $header = $this->buildHeader($drequest); $crumbs = $this->buildCrumbs( @@ -70,44 +50,32 @@ final class DiffusionHistoryController extends DiffusionController { )); $crumbs->setBorder(true); + $title = array( + pht('History'), + $repository->getDisplayName(), + ); + + $pager = id(new PHUIBoxView()) + ->addClass('mlb') + ->appendChild($pager); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( - $history_panel, + $history_list, + $pager, )); return $this->newPage() - ->setTitle( - array( - pht('History'), - $repository->getDisplayName(), - )) + ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } private function buildHeader(DiffusionRequest $drequest) { $viewer = $this->getViewer(); $tag = $this->renderCommitHashTag($drequest); - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()) - ->addTag($tag) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) - ->setHeaderIcon('fa-clock-o'); - - return $header; - - } - - private function buildHistoryHeader(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - $browse_uri = $drequest->generateURI( array( 'action' => 'browse', @@ -117,36 +85,18 @@ final class DiffusionHistoryController extends DiffusionController { ->setTag('a') ->setText(pht('Browse')) ->setHref($browse_uri) - ->setIcon('fa-files-o'); - - // TODO: Sometimes we do have a change view, we need to look at the most - // recent history entry to figure it out. - - $request = $this->getRequest(); - if ($request->getBool('copies')) { - $branch_name = pht('Hide Copies/Branches'); - $branch_uri = $request->getRequestURI() - ->alter('offset', null) - ->alter('copies', null); - } else { - $branch_name = pht('Show Copies/Branches'); - $branch_uri = $request->getRequestURI() - ->alter('offset', null) - ->alter('copies', true); - } - - $branch_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText($branch_name) - ->setIcon('fa-code-fork') - ->setHref($branch_uri); + ->setIcon('fa-code'); $header = id(new PHUIHeaderView()) - ->setHeader(pht('History')) - ->addActionLink($browse_button) - ->addActionLink($branch_button); + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag) + ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeaderIcon('fa-clock-o') + ->addActionLink($browse_button); return $header; + } } diff --git a/src/applications/diffusion/view/DiffusionCommitListView.php b/src/applications/diffusion/view/DiffusionCommitListView.php index b3f2a9fb0c..f1d1ebc8e4 100644 --- a/src/applications/diffusion/view/DiffusionCommitListView.php +++ b/src/applications/diffusion/view/DiffusionCommitListView.php @@ -25,6 +25,28 @@ final class DiffusionCommitListView extends AphrontView { return $this->commits; } + public function setHandles(array $handles) { + assert_instances_of($handles, 'PhabricatorObjectHandle'); + $this->handles = $handles; + return $this; + } + + private function getRequiredHandlePHIDs() { + $phids = array(); + foreach ($this->history as $item) { + $data = $item->getCommitData(); + if ($data) { + if ($data->getCommitDetail('authorPHID')) { + $phids[$data->getCommitDetail('authorPHID')] = true; + } + if ($data->getCommitDetail('committerPHID')) { + $phids[$data->getCommitDetail('committerPHID')] = true; + } + } + } + return array_keys($phids); + } + private function getCommitDescription($phid) { if ($this->commits === null) { return pht('(Unknown Commit)'); @@ -114,12 +136,10 @@ final class DiffusionCommitListView extends AphrontView { if ($author_phid) { $author_name = $handles[$author_phid]->renderLink(); $author_image_uri = $handles[$author_phid]->getImageURI(); - $author_image_href = $handles[$author_phid]->getURI(); } else { $author_name = $commit->getCommitData()->getAuthorName(); $author_image_uri = celerity_get_resource_uri('/rsrc/image/people/user0.png'); - $author_image_href = null; } $commit_tag = id(new PHUITagView()) @@ -134,7 +154,6 @@ final class DiffusionCommitListView extends AphrontView { ->setDisabled($commit->isUnreachable()) ->setDescription($message) ->setImageURI($author_image_uri) - ->setImageHref($author_image_href) ->addByline(pht('Author: %s', $author_name)) ->addIcon('none', $committed) ->addAttribute($commit_tag); diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php new file mode 100644 index 0000000000..f72bffcbe6 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -0,0 +1,225 @@ +getDiffusionRequest(); + $viewer = $this->getUser(); + $repository = $drequest->getRepository(); + + require_celerity_resource('diffusion-history-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $buildables = $this->loadBuildables( + mpull($this->getHistory(), 'getCommit')); + + $show_revisions = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + + $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); + + $show_builds = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorHarbormasterApplication', + $this->getUser()); + + $rows = array(); + $ii = 0; + $cur_date = 0; + $list = null; + $header = null; + $view = array(); + foreach ($this->getHistory() as $history) { + $epoch = $history->getEpoch(); + $new_date = date('Ymd', $history->getEpoch()); + if ($cur_date != $new_date) { + if ($list) { + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + $date = ucfirst( + phabricator_relative_date($history->getEpoch(), $viewer)); + $header = id(new PHUIHeaderView()) + ->setHeader($date); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list'); + } + + if ($epoch) { + $committed = $viewer->formatShortDateTime($epoch); + } else { + $committed = null; + } + + $data = $history->getCommitData(); + $author_phid = $committer = $committer_phid = null; + if ($data) { + $author_phid = $data->getCommitDetail('authorPHID'); + $committer_phid = $data->getCommitDetail('committerPHID'); + $committer = $data->getCommitDetail('committer'); + } + + if ($author_phid && isset($handles[$author_phid])) { + $author_name = $handles[$author_phid]->renderLink(); + $author_image = $handles[$author_phid]->getImageURI(); + } else { + $author_name = self::renderName($history->getAuthorName()); + $author_image = + celerity_get_resource_uri('/rsrc/image/people/user0.png'); + } + + $different_committer = false; + if ($committer_phid) { + $different_committer = ($committer_phid != $author_phid); + } else if ($committer != '') { + $different_committer = ($committer != $history->getAuthorName()); + } + if ($different_committer) { + if ($committer_phid && isset($handles[$committer_phid])) { + $committer = $handles[$committer_phid]->renderLink(); + } else { + $committer = self::renderName($committer); + } + $author_name = hsprintf('%s / %s', $author_name, $committer); + } + + // We can show details once the message and change have been imported. + $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE | + PhabricatorRepositoryCommit::IMPORTED_CHANGE; + + $commit = $history->getCommit(); + if ($commit && $commit->isPartiallyImported($partial_import) && $data) { + $commit_desc = $history->getSummary(); + } else { + $commit_desc = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); + } + + $build_view = null; + if ($show_builds) { + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable !== null) { + $build_view = $this->renderBuildable($buildable); + } + } + + $browse = $this->linkBrowse( + $history->getPath(), + array( + 'commit' => $history->getCommitIdentifier(), + 'branch' => $drequest->getBranch(), + 'type' => $history->getFileType(), + )); + + $differential_view = null; + if ($show_revisions && $commit) { + $d_id = idx($this->getRevisions(), $commit->getPHID()); + if ($d_id) { + $differential_view = id(new PHUIIconCircleView()) + ->setIcon('fa-gear') + ->setColor('green') + ->setState(PHUIIconCircleView::STATE_SUCCESS) + ->addSigil('has-tooltip') + ->setSize(PHUIIconCircleView::SMALL) + ->setHref('/D'.$d_id) + ->addClass('mmr') + ->setMetadata( + array( + 'tip' => 'Revision D'.$d_id, + )); + } + } + + $status = $commit->getAuditStatus(); + $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon($status); + $color = PhabricatorAuditCommitStatusConstants::getStatusColor($status); + $name = PhabricatorAuditCommitStatusConstants::getStatusName($status); + $audit_view = id(new PHUIIconCircleView()) + ->setIcon('fa-code') + ->setColor($color) + ->setState($icon) + ->addSigil('has-tooltip') + ->setSize(PHUIIconCircleView::SMALL) + ->addClass('mmr') + ->setMetadata( + array( + 'tip' => $name, + )); + + if ($show_builds) { + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable !== null) { + $status = $buildable->getBuildableStatus(); + $icon = HarbormasterBuildable::getBuildableStatusIcon($status); + $color = HarbormasterBuildable::getBuildableStatusColor($status); + $name = HarbormasterBuildable::getBuildableStatusName($status); + $build_view = id(new PHUIIconCircleView()) + ->setIcon('fa-recycle') + ->setColor($color) + ->setState($icon) + ->addSigil('has-tooltip') + ->setSize(PHUIIconCircleView::SMALL) + ->setHref('/'.$buildable->getMonogram()) + ->addClass('mmr') + ->setMetadata( + array( + 'tip' => $name, + )); + } + } + + $message = null; + $commit_link = $repository->getCommitURI( + $history->getCommitIdentifier()); + + $commit_name = $repository->formatCommitName( + $history->getCommitIdentifier(), $local = true); + + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $author_name = phutil_tag( + 'strong', + array( + 'class' => 'diffusion-history-author-name', + ), + $author_name); + $authored = pht('%s on %s.', $author_name, $committed); + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setSlimShady(true); + + $browse_button = id(new PHUIButtonView()) + ->setText(pht('Browse')) + ->setIcon('fa-code') + ->setTag('a') + ->setColor(PHUIButtonView::SIMPLE) + ->appendChild($audit_view); + + $item = id(new PHUIObjectItemView()) + ->setHeader($commit_desc) + ->setHref($commit_link) + ->setDisabled($commit->isUnreachable()) + ->setDescription($message) + ->setImageURI($author_image) + ->addAttribute($commit_tag) + ->addAttribute($authored) + ->setSideColumn(array( + $differential_view, + $audit_view, + $build_view, + $browse_button, + )); + + $list->addItem($item); + $cur_date = $new_date; + } + + + return $view; + } + +} diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index bb3aee37da..3885bbf47c 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -1,87 +1,14 @@ history = $history; - return $this; - } - - public function loadRevisions() { - $commit_phids = array(); - foreach ($this->history as $item) { - if ($item->getCommit()) { - $commit_phids[] = $item->getCommit()->getPHID(); - } - } - - // TODO: Get rid of this. - $this->revisions = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs($commit_phids); - return $this; - } - - public function setHandles(array $handles) { - assert_instances_of($handles, 'PhabricatorObjectHandle'); - $this->handles = $handles; - return $this; - } - - private function getRequiredHandlePHIDs() { - $phids = array(); - foreach ($this->history as $item) { - $data = $item->getCommitData(); - if ($data) { - if ($data->getCommitDetail('authorPHID')) { - $phids[$data->getCommitDetail('authorPHID')] = true; - } - if ($data->getCommitDetail('committerPHID')) { - $phids[$data->getCommitDetail('committerPHID')] = true; - } - } - } - return array_keys($phids); - } - - public function setParents(array $parents) { - $this->parents = $parents; - return $this; - } - - public function setIsHead($is_head) { - $this->isHead = $is_head; - return $this; - } - - public function setIsTail($is_tail) { - $this->isTail = $is_tail; - return $this; - } - - public function setFilterParents($filter_parents) { - $this->filterParents = $filter_parents; - return $this; - } - - public function getFilterParents() { - return $this->filterParents; - } +final class DiffusionHistoryTableView extends DiffusionHistoryView { public function render() { $drequest = $this->getDiffusionRequest(); $viewer = $this->getUser(); - $buildables = $this->loadBuildables(mpull($this->history, 'getCommit')); + $buildables = $this->loadBuildables( + mpull($this->getHistory(), 'getCommit')); $has_any_build = false; $show_revisions = PhabricatorApplication::isClassInstalledForViewer( @@ -91,14 +18,14 @@ final class DiffusionHistoryTableView extends DiffusionView { $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); $graph = null; - if ($this->parents) { - $parents = $this->parents; + if ($this->getParents()) { + $parents = $this->getParents(); // If we're filtering parents, remove relationships which point to // commits that are not part of the visible graph. Otherwise, we get // a big tree of nonsense when viewing release branches like "stable" // versus "master". - if ($this->filterParents) { + if ($this->getFilterParents()) { foreach ($parents as $key => $nodes) { foreach ($nodes as $nkey => $node) { if (empty($parents[$node])) { @@ -109,8 +36,8 @@ final class DiffusionHistoryTableView extends DiffusionView { } $graph = id(new PHUIDiffGraphView()) - ->setIsHead($this->isHead) - ->setIsTail($this->isTail) + ->setIsHead($this->getIsHead()) + ->setIsTail($this->getIsTail()) ->renderGraph($parents); } @@ -120,7 +47,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $rows = array(); $ii = 0; - foreach ($this->history as $history) { + foreach ($this->getHistory() as $history) { $epoch = $history->getEpoch(); if ($epoch) { @@ -209,7 +136,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $build, $audit_view, ($commit ? - self::linkRevision(idx($this->revisions, $commit->getPHID())) : + self::linkRevision(idx($this->getRevisions(), $commit->getPHID())) : null), $author, $summary, diff --git a/src/applications/diffusion/view/DiffusionHistoryView.php b/src/applications/diffusion/view/DiffusionHistoryView.php new file mode 100644 index 0000000000..56a0f3b75f --- /dev/null +++ b/src/applications/diffusion/view/DiffusionHistoryView.php @@ -0,0 +1,101 @@ +history = $history; + return $this; + } + + public function getHistory() { + return $this->history; + } + + public function loadRevisions() { + $commit_phids = array(); + foreach ($this->history as $item) { + if ($item->getCommit()) { + $commit_phids[] = $item->getCommit()->getPHID(); + } + } + + // TODO: Get rid of this. + $this->revisions = id(new DifferentialRevision()) + ->loadIDsByCommitPHIDs($commit_phids); + return $this; + } + + public function getRevisions() { + return $this->revisions; + } + + public function setHandles(array $handles) { + assert_instances_of($handles, 'PhabricatorObjectHandle'); + $this->handles = $handles; + return $this; + } + + public function getRequiredHandlePHIDs() { + $phids = array(); + foreach ($this->history as $item) { + $data = $item->getCommitData(); + if ($data) { + if ($data->getCommitDetail('authorPHID')) { + $phids[$data->getCommitDetail('authorPHID')] = true; + } + if ($data->getCommitDetail('committerPHID')) { + $phids[$data->getCommitDetail('committerPHID')] = true; + } + } + } + return array_keys($phids); + } + + public function setParents(array $parents) { + $this->parents = $parents; + return $this; + } + + public function getParents() { + return $this->parents; + } + + public function setIsHead($is_head) { + $this->isHead = $is_head; + return $this; + } + + public function getIsHead() { + return $this->isHead; + } + + public function setIsTail($is_tail) { + $this->isTail = $is_tail; + return $this; + } + + public function getIsTail() { + return $this->isTail; + } + + public function setFilterParents($filter_parents) { + $this->filterParents = $filter_parents; + return $this; + } + + public function getFilterParents() { + return $this->filterParents; + } + + public function render() {} + +} diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 5aa1fad33c..b615ce855c 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -168,7 +168,7 @@ abstract class DiffusionView extends AphrontView { 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $email->getAddress(), - 'align' => 'E', + 'align' => 'S', 'size' => 'auto', ), ), @@ -177,30 +177,24 @@ abstract class DiffusionView extends AphrontView { return hsprintf('%s', $name); } - final protected function renderBuildable(HarbormasterBuildable $buildable) { + final protected function renderBuildable( + HarbormasterBuildable $buildable) { $status = $buildable->getBuildableStatus(); + Javelin::initBehavior('phabricator-tooltips'); $icon = HarbormasterBuildable::getBuildableStatusIcon($status); $color = HarbormasterBuildable::getBuildableStatusColor($status); $name = HarbormasterBuildable::getBuildableStatusName($status); - $icon_view = id(new PHUIIconView()) - ->setIcon($icon.' '.$color); + return id(new PHUIIconView()) + ->setIcon($icon.' '.$color) + ->addSigil('has-tooltip') + ->setHref('/'.$buildable->getMonogram()) + ->setMetadata( + array( + 'tip' => $name, + )); - $tooltip_view = javelin_tag( - 'span', - array( - 'sigil' => 'has-tooltip', - 'meta' => array('tip' => $name), - ), - $icon_view); - - Javelin::initBehavior('phabricator-tooltips'); - - return phutil_tag( - 'a', - array('href' => '/'.$buildable->getMonogram()), - $tooltip_view); } final protected function loadBuildables(array $commits) { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index b340cd913d..43e05c8958 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -3,7 +3,7 @@ */ .diffusion-history-list .phui-oi-link { - color: {$darkbluetext}; + color: #000; font-size: {$biggerfontsize}; } @@ -11,6 +11,10 @@ border-color: transparent; } +.diffusion-history-list .phui-oi-attribute .phui-tag-indigo a { + color: {$indigo}; +} + .diffusion-history-message { background-color: {$bluebackground}; padding: 16px; @@ -18,3 +22,12 @@ border-radius: 5px; color: {$darkbluetext}; } + +.diffusion-history-list .phui-oi-attribute { + font-size: {$smallerfontsize}; + letter-spacing: 0.01em; +} + +.diffusion-history-author-name a { + color: {$darkbluetext}; +} diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 788b0e73ec..f6d1589a9a 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -182,6 +182,10 @@ ul.phui-oi-list-view { vertical-align: top; } +.phui-oi-col2.phui-oi-side-column { + width: 200px; +} + .device-phone .phui-oi-col1, .device-phone .phui-oi-col2 { display: block; diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css index 627b7632fe..5fd7fd97bc 100644 --- a/webroot/rsrc/css/phui/phui-icon.css +++ b/webroot/rsrc/css/phui/phui-icon.css @@ -144,7 +144,7 @@ a.phui-icon-circle.hover-red:hover .phui-icon-view { color: {$red}; } -a.phui-icon-circle .phui-icon-view.phui-icon-circle-state-icon { +.phui-icon-circle .phui-icon-view.phui-icon-circle-state-icon { position: absolute; width: 14px; height: 14px; From d161a07781e01f194e25216137568c32304df0da Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 17:41:30 -0700 Subject: [PATCH 206/543] Improve Differential behavior when scrolling with anchors Summary: Fixes T12779. Currently, we scroll down if the midline of the changeset is above the midline of the viewport. This rule can cause us to scroll improperly when loading changesets //after// jumping to their anchors, since the changeset we want to look at will likely have a midpoint above the document midline. That is, we follow an anchor to `X.c`, then it loads, then we scroll past it. Instead, scroll only if the changeset is (almost) entirely above the viewport. Test Plan: Followed an anchor to `PHUIFeedStoryExample`: {F4984154} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12779 Differential Revision: https://secure.phabricator.com/D18052 --- .../rsrc/js/application/diff/DiffChangeset.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index cd7f888a40..2b275b0e65 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -501,12 +501,17 @@ JX.install('DiffChangeset', { // diff with a large number of changes don't constantly have the text // area scrolled off the bottom of the screen until the entire diff loads. // - // There are two three major cases here: + // There are several major cases here: // // - If we're near the top of the document, never scroll. - // - If we're near the bottom of the document, always scroll. - // - Otherwise, scroll if the changes were above the midline of the - // viewport. + // - If we're near the bottom of the document, always scroll, unless + // we have an anchor. + // - Otherwise, scroll if the changes were above (or, at least, + // almost entirely above) the viewport. + // + // We don't scroll if the changes were just near the top of the viewport + // because this makes us scroll incorrectly when an anchored change is + // visible. See T12779. var target = this._node; @@ -529,17 +534,18 @@ JX.install('DiffChangeset', { var target_pos = JX.Vector.getPos(target); var target_dim = JX.Vector.getDim(target); - var target_mid = (target_pos.y + (target_dim.y / 2)); + var target_bot = (target_pos.y + target_dim.y); - var view_mid = (old_pos.y + (old_view.y / 2)); - var above_mid = (target_mid < view_mid); + // Detect if the changeset is entirely (or, at least, almost entirely) + // above us. + var above_screen = (target_bot < old_pos.y + 128); var frame = this._getContentFrame(); JX.DOM.setContent(frame, JX.$H(response.changeset)); if (this._stabilize) { if (!near_top) { - if (near_bot || above_mid) { + if (near_bot || above_screen) { // Figure out how much taller the document got. var delta = (JX.Vector.getDocument().y - old_dim.y); JX.DOM.scrollToPosition(old_pos.x, old_pos.y + delta); From d20221dc7d823819ca10f1a72deadfca543ed228 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 13:23:54 -0700 Subject: [PATCH 207/543] Remove Differential "objectives" UI Summary: Ref T12733. Completely removes the objectives UI. Test Plan: - Grepped for `objective`, etc. - Browsed revisions, no JS errors / broken stuff. - (If I missed anything, it's likely to turn up in followup changes.) Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18043 --- resources/celerity/map.php | 109 +++++-------- resources/celerity/packages.php | 3 - .../view/DifferentialChangesetDetailView.php | 1 - .../view/DifferentialChangesetListView.php | 4 - .../differential/changeset-view.css | 34 ---- webroot/rsrc/css/core/z-index.css | 4 - .../rsrc/js/application/diff/DiffChangeset.js | 27 ---- .../js/application/diff/DiffChangesetList.js | 34 ---- .../rsrc/js/application/diff/DiffInline.js | 90 ----------- .../js/application/diff/ScrollObjective.js | 141 ---------------- .../application/diff/ScrollObjectiveList.js | 150 ------------------ .../differential/behavior-populate.js | 3 +- 12 files changed, 44 insertions(+), 556 deletions(-) delete mode 100644 webroot/rsrc/js/application/diff/ScrollObjective.js delete mode 100644 webroot/rsrc/js/application/diff/ScrollObjectiveList.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 677e781ccd..87bc02214d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,11 +9,11 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '525ecd1c', + 'core.pkg.css' => 'bb7f0446', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => '1d120743', + 'differential.pkg.css' => '9ebe4f44', + 'differential.pkg.js' => '78b8497f', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'acfd58f6', + 'rsrc/css/application/differential/changeset-view.css' => '2971e2a2', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -117,7 +117,7 @@ return array( 'rsrc/css/core/core.css' => '9f4cb463', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', - 'rsrc/css/core/z-index.css' => '998f3ce1', + 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', @@ -391,15 +391,13 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => '7a184082', - 'rsrc/js/application/diff/DiffInline.js' => '19582231', - 'rsrc/js/application/diff/ScrollObjective.js' => '7e8877e7', - 'rsrc/js/application/diff/ScrollObjectiveList.js' => '6120e99a', + 'rsrc/js/application/diff/DiffChangeset.js' => '3359ad02', + 'rsrc/js/application/diff/DiffChangesetList.js' => '675f1ca3', + 'rsrc/js/application/diff/DiffInline.js' => '45d37835', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '1de8bf63', + 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -568,7 +566,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'acfd58f6', + 'differential-changeset-view-css' => '2971e2a2', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -622,7 +620,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '1de8bf63', + 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -779,9 +777,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => '7a184082', - 'phabricator-diff-inline' => '19582231', + 'phabricator-diff-changeset' => '3359ad02', + 'phabricator-diff-changeset-list' => '675f1ca3', + 'phabricator-diff-inline' => '45d37835', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -801,8 +799,6 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '7e8877e7', - 'phabricator-scroll-objective-list' => '6120e99a', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -822,7 +818,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '998f3ce1', + 'phabricator-zindex-css' => '9d8f7c4b', 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -1002,9 +998,6 @@ return array( '185bbd53' => array( 'javelin-install', ), - 19582231 => array( - 'javelin-dom', - ), '19f9369b' => array( 'phui-oi-list-view-css', ), @@ -1026,14 +1019,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '1de8bf63' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1087,6 +1072,9 @@ return array( 'javelin-install', 'javelin-util', ), + '2971e2a2' => array( + 'phui-inline-comment-view-css', + ), '2ae077e1' => array( 'javelin-behavior', 'javelin-dom', @@ -1122,6 +1110,17 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '3359ad02' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '358b8c04' => array( 'javelin-install', 'javelin-util', @@ -1204,6 +1203,9 @@ return array( 'javelin-behavior', 'javelin-dom', ), + '45d37835' => array( + 'javelin-dom', + ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1371,6 +1373,14 @@ return array( 'phabricator-phtize', 'javelin-dom', ), + '5e41c819' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1388,15 +1398,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '6120e99a' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-scrollbar', - 'phabricator-scroll-objective', - ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', @@ -1411,6 +1412,9 @@ return array( 'javelin-workflow', 'javelin-dom', ), + '675f1ca3' => array( + 'javelin-install', + ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', @@ -1496,10 +1500,6 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), - '7a184082' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1513,13 +1513,6 @@ return array( '7e41274a' => array( 'javelin-install', ), - '7e8877e7' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', @@ -1787,9 +1780,6 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), - 'acfd58f6' => array( - 'phui-inline-comment-view-css', - ), 'ae95d984' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2002,17 +1992,6 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), - 'cf4e2140' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'd0a99ab4' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2435,8 +2414,6 @@ return array( 'javelin-behavior-load-blame', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'phabricator-scroll-objective', - 'phabricator-scroll-objective-list', 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index afa6c456a6..e756e696cb 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -203,9 +203,6 @@ return array( 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'phabricator-scroll-objective', - 'phabricator-scroll-objective-list', - 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index e80dc20ec7..a1a3de26bb 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -204,7 +204,6 @@ final class DifferentialChangesetDetailView extends AphrontView { 'loaded' => $this->getLoaded(), 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), 'displayPath' => hsprintf('%s', $display_parts), - 'objectiveName' => basename($display_filename), 'icon' => $display_icon, ), 'class' => $class, diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 880fcb872d..b8090b88a2 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -203,15 +203,11 @@ final class DifferentialChangesetListView extends AphrontView { $this->requireResource('aphront-tooltip-css'); - $show_objectives = - PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); - $this->initBehavior( 'differential-populate', array( 'changesetViewIDs' => $ids, 'inlineURI' => $this->inlineURI, - 'showObjectives' => $show_objectives, 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show All Context' => pht('Show All Context'), diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 6a29e1269b..39df37003c 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -408,37 +408,3 @@ tr.differential-inline-loading { .diff-banner-path { color: {$greytext}; } - -.scroll-objective-list { - position: fixed; - right: 0; - width: 24px; - top: 48px; - bottom: 48px; - background: rgba(255, 255, 255, 0.50); - border-style: solid; - border-color: rgba(255, 255, 255, 0.95); - border-width: 1px 0 1px 1px; - box-shadow: -1px 0 2px rgba(255, 255, 255, 0.10); - overflow: hidden; -} - -.scroll-objective-list.has-aesthetic-scrollbar { - /* For now, hide this element on systems with aesthetic scrollbars. */ - display: none; -} - -.scroll-objective { - display: block; - position: absolute; - box-sizing: border-box; - cursor: pointer; - text-align: middle; - left: 7px; -} - -.scroll-objective .phui-icon-view { - text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.25); - display: block; - height: 14px; -} diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 04b013386d..75cd394988 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -97,10 +97,6 @@ div.phui-calendar-day-event { z-index: 6; } -.scroll-objective-list { - z-index: 6; -} - .conpherence-durable-column { z-index: 7; } diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 2b275b0e65..8e70c5ac47 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -32,7 +32,6 @@ JX.install('DiffChangeset', { this._rightID = data.right; this._displayPath = JX.$H(data.displayPath); - this._objectiveName = data.objectiveName; this._icon = data.icon; this._inlines = []; @@ -62,8 +61,6 @@ JX.install('DiffChangeset', { _displayPath: null, _changesetList: null, - _objective: null, - _objectiveName: null, _icon: null, getLeftChangesetID: function() { @@ -76,23 +73,9 @@ JX.install('DiffChangeset', { setChangesetList: function(list) { this._changesetList = list; - - var objectives = list.getObjectives(); - this._objective = objectives.newObjective() - .setAnchor(this._node); - - this._updateObjective(); - return this; }, - _updateObjective: function() { - this._objective - .setIcon(this.getIcon()) - .setColor(this.getColor()) - .setTooltip(this.getObjectiveName()); - }, - getIcon: function() { if (!this._visible) { return 'fa-file-o'; @@ -109,10 +92,6 @@ JX.install('DiffChangeset', { return 'blue'; }, - getObjectiveName: function() { - return this._objectiveName; - }, - getChangesetList: function() { return this._changesetList; }, @@ -576,7 +555,6 @@ JX.install('DiffChangeset', { JX.Stratcom.invoke('differential-inline-comment-refresh'); - this._objective.show(); this._rebuildAllInlines(); JX.Stratcom.invoke('resize'); @@ -729,11 +707,6 @@ JX.install('DiffChangeset', { JX.DOM.appendContent(diff.parentNode, undo); } - this._updateObjective(); - for (var ii = 0; ii < this._inlines.length; ii++) { - this._inlines[ii].updateObjective(); - } - JX.Stratcom.invoke('resize'); }, diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index c344c96de8..0b9fa612a0 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1,7 +1,6 @@ /** * @provides phabricator-diff-changeset-list * @requires javelin-install - * phabricator-scroll-objective-list * @javelin */ @@ -9,7 +8,6 @@ JX.install('DiffChangesetList', { construct: function() { this._changesets = []; - this._objectives = new JX.ScrollObjectiveList(); var onload = JX.bind(this, this._ifawake, this._onload); JX.Stratcom.listen('click', 'differential-load', onload); @@ -102,7 +100,6 @@ JX.install('DiffChangesetList', { _initialized: false, _asleep: true, _changesets: null, - _objectives: null, _cursorItem: null, @@ -120,7 +117,6 @@ JX.install('DiffChangesetList', { _rangeTarget: null, _bannerNode: null, - _showObjectives: false, sleep: function() { this._asleep = true; @@ -128,8 +124,6 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); this.resetHover(); - - this._objectives.hide(); }, wake: function() { @@ -138,10 +132,6 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); - if (this._showObjectives) { - this._objectives.show(); - } - if (this._initialized) { return; } @@ -198,19 +188,10 @@ JX.install('DiffChangesetList', { this._installKey('q', label, this._onkeyhide); }, - setShowObjectives: function(show) { - this._showObjectives = show; - return this; - }, - isAsleep: function() { return this._asleep; }, - getObjectives: function() { - return this._objectives; - }, - newChangesetForNode: function(node) { var changeset = JX.DiffChangeset.getForNode(node); @@ -538,24 +519,9 @@ JX.install('DiffChangesetList', { }, _setSelectionState: function(item, manager) { - // If we had an inline selected before, we need to update it after - // changing our selection to clear the selected state. Then, update the - // new one to add the selected state. - var old_inline = this.getSelectedInline(); - this._cursorItem = item; this._redrawSelection(manager, true); - var new_inline = this.getSelectedInline(); - - if (old_inline) { - old_inline.updateObjective(); - } - - if (new_inline) { - new_inline.updateObjective(); - } - return this; }, diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index bfc2a3e258..b0eb9ca2cb 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -29,7 +29,6 @@ JX.install('DiffInline', { _isLoading: false, _changeset: null, - _objective: null, _isDraft: null, _isFixed: null, @@ -38,7 +37,6 @@ JX.install('DiffInline', { bindToRow: function(row) { this._row = row; - this._objective.setAnchor(this._row); var row_data = JX.Stratcom.getData(row); row_data.inline = this; @@ -80,8 +78,6 @@ JX.install('DiffInline', { this.setInvisible(false); - this.updateObjective(); - return this; }, @@ -171,14 +167,6 @@ JX.install('DiffInline', { setChangeset: function(changeset) { this._changeset = changeset; - - var objectives = changeset.getChangesetList().getObjectives(); - - // Create this inline's objective, but don't show it yet. - this._objective = objectives.newObjective() - .setCallback(JX.bind(this, this._onobjective)) - .hide(); - return this; }, @@ -188,84 +176,9 @@ JX.install('DiffInline', { setEditing: function(editing) { this._isEditing = editing; - this.updateObjective(); return this; }, - _onobjective: function() { - this.getChangeset().getChangesetList().selectInline(this); - }, - - updateObjective: function() { - var objective = this._objective; - - if (this.isHidden() || this._isDeleted) { - objective.hide(); - return; - } - - // If this is a new comment which we aren't editing, don't show anything: - // the use started a comment or reply, then cancelled it. - if (this._isNew && !this._isEditing) { - objective.hide(); - return; - } - - var changeset = this.getChangeset(); - if (!changeset.isVisible()) { - objective.hide(); - return; - } - - var pht = changeset.getChangesetList().getTranslations(); - - var icon = 'fa-comment'; - var color = 'bluegrey'; - var tooltip = this._snippet; - var anchor = this._row; - var should_stack = false; - - if (this._isEditing) { - icon = 'fa-star'; - color = 'pink'; - tooltip = pht('Editing Comment'); - - // If we're editing, anchor to the row with the editor instead of the - // actual comment row (which is invisible and can have a misleading - // position). - anchor = this._row.nextSibling; - } else if (this._isDraft) { - // This inline is an unsubmitted draft. - icon = 'fa-pencil'; - color = 'indigo'; - } else if (this._isFixed) { - // This inline has been marked done. - icon = 'fa-check'; - color = 'grey'; - } else if (this._isGhost) { - icon = 'fa-comment-o'; - color = 'grey'; - } else if (this._replyToCommentPHID) { - icon = 'fa-reply'; - should_stack = true; - } - - if (changeset.getChangesetList().getSelectedInline() === this) { - // TODO: Maybe add some other kind of effect here, since we're only - // using color to show this? - color = 'yellow'; - } - - - objective - .setAnchor(anchor) - .setIcon(icon) - .setColor(color) - .setTooltip(tooltip) - .setShouldStack(should_stack) - .show(); - }, - canReply: function() { if (!this._hasAction('reply')) { return false; @@ -316,7 +229,6 @@ JX.install('DiffInline', { JX.Stratcom.getData(row).inline = this; this._row = row; - this._objective.setAnchor(this._row); this._id = null; this._phid = null; @@ -759,8 +671,6 @@ JX.install('DiffInline', { this.getChangeset().getChangesetList().redrawPreview(); } - this.updateObjective(); - this.getChangeset().getChangesetList().redrawCursor(); this.getChangeset().getChangesetList().resetHover(); diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js deleted file mode 100644 index 1e596bd816..0000000000 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * @provides phabricator-scroll-objective - * @requires javelin-dom - * javelin-util - * javelin-stratcom - * javelin-install - * javelin-workflow - * @javelin - */ - - -JX.install('ScrollObjective', { - - construct : function() { - var node = this.getNode(); - - var onclick = JX.bind(this, this._onclick); - JX.DOM.listen(node, 'click', null, onclick); - }, - - members: { - _list: null, - - _node: null, - _anchor: null, - - _visible: false, - _callback: false, - _stack: false, - - getNode: function() { - if (!this._node) { - var attributes = { - className: 'scroll-objective' - }; - - var content = this._getIconObject().getNode(); - - var node = JX.$N('div', attributes, content); - - this._node = node; - } - - return this._node; - }, - - setCallback: function(callback) { - this._callback = callback; - return this; - }, - - setObjectiveList: function(list) { - this._list = list; - return this; - }, - - _getIconObject: function() { - if (!this._iconObject) { - this._iconObject = new JX.PHUIXIconView(); - } - return this._iconObject; - }, - - _onclick: function(e) { - (this._callback && this._callback(e)); - - if (e.getPrevented()) { - return; - } - - e.kill(); - - // This is magic to account for the banner, and should probably be made - // less hard-coded. - var buffer = 48; - - JX.DOM.scrollToPosition(null, JX.$V(this.getAnchor()).y - buffer); - }, - - setAnchor: function(node) { - this._anchor = node; - return this; - }, - - getAnchor: function() { - return this._anchor; - }, - - setIcon: function(icon) { - this._getIconObject().setIcon(icon); - return this; - }, - - setColor: function(color) { - this._getIconObject().setColor(color); - return this; - }, - - setTooltip: function(tip) { - var node = this._getIconObject().getNode(); - JX.Stratcom.addSigil(node, 'has-tooltip'); - JX.Stratcom.getData(node).tip = tip; - JX.Stratcom.getData(node).align = 'W'; - JX.Stratcom.getData(node).size = 'auto'; - return this; - }, - - - /** - * Should this objective always stack immediately under the previous - * objective? - * - * This allows related objectives (like "comment, reply, reply") to be - * rendered in a tight sequence. - */ - setShouldStack: function(stack) { - this._stack = stack; - return this; - }, - - shouldStack: function() { - return this._stack; - }, - - show: function() { - this._visible = true; - return this; - }, - - hide: function() { - this._visible = false; - return this; - }, - - isVisible: function() { - return this._visible; - } - - } - -}); diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js deleted file mode 100644 index ce47877d1a..0000000000 --- a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @provides phabricator-scroll-objective-list - * @requires javelin-dom - * javelin-util - * javelin-stratcom - * javelin-install - * javelin-workflow - * javelin-scrollbar - * phabricator-scroll-objective - * @javelin - */ - - -JX.install('ScrollObjectiveList', { - - construct : function() { - this._objectives = []; - - var onresize = JX.bind(this, this._dirty); - JX.Stratcom.listen('resize', null, onresize); - }, - - members: { - _objectives: null, - _visible: false, - _trigger: null, - - newObjective: function() { - var objective = new JX.ScrollObjective() - .setObjectiveList(this); - - this._objectives.push(objective); - this._getNode().appendChild(objective.getNode()); - - this._dirty(); - - return objective; - }, - - show: function() { - this._visible = true; - this._dirty(); - return this; - }, - - hide: function() { - this._visible = false; - this._dirty(); - return this; - }, - - _getNode: function() { - if (!this._node) { - var node = new JX.$N('div', {className: 'scroll-objective-list'}); - this._node = node; - } - return this._node; - }, - - _dirty: function() { - if (this._trigger !== null) { - return; - } - - this._trigger = setTimeout(JX.bind(this, this._redraw), 0); - }, - - _redraw: function() { - this._trigger = null; - - var node = this._getNode(); - - var is_visible = - (this._visible) && - (JX.Device.getDevice() == 'desktop') && - (this._objectives.length); - - if (!is_visible) { - JX.DOM.remove(node); - return; - } - - document.body.appendChild(node); - - // If we're on OSX without a mouse or some other system with zero-width - // trackpad-style scrollbars, adjust the display appropriately. - var aesthetic = (JX.Scrollbar.getScrollbarControlWidth() === 0); - JX.DOM.alterClass(node, 'has-aesthetic-scrollbar', aesthetic); - - var d = JX.Vector.getDocument(); - - var list_dimensions = JX.Vector.getDim(node); - var icon_height = 16; - var list_y = (list_dimensions.y - icon_height); - - var ii; - var offset; - - // First, build a list of all the items we're going to show. - var items = []; - for (ii = 0; ii < this._objectives.length; ii++) { - var objective = this._objectives[ii]; - var objective_node = objective.getNode(); - - var anchor = objective.getAnchor(); - if (!anchor || !objective.isVisible()) { - JX.DOM.remove(objective_node); - continue; - } - - offset = (JX.$V(anchor).y / d.y) * (list_y); - - items.push({ - offset: offset, - node: objective_node, - objective: objective - }); - } - - // Now, sort it from top to bottom. - items.sort(function(u, v) { - return u.offset - v.offset; - }); - - // Lay out the items in the objective list, leaving a minimum amount - // of space between them so they do not overlap. - var min = null; - for (ii = 0; ii < items.length; ii++) { - var item = items[ii]; - - offset = item.offset; - - if (min !== null) { - if (item.objective.shouldStack()) { - offset = min; - } else { - offset = Math.max(offset, min); - } - } - min = offset + 15; - - item.node.style.top = offset + 'px'; - node.appendChild(item.node); - } - - } - - } - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index b1b5bd415c..e5b0d5039d 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -60,8 +60,7 @@ JX.behavior('differential-populate', function(config, statics) { var changeset_list = new JX.DiffChangesetList() .setTranslations(JX.phtize(config.pht)) - .setInlineURI(config.inlineURI) - .setShowObjectives(config.showObjectives); + .setInlineURI(config.inlineURI); // Install and activate the current page. var page_id = JX.Quicksand.getCurrentPageID(); From cc0a6fd3aa45c272a2560a70a4826a4c20070e5f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 13:35:27 -0700 Subject: [PATCH 208/543] Remove the ability to leave multi-line inline comments on touchscreen devices Summary: Ref T12733. This ultimately conflicts with scrolling and took about two days to get reported as a bug/regression. See T12733 for a bunch of additional discussion. See T1026 for original discussion. Test Plan: - Left single-line and multi-line comments on desktop. - Tapped to leave single-line comments on mobile. - Dragged lines on mobile, got a scroll instead of a range comment. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18044 --- resources/celerity/map.php | 12 +++--- .../js/application/diff/DiffChangesetList.js | 39 ++----------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 87bc02214d..b6cf443b35 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '9ebe4f44', - 'differential.pkg.js' => '78b8497f', + 'differential.pkg.js' => '40f4acb3', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '3359ad02', - 'rsrc/js/application/diff/DiffChangesetList.js' => '675f1ca3', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'b42eb5ff', 'rsrc/js/application/diff/DiffInline.js' => '45d37835', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '3359ad02', - 'phabricator-diff-changeset-list' => '675f1ca3', + 'phabricator-diff-changeset-list' => 'b42eb5ff', 'phabricator-diff-inline' => '45d37835', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1412,9 +1412,6 @@ return array( 'javelin-workflow', 'javelin-dom', ), - '675f1ca3' => array( - 'javelin-install', - ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', @@ -1822,6 +1819,9 @@ return array( 'b3e7d692' => array( 'javelin-install', ), + 'b42eb5ff' => array( + 'javelin-install', + ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 0b9fa612a0..60575e2aa2 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -68,7 +68,7 @@ JX.install('DiffChangesetList', { var onrangedown = JX.bind(this, this._ifawake, this._onrangedown); JX.Stratcom.listen( - ['touchstart', 'mousedown'], + 'mousedown', ['differential-changeset', 'tag:th'], onrangedown); @@ -78,15 +78,9 @@ JX.install('DiffChangesetList', { ['differential-changeset', 'tag:th'], onrangemove); - var onrangetouchmove = JX.bind(this, this._ifawake, this._onrangetouchmove); - JX.Stratcom.listen( - 'touchmove', - null, - onrangetouchmove); - var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); JX.Stratcom.listen( - ['touchend', 'mouseup'], + 'mouseup', null, onrangeup); }, @@ -1147,8 +1141,8 @@ JX.install('DiffChangesetList', { }, _onrangedown: function(e) { - // NOTE: We're allowing touch events through, including "touchstart". We - // need to kill the "touchstart" event so the page doesn't scroll. + // NOTE: We're allowing "mousedown" from a touch event through so users + // can leave inlines on a single line. if (e.isRightButton()) { return; } @@ -1238,31 +1232,6 @@ JX.install('DiffChangesetList', { this._setHoverRange(this._rangeOrigin, this._rangeTarget); }, - _onrangetouchmove: function(e) { - if (!this._rangeActive) { - return; - } - - // NOTE: The target of a "touchmove" event is bogus. Use dark magic to - // identify the actual target. Some day, this might move into the core - // libraries. If this doesn't work, just bail. - - var target; - try { - var raw_event = e.getRawEvent(); - var touch = raw_event.touches[0]; - target = document.elementFromPoint(touch.clientX, touch.clientY); - } catch (ex) { - return; - } - - if (!JX.DOM.isType(target, 'th')) { - return; - } - - this._updateRange(target, false); - }, - _onrangeup: function(e) { if (!this._rangeActive) { return; From 83e99fb691aa350d6990d3e467a3af1ba0d47213 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 14:00:02 -0700 Subject: [PATCH 209/543] Add a class to the Differential banner when unsubmitted/unsaved changes are present Summary: Ref T12733. This adds classes for unsubmitted/unsaved changes, and lays some groundwork for additional buttons. Test Plan: - Added, edited, deleted comments. - Saw bar background color update appropriately. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18045 --- resources/celerity/map.php | 60 +++++++++---------- .../differential/changeset-view.css | 5 ++ .../rsrc/js/application/diff/DiffChangeset.js | 5 ++ .../js/application/diff/DiffChangesetList.js | 42 +++++++++++++ .../rsrc/js/application/diff/DiffInline.js | 16 +++++ 5 files changed, 98 insertions(+), 30 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b6cf443b35..d6ad7e41fd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => 'bb7f0446', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '9ebe4f44', - 'differential.pkg.js' => '40f4acb3', + 'differential.pkg.css' => 'a2755617', + 'differential.pkg.js' => '5bf658f0', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '2971e2a2', + 'rsrc/css/application/differential/changeset-view.css' => '983751ee', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -391,9 +391,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '3359ad02', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'b42eb5ff', - 'rsrc/js/application/diff/DiffInline.js' => '45d37835', + 'rsrc/js/application/diff/DiffChangeset.js' => '1f6748ae', + 'rsrc/js/application/diff/DiffChangesetList.js' => '85abc805', + 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -566,7 +566,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '2971e2a2', + 'differential-changeset-view-css' => '983751ee', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -777,9 +777,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '3359ad02', - 'phabricator-diff-changeset-list' => 'b42eb5ff', - 'phabricator-diff-inline' => '45d37835', + 'phabricator-diff-changeset' => '1f6748ae', + 'phabricator-diff-changeset-list' => '85abc805', + 'phabricator-diff-inline' => '1d17130f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1019,6 +1019,9 @@ return array( 'javelin-request', 'javelin-uri', ), + '1d17130f' => array( + 'javelin-dom', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1035,6 +1038,17 @@ return array( 'javelin-uri', 'javelin-routable', ), + '1f6748ae' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '1f6794f6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1072,9 +1086,6 @@ return array( 'javelin-install', 'javelin-util', ), - '2971e2a2' => array( - 'phui-inline-comment-view-css', - ), '2ae077e1' => array( 'javelin-behavior', 'javelin-dom', @@ -1110,17 +1121,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '3359ad02' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '358b8c04' => array( 'javelin-install', 'javelin-util', @@ -1203,9 +1203,6 @@ return array( 'javelin-behavior', 'javelin-dom', ), - '45d37835' => array( - 'javelin-dom', - ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1541,6 +1538,9 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + '85abc805' => array( + 'javelin-install', + ), '85ee8ce6' => array( 'aphront-dialog-view-css', ), @@ -1652,6 +1652,9 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), + '983751ee' => array( + 'phui-inline-comment-view-css', + ), '988040b4' => array( 'javelin-install', 'javelin-dom', @@ -1819,9 +1822,6 @@ return array( 'b3e7d692' => array( 'javelin-install', ), - 'b42eb5ff' => array( - 'javelin-install', - ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 39df37003c..f1be2b5f99 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -408,3 +408,8 @@ tr.differential-inline-loading { .diff-banner-path { color: {$greytext}; } + +.diff-banner-has-unsaved, +.diff-banner-has-unsubmitted { + background: {$sh-yellowbackground}; +} diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 8e70c5ac47..6b5ebf5317 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -679,6 +679,11 @@ JX.install('DiffChangeset', { return null; }, + getInlines: function() { + this._rebuildAllInlines(); + return this._inlines; + }, + _rebuildAllInlines: function() { var rows = JX.DOM.scry(this._node, 'tr'); for (var ii = 0; ii < rows.length; ii++) { diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 60575e2aa2..b79f05296e 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -818,6 +818,11 @@ JX.install('DiffChangesetList', { this._redrawSelection(); this._redrawHover(); + // Force a banner redraw after a resize event. Particularly, this makes + // sure the inline state updates immediately after an inline edit + // operation, even if the changeset itself has not changed. + this._bannerChangeset = null; + this._redrawBanner(); }, @@ -1278,6 +1283,43 @@ JX.install('DiffChangesetList', { return; } + var changesets = this._changesets; + var unsaved = []; + var unsubmitted = []; + var undone = []; + var all = []; + + for (var ii = 0; ii < changesets.length; ii++) { + var inlines = changesets[ii].getInlines(); + for (var jj = 0; jj < inlines.length; jj++) { + var inline = inlines[jj]; + + if (inline.isDeleted()) { + continue; + } + + all.push(inline); + + if (inline.isEditing()) { + unsaved.push(inline); + } else if (inline.isDraft()) { + unsubmitted.push(inline); + } else if (!inline.isDone()) { + undone.push(inline); + } + } + } + + JX.DOM.alterClass( + node, + 'diff-banner-has-unsaved', + !!unsaved.length); + + JX.DOM.alterClass( + node, + 'diff-banner-has-unsubmitted', + !!unsubmitted.length); + var icon = new JX.PHUIXIconView() .setIcon(changeset.getIcon()) .getNode(); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index b0eb9ca2cb..6d897c27ae 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -81,6 +81,22 @@ JX.install('DiffInline', { return this; }, + isDraft: function() { + return this._isDraft; + }, + + isDone: function() { + return this._isFixed; + }, + + isEditing: function() { + return this._isEditing; + }, + + isDeleted: function() { + return this._isDeleted; + }, + bindToRange: function(data) { this._displaySide = data.displaySide; this._number = parseInt(data.number, 10); From 2c0dab055f73e53f2989c9d1ce8ca26ae402a302 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 14:32:54 -0700 Subject: [PATCH 210/543] Make "simple" a "button type", not a "color" Summary: Ref M1476. Currently, `setColor('simple')` is meaningful. Instead, `setButtonType('simple')`. Depends on D18047. Test Plan: Looked at UI examples, Phame, Auth. Notifications mooted by D18047. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18048 --- .../config/PhabricatorAuthListController.php | 2 +- .../guides/view/PhabricatorGuideListView.php | 2 +- .../phame/query/PhameBlogSearchEngine.php | 2 +- .../uiexample/examples/PHUIBoxExample.php | 10 ++++----- .../examples/PHUIButtonBarExample.php | 4 ++-- .../uiexample/examples/PHUIButtonExample.php | 8 +++---- src/view/phui/PHUIButtonView.php | 22 +++++++++++++++++++ src/view/phui/PHUIDocumentViewPro.php | 2 +- 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index ae5808ed78..c5d4f7ad77 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -103,7 +103,7 @@ final class PhabricatorAuthListController $button = id(new PHUIButtonView()) ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setHref($this->getApplicationURI('config/new/')) ->setIcon('fa-plus') ->setDisabled(!$can_manage) diff --git a/src/applications/guides/view/PhabricatorGuideListView.php b/src/applications/guides/view/PhabricatorGuideListView.php index 04ab7c3058..089f325e0c 100644 --- a/src/applications/guides/view/PhabricatorGuideListView.php +++ b/src/applications/guides/view/PhabricatorGuideListView.php @@ -30,7 +30,7 @@ final class PhabricatorGuideListView extends AphrontView { ->setText(pht('Skip')) ->setTag('a') ->setHref($skip_href) - ->setColor(PHUIButtonView::SIMPLE); + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); $list_item->setSideColumn($skip); } $list->addItem($list_item); diff --git a/src/applications/phame/query/PhameBlogSearchEngine.php b/src/applications/phame/query/PhameBlogSearchEngine.php index 3d23a9763d..eaef32af1c 100644 --- a/src/applications/phame/query/PhameBlogSearchEngine.php +++ b/src/applications/phame/query/PhameBlogSearchEngine.php @@ -98,7 +98,7 @@ final class PhameBlogSearchEngine ->setTag('a') ->setText('New Post') ->setHref($this->getApplicationURI('/post/edit/?blog='.$id)) - ->setColor(PHUIButtonView::SIMPLE); + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); $item->setSideColumn($button); } diff --git a/src/applications/uiexample/examples/PHUIBoxExample.php b/src/applications/uiexample/examples/PHUIBoxExample.php index 7a2674da94..4a551de707 100644 --- a/src/applications/uiexample/examples/PHUIBoxExample.php +++ b/src/applications/uiexample/examples/PHUIBoxExample.php @@ -62,11 +62,11 @@ final class PHUIBoxExample extends PhabricatorUIExample { ); $button = id(new PHUIButtonView()) - ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) - ->setIcon('fa-heart') - ->setText(pht('Such Wow')) - ->addClass(PHUI::MARGIN_SMALL_RIGHT); + ->setTag('a') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setIcon('fa-heart') + ->setText(pht('Such Wow')) + ->addClass(PHUI::MARGIN_SMALL_RIGHT); $badge1 = id(new PHUIBadgeMiniView()) ->setIcon('fa-bug') diff --git a/src/applications/uiexample/examples/PHUIButtonBarExample.php b/src/applications/uiexample/examples/PHUIButtonBarExample.php index 1501770dcf..d7992aa830 100644 --- a/src/applications/uiexample/examples/PHUIButtonBarExample.php +++ b/src/applications/uiexample/examples/PHUIButtonBarExample.php @@ -36,7 +36,7 @@ final class PHUIButtonBarExample extends PhabricatorUIExample { foreach ($icons as $text => $icon) { $button = id(new PHUIButtonView()) ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setTitle($text) ->setText($text); @@ -47,7 +47,7 @@ final class PHUIButtonBarExample extends PhabricatorUIExample { foreach ($icons as $text => $icon) { $button = id(new PHUIButtonView()) ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setTitle($text) ->setTooltip($text) ->setIcon($icon); diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index 900125cbc2..8f2e1e57a7 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -129,15 +129,15 @@ final class PHUIButtonExample extends PhabricatorUIExample { 'Subscribe' => 'fa-check-circle bluegrey', 'Edit' => 'fa-pencil bluegrey', ); - $colors = array( - PHUIButtonView::SIMPLE, + $designs = array( + PHUIButtonView::BUTTONTYPE_SIMPLE, ); $column = array(); - foreach ($colors as $color) { + foreach ($designs as $design) { foreach ($icons as $text => $icon) { $column[] = id(new PHUIButtonView()) ->setTag('a') - ->setColor($color) + ->setButtonType($design) ->setIcon($icon) ->setText($text) ->addClass(PHUI::MARGIN_SMALL_RIGHT); diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index ac3f8aabe0..0d0e0f3363 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -10,6 +10,9 @@ final class PHUIButtonView extends AphrontTagView { const SMALL = 'small'; const BIG = 'big'; + const BUTTONTYPE_DEFAULT = 'buttontype.default'; + const BUTTONTYPE_SIMPLE = 'buttontype.simple'; + private $size; private $text; private $subtext; @@ -25,6 +28,7 @@ final class PHUIButtonView extends AphrontTagView { private $tooltip; private $noCSS; private $hasCaret; + private $buttonType = self::BUTTONTYPE_DEFAULT; public function setName($name) { $this->name = $name; @@ -103,6 +107,15 @@ final class PHUIButtonView extends AphrontTagView { return $this->hasCaret; } + public function setButtonType($button_type) { + $this->buttonType = $button_type; + return $this; + } + + public function getButtonType() { + return $this->buttonType; + } + public function setIcon($icon, $first = true) { if (!($icon instanceof PHUIIconView)) { $icon = id(new PHUIIconView()) @@ -169,6 +182,15 @@ final class PHUIButtonView extends AphrontTagView { $classes[] = 'disabled'; } + switch ($this->getButtonType()) { + case self::BUTTONTYPE_DEFAULT: + // Nothing special for default buttons. + break; + case self::BUTTONTYPE_SIMPLE: + $classes[] = 'simple'; + break; + } + $sigil = null; $meta = null; if ($this->tooltip) { diff --git a/src/view/phui/PHUIDocumentViewPro.php b/src/view/phui/PHUIDocumentViewPro.php index d60cd78d44..51ecc3526e 100644 --- a/src/view/phui/PHUIDocumentViewPro.php +++ b/src/view/phui/PHUIDocumentViewPro.php @@ -79,7 +79,7 @@ final class PHUIDocumentViewPro extends AphrontTagView { $toc[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-align-left') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->addClass('phui-document-toc') ->addSigil('jx-toggle-class') ->setMetaData(array( From 7725d7cc45c7792610746b4a773e4cb12743d873 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 15:49:41 -0700 Subject: [PATCH 211/543] Remove some old UIExamples Summary: Ref M1476. I'm going to see if I can set up side-by-side "PHUI" vs "PHUIX" to make maintaining them a touch easier. Before doing that, nuke some really old UI examples that don't seem very useful. Test Plan: Viewed UIExamples, saw fewer bad ones. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18049 --- resources/celerity/map.php | 80 -------------- src/__phutil_library_map__.php | 16 --- .../examples/JavelinReactorUIExample.php | 95 ---------------- .../uiexample/examples/JavelinUIExample.php | 66 ------------ .../examples/JavelinViewUIExample.php | 45 -------- .../examples/PhabricatorBarePageUIExample.php | 25 ----- .../examples/PhabricatorBusyUIExample.php | 17 --- .../PhabricatorListFilterUIExample.php | 35 ------ .../PhabricatorSortTableUIExample.php | 96 ----------------- .../examples/PhabricatorTooltipUIExample.php | 102 ------------------ .../uiexample/JavelinViewExample.js | 19 ---- .../uiexample/ReactorButtonExample.js | 38 ------- .../uiexample/ReactorCheckboxExample.js | 17 --- .../uiexample/ReactorFocusExample.js | 16 --- .../uiexample/ReactorInputExample.js | 32 ------ .../uiexample/ReactorMouseoverExample.js | 16 --- .../uiexample/ReactorRadioExample.js | 24 ----- .../uiexample/ReactorSelectExample.js | 21 ---- .../uiexample/ReactorSendClassExample.js | 18 ---- .../uiexample/ReactorSendPropertiesExample.js | 26 ----- .../js/application/uiexample/busy-example.js | 9 -- 21 files changed, 813 deletions(-) delete mode 100644 src/applications/uiexample/examples/JavelinReactorUIExample.php delete mode 100644 src/applications/uiexample/examples/JavelinUIExample.php delete mode 100644 src/applications/uiexample/examples/JavelinViewUIExample.php delete mode 100644 src/applications/uiexample/examples/PhabricatorBarePageUIExample.php delete mode 100644 src/applications/uiexample/examples/PhabricatorBusyUIExample.php delete mode 100644 src/applications/uiexample/examples/PhabricatorListFilterUIExample.php delete mode 100644 src/applications/uiexample/examples/PhabricatorSortTableUIExample.php delete mode 100644 src/applications/uiexample/examples/PhabricatorTooltipUIExample.php delete mode 100644 webroot/rsrc/js/application/uiexample/JavelinViewExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorButtonExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorFocusExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorInputExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorRadioExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorSelectExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js delete mode 100644 webroot/rsrc/js/application/uiexample/busy-example.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d6ad7e41fd..56a38061a1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -452,17 +452,6 @@ return array( 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', 'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3', - 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', - 'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8', - 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea', - 'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403', - 'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850', - 'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8', - 'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4', - 'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f', - 'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711', - 'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee', - 'rsrc/js/application/uiexample/busy-example.js' => '60479091', 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', @@ -653,7 +642,6 @@ return array( 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', - 'javelin-behavior-phabricator-busy-example' => '60479091', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', @@ -808,16 +796,6 @@ return array( 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '358b8c04', 'phabricator-ui-example-css' => '528b19de', - 'phabricator-uiexample-javelin-view' => 'd4a14807', - 'phabricator-uiexample-reactor-button' => 'd19198c8', - 'phabricator-uiexample-reactor-checkbox' => '519705ea', - 'phabricator-uiexample-reactor-focus' => '40a6a403', - 'phabricator-uiexample-reactor-input' => '886fd850', - 'phabricator-uiexample-reactor-mouseover' => '47c794d8', - 'phabricator-uiexample-reactor-radio' => '988040b4', - 'phabricator-uiexample-reactor-select' => 'a155550f', - 'phabricator-uiexample-reactor-sendclass' => '1def2711', - 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '9d8f7c4b', 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', @@ -1022,11 +1000,6 @@ return array( '1d17130f' => array( 'javelin-dom', ), - '1def2711' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), '1e911d0f' => array( 'javelin-stratcom', 'javelin-request', @@ -1175,11 +1148,6 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), - '40a6a403' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1214,11 +1182,6 @@ return array( 'javelin-view-renderer', 'javelin-install', ), - '47c794d8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 48086888 => array( 'javelin-behavior', 'javelin-dom', @@ -1285,11 +1248,6 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), - '519705ea' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), '51c5ad07' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1386,10 +1344,6 @@ return array( 'phabricator-prefab', 'javelin-json', ), - 60479091 => array( - 'phabricator-busy', - 'javelin-behavior', - ), '60821bc7' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1558,13 +1512,6 @@ return array( 'phabricator-keyboard-shortcut', 'javelin-stratcom', ), - '886fd850' => array( - 'javelin-install', - 'javelin-reactor-dom', - 'javelin-view-html', - 'javelin-view-interpreter', - 'javelin-view-renderer', - ), '887ad43f' => array( 'javelin-behavior', 'javelin-request', @@ -1655,11 +1602,6 @@ return array( '983751ee' => array( 'phui-inline-comment-view-css', ), - '988040b4' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1697,11 +1639,6 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a155550f' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1792,11 +1729,6 @@ return array( 'javelin-dom', 'phuix-dropdown-menu', ), - 'b1f0ccee' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 'b23b49e6' => array( 'javelin-behavior', 'javelin-dom', @@ -2013,13 +1945,6 @@ return array( 'javelin-workflow', 'phuix-icon-view', ), - 'd19198c8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - 'javelin-dynval', - 'javelin-reactor-dom', - ), 'd254d646' => array( 'javelin-util', ), @@ -2029,11 +1954,6 @@ return array( 'javelin-uri', 'javelin-util', ), - 'd4a14807' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-view', - ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index aa11ace40c..7516619a01 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1395,10 +1395,7 @@ phutil_register_library_map(array( 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', 'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php', 'Javelin' => 'infrastructure/javelin/Javelin.php', - 'JavelinReactorUIExample' => 'applications/uiexample/examples/JavelinReactorUIExample.php', - 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', 'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php', - 'JavelinViewUIExample' => 'applications/uiexample/examples/JavelinViewUIExample.php', 'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php', 'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php', 'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php', @@ -2146,7 +2143,6 @@ phutil_register_library_map(array( 'PhabricatorBadgesTransactionComment' => 'applications/badges/storage/PhabricatorBadgesTransactionComment.php', 'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php', 'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php', - 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', 'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php', @@ -2161,7 +2157,6 @@ phutil_register_library_map(array( 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', - 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php', 'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php', @@ -3030,7 +3025,6 @@ phutil_register_library_map(array( 'PhabricatorLiskFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php', 'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php', 'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php', - 'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', 'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php', 'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php', @@ -3996,7 +3990,6 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php', 'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php', 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', - 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', 'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php', 'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php', @@ -4160,7 +4153,6 @@ phutil_register_library_map(array( 'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTokensToken' => 'applications/tokens/storage/PhabricatorTokensToken.php', - 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php', 'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', @@ -6483,10 +6475,7 @@ phutil_register_library_map(array( 'HeraldTranscriptTestCase' => 'PhabricatorTestCase', 'HeraldUtilityActionGroup' => 'HeraldActionGroup', 'Javelin' => 'Phobject', - 'JavelinReactorUIExample' => 'PhabricatorUIExample', - 'JavelinUIExample' => 'PhabricatorUIExample', 'JavelinViewExampleServerView' => 'AphrontView', - 'JavelinViewUIExample' => 'PhabricatorUIExample', 'LegalpadController' => 'PhabricatorController', 'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability', 'LegalpadDAO' => 'PhabricatorLiskDAO', @@ -7342,7 +7331,6 @@ phutil_register_library_map(array( 'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorBadgesViewController' => 'PhabricatorBadgesProfileController', - 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 'PhabricatorBarePageView' => 'AphrontPageView', 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher', @@ -7357,7 +7345,6 @@ phutil_register_library_map(array( 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', - 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 'PhabricatorCacheEngine' => 'Phobject', 'PhabricatorCacheEngineExtension' => 'Phobject', @@ -8358,7 +8345,6 @@ phutil_register_library_map(array( 'PhabricatorLiskFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorLiskSerializer' => 'Phobject', - 'PhabricatorListFilterUIExample' => 'PhabricatorUIExample', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorLocaleScopeGuard' => 'Phobject', @@ -9523,7 +9509,6 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController', 'PhabricatorSlug' => 'Phobject', 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', - 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 'PhabricatorSourceCodeView' => 'AphrontView', 'PhabricatorSpaceEditField' => 'PhabricatorEditField', 'PhabricatorSpacesApplication' => 'PhabricatorApplication', @@ -9704,7 +9689,6 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorConduitResultInterface', ), - 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTransactionChange' => 'Phobject', 'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange', 'PhabricatorTransactions' => 'Phobject', diff --git a/src/applications/uiexample/examples/JavelinReactorUIExample.php b/src/applications/uiexample/examples/JavelinReactorUIExample.php deleted file mode 100644 index 9e47545072..0000000000 --- a/src/applications/uiexample/examples/JavelinReactorUIExample.php +++ /dev/null @@ -1,95 +0,0 @@ - true), - ), - array( - pht('Reactive focus detector generates a boolean dynamic value'), - 'ReactorFocusExample', - 'phabricator-uiexample-reactor-focus', - array(), - ), - array( - pht('Reactive input box, with normal and calmed output'), - 'ReactorInputExample', - 'phabricator-uiexample-reactor-input', - array('init' => 'Initial value'), - ), - array( - pht('Reactive mouseover detector generates a boolean dynamic value'), - 'ReactorMouseoverExample', - 'phabricator-uiexample-reactor-mouseover', - array(), - ), - array( - pht('Reactive radio buttons generate a string dynamic value'), - 'ReactorRadioExample', - 'phabricator-uiexample-reactor-radio', - array(), - ), - array( - pht('Reactive select box generates a string dynamic value'), - 'ReactorSelectExample', - 'phabricator-uiexample-reactor-select', - array(), - ), - array( - pht( - '%s makes the class of an element a string dynamic value', - 'sendclass'), - 'ReactorSendClassExample', - 'phabricator-uiexample-reactor-sendclass', - array(), - ), - array( - pht( - '%s makes some properties of an object into dynamic values', - 'sendproperties'), - 'ReactorSendPropertiesExample', - 'phabricator-uiexample-reactor-sendproperties', - array(), - ), - ); - - foreach ($examples as $example) { - list($desc, $name, $resource, $params) = $example; - $template = new AphrontJavelinView(); - $template - ->setName($name) - ->setParameters($params) - ->setCelerityResource($resource); - $rows[] = array($desc, $template->render()); - } - - $table = new AphrontTableView($rows); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Example')); - $panel->appendChild($table); - - return $panel; - } -} diff --git a/src/applications/uiexample/examples/JavelinUIExample.php b/src/applications/uiexample/examples/JavelinUIExample.php deleted file mode 100644 index c85a2d4cde..0000000000 --- a/src/applications/uiexample/examples/JavelinUIExample.php +++ /dev/null @@ -1,66 +0,0 @@ -getRequest(); - $user = $request->getUser(); - - // toggle-class - - $container_id = celerity_generate_unique_node_id(); - $button_red_id = celerity_generate_unique_node_id(); - $button_blue_id = celerity_generate_unique_node_id(); - - $button_red = javelin_tag( - 'a', - array( - 'class' => 'button', - 'sigil' => 'jx-toggle-class', - 'href' => '#', - 'id' => $button_red_id, - 'meta' => array( - 'map' => array( - $container_id => 'jxui-red-border', - $button_red_id => 'jxui-active', - ), - ), - ), - pht('Toggle Red Border')); - - $button_blue = javelin_tag( - 'a', - array( - 'class' => 'button jxui-active', - 'sigil' => 'jx-toggle-class', - 'href' => '#', - 'id' => $button_blue_id, - 'meta' => array( - 'state' => true, - 'map' => array( - $container_id => 'jxui-blue-background', - $button_blue_id => 'jxui-active', - ), - ), - ), - pht('Toggle Blue Background')); - - $div = phutil_tag( - 'div', - array( - 'id' => $container_id, - 'class' => 'jxui-example-container jxui-blue-background', - ), - array($button_red, $button_blue)); - - return array($div); - } -} diff --git a/src/applications/uiexample/examples/JavelinViewUIExample.php b/src/applications/uiexample/examples/JavelinViewUIExample.php deleted file mode 100644 index f7df1749b4..0000000000 --- a/src/applications/uiexample/examples/JavelinViewUIExample.php +++ /dev/null @@ -1,45 +0,0 @@ -getRequest(); - - $init = $request->getStr('init'); - - $parent_server_template = new JavelinViewExampleServerView(); - - $parent_client_template = new AphrontJavelinView(); - $parent_client_template - ->setName('JavelinViewExample') - ->setCelerityResource('phabricator-uiexample-javelin-view'); - - $child_server_template = new JavelinViewExampleServerView(); - - $child_client_template = new AphrontJavelinView(); - $child_client_template - ->setName('JavelinViewExample') - ->setCelerityResource('phabricator-uiexample-javelin-view'); - - $parent_server_template->appendChild($parent_client_template); - $parent_client_template->appendChild($child_server_template); - $child_server_template->appendChild($child_client_template); - $child_client_template->appendChild(pht('Hey, it worked.')); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Example')); - $panel->appendChild( - phutil_tag_div('ml', $parent_server_template)); - - return $panel; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorBarePageUIExample.php b/src/applications/uiexample/examples/PhabricatorBarePageUIExample.php deleted file mode 100644 index 3f377edadd..0000000000 --- a/src/applications/uiexample/examples/PhabricatorBarePageUIExample.php +++ /dev/null @@ -1,25 +0,0 @@ -appendChild( - phutil_tag( - 'h1', - array(), - $this->getDescription())); - - $response = new AphrontWebpageResponse(); - $response->setContent($view->render()); - return $response; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorBusyUIExample.php b/src/applications/uiexample/examples/PhabricatorBusyUIExample.php deleted file mode 100644 index ab23e5cf79..0000000000 --- a/src/applications/uiexample/examples/PhabricatorBusyUIExample.php +++ /dev/null @@ -1,17 +0,0 @@ -setUser($this->getRequest()->getUser()); - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Query'))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Search'))); - - $filter->appendChild($form); - - - return $filter; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php b/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php deleted file mode 100644 index 113c38b92a..0000000000 --- a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php +++ /dev/null @@ -1,96 +0,0 @@ - 'Honda', - 'model' => 'Civic', - 'year' => 2004, - 'price' => 3199, - 'color' => pht('Blue'), - ), - array( - 'make' => 'Ford', - 'model' => 'Focus', - 'year' => 2001, - 'price' => 2549, - 'color' => pht('Red'), - ), - array( - 'make' => 'Toyota', - 'model' => 'Camry', - 'year' => 2009, - 'price' => 4299, - 'color' => pht('Black'), - ), - array( - 'make' => 'NASA', - 'model' => 'Shuttle', - 'year' => 1998, - 'price' => 1000000000, - 'color' => pht('White'), - ), - ); - - $request = $this->getRequest(); - - $orders = array( - 'make', - 'model', - 'year', - 'price', - ); - - $sort = $request->getStr('sort'); - list($sort, $reverse) = AphrontTableView::parseSort($sort); - if (!in_array($sort, $orders)) { - $sort = 'make'; - } - - $rows = isort($rows, $sort); - if ($reverse) { - $rows = array_reverse($rows); - } - - $table = new AphrontTableView($rows); - $table->setHeaders( - array( - pht('Make'), - pht('Model'), - pht('Year'), - pht('Price'), - pht('Color'), - )); - $table->setColumnClasses( - array( - '', - 'wide', - 'n', - 'n', - '', - )); - $table->makeSortable( - $request->getRequestURI(), - 'sort', - $sort, - $reverse, - $orders); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Sortable Table of Vehicles')); - $panel->setTable($table); - - return $panel; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorTooltipUIExample.php b/src/applications/uiexample/examples/PhabricatorTooltipUIExample.php deleted file mode 100644 index 373161612a..0000000000 --- a/src/applications/uiexample/examples/PhabricatorTooltipUIExample.php +++ /dev/null @@ -1,102 +0,0 @@ - array( - 'tip' => 'Hi', - ), - 'lorem (north)' => array( - 'tip' => $lorem, - ), - 'lorem (east)' => array( - 'tip' => $lorem, - 'align' => 'E', - ), - 'lorem (south)' => array( - 'tip' => $lorem, - 'align' => 'S', - ), - 'lorem (west)' => array( - 'tip' => $lorem, - 'align' => 'W', - ), - 'lorem (large, north)' => array( - 'tip' => $lorem, - 'size' => 300, - ), - 'lorem (large, east)' => array( - 'tip' => $lorem, - 'size' => 300, - 'align' => 'E', - ), - 'lorem (large, west)' => array( - 'tip' => $lorem, - 'size' => 300, - 'align' => 'W', - ), - 'lorem (large, south)' => array( - 'tip' => $lorem, - 'size' => 300, - 'align' => 'S', - ), - 'overflow (north)' => array( - 'tip' => $overflow, - ), - 'overflow (east)' => array( - 'tip' => $overflow, - 'align' => 'E', - ), - 'overflow (south)' => array( - 'tip' => $overflow, - 'align' => 'S', - ), - 'overflow (west)' => array( - 'tip' => $overflow, - 'align' => 'W', - ), - ); - - $content = array(); - foreach ($metas as $key => $meta) { - $content[] = javelin_tag( - 'div', - array( - 'sigil' => 'has-tooltip', - 'meta' => $meta, - 'style' => $style, - ), - $key); - } - - return $content; - } -} diff --git a/webroot/rsrc/js/application/uiexample/JavelinViewExample.js b/webroot/rsrc/js/application/uiexample/JavelinViewExample.js deleted file mode 100644 index a8e17923b2..0000000000 --- a/webroot/rsrc/js/application/uiexample/JavelinViewExample.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @provides phabricator-uiexample-javelin-view - * @requires javelin-install - * javelin-dom - * javelin-view - */ - -JX.install('JavelinViewExample', { - extend: 'View', - members: { - render: function(rendered_children) { - return JX.$N( - 'div', - { className: 'client-view' }, - rendered_children - ); - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorButtonExample.js b/webroot/rsrc/js/application/uiexample/ReactorButtonExample.js deleted file mode 100644 index 41982f192f..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorButtonExample.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-button - * @requires javelin-install - * javelin-dom - * javelin-util - * javelin-dynval - * javelin-reactor-dom - */ - -JX.install('ReactorButtonExample', { - extend: 'View', - members: { - render: function() { - var button = JX.$N('button', {}, 'Fun'); - var clicks = JX.RDOM.clickPulses(button); - - var time = JX.RDOM.time(); - - // function snapshot(pulses, dynval) { - // return new DynVal( - // pulses.transform(JX.bind(dynval, dynval.getValueNow)), - // dynval.getValueNow() - // ); - // } - // - // Below could be... - // time.snapshot(clicks) - // clicks.snapshot(time) - - var snapshot_time = new JX.DynVal( - clicks.transform(JX.bind(time, time.getValueNow)), - time.getValueNow() - ); - - return [button, JX.RDOM.$DT(snapshot_time)]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js b/webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js deleted file mode 100644 index 983f6d96ab..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-checkbox - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorCheckboxExample', { - extend: 'View', - members: { - render: function() { - var checkbox = JX.$N('input', {type: 'checkbox'}); - - return [checkbox, JX.RDOM.$DT(JX.RDOM.checkbox(checkbox))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorFocusExample.js b/webroot/rsrc/js/application/uiexample/ReactorFocusExample.js deleted file mode 100644 index f4c53b2865..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorFocusExample.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-focus - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorFocusExample', { - extend: 'View', - members: { - render: function() { - var input = JX.$N('input'); - return [input, JX.RDOM.$DT(JX.RDOM.hasFocus(input))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorInputExample.js b/webroot/rsrc/js/application/uiexample/ReactorInputExample.js deleted file mode 100644 index 70f4c9fb70..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorInputExample.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-input - * @requires javelin-install - * javelin-reactor-dom - * javelin-view-html - * javelin-view-interpreter - * javelin-view-renderer - */ - -JX.install('ReactorInputExample', { - extend: 'View', - members: { - render: function() { - var html = JX.HTMLView.registerToInterpreter(new JX.ViewInterpreter()); - - var raw_input = JX.ViewRenderer.render( - html.input({ value: this.getAttr('init') }) - ); - var input = JX.RDOM.input(raw_input); - - return JX.ViewRenderer.render( - html.div( - raw_input, - html.br(), - html.span(JX.RDOM.$DT(input)), - html.br(), - html.span(JX.RDOM.$DT(input.calm(500))) - ) - ); - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js b/webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js deleted file mode 100644 index 5f9656e2f3..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-mouseover - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorMouseoverExample', { - extend: 'View', - members: { - render: function() { - var target = JX.$N('span', 'mouseover me '); - return [target, JX.RDOM.$DT(JX.RDOM.isMouseOver(target))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorRadioExample.js b/webroot/rsrc/js/application/uiexample/ReactorRadioExample.js deleted file mode 100644 index 3e99e0e674..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorRadioExample.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-radio - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorRadioExample', { - extend: 'View', - members: { - render: function() { - var radio_one = JX.$N('input', {type: 'radio', name: 'n', value: 'one'}); - var radio_two = JX.$N('input', {type: 'radio', name: 'n', value: 'two'}); - - radio_one.checked = true; - - return [ - radio_one, - radio_two, - JX.RDOM.$DT(JX.RDOM.radio([radio_one, radio_two])) - ]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorSelectExample.js b/webroot/rsrc/js/application/uiexample/ReactorSelectExample.js deleted file mode 100644 index 628fbed122..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorSelectExample.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-select - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorSelectExample', { - extend: 'View', - members: { - render: function() { - var select = JX.$N('select', {}, [ - JX.$N('option', { value: 'goat' }, 'Goat'), - JX.$N('option', { value: 'bat' }, 'Bat'), - JX.$N('option', { value: 'duck' }, 'Duck') - ]); - - return [select, JX.RDOM.$DT(JX.RDOM.select(select))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js b/webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js deleted file mode 100644 index 62e44b7d0c..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-sendclass - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorSendClassExample', { - extend: 'View', - members: { - render: function() { - var input = JX.$N('input', { type: 'checkbox' }); - var span = JX.$N('a', 'Hey'); - JX.RDOM.sendClass(JX.RDOM.checkbox(input), span, 'disabled'); - return [input, span]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js b/webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js deleted file mode 100644 index f297e413dc..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-sendproperties - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorSendPropertiesExample', { - extend: 'View', - members: { - render: function() { - var color = JX.$N('input', {value: '#fff000'}); - var title = JX.$N('input', {value: 'seen on hover'}); - var target = JX.$N('span', 'Change my color and title'); - - JX.RDOM.sendProps(target, { - style: { - backgroundColor: JX.RDOM.input(color) - }, - title: JX.RDOM.input(title) - }); - - return [color, title, target]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/busy-example.js b/webroot/rsrc/js/application/uiexample/busy-example.js deleted file mode 100644 index 64ee390112..0000000000 --- a/webroot/rsrc/js/application/uiexample/busy-example.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @provides javelin-behavior-phabricator-busy-example - * @requires phabricator-busy - * javelin-behavior - */ - -JX.behavior('phabricator-busy-example', function() { - JX.Busy.start(); -}); From 2ad521b96d39db43a86dd68acf3d0e463e632ea7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 16:05:03 -0700 Subject: [PATCH 212/543] Categorize UIExamples a little bit Summary: Ref M1476. I'm planning to add some PHUIX examples, but sort out the existing examples a little first. I added some categories: - Catalogs: these are what I look at most often (emoji, icons, colors). - Single use: elements with only one use (badges, feed stories, hovercards, setup issues). - Technical: examples which are really just "test this thing in the browser" (avatars, gestures, notifications, remarkup). - Other: evrything else, mostly general-purpose multi-use components. Test Plan: (See left nav.) {F4984042} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18050 --- src/__phutil_library_map__.php | 2 -- .../PhabricatorUIExampleRenderController.php | 11 ++++++++--- .../examples/JavelinViewExampleServerView.php | 14 -------------- .../uiexample/examples/MacroEmojiExample.php | 8 ++++++-- .../uiexample/examples/PHUIBadgeExample.php | 4 ++++ .../examples/PHUIColorPalletteExample.php | 4 ++++ .../uiexample/examples/PHUIFeedStoryExample.php | 6 +++++- .../uiexample/examples/PHUIHovercardUIExample.php | 4 ++++ .../uiexample/examples/PHUIIconExample.php | 4 ++++ .../PhabricatorFilesComposeAvatarExample.php | 6 +++++- .../examples/PhabricatorGestureUIExample.php | 4 ++++ .../examples/PhabricatorNotificationUIExample.php | 4 ++++ .../examples/PhabricatorRemarkupUIExample.php | 4 ++++ .../examples/PhabricatorSetupIssueUIExample.php | 4 ++++ .../uiexample/examples/PhabricatorUIExample.php | 4 ++++ 15 files changed, 60 insertions(+), 23 deletions(-) delete mode 100644 src/applications/uiexample/examples/JavelinViewExampleServerView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7516619a01..5bf2681b1f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1395,7 +1395,6 @@ phutil_register_library_map(array( 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', 'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php', 'Javelin' => 'infrastructure/javelin/Javelin.php', - 'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php', 'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php', 'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php', 'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php', @@ -6475,7 +6474,6 @@ phutil_register_library_map(array( 'HeraldTranscriptTestCase' => 'PhabricatorTestCase', 'HeraldUtilityActionGroup' => 'HeraldActionGroup', 'Javelin' => 'Phobject', - 'JavelinViewExampleServerView' => 'AphrontView', 'LegalpadController' => 'PhabricatorController', 'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability', 'LegalpadDAO' => 'PhabricatorLiskDAO', diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php index 6be978a6a9..025b9511b4 100644 --- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php +++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php @@ -17,9 +17,14 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI('view/'))); - foreach ($classes as $class => $obj) { - $name = $obj->getName(); - $nav->addFilter($class, $name); + $groups = mgroup($classes, 'getCategory'); + ksort($groups); + foreach ($groups as $group => $group_classes) { + $nav->addLabel($group); + foreach ($group_classes as $class => $obj) { + $name = $obj->getName(); + $nav->addFilter($class, $name); + } } $selected = $nav->selectFilter($id, head_key($classes)); diff --git a/src/applications/uiexample/examples/JavelinViewExampleServerView.php b/src/applications/uiexample/examples/JavelinViewExampleServerView.php deleted file mode 100644 index 2d59917a7c..0000000000 --- a/src/applications/uiexample/examples/JavelinViewExampleServerView.php +++ /dev/null @@ -1,14 +0,0 @@ - 'server-view', - ), - $this->renderChildren()); - } - -} diff --git a/src/applications/uiexample/examples/MacroEmojiExample.php b/src/applications/uiexample/examples/MacroEmojiExample.php index fddf20bc32..036a5a8932 100644 --- a/src/applications/uiexample/examples/MacroEmojiExample.php +++ b/src/applications/uiexample/examples/MacroEmojiExample.php @@ -3,11 +3,15 @@ final class MacroEmojiExample extends PhabricatorUIExample { public function getName() { - return pht('Emoji Support'); + return pht('Emoji'); } public function getDescription() { - return pht('Shiny happy people holding hands'); + return pht('Shiny happy people holding hands.'); + } + + public function getCategory() { + return pht('Catalogs'); } public function renderExample() { diff --git a/src/applications/uiexample/examples/PHUIBadgeExample.php b/src/applications/uiexample/examples/PHUIBadgeExample.php index 14eb3469d0..703595b76c 100644 --- a/src/applications/uiexample/examples/PHUIBadgeExample.php +++ b/src/applications/uiexample/examples/PHUIBadgeExample.php @@ -10,6 +10,10 @@ final class PHUIBadgeExample extends PhabricatorUIExample { return pht('Celebrate the moments of your life.'); } + public function getCategory() { + return pht('Single Use'); + } + public function renderExample() { $badges1 = array(); diff --git a/src/applications/uiexample/examples/PHUIColorPalletteExample.php b/src/applications/uiexample/examples/PHUIColorPalletteExample.php index 0e3193d24d..a8910b930d 100644 --- a/src/applications/uiexample/examples/PHUIColorPalletteExample.php +++ b/src/applications/uiexample/examples/PHUIColorPalletteExample.php @@ -10,6 +10,10 @@ final class PHUIColorPalletteExample extends PhabricatorUIExample { return pht('A Standard Palette of Colors for use.'); } + public function getCategory() { + return pht('Catalogs'); + } + public function renderExample() { $colors = array( diff --git a/src/applications/uiexample/examples/PHUIFeedStoryExample.php b/src/applications/uiexample/examples/PHUIFeedStoryExample.php index 375c6e42fd..0df757cada 100644 --- a/src/applications/uiexample/examples/PHUIFeedStoryExample.php +++ b/src/applications/uiexample/examples/PHUIFeedStoryExample.php @@ -8,7 +8,11 @@ final class PHUIFeedStoryExample extends PhabricatorUIExample { public function getDescription() { return pht( - 'An outlandish exaggeration of intricate tales from around the realm'); + 'An outlandish exaggeration of intricate tales from around the realm.'); + } + + public function getCategory() { + return pht('Single Use'); } public function renderExample() { diff --git a/src/applications/uiexample/examples/PHUIHovercardUIExample.php b/src/applications/uiexample/examples/PHUIHovercardUIExample.php index 8673441d09..8cdec56d68 100644 --- a/src/applications/uiexample/examples/PHUIHovercardUIExample.php +++ b/src/applications/uiexample/examples/PHUIHovercardUIExample.php @@ -12,6 +12,10 @@ final class PHUIHovercardUIExample extends PhabricatorUIExample { phutil_tag('tt', array(), 'PHUIHovercardView')); } + public function getCategory() { + return pht('Single Use'); + } + public function renderExample() { $request = $this->getRequest(); $user = $request->getUser(); diff --git a/src/applications/uiexample/examples/PHUIIconExample.php b/src/applications/uiexample/examples/PHUIIconExample.php index 1bf96ce8e5..35395fbff0 100644 --- a/src/applications/uiexample/examples/PHUIIconExample.php +++ b/src/applications/uiexample/examples/PHUIIconExample.php @@ -10,6 +10,10 @@ final class PHUIIconExample extends PhabricatorUIExample { return pht('Easily render icons or images with links and sprites.'); } + public function getCategory() { + return pht('Catalogs'); + } + private function listTransforms() { return array( 'ph-rotate-90', diff --git a/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php b/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php index 6ea5537f9d..57a65863db 100644 --- a/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php +++ b/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php @@ -3,13 +3,17 @@ final class PhabricatorFilesComposeAvatarExample extends PhabricatorUIExample { public function getName() { - return pht('Generate Avatar Images'); + return pht('Avatars'); } public function getDescription() { return pht('Tests various color palettes and sizes.'); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { $request = $this->getRequest(); $viewer = $request->getUser(); diff --git a/src/applications/uiexample/examples/PhabricatorGestureUIExample.php b/src/applications/uiexample/examples/PhabricatorGestureUIExample.php index 1adb7dee16..2adfb795b2 100644 --- a/src/applications/uiexample/examples/PhabricatorGestureUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorGestureUIExample.php @@ -14,6 +14,10 @@ final class PhabricatorGestureUIExample extends PhabricatorUIExample { phutil_tag('tt', array(), 'touchable')); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { $id = celerity_generate_unique_node_id(); diff --git a/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php b/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php index 1b825693fa..5d15d884cc 100644 --- a/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php @@ -12,6 +12,10 @@ final class PhabricatorNotificationUIExample extends PhabricatorUIExample { phutil_tag('tt', array(), 'JX.Notification')); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { require_celerity_resource('phabricator-notification-css'); Javelin::initBehavior('phabricator-notification-example'); diff --git a/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php b/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php index 46f0262bbb..5044821d82 100644 --- a/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php @@ -11,6 +11,10 @@ final class PhabricatorRemarkupUIExample extends PhabricatorUIExample { 'Demonstrates the visual appearance of various Remarkup elements.'); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php b/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php index e5370d30cf..d6386e59d4 100644 --- a/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php @@ -10,6 +10,10 @@ final class PhabricatorSetupIssueUIExample extends PhabricatorUIExample { return pht('Setup errors and warnings.'); } + public function getCategory() { + return pht('Single Use'); + } + public function renderExample() { $request = $this->getRequest(); $user = $request->getUser(); diff --git a/src/applications/uiexample/examples/PhabricatorUIExample.php b/src/applications/uiexample/examples/PhabricatorUIExample.php index 5326cba3fe..7c84f6c9e4 100644 --- a/src/applications/uiexample/examples/PhabricatorUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorUIExample.php @@ -17,6 +17,10 @@ abstract class PhabricatorUIExample extends Phobject { abstract public function getDescription(); abstract public function renderExample(); + public function getCategory() { + return pht('General'); + } + protected function createBasicDummyHandle($name, $type, $fullname = null, $uri = null) { From 683647f1fb9091b8928056d068be7bb954c127c9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 14:47:50 -0700 Subject: [PATCH 213/543] Add PHUIXButtonView and a UIExample Summary: Ref T12733. Ref M1476. This adds `PHUIXButtonView`, for client-side button rendering. It also adds a PHUIX example which renders the server and client versions of each component side-by-side so it's easier to see if they're messed up. Test Plan: {F4984128} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18051 --- resources/celerity/map.php | 13 ++ src/__phutil_library_map__.php | 2 + .../examples/PHUIXComponentsExample.php | 122 ++++++++++++++++++ webroot/rsrc/js/phuix/PHUIXButtonView.js | 109 ++++++++++++++++ webroot/rsrc/js/phuix/PHUIXExample.js | 65 ++++++++++ 5 files changed, 311 insertions(+) create mode 100644 src/applications/uiexample/examples/PHUIXComponentsExample.php create mode 100644 webroot/rsrc/js/phuix/PHUIXButtonView.js create mode 100644 webroot/rsrc/js/phuix/PHUIXExample.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 56a38061a1..cbebfa5aef 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -523,7 +523,9 @@ return array( 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', + 'rsrc/js/phuix/PHUIXButtonView.js' => 'da1c2a6b', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', + 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), @@ -667,6 +669,7 @@ return array( 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-submenu' => 'a6f7a73b', 'javelin-behavior-phui-tab-group' => '0a0b10e9', + 'javelin-behavior-phuix-example' => '68af71ca', 'javelin-behavior-policy-control' => 'd0c516d5', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-project-boards' => '4250a34e', @@ -869,6 +872,7 @@ return array( 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', 'phuix-autocomplete' => 'f6699267', + 'phuix-button-view' => 'da1c2a6b', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -1371,6 +1375,11 @@ return array( '6882e80a' => array( 'javelin-dom', ), + '68af71ca' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-button-view', + ), '69adf288' => array( 'javelin-install', ), @@ -1988,6 +1997,10 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + 'da1c2a6b' => array( + 'javelin-install', + 'javelin-dom', + ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5bf2681b1f..13e447c48d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1806,6 +1806,7 @@ phutil_register_library_map(array( 'PHUIUserAvailabilityView' => 'applications/calendar/view/PHUIUserAvailabilityView.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', + 'PHUIXComponentsExample' => 'applications/uiexample/examples/PHUIXComponentsExample.php', 'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php', 'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php', 'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php', @@ -6938,6 +6939,7 @@ phutil_register_library_map(array( 'PHUIUserAvailabilityView' => 'AphrontTagView', 'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView', + 'PHUIXComponentsExample' => 'PhabricatorUIExample', 'PassphraseAbstractKey' => 'Phobject', 'PassphraseConduitAPIMethod' => 'ConduitAPIMethod', 'PassphraseController' => 'PhabricatorController', diff --git a/src/applications/uiexample/examples/PHUIXComponentsExample.php b/src/applications/uiexample/examples/PHUIXComponentsExample.php new file mode 100644 index 0000000000..a5c76a1e77 --- /dev/null +++ b/src/applications/uiexample/examples/PHUIXComponentsExample.php @@ -0,0 +1,122 @@ + 'fa-rocket', + ), + array( + 'icon' => 'fa-cloud', + 'color' => 'indigo', + ), + ); + + foreach ($icons as $spec) { + $icon = new PHUIIconView(); + + $icon->setIcon(idx($spec, 'icon'), idx($spec, 'color')); + + $client_id = celerity_generate_unique_node_id(); + + $server_view = $icon; + $client_view = javelin_tag( + 'div', + array( + 'id' => $client_id, + )); + + Javelin::initBehavior( + 'phuix-example', + array( + 'type' => 'icon', + 'id' => $client_id, + 'spec' => $spec, + )); + + $content[] = id(new AphrontMultiColumnView()) + ->addColumn($server_view) + ->addColumn($client_view); + } + + + $buttons = array( + array( + 'text' => pht('Submit'), + ), + array( + 'text' => pht('Activate'), + 'icon' => 'fa-rocket', + ), + array( + 'type' => PHUIButtonView::BUTTONTYPE_SIMPLE, + 'text' => pht('3 / 5 Comments'), + 'icon' => 'fa-comment', + ), + array( + 'color' => PHUIButtonView::GREEN, + 'text' => pht('Environmental!'), + ), + ); + + foreach ($buttons as $spec) { + $button = new PHUIButtonView(); + + if (idx($spec, 'text') !== null) { + $button->setText($spec['text']); + } + + if (idx($spec, 'icon') !== null) { + $button->setIcon($spec['icon']); + } + + if (idx($spec, 'type') !== null) { + $button->setButtonType($spec['type']); + } + + if (idx($spec, 'color') !== null) { + $button->setColor($spec['color']); + } + + $client_id = celerity_generate_unique_node_id(); + + $server_view = $button; + $client_view = javelin_tag( + 'div', + array( + 'id' => $client_id, + )); + + Javelin::initBehavior( + 'phuix-example', + array( + 'type' => 'button', + 'id' => $client_id, + 'spec' => $spec, + )); + + $content[] = id(new AphrontMultiColumnView()) + ->addColumn($server_view) + ->addColumn($client_view); + } + + return id(new PHUIBoxView()) + ->appendChild($content) + ->addMargin(PHUI::MARGIN_LARGE); + } +} diff --git a/webroot/rsrc/js/phuix/PHUIXButtonView.js b/webroot/rsrc/js/phuix/PHUIXButtonView.js new file mode 100644 index 0000000000..9fbefb8b1a --- /dev/null +++ b/webroot/rsrc/js/phuix/PHUIXButtonView.js @@ -0,0 +1,109 @@ +/** + * @provides phuix-button-view + * @requires javelin-install + * javelin-dom + */ +JX.install('PHUIXButtonView', { + + statics: { + BUTTONTYPE_DEFAULT: 'buttontype.default', + BUTTONTYPE_SIMPLE: 'buttontype.simple' + }, + + members: { + _node: null, + _textNode: null, + + _iconView: null, + _color: null, + _buttonType: null, + + setIcon: function(icon) { + this.getIconView().setIcon(icon); + return this; + }, + + getIconView: function() { + if (!this._iconView) { + this._iconView = new JX.PHUIXIconView(); + this._redraw(); + } + return this._iconView; + }, + + setColor: function(color) { + var node = this.getNode(); + + if (this._color) { + JX.DOM.alterClass(node, this._color, false); + } + this._color = color; + JX.DOM.alterClass(node, this._color, true); + + return this; + }, + + setButtonType: function(button_type) { + var self = JX.PHUIXButtonView; + + this._buttonType = button_type; + var node = this.getNode(); + + var is_simple = (this._buttonType == self.BUTTONTYPE_SIMPLE); + JX.DOM.alterClass(node, 'simple', is_simple); + + return this; + }, + + setText: function(text) { + JX.DOM.setContent(this._getTextNode(), text); + return this; + }, + + getNode: function() { + if (!this._node) { + var attrs = { + className: 'button' + }; + + this._node = JX.$N('button', attrs); + + this._redraw(); + } + + return this._node; + }, + + _getTextNode: function() { + if (!this._textNode) { + var attrs = { + className: 'phui-button-text' + }; + + this._textNode = JX.$N('div', attrs); + } + + return this._textNode; + }, + + _redraw: function() { + var node = this.getNode(); + + var icon = this._iconView; + var text = this._textNode; + + var content = []; + if (icon) { + content.push(icon.getNode()); + } + + if (text) { + content.push(text); + } + + JX.DOM.alterClass(node, 'has-icon', !!icon); + JX.DOM.setContent(node, content); + } + } + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXExample.js b/webroot/rsrc/js/phuix/PHUIXExample.js new file mode 100644 index 0000000000..87f36b04c3 --- /dev/null +++ b/webroot/rsrc/js/phuix/PHUIXExample.js @@ -0,0 +1,65 @@ +/** + * @provides javelin-behavior-phuix-example + * @requires javelin-install + * javelin-dom + * phuix-button-view + */ + +JX.behavior('phuix-example', function(config) { + var node; + var spec; + var defaults; + + switch (config.type) { + case 'button': + var button = new JX.PHUIXButtonView(); + defaults = { + text: null, + icon: null, + type: null, + color: null + }; + + spec = JX.copy(defaults, config.spec); + + if (spec.text !== null) { + button.setText(spec.text); + } + + if (spec.icon !== null) { + button.setIcon(spec.icon); + } + + if (spec.type !== null) { + button.setButtonType(spec.type); + } + + if (spec.color !== null) { + button.setColor(spec.color); + } + + node = button.getNode(); + break; + case 'icon': + var icon = new JX.PHUIXIconView(); + defaults = { + icon: null, + color: null + }; + + spec = JX.copy(defaults, config.spec); + + if (spec.icon !== null) { + icon.setIcon(spec.icon); + } + + if (spec.color !== null) { + icon.setColor(spec.color); + } + + node = icon.getNode(); + break; + } + + JX.DOM.setContent(JX.$(config.id), node); +}); From 6295e37857085321b0490d4412c51ea914e67501 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 30 May 2017 20:18:13 -0700 Subject: [PATCH 214/543] Have Browse button in History actually work Summary: Ref T12780. Makes the button do something useful, like link to the history at the right spot in the graph. Test Plan: Click on various browse buttons, get correct url. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12780 Differential Revision: https://secure.phabricator.com/D18054 --- .../diffusion/view/DiffusionHistoryListView.php | 12 +++--------- src/applications/diffusion/view/DiffusionView.php | 14 +++++++++++++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index f72bffcbe6..50713695be 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -105,13 +105,14 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { } } - $browse = $this->linkBrowse( + $browse_button = $this->linkBrowse( $history->getPath(), array( 'commit' => $history->getCommitIdentifier(), 'branch' => $drequest->getBranch(), 'type' => $history->getFileType(), - )); + ), + true); $differential_view = null; if ($show_revisions && $commit) { @@ -192,13 +193,6 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ->setColor(PHUITagView::COLOR_INDIGO) ->setSlimShady(true); - $browse_button = id(new PHUIButtonView()) - ->setText(pht('Browse')) - ->setIcon('fa-code') - ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) - ->appendChild($audit_view); - $item = id(new PHUIObjectItemView()) ->setHeader($commit_desc) ->setHref($commit_link) diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index b615ce855c..1d89a9dbd7 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -58,7 +58,10 @@ abstract class DiffusionView extends AphrontView { id(new PHUIIconView())->setIcon('fa-history bluegrey')); } - final public function linkBrowse($path, array $details = array()) { + final public function linkBrowse( + $path, + array $details = array(), + $button = false) { require_celerity_resource('diffusion-icons-css'); Javelin::initBehavior('phabricator-tooltips'); @@ -111,6 +114,15 @@ abstract class DiffusionView extends AphrontView { ); } + if ($button) { + return id(new PHUIButtonView()) + ->setText(pht('Browse')) + ->setIcon('fa-code') + ->setHref($href) + ->setTag('a') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); + } + return javelin_tag( 'a', array( From f8581f687c9b1634d7a020d11ed121a067ddefa0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 31 May 2017 17:02:36 +0000 Subject: [PATCH 215/543] Restrict green button to buttons Summary: Ref T12780. Button styles are bleeding over here on the icon, restrict to .button classes Test Plan: uiexamples. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12780 Differential Revision: https://secure.phabricator.com/D18055 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-button.css | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cbebfa5aef..3da70886c5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'bb7f0446', + 'core.pkg.css' => 'c56695d0', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -140,7 +140,7 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => 'ccd8c6c5', + 'rsrc/css/phui/phui-button.css' => 'e14854c3', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -815,7 +815,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => 'ccd8c6c5', + 'phui-button-css' => 'e14854c3', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index e9d446a0ac..8b609c6cad 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -69,8 +69,8 @@ a.icon:visited { } button.green, -a.green, -a.green:visited { +a.green.button, +a.green.button:visited { background-color: {$green}; border-color: {$green}; background-image: linear-gradient(to bottom, #23BB5B, #139543); From f2fcafb40dde94ddf4ee22716fea74fca0334a64 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 May 2017 20:00:15 -0700 Subject: [PATCH 216/543] Help PROFESSIONAL SOFTWARE ENGINEERS copy text to their clipboard Summary: Ref T12780. I'd like 18,000 GitHub stars now please thank you Test Plan: this feature is awful Reviewers: chad Reviewed By: chad Subscribers: cspeckmim Maniphest Tasks: T12780 Differential Revision: https://secure.phabricator.com/D18053 --- resources/celerity/map.php | 13 +++-- .../uiexample/examples/PHUIButtonExample.php | 47 +++++++++++++++---- webroot/rsrc/css/core/core.css | 13 +++++ webroot/rsrc/js/core/behavior-copy.js | 43 +++++++++++++++++ 4 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 webroot/rsrc/js/core/behavior-copy.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3da70886c5..90823f0bb8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'c56695d0', + 'core.pkg.css' => '0c6e11ed', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -114,7 +114,7 @@ return array( 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '9f4cb463', + 'rsrc/css/core/core.css' => '23beb330', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', @@ -474,6 +474,7 @@ return array( 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', + 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', @@ -644,6 +645,7 @@ return array( 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', + 'javelin-behavior-phabricator-clipboard-copy' => 'b0b8f86d', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', @@ -763,7 +765,7 @@ return array( 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '9f4cb463', + 'phabricator-core-css' => '23beb330', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', @@ -1738,6 +1740,11 @@ return array( 'javelin-dom', 'phuix-dropdown-menu', ), + 'b0b8f86d' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), 'b23b49e6' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index 8f2e1e57a7..fa4d317a5c 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -106,18 +106,49 @@ final class PHUIButtonExample extends PhabricatorUIExample { $column = array(); $icons = array( - 'Comment' => 'fa-comment', - 'Give Token' => 'fa-trophy', - 'Reverse Time' => 'fa-clock-o', - 'Implode Earth' => 'fa-exclamation-triangle red', + array( + 'text' => pht('Comment'), + 'icon' => 'fa-comment', + ), + array( + 'text' => pht('Give Token'), + 'icon' => 'fa-trophy', + ), + array( + 'text' => pht('Reverse Time'), + 'icon' => 'fa-clock-o', + ), + array( + 'text' => pht('Implode Earth'), + 'icon' => 'fa-exclamation-triangle', + ), + array( + 'text' => pht('Copy "Quack" to Clipboard'), + 'icon' => 'fa-clipboard', + 'copy' => pht('Quack'), + ), ); - foreach ($icons as $text => $icon) { - $column[] = id(new PHUIButtonView()) + foreach ($icons as $text => $spec) { + $button = id(new PHUIButtonView()) ->setTag('a') ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($text) + ->setIcon(idx($spec, 'icon')) + ->setText(idx($spec, 'text')) ->addClass(PHUI::MARGIN_SMALL_RIGHT); + + $copy = idx($spec, 'copy'); + if ($copy !== null) { + Javelin::initBehavior('phabricator-clipboard-copy'); + + $button->addClass('clipboard-copy'); + $button->addSigil('clipboard-copy'); + $button->setMetadata( + array( + 'text' => $copy, + )); + } + + $column[] = $button; } $layout3 = id(new AphrontMultiColumnView()) diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index 01c4414454..d7145bb485 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -173,3 +173,16 @@ hr { height: 2px; background: {$sky}; } + +.clipboard-copy { + visibility: hidden; +} + +.supports-clipboard .clipboard-copy { + visibility: visible; +} + +.clipboard-buffer { + position: absolute; + left: -9999px; +} diff --git a/webroot/rsrc/js/core/behavior-copy.js b/webroot/rsrc/js/core/behavior-copy.js new file mode 100644 index 0000000000..4457f6e022 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-copy.js @@ -0,0 +1,43 @@ +/** + * @provides javelin-behavior-phabricator-clipboard-copy + * @requires javelin-behavior + * javelin-dom + * javelin-stratcom + * @javelin + */ + +JX.behavior('phabricator-clipboard-copy', function() { + + if (!document.queryCommandSupported) { + return; + } + + if (!document.queryCommandSupported('copy')) { + return; + } + + JX.DOM.alterClass(document.body, 'supports-clipboard', true); + + JX.Stratcom.listen('click', 'clipboard-copy', function(e) { + e.kill(); + + var data = e.getNodeData('clipboard-copy'); + var attr = { + value: data.text || '', + className: 'clipboard-buffer' + }; + + var node = JX.$N('textarea', attr); + document.body.appendChild(node); + + try { + node.select(); + document.execCommand('copy'); + } catch (ignored) { + // Ignore any errors we hit. + } + + JX.DOM.remove(node); + }); + +}); From c001781264b47ad936badd30a3d278eefea752e3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 31 May 2017 13:11:15 -0700 Subject: [PATCH 217/543] Allow buttons to just be icons Summary: Let's buttons just be an icon, no pressure to also have text. Test Plan: UIExamples, Search, Home, Policy Controls... Probably 99% of them. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18056 --- resources/celerity/map.php | 28 +++++++++---------- .../uiexample/examples/PHUIButtonExample.php | 12 ++++++++ .../form/control/AphrontFormPolicyControl.php | 2 +- src/view/phui/PHUIButtonView.php | 24 ++++++++++++++-- .../css/application/base/main-menu-view.css | 1 + webroot/rsrc/css/phui/phui-button.css | 14 ++-------- webroot/rsrc/js/phuix/PHUIXButtonView.js | 1 + 7 files changed, 52 insertions(+), 30 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 90823f0bb8..25bebbcdd0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '0c6e11ed', + 'core.pkg.css' => 'eb39b754', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -39,7 +39,7 @@ return array( 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => 'de9fe8c4', + 'rsrc/css/application/base/main-menu-view.css' => '16053029', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', @@ -140,7 +140,7 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => 'e14854c3', + 'rsrc/css/phui/phui-button.css' => '7ffbeee7', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -524,7 +524,7 @@ return array( 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', - 'rsrc/js/phuix/PHUIXButtonView.js' => 'da1c2a6b', + 'rsrc/js/phuix/PHUIXButtonView.js' => '0f13520b', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', @@ -783,7 +783,7 @@ return array( 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', - 'phabricator-main-menu-view' => 'de9fe8c4', + 'phabricator-main-menu-view' => '16053029', 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', @@ -817,7 +817,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => 'e14854c3', + 'phui-button-css' => '7ffbeee7', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -874,7 +874,7 @@ return array( 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', 'phuix-autocomplete' => 'f6699267', - 'phuix-button-view' => 'da1c2a6b', + 'phuix-button-view' => '0f13520b', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -953,6 +953,10 @@ return array( 'javelin-dom', 'javelin-router', ), + '0f13520b' => array( + 'javelin-install', + 'javelin-dom', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -969,6 +973,9 @@ return array( 'javelin-dom', 'javelin-history', ), + 16053029 => array( + 'phui-theme-css', + ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2004,10 +2011,6 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), - 'da1c2a6b' => array( - 'javelin-install', - 'javelin-dom', - ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', @@ -2015,9 +2018,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'de9fe8c4' => array( - 'phui-theme-css', - ), 'e0ec7f2f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index fa4d317a5c..8758264e7d 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -122,6 +122,18 @@ final class PHUIButtonExample extends PhabricatorUIExample { 'text' => pht('Implode Earth'), 'icon' => 'fa-exclamation-triangle', ), + array( + 'icon' => 'fa-rocket', + ), + array( + 'icon' => 'fa-clipboard', + ), + array( + 'icon' => 'fa-upload', + ), + array( + 'icon' => 'fa-street-view', + ), array( 'text' => pht('Copy "Quack" to Clipboard'), 'icon' => 'fa-clipboard', diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 2ce13491af..6f343bf00e 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -324,7 +324,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl { javelin_tag( 'a', array( - 'class' => 'grey button dropdown has-icon policy-control', + 'class' => 'grey button dropdown has-icon has-text policy-control', 'href' => '#', 'mustcapture' => true, 'sigil' => 'policy-control', diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index 0d0e0f3363..111a257108 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -174,6 +174,10 @@ final class PHUIButtonView extends AphrontTagView { $classes[] = 'has-icon'; } + if (strlen($this->text)) { + $classes[] = 'has-text'; + } + if ($this->iconFirst == false) { $classes[] = 'icon-last'; } @@ -226,10 +230,24 @@ final class PHUIButtonView extends AphrontTagView { $subtext = null; if ($this->subtext) { $subtext = phutil_tag( - 'div', array('class' => 'phui-button-subtext'), $this->subtext); + 'div', + array( + 'class' => 'phui-button-subtext', + ), + $this->subtext); + } + + if (strlen($this->text)) { + $text = phutil_tag( + 'div', + array( + 'class' => 'phui-button-text', + ), + array( + $text, + $subtext, + )); } - $text = phutil_tag( - 'div', array('class' => 'phui-button-text'), array($text, $subtext)); } $caret = null; diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index eb5cfad8c7..4de30fb8ce 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -247,6 +247,7 @@ a.phabricator-core-user-menu .caret:before { font-size: 15px; top: 4px; left: 8px; + position: absolute; } .phabricator-main-menu-search-dropdown .caret { diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 8b609c6cad..cc8dce01f1 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -41,7 +41,7 @@ input[type="submit"] { font-weight: bold; font-size: {$normalfontsize}; display: inline-block; - padding: 4px 16px 5px; + padding: 4px 14px 5px; text-align: center; white-space: nowrap; border-radius: 3px; @@ -301,7 +301,7 @@ a.policy-control .phui-button-text { position: relative; } -.button .phui-icon-view { +.button.has-text .phui-icon-view { display: inline-block; position: absolute; top: 7px; @@ -313,10 +313,6 @@ a.policy-control .phui-button-text { right: 10px; } -.phui-button-bar .button .phui-icon-view { - left: 14px; -} - .button.has-icon .phui-button-text { margin-left: 16px; } @@ -374,12 +370,6 @@ a.policy-control .phui-button-text { border: 0; } -.phui-button-bar a.button.has-icon { - display: inline-block; - height: 18px; - width: 6px; -} - .phui-button-bar .phui-button-bar-first { border-top-right-radius: 0px; border-bottom-right-radius: 0px; diff --git a/webroot/rsrc/js/phuix/PHUIXButtonView.js b/webroot/rsrc/js/phuix/PHUIXButtonView.js index 9fbefb8b1a..67b59f24f1 100644 --- a/webroot/rsrc/js/phuix/PHUIXButtonView.js +++ b/webroot/rsrc/js/phuix/PHUIXButtonView.js @@ -102,6 +102,7 @@ JX.install('PHUIXButtonView', { } JX.DOM.alterClass(node, 'has-icon', !!icon); + JX.DOM.alterClass(node, 'has-text', !!text); JX.DOM.setContent(node, content); } } From fbb767343961b550c8df3de3d2b635c96c0a90cd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 1 Jun 2017 06:47:23 -0700 Subject: [PATCH 218/543] Diffusion History List cleanup Summary: Removes the odd circle buttons, adds copy-pasta button. Test Plan: Review new layout locally. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18057 --- .../view/DiffusionHistoryListView.php | 101 ++++-------------- 1 file changed, 22 insertions(+), 79 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 50713695be..55cda33153 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -10,19 +10,8 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { require_celerity_resource('diffusion-history-css'); Javelin::initBehavior('phabricator-tooltips'); - $buildables = $this->loadBuildables( - mpull($this->getHistory(), 'getCommit')); - - $show_revisions = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorDifferentialApplication', - $viewer); - $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); - $show_builds = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorHarbormasterApplication', - $this->getUser()); - $rows = array(); $ii = 0; $cur_date = 0; @@ -97,14 +86,6 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { $commit_desc = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); } - $build_view = null; - if ($show_builds) { - $buildable = idx($buildables, $commit->getPHID()); - if ($buildable !== null) { - $build_view = $this->renderBuildable($buildable); - } - } - $browse_button = $this->linkBrowse( $history->getPath(), array( @@ -114,63 +95,6 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ), true); - $differential_view = null; - if ($show_revisions && $commit) { - $d_id = idx($this->getRevisions(), $commit->getPHID()); - if ($d_id) { - $differential_view = id(new PHUIIconCircleView()) - ->setIcon('fa-gear') - ->setColor('green') - ->setState(PHUIIconCircleView::STATE_SUCCESS) - ->addSigil('has-tooltip') - ->setSize(PHUIIconCircleView::SMALL) - ->setHref('/D'.$d_id) - ->addClass('mmr') - ->setMetadata( - array( - 'tip' => 'Revision D'.$d_id, - )); - } - } - - $status = $commit->getAuditStatus(); - $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon($status); - $color = PhabricatorAuditCommitStatusConstants::getStatusColor($status); - $name = PhabricatorAuditCommitStatusConstants::getStatusName($status); - $audit_view = id(new PHUIIconCircleView()) - ->setIcon('fa-code') - ->setColor($color) - ->setState($icon) - ->addSigil('has-tooltip') - ->setSize(PHUIIconCircleView::SMALL) - ->addClass('mmr') - ->setMetadata( - array( - 'tip' => $name, - )); - - if ($show_builds) { - $buildable = idx($buildables, $commit->getPHID()); - if ($buildable !== null) { - $status = $buildable->getBuildableStatus(); - $icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $color = HarbormasterBuildable::getBuildableStatusColor($status); - $name = HarbormasterBuildable::getBuildableStatusName($status); - $build_view = id(new PHUIIconCircleView()) - ->setIcon('fa-recycle') - ->setColor($color) - ->setState($icon) - ->addSigil('has-tooltip') - ->setSize(PHUIIconCircleView::SMALL) - ->setHref('/'.$buildable->getMonogram()) - ->addClass('mmr') - ->setMetadata( - array( - 'tip' => $name, - )); - } - } - $message = null; $commit_link = $repository->getCommitURI( $history->getCommitIdentifier()); @@ -193,6 +117,27 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ->setColor(PHUITagView::COLOR_INDIGO) ->setSlimShady(true); + $clippy = null; + if ($commit) { + Javelin::initBehavior('phabricator-clipboard-copy'); + $clippy = id(new PHUIButtonView()) + ->setIcon('fa-clipboard') + ->setHref('#') + ->setTag('a') + ->addSigil('has-tooltip') + ->addSigil('clipboard-copy') + ->addClass('clipboard-copy') + ->addClass('mmr') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setMetadata( + array( + 'text' => $history->getCommitIdentifier(), + 'tip' => pht('Copy'), + 'align' => 'N', + 'size' => 'auto', + )); + } + $item = id(new PHUIObjectItemView()) ->setHeader($commit_desc) ->setHref($commit_link) @@ -202,9 +147,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ->addAttribute($commit_tag) ->addAttribute($authored) ->setSideColumn(array( - $differential_view, - $audit_view, - $build_view, + $clippy, $browse_button, )); From 995c1503e7ef7fa8ebe067dcddd3f1ad6b58172a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Jun 2017 07:58:10 -0700 Subject: [PATCH 219/543] Hide "X created Y, a subtask of P." feed stories again Summary: Fixes T12787. Modular Transactions don't actually support `shouldHideForFeed()`. I'll add some discussion to the task. Test Plan: Created a subtask, saw no more "X reopened Y, a subtask of P" feed story. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12787 Differential Revision: https://secure.phabricator.com/D18058 --- .../maniphest/storage/ManiphestTransaction.php | 16 ++++++++++++++++ .../xaction/ManiphestTaskUnblockTransaction.php | 9 --------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index e2739c1d09..6301241050 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -40,6 +40,22 @@ final class ManiphestTransaction return parent::shouldGenerateOldValue(); } + public function shouldHideForFeed() { + // NOTE: Modular transactions don't currently support this, and it has + // very few callsites, and it's publish-time rather than display-time. + // This should probably become a supported, display-time behavior. For + // discussion, see T12787. + + // Hide "alice created X, a task blocking Y." from feed because it + // will almost always appear adjacent to "alice created Y". + $is_new = $this->getMetadataValue('blocker.new'); + if ($is_new) { + return true; + } + + return parent::shouldHideForFeed(); + } + public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); diff --git a/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php index 905554a3a3..1c7a88dec7 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php @@ -9,15 +9,6 @@ final class ManiphestTaskUnblockTransaction return null; } - public function shouldHideForFeed() { - // Hide "alice created X, a task blocking Y." from feed because it - // will almost always appear adjacent to "alice created Y". - $is_new = $this->getMetadataValue('blocker.new'); - if ($is_new) { - return true; - } - } - public function getActionName() { $old = $this->getOldValue(); $new = $this->getNewValue(); From b69174a4c8f70d5f90d1cd703b27824e464ebcec Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Jun 2017 08:17:54 -0700 Subject: [PATCH 220/543] Remove non-operational `shouldHideFromFeed()` from ManiphestTaskPointsTransaction Summary: See D18018. Ref T12787. This doesn't actually work; we started publishing these stories as a side effect of converting to ModularTransactions, then I fixed the rendering. This mechanism has very few callsites and I suspect we may want to get rid of it (see T12787) so just keep publishing these stories for now. Test Plan: Changed the point value of a task, saw a feed story both before and after the patch. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12787 Differential Revision: https://secure.phabricator.com/D18059 --- .../maniphest/xaction/ManiphestTaskPointsTransaction.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index b1c20af9e6..8953664f27 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -17,10 +17,6 @@ final class ManiphestTaskPointsTransaction $object->setPoints($value); } - public function shouldHideForFeed() { - return true; - } - public function shouldHide() { if (!ManiphestTaskPoints::getIsEnabled()) { return true; From 0d8aba855021f666d9337a56e04d302022983677 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 1 Jun 2017 12:36:20 -0700 Subject: [PATCH 221/543] Revert some changes to Diffusion History List Summary: Ref rPf2fcafb40dde94ddf4ee22716fea74fca0334a64#38208, I think this is a more usable layout. Gets rid of clippy, audit. Adds back Differential link as tag, Build Status as button. Test Plan: Faked data on this for Differential, Builds, should all work though. Test on real and fake repositories. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18061 --- resources/celerity/map.php | 10 +-- .../view/DiffusionHistoryListView.php | 70 +++++++++++++------ .../diffusion/diffusion-history.css | 9 +++ webroot/rsrc/css/phui/phui-button.css | 32 +++++++-- 4 files changed, 86 insertions(+), 35 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 25bebbcdd0..fcd5a4bc50 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'eb39b754', + 'core.pkg.css' => '38689e09', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '4faf40cd', + 'rsrc/css/application/diffusion/diffusion-history.css' => 'de70e348', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -140,7 +140,7 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '7ffbeee7', + 'rsrc/css/phui/phui-button.css' => '836844c9', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -565,7 +565,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '4faf40cd', + 'diffusion-history-css' => 'de70e348', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', @@ -817,7 +817,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '7ffbeee7', + 'phui-button-css' => '836844c9', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 55cda33153..80ca5d76a8 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -10,8 +10,19 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { require_celerity_resource('diffusion-history-css'); Javelin::initBehavior('phabricator-tooltips'); + $buildables = $this->loadBuildables( + mpull($this->getHistory(), 'getCommit')); + + $show_revisions = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); + $show_builds = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorHarbormasterApplication', + $this->getUser()); + $rows = array(); $ii = 0; $cur_date = 0; @@ -95,6 +106,40 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ), true); + $diff_tag = null; + if ($show_revisions && $commit) { + $d_id = idx($this->getRevisions(), $commit->getPHID()); + if ($d_id) { + $diff_tag = id(new PHUITagView()) + ->setName('D'.$d_id) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_BLUE) + ->setHref('/D'.$d_id) + ->addClass('diffusion-differential-tag') + ->setSlimShady(true); + } + } + + $build_view = null; + if ($show_builds) { + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable !== null) { + $status = $buildable->getBuildableStatus(); + $icon = HarbormasterBuildable::getBuildableStatusIcon($status); + $color = HarbormasterBuildable::getBuildableStatusColor($status); + $name = HarbormasterBuildable::getBuildableStatusName($status); + $build_view = id(new PHUIButtonView()) + ->setTag('a') + ->setText($name) + ->setIcon($icon) + ->setColor($color) + ->setHref('/'.$buildable->getMonogram()) + ->addClass('mmr') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->addClass('diffusion-list-build-status'); + } + } + $message = null; $commit_link = $repository->getCommitURI( $history->getCommitIdentifier()); @@ -117,37 +162,16 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ->setColor(PHUITagView::COLOR_INDIGO) ->setSlimShady(true); - $clippy = null; - if ($commit) { - Javelin::initBehavior('phabricator-clipboard-copy'); - $clippy = id(new PHUIButtonView()) - ->setIcon('fa-clipboard') - ->setHref('#') - ->setTag('a') - ->addSigil('has-tooltip') - ->addSigil('clipboard-copy') - ->addClass('clipboard-copy') - ->addClass('mmr') - ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) - ->setMetadata( - array( - 'text' => $history->getCommitIdentifier(), - 'tip' => pht('Copy'), - 'align' => 'N', - 'size' => 'auto', - )); - } - $item = id(new PHUIObjectItemView()) ->setHeader($commit_desc) ->setHref($commit_link) ->setDisabled($commit->isUnreachable()) ->setDescription($message) ->setImageURI($author_image) - ->addAttribute($commit_tag) + ->addAttribute(array($commit_tag, ' ', $diff_tag)) // For Copy Pasta ->addAttribute($authored) ->setSideColumn(array( - $clippy, + $build_view, $browse_button, )); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index 43e05c8958..fd22f9b1a5 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -31,3 +31,12 @@ .diffusion-history-author-name a { color: {$darkbluetext}; } + +.diffusion-history-list .diffusion-differential-tag { + margin-left: 4px; +} + +a.phui-tag-view:hover.diffusion-differential-tag .phui-tag-core { + border-color: transparent; + text-decoration: underline; +} diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index cc8dce01f1..5b35eaa681 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -106,6 +106,22 @@ a.simple:visited .phui-icon-view { color: {$lightbluetext}; } +button.simple.red, +input[type="submit"].simple.red, +a.simple.red, +a.simple.red:visited { + background: #fff; + color: {$redtext}; + border: 1px solid {$sh-redborder}; +} + +button.simple.red .phui-icon-view, +input[type="submit"].simple.red .phui-icon-view, +a.simple.red .phui-icon-view, +a.simple.red:visited .phui-icon-view { + color: {$redtext}; +} + a.disabled, button.disabled, button[disabled] { @@ -145,16 +161,18 @@ button.green:hover { a.button.simple:hover, button.simple:hover { - background-color: {$lightblue}; - background-image: linear-gradient(to bottom, {$blue}, {$blue}); - color: #fff; + border-color: {$blueborder}; + background-image: none; + background-color: #fff; transition: 0s; } -a.button.simple:hover .phui-icon-view, -button.simple:hover .phui-icon-view { - color: #fff; - transition: 0.1s; +a.button.simple.red:hover, +button.simple.red:hover { + border-color: {$red}; + background-image: none; + background-color: #fff; + transition: 0s; } a.button.simple .phui-icon-view { From b9a4988df36ceac490af7a55b528c08594d75d2a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Jun 2017 12:31:22 -0700 Subject: [PATCH 222/543] Mark "Settings" and "Nuance" as launchable applications Summary: Fixes T12790. I don't think this was actually a regression, Settings just wasn't launchable before global settings (since it had no real landing page, and the profile menu always had a link) and didn't get marked launchable once we added them. I also double-checked other un-launchable apps; Nuance is probably close enough to make launchable now while I'm in here. Test Plan: Typed "settings" into global typeahead, got settings. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12790 Differential Revision: https://secure.phabricator.com/D18062 --- .../nuance/application/PhabricatorNuanceApplication.php | 5 ----- .../settings/application/PhabricatorSettingsApplication.php | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index cd268dd95e..dc18a6585f 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -18,11 +18,6 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { return true; } - public function isLaunchable() { - // Try to hide this even more for now. - return false; - } - public function getBaseURI() { return '/nuance/'; } diff --git a/src/applications/settings/application/PhabricatorSettingsApplication.php b/src/applications/settings/application/PhabricatorSettingsApplication.php index d5add43d46..e5e0034e1a 100644 --- a/src/applications/settings/application/PhabricatorSettingsApplication.php +++ b/src/applications/settings/application/PhabricatorSettingsApplication.php @@ -22,10 +22,6 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication { return false; } - public function isLaunchable() { - return false; - } - public function getRoutes() { $panel_pattern = '(?:page/(?P[^/]+)/(?:(?Psaved)/)?)?'; From b66bf6af9267b0ec40ab0383a1c2a914e05cac21 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Jun 2017 10:41:57 -0700 Subject: [PATCH 223/543] When stabilizing document scroll position for diffs, stick to anchors harder Summary: Ref T12779. Try a little harder to get the autoscroll heuristic right, but also just stick to anchors if the URL has an anchor and the scroll position is near that anchor. Test Plan: - Loaded an anchored diff at a bunch of window sizes, seemed pretty sticky. - Added `usleep(100000 * mt_rand(1, 15))` to `ChangesetViewController` to make changesets load slowly and in random order, reloaded a bunch of times while scrolling around, things appeared reasonable. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12779 Differential Revision: https://secure.phabricator.com/D18060 --- resources/celerity/map.php | 28 +++++++++---------- .../rsrc/js/application/diff/DiffChangeset.js | 27 ++++++++++++++++-- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fcd5a4bc50..0bde2d3862 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', - 'differential.pkg.js' => '5bf658f0', + 'differential.pkg.js' => '9cab3335', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '1f6748ae', + 'rsrc/js/application/diff/DiffChangeset.js' => 'aaaf4cb5', 'rsrc/js/application/diff/DiffChangesetList.js' => '85abc805', 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -770,7 +770,7 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '1f6748ae', + 'phabricator-diff-changeset' => 'aaaf4cb5', 'phabricator-diff-changeset-list' => '85abc805', 'phabricator-diff-inline' => '1d17130f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', @@ -1024,17 +1024,6 @@ return array( 'javelin-uri', 'javelin-routable', ), - '1f6748ae' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '1f6794f6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1716,6 +1705,17 @@ return array( 'javelin-util', 'phabricator-prefab', ), + 'aaaf4cb5' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'ab2f381b' => array( 'javelin-request', 'javelin-behavior', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 6b5ebf5317..b5ebe4ce70 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -516,14 +516,35 @@ JX.install('DiffChangeset', { var target_bot = (target_pos.y + target_dim.y); // Detect if the changeset is entirely (or, at least, almost entirely) - // above us. - var above_screen = (target_bot < old_pos.y + 128); + // above us. The height here is roughly the height of the persistent + // banner. + var above_screen = (target_bot < old_pos.y + 64); + + // If we have a URL anchor and are currently nearby, stick to it + // no matter what. + var on_target = null; + if (window.location.hash) { + try { + var anchor = JX.$(window.location.hash.replace('#', '')); + if (anchor) { + var anchor_pos = JX.$V(anchor); + if ((anchor_pos.y > old_pos.y) && + (anchor_pos.y < old_pos.y + 96)) { + on_target = anchor; + } + } + } catch (ignored) { + // If we have a bogus anchor, just ignore it. + } + } var frame = this._getContentFrame(); JX.DOM.setContent(frame, JX.$H(response.changeset)); if (this._stabilize) { - if (!near_top) { + if (on_target) { + JX.DOM.scrollToPosition(old_pos.x, JX.$V(on_target).y - 60); + } else if (!near_top) { if (near_bot || above_screen) { // Figure out how much taller the document got. var delta = (JX.Vector.getDocument().y - old_dim.y); From 9f8907ccf7f3943ee0ca846ff8712cdc32c1c72d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Jun 2017 13:00:16 -0700 Subject: [PATCH 224/543] Make Owners behavior when multiple packages own the same path with different dominion rules more consistent Summary: Fixes T12789. See that task for discussion. Currently, when multiple packages own the same path but have different dominion rules we get some weird/aribtrary/inconsistent results. Instead, implement these rules: - If zero or more weak and one or more strong packages claim a path, the strong packages (exactly) all own it. - If one or more weak packages and zero strong packages claim a path, the weak packages all own it. The major change here is that instead of keeping the //first// weak package we run into, we keep all the weak packages with the longest claim that we run into. This needs to be implemented twice because Owners has two different near-copies of this logic, only one of which has test coverage. Some day maybe this will get fixed. Test Plan: - Added failing unit tests, made them pass. - Viewed all A/B strong/weak combinations in Diffusion, saw sensible ownership results. Reviewers: chad, lvital Reviewed By: lvital Subscribers: lvital Maniphest Tasks: T12789 Differential Revision: https://secure.phabricator.com/D18064 --- .../query/PhabricatorOwnersPackageQuery.php | 29 +++++- .../storage/PhabricatorOwnersPackage.php | 55 +++++++++--- .../PhabricatorOwnersPackageTestCase.php | 89 +++++++++++++++++++ 3 files changed, 156 insertions(+), 17 deletions(-) diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index a1c10cd5e9..ce411aad35 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -398,13 +398,36 @@ final class PhabricatorOwnersPackageQuery } } + // At each strength level, drop weak packages if there are also strong + // packages of the same strength. + $strength_map = igroup($matches, 'strength'); + foreach ($strength_map as $strength => $package_list) { + $any_strong = false; + foreach ($package_list as $package_id => $package) { + if (!$package['weak']) { + $any_strong = true; + break; + } + } + if ($any_strong) { + foreach ($package_list as $package_id => $package) { + if ($package['weak']) { + unset($matches[$package_id]); + } + } + } + } + $matches = isort($matches, 'strength'); $matches = array_reverse($matches); - $first_id = null; + $strongest = null; foreach ($matches as $package_id => $match) { - if ($first_id === null) { - $first_id = $package_id; + if ($strongest === null) { + $strongest = $match['strength']; + } + + if ($match['strength'] === $strongest) { continue; } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 2b22a7a145..5f7b4f28c1 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -297,27 +297,54 @@ final class PhabricatorOwnersPackage // a more specific package. if ($weak) { foreach ($path_packages as $match => $packages) { + + // Group packages by length. + $length_map = array(); + foreach ($packages as $package_id => $package) { + $length_map[$package['length']][$package_id] = $package; + } + + // For each path length, remove all weak packages if there are any + // strong packages of the same length. This makes sure that if there + // are one or more strong claims on a particular path, only those + // claims stand. + foreach ($length_map as $package_list) { + $any_strong = false; + foreach ($package_list as $package_id => $package) { + if (!isset($weak[$package_id])) { + $any_strong = true; + break; + } + } + + if ($any_strong) { + foreach ($package_list as $package_id => $package) { + if (isset($weak[$package_id])) { + unset($packages[$package_id]); + } + } + } + } + $packages = isort($packages, 'length'); $packages = array_reverse($packages, true); - $first = null; + $best_length = null; foreach ($packages as $package_id => $package) { - // If this is the first package we've encountered, note it and - // continue. We're iterating over the packages from longest to - // shortest match, so this package always has the strongest claim - // on the path. - if ($first === null) { - $first = $package_id; + // If this is the first package we've encountered, note its length. + // We're iterating over the packages from longest to shortest match, + // so packages of this length always have the best claim on the path. + if ($best_length === null) { + $best_length = $package['length']; + } + + // If this package has the same length as the best length, its claim + // stands. + if ($package['length'] === $best_length) { continue; } - // If this is the first package we saw, its claim stands even if it - // is a weak package. - if ($first === $package_id) { - continue; - } - - // If this is a weak package and not the first package we saw, + // If this is a weak package and does not have the best length, // cede its claim to the stronger package. if (isset($weak[$package_id])) { unset($packages[$package_id]); diff --git a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php index 7d24fc6e8d..f21f09b44e 100644 --- a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php +++ b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php @@ -100,6 +100,95 @@ final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase { PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); + // Test cases where multiple packages own the same path, with various + // dominion rules. + + $main_c = 'src/applications/main/main.c'; + + $rules = array( + // All claims strong. + array( + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + ), + // All claims weak. + array( + PhabricatorOwnersPackage::DOMINION_WEAK, + PhabricatorOwnersPackage::DOMINION_WEAK, + PhabricatorOwnersPackage::DOMINION_WEAK, + ), + // Mixture of strong and weak claims, strong first. + array( + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_WEAK, + ), + // Mixture of strong and weak claims, weak first. + array( + PhabricatorOwnersPackage::DOMINION_WEAK, + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + ), + ); + + foreach ($rules as $rule_idx => $rule) { + $rows = array( + array( + 'id' => 1, + 'excluded' => 0, + 'dominion' => $rule[0], + 'path' => $main_c, + ), + array( + 'id' => 2, + 'excluded' => 0, + 'dominion' => $rule[1], + 'path' => $main_c, + ), + array( + 'id' => 3, + 'excluded' => 0, + 'dominion' => $rule[2], + 'path' => $main_c, + ), + ); + + $paths = array( + $main_c => $pvalue, + ); + + // If one or more packages have strong dominion, they should own the + // path. If not, all the packages with weak dominion should own the + // path. + $strong = array(); + $weak = array(); + foreach ($rule as $idx => $dominion) { + if ($dominion == PhabricatorOwnersPackage::DOMINION_STRONG) { + $strong[] = $idx + 1; + } else { + $weak[] = $idx + 1; + } + } + + if ($strong) { + $expect = $strong; + } else { + $expect = $weak; + } + + $expect = array_fill_keys($expect, strlen($main_c)); + $actual = PhabricatorOwnersPackage::findLongestPathsPerPackage( + $rows, + $paths); + + ksort($actual); + + $this->assertEqual( + $expect, + $actual, + pht('Ruleset "%s" for Identical Ownership', $rule_idx)); + } } } From b8ad999d50f6d7e28f2e2f76e453675f09335069 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 1 Jun 2017 13:30:00 -0700 Subject: [PATCH 225/543] Move simple buttons, bar to own CSS file Summary: - Add a simple green button... maybe don't need - Fix tokenizer search icon - Splite simple and button-bar into own files Test Plan: uiexamples, various pages with buttons, diffusion Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18063 --- resources/celerity/map.php | 30 +++-- resources/celerity/packages.php | 1 + src/view/phui/PHUIButtonBarView.php | 2 +- src/view/phui/PHUIButtonView.php | 1 + webroot/rsrc/css/aphront/tokenizer.css | 1 + .../rsrc/css/phui/button/phui-button-bar.css | 61 +++++++++ .../css/phui/button/phui-button-simple.css | 105 +++++++++++++++ .../css/phui/{ => button}/phui-button.css | 123 ------------------ 8 files changed, 191 insertions(+), 133 deletions(-) create mode 100644 webroot/rsrc/css/phui/button/phui-button-bar.css create mode 100644 webroot/rsrc/css/phui/button/phui-button-simple.css rename webroot/rsrc/css/phui/{ => button}/phui-button.css (68%) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0bde2d3862..c78cd104c5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '38689e09', + 'core.pkg.css' => 'e8d63571', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -33,7 +33,7 @@ return array( 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc', 'rsrc/css/aphront/table-view.css' => '34cf86b4', - 'rsrc/css/aphront/tokenizer.css' => '9a8cb501', + 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', 'rsrc/css/aphront/tooltip.css' => '173b9431', 'rsrc/css/aphront/typeahead-browse.css' => '4f82e510', 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', @@ -124,6 +124,9 @@ return array( 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', + 'rsrc/css/phui/button/phui-button-bar.css' => '39fe680c', + 'rsrc/css/phui/button/phui-button-simple.css' => 'd410596b', + 'rsrc/css/phui/button/phui-button.css' => '9f13ddcc', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', @@ -140,7 +143,6 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '836844c9', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -539,7 +541,7 @@ return array( 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', 'aphront-table-view-css' => '34cf86b4', - 'aphront-tokenizer-control-css' => '9a8cb501', + 'aphront-tokenizer-control-css' => '15d5ff71', 'aphront-tooltip-css' => '173b9431', 'aphront-typeahead-control-css' => '8a84cc7d', 'application-search-view-css' => '66ee5d46', @@ -817,7 +819,9 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '836844c9', + 'phui-button-bar-css' => '39fe680c', + 'phui-button-css' => '9f13ddcc', + 'phui-button-simple-css' => 'd410596b', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -973,6 +977,10 @@ return array( 'javelin-dom', 'javelin-history', ), + '15d5ff71' => array( + 'aphront-typeahead-control-css', + 'phui-tag-view-css', + ), 16053029 => array( 'phui-theme-css', ), @@ -1102,6 +1110,10 @@ return array( 'javelin-dom', 'javelin-vector', ), + '39fe680c' => array( + 'phui-button-css', + 'phui-button-simple-css', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1618,10 +1630,6 @@ return array( 'phuix-icon-view', 'javelin-behavior-phabricator-gesture', ), - '9a8cb501' => array( - 'aphront-typeahead-control-css', - 'phui-tag-view-css', - ), '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', @@ -1971,6 +1979,9 @@ return array( 'd254d646' => array( 'javelin-util', ), + 'd410596b' => array( + 'phui-button-css', + ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', @@ -2177,6 +2188,7 @@ return array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', + 'phui-button-simple-css', 'phui-theme-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index e756e696cb..abbf1d77e2 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -93,6 +93,7 @@ return array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', + 'phui-button-simple-css', 'phui-theme-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', diff --git a/src/view/phui/PHUIButtonBarView.php b/src/view/phui/PHUIButtonBarView.php index add91bbc8f..78443929d8 100644 --- a/src/view/phui/PHUIButtonBarView.php +++ b/src/view/phui/PHUIButtonBarView.php @@ -29,7 +29,7 @@ final class PHUIButtonBarView extends AphrontTagView { } protected function getTagContent() { - require_celerity_resource('phui-button-css'); + require_celerity_resource('phui-button-bar-css'); $i = 1; $j = count($this->buttons); diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index 111a257108..fff6b089e4 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -154,6 +154,7 @@ final class PHUIButtonView extends AphrontTagView { protected function getTagAttributes() { require_celerity_resource('phui-button-css'); + require_celerity_resource('phui-button-simple-css'); $classes = array(); $classes[] = 'button'; diff --git a/webroot/rsrc/css/aphront/tokenizer.css b/webroot/rsrc/css/aphront/tokenizer.css index 7116ce3ace..3f3f904e22 100644 --- a/webroot/rsrc/css/aphront/tokenizer.css +++ b/webroot/rsrc/css/aphront/tokenizer.css @@ -181,4 +181,5 @@ a.jx-tokenizer-token-invalid:hover { .button.tokenizer-browse-button .phui-icon-view { top: 7px; left: 9px; + position: absolute; } diff --git a/webroot/rsrc/css/phui/button/phui-button-bar.css b/webroot/rsrc/css/phui/button/phui-button-bar.css new file mode 100644 index 0000000000..17de25e808 --- /dev/null +++ b/webroot/rsrc/css/phui/button/phui-button-bar.css @@ -0,0 +1,61 @@ +/** + * @provides phui-button-bar-css + * @requires phui-button-css + * @requires phui-button-simple-css + */ + +.phui-button-bar-borderless .button { + border: 0; + background-color: transparent; + background-image: none; + padding-left: 10px; + padding-right: 10px; +} + +.phui-button-bar-borderless .button .phui-icon-view { + font-size: 15px; + color: rgba({$alphagrey},.4); +} + +.phui-button-bar-borderless .button:hover { + background-color: transparent; + background-image: none; + border-radius: 3px; +} + +.phui-button-bar-borderless .button:hover .phui-icon-view { + color: rgba({$alphagrey},.9); +} + +.phui-button-bar-borderless .button { + border: 0; +} + +.phui-button-bar .phui-button-bar-first { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} + +.phui-button-bar .phui-button-bar-middle { + border-radius: 0; + border-left: none; +} + +.phui-button-bar .phui-button-bar-last { + border-left: none; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; +} + +.phui-button-bar .button.simple:hover { + border-color: {$lightblueborder}; + background-color: #fff; + background-image: none; + color: {$sky}; +} + +.phui-button-bar .button.simple:hover .phui-icon-view { + border-color: {$lightblueborder}; + color: {$sky}; + background-image: none; +} diff --git a/webroot/rsrc/css/phui/button/phui-button-simple.css b/webroot/rsrc/css/phui/button/phui-button-simple.css new file mode 100644 index 0000000000..f631604936 --- /dev/null +++ b/webroot/rsrc/css/phui/button/phui-button-simple.css @@ -0,0 +1,105 @@ +/** + * @provides phui-button-simple-css + * @requires phui-button-css + */ + + +/* - Basic -------------------------------------------------------------------*/ + +button.simple, +input[type="submit"].simple, +a.simple, +a.simple:visited { + background: #fff; + color: {$bluetext}; + border: 1px solid {$lightblueborder}; +} + +button.simple .phui-icon-view, +input[type="submit"].simple .phui-icon-view, +a.simple .phui-icon-view, +a.simple:visited .phui-icon-view { + color: {$lightbluetext}; +} + +a.button.simple:hover, +button.simple:hover { + border-color: {$blueborder}; + background-image: none; + background-color: #fff; + transition: 0s; +} + +a.simple.current { + background: {$lightblue}; +} + + +/* - Red --------------------------------------------------------------------*/ + +button.simple.red, +input[type="submit"].simple.red, +a.simple.red, +a.simple.red:visited { + background: #fff; + color: {$redtext}; + border: 1px solid {$sh-redborder}; +} + +button.simple.red .phui-icon-view, +input[type="submit"].simple.red .phui-icon-view, +a.simple.red .phui-icon-view, +a.simple.red:visited .phui-icon-view { + color: {$redtext}; +} + +a.button.simple.red:hover, +button.simple.red:hover { + border-color: {$red}; + background-image: none; + background-color: #fff; + transition: 0s; +} + +/* - Green ------------------------------------------------------------------*/ + +button.simple.green, +input[type="submit"].simple.green, +a.simple.green, +a.simple.green:visited { + background: #fff; + color: {$greentext}; + border: 1px solid {$sh-greenborder}; +} + +button.simple.green .phui-icon-view, +input[type="submit"].simple.green .phui-icon-view, +a.simple.green .phui-icon-view, +a.simple.green:visited .phui-icon-view { + color: {$greentext}; +} + +a.button.simple.green:hover, +button.simple.green:hover { + border-color: {$green}; + background-image: none; + background-color: #fff; + transition: 0s; +} + + +/* - Misc -------------------------------------------------------------------*/ + +a.button.simple .phui-icon-view { + border: none; +} + +a.button.simple.phuix-dropdown-open { + background-color: #fff; + color: {$blue}; + box-shadow: none; +} + +a.button.simple.phuix-dropdown-open:hover .phui-icon-view { + color: {$blue}; +} diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css similarity index 68% rename from webroot/rsrc/css/phui/phui-button.css rename to webroot/rsrc/css/phui/button/phui-button.css index 5b35eaa681..d8183febd2 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -86,42 +86,6 @@ a.grey:visited { color: {$darkgreytext}; } -button.simple, -input[type="submit"].simple, -a.simple, -a.simple:visited { - background: #fff; - color: {$bluetext}; - border: 1px solid {$lightblueborder}; -} - -a.simple.current { - background: {$lightblue}; -} - -button.simple .phui-icon-view, -input[type="submit"].simple .phui-icon-view, -a.simple .phui-icon-view, -a.simple:visited .phui-icon-view { - color: {$lightbluetext}; -} - -button.simple.red, -input[type="submit"].simple.red, -a.simple.red, -a.simple.red:visited { - background: #fff; - color: {$redtext}; - border: 1px solid {$sh-redborder}; -} - -button.simple.red .phui-icon-view, -input[type="submit"].simple.red .phui-icon-view, -a.simple.red .phui-icon-view, -a.simple.red:visited .phui-icon-view { - color: {$redtext}; -} - a.disabled, button.disabled, button[disabled] { @@ -159,36 +123,6 @@ button.green:hover { transition: 0.1s; } -a.button.simple:hover, -button.simple:hover { - border-color: {$blueborder}; - background-image: none; - background-color: #fff; - transition: 0s; -} - -a.button.simple.red:hover, -button.simple.red:hover { - border-color: {$red}; - background-image: none; - background-color: #fff; - transition: 0s; -} - -a.button.simple .phui-icon-view { - border: none; -} - -a.button.simple.phuix-dropdown-open { - background-color: #fff; - color: {$blue}; - box-shadow: none; -} - -a.button.simple.phuix-dropdown-open:hover .phui-icon-view { - color: {$blue}; -} - body a.button.disabled:hover, body button.disabled:hover, body a.button.disabled:active, @@ -359,60 +293,3 @@ a.policy-control .phui-button-text { font-weight: normal; } -/* PHUI Button Bar */ - -.phui-button-bar-borderless .button { - border: 0; - background-color: transparent; - background-image: none; - padding-left: 10px; - padding-right: 10px; -} - -.phui-button-bar-borderless .button .phui-icon-view { - font-size: 15px; - color: rgba({$alphagrey},.4); -} - -.phui-button-bar-borderless .button:hover { - background-color: transparent; - background-image: none; - border-radius: 3px; -} - -.phui-button-bar-borderless .button:hover .phui-icon-view { - color: rgba({$alphagrey},.9); -} - -.phui-button-bar-borderless .button { - border: 0; -} - -.phui-button-bar .phui-button-bar-first { - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; -} - -.phui-button-bar .phui-button-bar-middle { - border-radius: 0; - border-left: none; -} - -.phui-button-bar .phui-button-bar-last { - border-left: none; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; -} - -.phui-button-bar .button.simple:hover { - border-color: {$lightblueborder}; - background-color: #fff; - background-image: none; - color: {$sky}; -} - -.phui-button-bar .button.simple:hover .phui-icon-view { - border-color: {$lightblueborder}; - color: {$sky}; - background-image: none; -} From d42d69aef6993d126428ba29e4ba9d8794ba2485 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Jun 2017 04:02:56 -0700 Subject: [PATCH 226/543] Clean up some PHUI/PHUIX button behaviors Summary: Ref T12733. Some minor issues: - The `strlen(...)` test against `$this->text` fails if a caller does something like `setText(array(...))`. This is rare, but used in `DiffusionBrowseController`, from D15487. - Add PHUIX examples for icon-only buttons. - Remove unused `SIMPLE` constant now that no callsites remain. Test Plan: - Viewed a directory in Diffusion's "Browse" view in a Git repository, no longer saw a warning / error log. - Viewed PHUIX Components UI examples. - Grepped for `::SIMPLE`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18065 --- .../examples/PHUIXComponentsExample.php | 17 +++++++++++++++++ src/view/phui/PHUIButtonView.php | 5 ++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/applications/uiexample/examples/PHUIXComponentsExample.php b/src/applications/uiexample/examples/PHUIXComponentsExample.php index a5c76a1e77..0b6d90f28f 100644 --- a/src/applications/uiexample/examples/PHUIXComponentsExample.php +++ b/src/applications/uiexample/examples/PHUIXComponentsExample.php @@ -72,6 +72,23 @@ final class PHUIXComponentsExample extends PhabricatorUIExample { 'color' => PHUIButtonView::GREEN, 'text' => pht('Environmental!'), ), + array( + 'icon' => 'fa-cog', + ), + array( + 'icon' => 'fa-cog', + 'type' => PHUIButtonView::BUTTONTYPE_SIMPLE, + ), + array( + 'text' => array('2 + 2', ' ', '=', ' ', '4'), + ), + array( + 'color' => PHUIButtonView::GREY, + 'text' => pht('Cancel'), + ), + array( + 'text' => array(''), + ), ); foreach ($buttons as $spec) { diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index fff6b089e4..2e269bd9af 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -5,7 +5,6 @@ final class PHUIButtonView extends AphrontTagView { const GREEN = 'green'; const GREY = 'grey'; const DISABLED = 'disabled'; - const SIMPLE = 'simple'; const SMALL = 'small'; const BIG = 'big'; @@ -175,7 +174,7 @@ final class PHUIButtonView extends AphrontTagView { $classes[] = 'has-icon'; } - if (strlen($this->text)) { + if ($this->text !== null) { $classes[] = 'has-text'; } @@ -238,7 +237,7 @@ final class PHUIButtonView extends AphrontTagView { $this->subtext); } - if (strlen($this->text)) { + if ($this->text !== null) { $text = phutil_tag( 'div', array( From 335c3a7d12bdcbf9a88454de7db8d7895b98cfbb Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Jun 2017 04:37:11 -0700 Subject: [PATCH 227/543] In commit history list view, show all commits Summary: Currently, the last group of commits is not shown in the list view because the final `$list` is never added to `$view`. For example, if the first page would contain commits from "April 7", "April 6", and "April 5", commits from "April 5" are not shown. (If a repository has 100 commits in a single day, nothing is shown.) On this server, here's the bottom of page 1: {F4987087} Here's the top of page 2: {F4987088} However, here's `git log` between those commits: ``` $ git log --oneline 7e46^..5f49f 5f49f9c793 Add sound to logged out Conpherence 1644b45050 Disperse task subpriorities in blocks c6a7bcfe89 Make Pholio description behave as a remarkup field (e.g., subscribe mentioned users) bbc5f79227 Make membership lock/unlock feed stories read more naturally 789d57522b Make editing project images redirect to "Manage" more consistently 10b3879232 Make Project slug/hashtag transactions render a little more nicely abd791889c Update Maniphest title transaction again 5a34b299e4 Update Maniphest title language 601622013d Clarify milestone/subproject creation language c9889e3d55 Fix an issue in Phriction where moving a document just copied it instead fdf00f6df4 Clean up some minor UI behaviors in Differential 6c46f27d98 Add quest objectives to the minimap d783299a19 Fix Phriction status not set property on new document 93e28da76e Add more "disabled" UI to PHUIObjectItemView 7e46d7ab6a Migrate Project color to modular transactions ``` This group of commits does not currently appear anywhere in the list. Test Plan: Viewed a page of commits, saw 100 commits. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18066 --- .../diffusion/view/DiffusionHistoryListView.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 80ca5d76a8..486955c4d0 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -26,19 +26,12 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { $rows = array(); $ii = 0; $cur_date = 0; - $list = null; $header = null; $view = array(); foreach ($this->getHistory() as $history) { $epoch = $history->getEpoch(); $new_date = date('Ymd', $history->getEpoch()); if ($cur_date != $new_date) { - if ($list) { - $view[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - } $date = ucfirst( phabricator_relative_date($history->getEpoch(), $viewer)); $header = id(new PHUIHeaderView()) @@ -46,6 +39,11 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { $list = id(new PHUIObjectItemListView()) ->setFlush(true) ->addClass('diffusion-history-list'); + + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); } if ($epoch) { From 5335f29ff237d8b61d7f83e23e9df26ce19c2505 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Jun 2017 04:47:59 -0700 Subject: [PATCH 228/543] Correct an issue where the commit list could group commits by server time Summary: Commits in the list are grouped by the date they occurred in server time. This may not be the date they occurred in client time. Use client time, not server time, to group commits. Test Plan: - Set server timezone to "Asia/Famagusta". - Set client timezone to "America/Los_Angeles". - Viewed Phabricator repository history. Here's what it looks like before the change: {F4987094} Note that the headers of the first two groups both say "Yesterday". This is because the first commits in each group occurred on June 1 and June 2, respectively, in Famagusta, but both occurred on June 1 in Los Angeles. Here's what it looks like after the change: {F4987095} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18067 --- .../diffusion/view/DiffusionHistoryListView.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 486955c4d0..533e15d644 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -23,15 +23,12 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { 'PhabricatorHarbormasterApplication', $this->getUser()); - $rows = array(); - $ii = 0; - $cur_date = 0; - $header = null; + $cur_date = null; $view = array(); foreach ($this->getHistory() as $history) { $epoch = $history->getEpoch(); - $new_date = date('Ymd', $history->getEpoch()); - if ($cur_date != $new_date) { + $new_date = phabricator_date($history->getEpoch(), $viewer); + if ($cur_date !== $new_date) { $date = ucfirst( phabricator_relative_date($history->getEpoch(), $viewer)); $header = id(new PHUIHeaderView()) From 0a3b3911360694ee4d29a6905b05a925d03bbcf9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 2 Jun 2017 17:32:39 +0000 Subject: [PATCH 229/543] Add more simple button colors Summary: Saturate the color a little more, add yellow Test Plan: uiexamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18068 --- resources/celerity/map.php | 12 +++--- .../uiexample/examples/PHUIButtonExample.php | 18 +++++---- .../css/phui/button/phui-button-simple.css | 38 ++++++++++++++++--- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c78cd104c5..702bc0e609 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'e8d63571', + 'core.pkg.css' => 'ea94e844', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -125,7 +125,7 @@ return array( 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', 'rsrc/css/phui/button/phui-button-bar.css' => '39fe680c', - 'rsrc/css/phui/button/phui-button-simple.css' => 'd410596b', + 'rsrc/css/phui/button/phui-button-simple.css' => '081cfeea', 'rsrc/css/phui/button/phui-button.css' => '9f13ddcc', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', @@ -821,7 +821,7 @@ return array( 'phui-box-css' => '269cbc99', 'phui-button-bar-css' => '39fe680c', 'phui-button-css' => '9f13ddcc', - 'phui-button-simple-css' => 'd410596b', + 'phui-button-simple-css' => '081cfeea', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -936,6 +936,9 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), + '081cfeea' => array( + 'phui-button-css', + ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -1979,9 +1982,6 @@ return array( 'd254d646' => array( 'javelin-util', ), - 'd410596b' => array( - 'phui-button-css', - ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index 8758264e7d..aed5d5bf08 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -175,15 +175,19 @@ final class PHUIButtonExample extends PhabricatorUIExample { $designs = array( PHUIButtonView::BUTTONTYPE_SIMPLE, ); + $colors = array('', 'red', 'green', 'yellow'); $column = array(); foreach ($designs as $design) { - foreach ($icons as $text => $icon) { - $column[] = id(new PHUIButtonView()) - ->setTag('a') - ->setButtonType($design) - ->setIcon($icon) - ->setText($text) - ->addClass(PHUI::MARGIN_SMALL_RIGHT); + foreach ($colors as $color) { + foreach ($icons as $text => $icon) { + $column[] = id(new PHUIButtonView()) + ->setTag('a') + ->setButtonType($design) + ->setColor($color) + ->setIcon($icon) + ->setText($text) + ->addClass(PHUI::MARGIN_SMALL_RIGHT); + } } } diff --git a/webroot/rsrc/css/phui/button/phui-button-simple.css b/webroot/rsrc/css/phui/button/phui-button-simple.css index f631604936..fa0a8d11b2 100644 --- a/webroot/rsrc/css/phui/button/phui-button-simple.css +++ b/webroot/rsrc/css/phui/button/phui-button-simple.css @@ -41,7 +41,7 @@ button.simple.red, input[type="submit"].simple.red, a.simple.red, a.simple.red:visited { - background: #fff; + background: {$sh-redbackground}; color: {$redtext}; border: 1px solid {$sh-redborder}; } @@ -55,9 +55,9 @@ a.simple.red:visited .phui-icon-view { a.button.simple.red:hover, button.simple.red:hover { - border-color: {$red}; + border-color: {$sh-redtext}; background-image: none; - background-color: #fff; + background-color: {$sh-redbackground}; transition: 0s; } @@ -67,7 +67,7 @@ button.simple.green, input[type="submit"].simple.green, a.simple.green, a.simple.green:visited { - background: #fff; + background: {$sh-greenbackground}; color: {$greentext}; border: 1px solid {$sh-greenborder}; } @@ -81,9 +81,35 @@ a.simple.green:visited .phui-icon-view { a.button.simple.green:hover, button.simple.green:hover { - border-color: {$green}; + border-color: {$sh-greentext}; background-image: none; - background-color: #fff; + background-color: {$sh-greenbackground}; + transition: 0s; +} + +/* - Yellow -----------------------------------------------------------------*/ + +button.simple.yellow, +input[type="submit"].simple.yellow, +a.simple.yellow, +a.simple.yellow:visited { + background-color: {$sh-yellowbackground}; + color: {$sh-yellowtext}; + border: 1px solid {$sh-yellowborder}; +} + +button.simple.yellow .phui-icon-view, +input[type="submit"].simple.yellow .phui-icon-view, +a.simple.yellow .phui-icon-view, +a.simple.yellow:visited .phui-icon-view { + color: {$sh-yellowicon}; +} + +a.button.simple.yellow:hover, +button.simple.yellow:hover { + border-color: {$sh-yellowtext}; + background-image: none; + background-color: {$sh-yellowbackground}; transition: 0s; } From 17f092307cdfe4891ee66075e2665abb3578a976 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Jun 2017 16:10:59 -0700 Subject: [PATCH 230/543] Fix an issue where Phriction moves to new locations would fail with a "content required" error Summary: Ref T12793. I'd like to understand exactly when we broke this, but this seems to be a minimal fix that shouldn't do anything surprising. When you move document `/a/` to `/a/b/` and that path doesn't exist yet, the Content transaction currently fails because there's "no content". The content gets added later by the "move" transaction but this is implicit. To make this work, just ignore the "missing field" error. This is a little roundabout but unlikely to break anything in weird ways. Test Plan: - Moved document `/a/b/` to `/a/b/c/`. - Before patch: error about missing content. - After patch: move worked properly. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12793 Differential Revision: https://secure.phabricator.com/D18069 --- .../phriction/controller/PhrictionMoveController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/phriction/controller/PhrictionMoveController.php b/src/applications/phriction/controller/PhrictionMoveController.php index f25465c3c0..b82681f0a7 100644 --- a/src/applications/phriction/controller/PhrictionMoveController.php +++ b/src/applications/phriction/controller/PhrictionMoveController.php @@ -60,6 +60,7 @@ final class PhrictionMoveController extends PhrictionController { ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) ->setDescription($v_note); $xactions = array(); From f66c6e5c1fc8c8d230993b85e412ec2148d4e144 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 4 Jun 2017 08:52:36 -0700 Subject: [PATCH 231/543] Fix Phriction toc button states Summary: Some unneeded CSS here. Test Plan: Click on ToC button, see more normal colors. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18073 --- resources/celerity/map.php | 10 +++++----- webroot/rsrc/css/phui/phui-document-pro.css | 17 ++--------------- webroot/rsrc/css/phui/phui-header-view.css | 2 +- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 702bc0e609..bced050105 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'ea94e844', + 'core.pkg.css' => '8ac2e6b8', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -149,7 +149,7 @@ return array( 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', - 'rsrc/css/phui/phui-document-pro.css' => 'bb18da6b', + 'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', @@ -157,7 +157,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'a3d1aecd', + 'rsrc/css/phui/phui-header-view.css' => '73edce66', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '4c46b6ba', @@ -834,14 +834,14 @@ return array( 'phui-curtain-view-css' => '55dd0e59', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', - 'phui-document-view-pro-css' => 'bb18da6b', + 'phui-document-view-pro-css' => '8af7ea27', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'a3d1aecd', + 'phui-header-view-css' => '73edce66', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index a4c05a0ecd..60b5106154 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -64,21 +64,13 @@ body.printable { .phui-document-view-pro .phui-document-toc { position: absolute; top: 34px; - left: -36px; + left: -44px; } .printable .phui-document-view-pro a.phui-document-toc { display: none; } -a.button.phui-document-toc { - display: inline-block; - height: 16px; - width: 20px; - padding-left: 8px; - padding-right: 8px; -} - .phui-document-view-pro .phui-document-toc-list { margin: 8px; border: 1px solid {$lightgreyborder}; @@ -105,12 +97,7 @@ a.button.phui-document-toc { } .phui-document-toc-open .phui-document-toc { - background-color: {$blue}; - border-color: {$blue}; -} - -.phui-document-toc-open .phui-document-toc .phui-icon-view { - color: #fff; + border-color: {$blueborder}; } .phui-document-view-pro .phui-document-toc-content { diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 470e821544..f0c37a1a75 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -120,7 +120,7 @@ body .phui-header-shell.phui-bleed-header .device-phone .phui-header-action-link .phui-button-text { visibility: hidden; width: 0; - margin-left: 4px; + margin-left: 8px; } .device-phone .phui-header-action-link.button .phui-icon-view { From 65c9d789d221774bd3bac5b46a57f04f74fa5723 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 4 Jun 2017 11:52:27 -0700 Subject: [PATCH 232/543] Add a borderless tag style Summary: Formally support borderless tags in PHUITagView. Test Plan: Used in Diffusion History List Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18074 --- resources/celerity/map.php | 10 +++++----- .../diffusion/controller/DiffusionController.php | 1 + .../diffusion/view/DiffusionHistoryListView.php | 3 ++- src/view/phui/PHUITagView.php | 12 ++++++++++++ .../css/application/diffusion/diffusion-history.css | 13 ------------- webroot/rsrc/css/phui/phui-tag-view.css | 9 +++++++++ 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bced050105..41f1a77b03 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '8ac2e6b8', + 'core.pkg.css' => '5284a0e0', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => 'de70e348', + 'rsrc/css/application/diffusion/diffusion-history.css' => 'cc283766', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -175,7 +175,7 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => '3fa7765e', + 'rsrc/css/phui/phui-tag-view.css' => '93b084cf', 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', @@ -567,7 +567,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => 'de70e348', + 'diffusion-history-css' => 'cc283766', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', @@ -867,7 +867,7 @@ return array( 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => '3fa7765e', + 'phui-tag-view-css' => '93b084cf', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => '313c7f22', 'phui-two-column-view-css' => 'ce9fa0b7', diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index a296761bdf..52465da410 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -333,6 +333,7 @@ abstract class DiffusionController extends PhabricatorController { $tag = id(new PHUITagView()) ->setName($commit) ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) ->setType(PHUITagView::TYPE_SHADE); return $tag; diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 533e15d644..9d6727a357 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -110,7 +110,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ->setType(PHUITagView::TYPE_SHADE) ->setColor(PHUITagView::COLOR_BLUE) ->setHref('/D'.$d_id) - ->addClass('diffusion-differential-tag') + ->setBorder(PHUITagView::BORDER_NONE) ->setSlimShady(true); } } @@ -155,6 +155,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { ->setName($commit_name) ->setType(PHUITagView::TYPE_SHADE) ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) ->setSlimShady(true); $item = id(new PHUIObjectItemView()) diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index 1811f8f1a9..292246c4a7 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -28,6 +28,8 @@ final class PHUITagView extends AphrontTagView { const COLOR_OBJECT = 'object'; const COLOR_PERSON = 'person'; + const BORDER_NONE = 'border-none'; + private $type; private $href; private $name; @@ -40,6 +42,7 @@ final class PHUITagView extends AphrontTagView { private $icon; private $shade; private $slimShady; + private $border; public function setType($type) { $this->type = $type; @@ -104,6 +107,11 @@ final class PHUITagView extends AphrontTagView { return $this; } + public function setBorder($border) { + $this->border = $border; + return $this; + } + public function setIcon($icon) { $this->icon = $icon; return $this; @@ -142,6 +150,10 @@ final class PHUITagView extends AphrontTagView { $classes[] = 'phui-tag-icon-view'; } + if ($this->border) { + $classes[] = 'phui-tag-'.$this->border; + } + if ($this->phid) { Javelin::initBehavior('phui-hovercards'); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index fd22f9b1a5..b90977373f 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -7,14 +7,6 @@ font-size: {$biggerfontsize}; } -.diffusion-history-list .phui-oi-attribute .phui-tag-core { - border-color: transparent; -} - -.diffusion-history-list .phui-oi-attribute .phui-tag-indigo a { - color: {$indigo}; -} - .diffusion-history-message { background-color: {$bluebackground}; padding: 16px; @@ -35,8 +27,3 @@ .diffusion-history-list .diffusion-differential-tag { margin-left: 4px; } - -a.phui-tag-view:hover.diffusion-differential-tag .phui-tag-core { - border-color: transparent; - text-decoration: underline; -} diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index 56b5badf8d..885b20c12b 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -154,6 +154,15 @@ a.phui-tag-view:hover margin: 0 4px 2px 0; } +.phui-tag-view.phui-tag-border-none .phui-tag-core { + border-color: transparent; +} + +a.phui-tag-view:hover.phui-tag-border-none .phui-tag-core { + border-color: transparent !important; + text-decoration: underline; +} + /* - Shaded Tags --------------------------------------------------------------- From 48c6ca40c44f276a403b4fb4782f78e3549f98de Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 3 Jun 2017 06:21:45 -0700 Subject: [PATCH 233/543] Add an "Unsaved" button to the Differential persistent header Summary: Ref T12733. Test Plan: {F4987956} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18070 --- resources/celerity/map.php | 63 ++++----- .../view/DifferentialChangesetListView.php | 4 + .../differential/changeset-view.css | 4 + .../rsrc/js/application/diff/DiffChangeset.js | 28 +++- .../js/application/diff/DiffChangesetList.js | 126 ++++++++++++++++-- webroot/rsrc/js/phuix/PHUIXButtonView.js | 1 + 6 files changed, 181 insertions(+), 45 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 41f1a77b03..b403d7199a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => '5284a0e0', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => 'a2755617', - 'differential.pkg.js' => '9cab3335', + 'differential.pkg.css' => '1ccbf3a9', + 'differential.pkg.js' => 'b453b745', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '983751ee', + 'rsrc/css/application/differential/changeset-view.css' => 'c3f44655', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -393,8 +393,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'aaaf4cb5', - 'rsrc/js/application/diff/DiffChangesetList.js' => '85abc805', + 'rsrc/js/application/diff/DiffChangeset.js' => 'fc3919c8', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'ffa063d8', 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -526,7 +526,7 @@ return array( 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', - 'rsrc/js/phuix/PHUIXButtonView.js' => '0f13520b', + 'rsrc/js/phuix/PHUIXButtonView.js' => 'b3c515be', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', @@ -560,7 +560,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '983751ee', + 'differential-changeset-view-css' => 'c3f44655', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -772,8 +772,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'aaaf4cb5', - 'phabricator-diff-changeset-list' => '85abc805', + 'phabricator-diff-changeset' => 'fc3919c8', + 'phabricator-diff-changeset-list' => 'ffa063d8', 'phabricator-diff-inline' => '1d17130f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -878,7 +878,7 @@ return array( 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', 'phuix-autocomplete' => 'f6699267', - 'phuix-button-view' => '0f13520b', + 'phuix-button-view' => 'b3c515be', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -960,10 +960,6 @@ return array( 'javelin-dom', 'javelin-router', ), - '0f13520b' => array( - 'javelin-install', - 'javelin-dom', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1514,9 +1510,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - '85abc805' => array( - 'javelin-install', - ), '85ee8ce6' => array( 'aphront-dialog-view-css', ), @@ -1621,9 +1614,6 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), - '983751ee' => array( - 'phui-inline-comment-view-css', - ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1716,17 +1706,6 @@ return array( 'javelin-util', 'phabricator-prefab', ), - 'aaaf4cb5' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'ab2f381b' => array( 'javelin-request', 'javelin-behavior', @@ -1785,6 +1764,10 @@ return array( 'javelin-behavior', 'phabricator-prefab', ), + 'b3c515be' => array( + 'javelin-install', + 'javelin-dom', + ), 'b3e7d692' => array( 'javelin-install', ), @@ -1877,6 +1860,9 @@ return array( 'javelin-dom', 'javelin-vector', ), + 'c3f44655' => array( + 'phui-inline-comment-view-css', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2155,6 +2141,17 @@ return array( 'javelin-behavior-device', 'phabricator-keyboard-shortcut', ), + 'fc3919c8' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'fc91ab6c' => array( 'javelin-behavior', 'javelin-dom', @@ -2166,6 +2163,10 @@ return array( 'javelin-view-visitor', 'javelin-util', ), + 'ffa063d8' => array( + 'javelin-install', + 'phuix-button-view', + ), ), 'packages' => array( 'conpherence.pkg.css' => array( diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index b8090b88a2..f113afacf8 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -275,6 +275,10 @@ final class DifferentialChangesetListView extends AphrontView { pht('Hide or show the current file.'), 'You must select a file to hide or show.' => pht('You must select a file to hide or show.'), + + 'Unsaved' => pht('Unsaved'), + 'Unsubmitted' => pht('Unsubmitted'), + 'Comments' => pht('Comments'), ), )); diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index f1be2b5f99..33dcea1213 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -413,3 +413,7 @@ tr.differential-inline-loading { .diff-banner-has-unsubmitted { background: {$sh-yellowbackground}; } + +.diff-banner-buttons { + float: right; +} diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index b5ebe4ce70..b00218bcfa 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -403,6 +403,8 @@ JX.install('DiffChangeset', { block.items.push(rows[ii]); } + var last_inline = null; + var last_inline_item = null; for (ii = 0; ii < blocks.length; ii++) { block = blocks[ii]; @@ -422,16 +424,38 @@ JX.install('DiffChangeset', { for (var jj = 0; jj < block.items.length; jj++) { var inline = this.getInlineForRow(block.items[jj]); - items.push({ + // When comments are being edited, they have a hidden row with + // the actual comment and then a visible row with the editor. + + // In this case, we only want to generate one item, but it should + // use the editor as a scroll target. To accomplish this, check if + // this row has the same inline as the previous row. If so, update + // the last item to use this row's nodes. + + if (inline === last_inline) { + last_inline_item.nodes.begin = block.items[jj]; + last_inline_item.nodes.end = block.items[jj]; + continue; + } else { + last_inline = inline; + } + + last_inline_item = { type: block.type, changeset: this, target: inline, hidden: inline.isHidden(), + deleted: !inline.getID() && !inline.isEditing(), nodes: { begin: block.items[jj], end: block.items[jj] + }, + attributes: { + unsaved: inline.isEditing() } - }); + }; + + items.push(last_inline_item); } } } diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index b79f05296e..ff0afc21e4 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1,6 +1,7 @@ /** * @provides phabricator-diff-changeset-list * @requires javelin-install + * phuix-button-view * @javelin */ @@ -111,6 +112,7 @@ JX.install('DiffChangesetList', { _rangeTarget: null, _bannerNode: null, + _unsavedButton: null, sleep: function() { this._asleep = true; @@ -258,7 +260,13 @@ JX.install('DiffChangesetList', { _installJumpKey: function(key, label, delta, filter, show_hidden) { filter = filter || null; - var handler = JX.bind(this, this._onjumpkey, delta, filter, show_hidden); + + var options = { + filter: filter, + hidden: show_hidden + }; + + var handler = JX.bind(this, this._onjumpkey, delta, options); return this._installKey(key, label, handler); }, @@ -440,9 +448,14 @@ JX.install('DiffChangesetList', { .show(); }, - _onjumpkey: function(delta, filter, show_hidden, manager) { + _onjumpkey: function(delta, options) { var state = this._getSelectionState(); + var filter = options.filter || null; + var hidden = options.hidden || false; + var wrap = options.wrap || false; + var attribute = options.attribute || null; + var cursor = state.cursor; var items = state.items; @@ -452,6 +465,7 @@ JX.install('DiffChangesetList', { return; } + var did_wrap = false; while (true) { if (cursor === null) { cursor = 0; @@ -464,9 +478,22 @@ JX.install('DiffChangesetList', { return; } - // If we've gone forward off the end of the list, bail out. + // If we've gone forward off the end of the list, figure out where we + // should end up. if (cursor >= items.length) { - return; + if (!wrap) { + // If we aren't wrapping around, we're done. + return; + } + + if (did_wrap) { + // If we're already wrapped around, we're done. + return; + } + + // Otherwise, wrap the cursor back to the top. + cursor = 0; + did_wrap = true; } // If we're selecting things of a particular type (like only files) @@ -479,17 +506,31 @@ JX.install('DiffChangesetList', { // If the item is hidden, don't select it when iterating with jump // keys. It can still potentially be selected in other ways. - if (!show_hidden) { + if (!hidden) { if (items[cursor].hidden) { continue; } } + // If the item has been deleted, don't select it when iterating. The + // cursor may remain on it until it is removed. + if (items[cursor].deleted) { + continue; + } + + // If we're selecting things with a particular attribute, like + // "unsaved", skip items without the attribute. + if (attribute !== null) { + if (!(items[cursor].attributes || {})[attribute]) { + continue; + } + } + // Otherwise, we've found a valid item to select. break; } - this._setSelectionState(items[cursor], manager); + this._setSelectionState(items[cursor]); }, _getSelectionState: function() { @@ -512,24 +553,34 @@ JX.install('DiffChangesetList', { }; }, - _setSelectionState: function(item, manager) { + _setSelectionState: function(item) { this._cursorItem = item; - this._redrawSelection(manager, true); + this._redrawSelection(true); return this; }, - _redrawSelection: function(manager, scroll) { + _redrawSelection: function(scroll) { var cursor = this._cursorItem; if (!cursor) { this.setFocus(null); return; } + // If this item has been removed from the document (for example: create + // a new empty comment, then use the "Unsaved" button to select it, then + // cancel it), we can still keep the cursor here but do not want to show + // a selection reticle over an invisible node. + if (cursor.deleted) { + this.setFocus(null); + return; + } + this.setFocus(cursor.nodes.begin, cursor.nodes.end); - if (manager && scroll) { - manager.scrollTo(cursor.nodes.begin); + if (scroll) { + var pos = JX.$V(cursor.nodes.begin); + JX.DOM.scrollToPosition(0, pos.y - 60); } return this; @@ -1320,14 +1371,65 @@ JX.install('DiffChangesetList', { 'diff-banner-has-unsubmitted', !!unsubmitted.length); + var unsaved_button = this._getUnsavedButton(); + var pht = this.getTranslations(); + + if (unsaved.length) { + unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved')); + JX.DOM.show(unsaved_button.getNode()); + } else { + JX.DOM.hide(unsaved_button.getNode()); + } + + var path_view = [icon, ' ', changeset.getDisplayPath()]; + + var buttons_attrs = { + className: 'diff-banner-buttons' + }; + + var buttons_list = [ + unsaved_button.getNode() + ]; + + var buttons_view = JX.$N('div', buttons_attrs, buttons_list); + var icon = new JX.PHUIXIconView() .setIcon(changeset.getIcon()) .getNode(); - JX.DOM.setContent(node, [icon, ' ', changeset.getDisplayPath()]); + JX.DOM.setContent(node, [buttons_view, path_view]); document.body.appendChild(node); }, + _getUnsavedButton: function() { + if (!this._unsavedButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-commenting-o') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var node = button.getNode(); + + var onunsaved = JX.bind(this, this._onunsavedclick); + JX.DOM.listen(node, 'click', null, onunsaved); + + this._unsavedButton = button; + } + + return this._unsavedButton; + }, + + _onunsavedclick: function(e) { + e.kill(); + + var options = { + filter: 'comment', + wrap: true, + attribute: 'unsaved' + }; + + this._onjumpkey(1, options); + }, + _getBannerNode: function() { if (!this._bannerNode) { var attributes = { diff --git a/webroot/rsrc/js/phuix/PHUIXButtonView.js b/webroot/rsrc/js/phuix/PHUIXButtonView.js index 67b59f24f1..e060cf0e9a 100644 --- a/webroot/rsrc/js/phuix/PHUIXButtonView.js +++ b/webroot/rsrc/js/phuix/PHUIXButtonView.js @@ -57,6 +57,7 @@ JX.install('PHUIXButtonView', { setText: function(text) { JX.DOM.setContent(this._getTextNode(), text); + this._redraw(); return this; }, From 863b7ab766857a7b136921d306741d9eca29ae16 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 3 Jun 2017 06:31:09 -0700 Subject: [PATCH 234/543] Add an "Unsubmitted" button to the Differential persistent header Summary: Ref M1476. Same as D18070 but that did most of the magic. Test Plan: {F4987961} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18071 --- resources/celerity/map.php | 40 ++++++++--------- .../rsrc/js/application/diff/DiffChangeset.js | 3 +- .../js/application/diff/DiffChangesetList.js | 44 ++++++++++++++++++- 3 files changed, 64 insertions(+), 23 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b403d7199a..530ac29ea0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', - 'differential.pkg.js' => 'b453b745', + 'differential.pkg.js' => 'b2c4cbfa', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -393,8 +393,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'fc3919c8', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'ffa063d8', + 'rsrc/js/application/diff/DiffChangeset.js' => 'ee50a0ec', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'b4cf2217', 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -772,8 +772,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'fc3919c8', - 'phabricator-diff-changeset-list' => 'ffa063d8', + 'phabricator-diff-changeset' => 'ee50a0ec', + 'phabricator-diff-changeset-list' => 'b4cf2217', 'phabricator-diff-inline' => '1d17130f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1771,6 +1771,10 @@ return array( 'b3e7d692' => array( 'javelin-install', ), + 'b4cf2217' => array( + 'javelin-install', + 'phuix-button-view', + ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2088,6 +2092,17 @@ return array( 'javelin-behavior', 'javelin-uri', ), + 'ee50a0ec' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'efe49472' => array( 'javelin-install', 'javelin-util', @@ -2141,17 +2156,6 @@ return array( 'javelin-behavior-device', 'phabricator-keyboard-shortcut', ), - 'fc3919c8' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'fc91ab6c' => array( 'javelin-behavior', 'javelin-dom', @@ -2163,10 +2167,6 @@ return array( 'javelin-view-visitor', 'javelin-util', ), - 'ffa063d8' => array( - 'javelin-install', - 'phuix-button-view', - ), ), 'packages' => array( 'conpherence.pkg.css' => array( diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index b00218bcfa..ee20682650 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -451,7 +451,8 @@ JX.install('DiffChangeset', { end: block.items[jj] }, attributes: { - unsaved: inline.isEditing() + unsaved: inline.isEditing(), + unsubmitted: inline.isDraft() } }; diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index ff0afc21e4..361637641d 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -113,6 +113,7 @@ JX.install('DiffChangesetList', { _bannerNode: null, _unsavedButton: null, + _unsubmittedButton: null, sleep: function() { this._asleep = true; @@ -1371,8 +1372,9 @@ JX.install('DiffChangesetList', { 'diff-banner-has-unsubmitted', !!unsubmitted.length); - var unsaved_button = this._getUnsavedButton(); var pht = this.getTranslations(); + var unsaved_button = this._getUnsavedButton(); + var unsubmitted_button = this._getUnsubmittedButton(); if (unsaved.length) { unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved')); @@ -1381,6 +1383,14 @@ JX.install('DiffChangesetList', { JX.DOM.hide(unsaved_button.getNode()); } + if (unsubmitted.length) { + unsubmitted_button.setText( + unsubmitted.length + ' ' + pht('Unsubmitted')); + JX.DOM.show(unsubmitted_button.getNode()); + } else { + JX.DOM.hide(unsubmitted_button.getNode()); + } + var path_view = [icon, ' ', changeset.getDisplayPath()]; var buttons_attrs = { @@ -1388,7 +1398,8 @@ JX.install('DiffChangesetList', { }; var buttons_list = [ - unsaved_button.getNode() + unsaved_button.getNode(), + unsubmitted_button.getNode() ]; var buttons_view = JX.$N('div', buttons_attrs, buttons_list); @@ -1418,6 +1429,23 @@ JX.install('DiffChangesetList', { return this._unsavedButton; }, + _getUnsubmittedButton: function() { + if (!this._unsubmittedButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-comment-o') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var node = button.getNode(); + + var onunsubmitted = JX.bind(this, this._onunsubmittedclick); + JX.DOM.listen(node, 'click', null, onunsubmitted); + + this._unsubmittedButton = button; + } + + return this._unsubmittedButton; + }, + _onunsavedclick: function(e) { e.kill(); @@ -1430,6 +1458,18 @@ JX.install('DiffChangesetList', { this._onjumpkey(1, options); }, + _onunsubmittedclick: function(e) { + e.kill(); + + var options = { + filter: 'comment', + wrap: true, + attribute: 'unsubmitted' + }; + + this._onjumpkey(1, options); + }, + _getBannerNode: function() { if (!this._bannerNode) { var attributes = { From 9773dc0e6cc4e1c2863e89fd6d5157ffdc8e022d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 3 Jun 2017 06:42:52 -0700 Subject: [PATCH 235/543] Add "X / Y Comments" button to Differential persistent header Summary: Ref M1476. Test Plan: {F4987991} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18072 --- resources/celerity/map.php | 40 +++++----- .../rsrc/js/application/diff/DiffChangeset.js | 6 +- .../js/application/diff/DiffChangesetList.js | 76 ++++++++++++++++--- 3 files changed, 91 insertions(+), 31 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 530ac29ea0..4dedae2fec 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', - 'differential.pkg.js' => 'b2c4cbfa', + 'differential.pkg.js' => '889ab0ab', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -393,8 +393,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'ee50a0ec', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'b4cf2217', + 'rsrc/js/application/diff/DiffChangeset.js' => 'd498bddb', + 'rsrc/js/application/diff/DiffChangesetList.js' => '0db8cdca', 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -772,8 +772,8 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'ee50a0ec', - 'phabricator-diff-changeset-list' => 'b4cf2217', + 'phabricator-diff-changeset' => 'd498bddb', + 'phabricator-diff-changeset-list' => '0db8cdca', 'phabricator-diff-inline' => '1d17130f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -960,6 +960,10 @@ return array( 'javelin-dom', 'javelin-router', ), + '0db8cdca' => array( + 'javelin-install', + 'phuix-button-view', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1771,10 +1775,6 @@ return array( 'b3e7d692' => array( 'javelin-install', ), - 'b4cf2217' => array( - 'javelin-install', - 'phuix-button-view', - ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1978,6 +1978,17 @@ return array( 'javelin-uri', 'javelin-util', ), + 'd498bddb' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', @@ -2092,17 +2103,6 @@ return array( 'javelin-behavior', 'javelin-uri', ), - 'ee50a0ec' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index ee20682650..c697ba5a6c 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -440,6 +440,8 @@ JX.install('DiffChangeset', { last_inline = inline; } + var is_saved = (!inline.isDraft() && !inline.isEditing()); + last_inline_item = { type: block.type, changeset: this, @@ -452,7 +454,9 @@ JX.install('DiffChangeset', { }, attributes: { unsaved: inline.isEditing(), - unsubmitted: inline.isDraft() + unsubmitted: inline.isDraft(), + undone: (is_saved && !inline.isDone()), + done: (is_saved && inline.isDone()) } }; diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 361637641d..400b828a9a 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -114,6 +114,8 @@ JX.install('DiffChangesetList', { _bannerNode: null, _unsavedButton: null, _unsubmittedButton: null, + _doneButton: null, + _doneMode: null, sleep: function() { this._asleep = true; @@ -531,7 +533,7 @@ JX.install('DiffChangesetList', { break; } - this._setSelectionState(items[cursor]); + this._setSelectionState(items[cursor], true); }, _getSelectionState: function() { @@ -554,9 +556,9 @@ JX.install('DiffChangesetList', { }; }, - _setSelectionState: function(item) { + _setSelectionState: function(item, scroll) { this._cursorItem = item; - this._redrawSelection(true); + this._redrawSelection(scroll); return this; }, @@ -598,7 +600,7 @@ JX.install('DiffChangesetList', { var state = this._getSelectionState(); if (state.cursor !== null) { - this._setSelectionState(state.items[state.cursor]); + this._setSelectionState(state.items[state.cursor], false); } }, @@ -910,7 +912,7 @@ JX.install('DiffChangesetList', { if (selection.cursor !== null) { item = selection.items[selection.cursor]; if (item.target === inline) { - this._setSelectionState(null); + this._setSelectionState(null, false); return; } } @@ -922,7 +924,7 @@ JX.install('DiffChangesetList', { for (var ii = 0; ii < items.length; ii++) { item = items[ii]; if (item.target === inline) { - this._setSelectionState(item); + this._setSelectionState(item, false); } } }, @@ -1339,7 +1341,7 @@ JX.install('DiffChangesetList', { var unsaved = []; var unsubmitted = []; var undone = []; - var all = []; + var done = []; for (var ii = 0; ii < changesets.length; ii++) { var inlines = changesets[ii].getInlines(); @@ -1350,14 +1352,14 @@ JX.install('DiffChangesetList', { continue; } - all.push(inline); - if (inline.isEditing()) { unsaved.push(inline); } else if (inline.isDraft()) { unsubmitted.push(inline); } else if (!inline.isDone()) { undone.push(inline); + } else { + done.push(inline); } } } @@ -1375,6 +1377,7 @@ JX.install('DiffChangesetList', { var pht = this.getTranslations(); var unsaved_button = this._getUnsavedButton(); var unsubmitted_button = this._getUnsubmittedButton(); + var done_button = this._getDoneButton(); if (unsaved.length) { unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved')); @@ -1391,6 +1394,30 @@ JX.install('DiffChangesetList', { JX.DOM.hide(unsubmitted_button.getNode()); } + if (done.length || undone.length) { + done_button.setText([ + done.length, + ' / ', + (done.length + undone.length), + ' ', + pht('Comments') + ]); + + JX.DOM.show(done_button.getNode()); + + // If any comments are not marked "Done", this cycles through the + // missing comments. Otherwise, it cycles through all the saved + // comments. + if (undone.length) { + this._doneMode = 'undone'; + } else { + this._doneMode = 'done'; + } + + } else { + JX.DOM.hide(done_button.getNode()); + } + var path_view = [icon, ' ', changeset.getDisplayPath()]; var buttons_attrs = { @@ -1399,7 +1426,8 @@ JX.install('DiffChangesetList', { var buttons_list = [ unsaved_button.getNode(), - unsubmitted_button.getNode() + unsubmitted_button.getNode(), + done_button.getNode() ]; var buttons_view = JX.$N('div', buttons_attrs, buttons_list); @@ -1446,6 +1474,22 @@ JX.install('DiffChangesetList', { return this._unsubmittedButton; }, + _getDoneButton: function() { + if (!this._doneButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-comment') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var node = button.getNode(); + + var ondone = JX.bind(this, this._ondoneclick); + JX.DOM.listen(node, 'click', null, ondone); + + this._doneButton = button; + } + + return this._doneButton; + }, _onunsavedclick: function(e) { e.kill(); @@ -1470,6 +1514,18 @@ JX.install('DiffChangesetList', { this._onjumpkey(1, options); }, + _ondoneclick: function(e) { + e.kill(); + + var options = { + filter: 'comment', + wrap: true, + attribute: this._doneMode + }; + + this._onjumpkey(1, options); + }, + _getBannerNode: function() { if (!this._bannerNode) { var attributes = { From d3c464a610bc3a708366fc731f0099823736d427 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 5 Jun 2017 20:14:34 +0000 Subject: [PATCH 236/543] Separate button CSS classes Summary: Try to dis-ambiguate various button types and colors. Moves `simple` to `phui-button-simple` and moves colors to `button-color`. Test Plan: Grep for buttons still inline, UIExamples, PHUIX, Herald, and Email Preferences. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18077 --- resources/celerity/map.php | 28 +++--- .../almanac/controller/AlmanacController.php | 4 +- .../almanac/view/AlmanacBindingTableView.php | 2 +- .../view/AlmanacInterfaceTableView.php | 4 +- .../PhabricatorConduitTokensSettingsPanel.php | 2 +- .../config/view/PhabricatorSetupIssueView.php | 6 +- .../controller/ConpherenceController.php | 2 +- .../view/ConpherenceDurableColumnView.php | 2 +- .../plugin/DarkConsoleServicesPlugin.php | 3 +- .../plugin/DarkConsoleXHProfPlugin.php | 2 +- .../view/PhabricatorDaemonTasksTableView.php | 2 +- .../DifferentialRevisionViewController.php | 2 +- .../view/DifferentialChangesetListView.php | 2 +- ...PhabricatorFileTransformListController.php | 2 +- .../controller/HeraldRuleController.php | 4 +- .../ManiphestBatchEditController.php | 2 +- .../view/ManiphestTaskResultListView.php | 6 +- ...habricatorMetaMTAApplicationEmailPanel.php | 4 +- ...OAuthServerAuthorizationsSettingsPanel.php | 2 +- .../PhabricatorOwnersPathsController.php | 2 +- .../view/PassphraseCredentialControl.php | 2 +- .../pholio/view/PholioUploadedImageView.php | 2 +- .../cart/PhortuneCartCheckoutController.php | 2 +- .../PhortuneSubscriptionEditController.php | 2 +- .../phortune/view/PhortuneOrderTableView.php | 2 +- .../controller/PhrictionDiffController.php | 12 +-- .../PhabricatorPolicyEditController.php | 2 +- ...PhabricatorEmailAddressesSettingsPanel.php | 6 +- .../PhabricatorMultiFactorSettingsPanel.php | 2 +- .../PhabricatorSessionsSettingsPanel.php | 4 +- .../panel/PhabricatorTokensSettingsPanel.php | 4 +- .../uiexample/examples/PHUIButtonExample.php | 73 ++------------- .../examples/PHUIColorPalletteExample.php | 2 +- .../PhabricatorNotificationUIExample.php | 2 +- src/view/AphrontDialogView.php | 2 +- .../form/control/AphrontFormPolicyControl.php | 3 +- .../form/control/PHUIFormIconSetControl.php | 2 +- src/view/layout/AphrontListFilterView.php | 4 +- src/view/phui/PHUIButtonView.php | 7 +- src/view/phui/PHUIHovercardView.php | 2 +- .../css/phui/button/phui-button-simple.css | 88 +++++++++---------- webroot/rsrc/css/phui/button/phui-button.css | 32 +++---- webroot/rsrc/js/phuix/PHUIXButtonView.js | 6 +- 43 files changed, 146 insertions(+), 200 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4dedae2fec..0745c41d4d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '5284a0e0', + 'core.pkg.css' => '1a935531', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', @@ -125,8 +125,8 @@ return array( 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', 'rsrc/css/phui/button/phui-button-bar.css' => '39fe680c', - 'rsrc/css/phui/button/phui-button-simple.css' => '081cfeea', - 'rsrc/css/phui/button/phui-button.css' => '9f13ddcc', + 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', + 'rsrc/css/phui/button/phui-button.css' => '022581b4', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', @@ -526,7 +526,7 @@ return array( 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', - 'rsrc/js/phuix/PHUIXButtonView.js' => 'b3c515be', + 'rsrc/js/phuix/PHUIXButtonView.js' => 'a37126bd', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', @@ -820,8 +820,8 @@ return array( 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', 'phui-button-bar-css' => '39fe680c', - 'phui-button-css' => '9f13ddcc', - 'phui-button-simple-css' => '081cfeea', + 'phui-button-css' => '022581b4', + 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -878,7 +878,7 @@ return array( 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', 'phuix-autocomplete' => 'f6699267', - 'phuix-button-view' => 'b3c515be', + 'phuix-button-view' => 'a37126bd', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -936,9 +936,6 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), - '081cfeea' => array( - 'phui-button-css', - ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -1561,6 +1558,9 @@ return array( 'javelin-stratcom', 'javelin-install', ), + '8e1baf68' => array( + 'phui-button-css', + ), '8ff5e24c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1651,6 +1651,10 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), + 'a37126bd' => array( + 'javelin-install', + 'javelin-dom', + ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1768,10 +1772,6 @@ return array( 'javelin-behavior', 'phabricator-prefab', ), - 'b3c515be' => array( - 'javelin-install', - 'javelin-dom', - ), 'b3e7d692' => array( 'javelin-install', ), diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index e4d9921660..d6d7188511 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -84,8 +84,8 @@ abstract class AlmanacController 'a', array( 'class' => ($can_edit - ? 'button grey small' - : 'button grey small disabled'), + ? 'button button-grey small' + : 'button button-grey small disabled'), 'sigil' => 'workflow', 'href' => $delete_uri, ), diff --git a/src/applications/almanac/view/AlmanacBindingTableView.php b/src/applications/almanac/view/AlmanacBindingTableView.php index 06797c912d..746d3de5c2 100644 --- a/src/applications/almanac/view/AlmanacBindingTableView.php +++ b/src/applications/almanac/view/AlmanacBindingTableView.php @@ -77,7 +77,7 @@ final class AlmanacBindingTableView extends AphrontView { phutil_tag( 'a', array( - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'href' => '/almanac/binding/'.$binding->getID().'/', ), pht('Details')), diff --git a/src/applications/almanac/view/AlmanacInterfaceTableView.php b/src/applications/almanac/view/AlmanacInterfaceTableView.php index f937e34f44..1fdcd0bf35 100644 --- a/src/applications/almanac/view/AlmanacInterfaceTableView.php +++ b/src/applications/almanac/view/AlmanacInterfaceTableView.php @@ -30,9 +30,9 @@ final class AlmanacInterfaceTableView extends AphrontView { $can_edit = $this->getCanEdit(); if ($can_edit) { - $button_class = 'small grey button'; + $button_class = 'small button button-grey'; } else { - $button_class = 'small grey button disabled'; + $button_class = 'small button button-grey disabled'; } $handles = $viewer->loadHandles(mpull($interfaces, 'getNetworkPHID')); diff --git a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php index 59161d2814..fc89d310e1 100644 --- a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php +++ b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php @@ -60,7 +60,7 @@ final class PhabricatorConduitTokensSettingsPanel javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => '/conduit/token/terminate/'.$token->getID().'/', 'sigil' => 'workflow', ), diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index 10e802eb3f..71bdb9d8dc 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -129,7 +129,7 @@ final class PhabricatorSetupIssueView extends AphrontView { array( 'href' => '/config/unignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Unignore Setup Issue')); } else { @@ -138,7 +138,7 @@ final class PhabricatorSetupIssueView extends AphrontView { array( 'href' => '/config/ignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Ignore Setup Issue')); } @@ -147,7 +147,7 @@ final class PhabricatorSetupIssueView extends AphrontView { 'a', array( 'href' => '/config/issue/'.$issue->getIssueKey().'/', - 'class' => 'button grey', + 'class' => 'button button-grey', 'style' => 'float: right', ), pht('Reload Page')); diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 9d7473e159..f47e7bf1ad 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -140,7 +140,7 @@ abstract class ConpherenceController extends PhabricatorController { 'button', array( 'type' => 'SUBMIT', - 'class' => 'button green mlr', + 'class' => 'button button-green mlr', ), pht('Join Room')); diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index 5d6a5eaaec..83c40aa32e 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -404,7 +404,7 @@ final class ConpherenceDurableColumnView extends AphrontTagView { 'a', array( 'href' => '/conpherence/search/', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Find Rooms')), ); diff --git a/src/applications/console/plugin/DarkConsoleServicesPlugin.php b/src/applications/console/plugin/DarkConsoleServicesPlugin.php index ea5a100550..7d779027da 100644 --- a/src/applications/console/plugin/DarkConsoleServicesPlugin.php +++ b/src/applications/console/plugin/DarkConsoleServicesPlugin.php @@ -170,7 +170,8 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin { 'a', array( 'href' => $data['analyzeURI'], - 'class' => $data['didAnalyze'] ? 'disabled button' : 'green button', + 'class' => $data['didAnalyze'] ? + 'disabled button' : 'button button-green', ), pht('Analyze Query Plans')), phutil_tag('h1', array(), pht('Calls to External Services')), diff --git a/src/applications/console/plugin/DarkConsoleXHProfPlugin.php b/src/applications/console/plugin/DarkConsoleXHProfPlugin.php index f751cc31a5..f76b77c850 100644 --- a/src/applications/console/plugin/DarkConsoleXHProfPlugin.php +++ b/src/applications/console/plugin/DarkConsoleXHProfPlugin.php @@ -67,7 +67,7 @@ final class DarkConsoleXHProfPlugin extends DarkConsolePlugin { 'a', array( 'href' => $profile_uri, - 'class' => $run ? 'disabled button' : 'green button', + 'class' => $run ? 'disabled button' : 'button button-green', ), pht('Profile Page')), phutil_tag('h1', array(), pht('XHProf Profiler')), diff --git a/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php b/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php index 4fded0b68a..440610be25 100644 --- a/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php +++ b/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php @@ -41,7 +41,7 @@ final class PhabricatorDaemonTasksTableView extends AphrontView { 'a', array( 'href' => '/daemon/task/'.$task->getID().'/', - 'class' => 'button small grey', + 'class' => 'button small button-grey', ), pht('View Task')), ); diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 769ac37e1a..69e45b377d 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -156,7 +156,7 @@ final class DifferentialRevisionViewController extends DifferentialController { phutil_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => $request_uri ->alter('large', 'true') ->setFragment('toc'), diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index f113afacf8..25f762ba66 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -174,7 +174,7 @@ final class DifferentialChangesetListView extends AphrontView { $load = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => '#'.$uniq_id, 'sigil' => 'differential-load', 'meta' => array( diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php index 026f60320e..ab5322fc1a 100644 --- a/src/applications/files/controller/PhabricatorFileTransformListController.php +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -68,7 +68,7 @@ final class PhabricatorFileTransformListController $view_link = phutil_tag( 'a', array( - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'href' => $view_href, ), $view_text); diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 74ce553f9c..0e7f6b0aa5 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -191,7 +191,7 @@ final class HeraldRuleController extends HeraldController { 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'create-condition', 'mustcapture' => true, ), @@ -212,7 +212,7 @@ final class HeraldRuleController extends HeraldController { 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'create-action', 'mustcapture' => true, ), diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php index 90fcda28ec..f2ec4b433f 100644 --- a/src/applications/maniphest/controller/ManiphestBatchEditController.php +++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php @@ -173,7 +173,7 @@ final class ManiphestBatchEditController extends ManiphestController { 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'add-action', 'mustcapture' => true, ), diff --git a/src/applications/maniphest/view/ManiphestTaskResultListView.php b/src/applications/maniphest/view/ManiphestTaskResultListView.php index 9e28e89d8d..0144df0e33 100644 --- a/src/applications/maniphest/view/ManiphestTaskResultListView.php +++ b/src/applications/maniphest/view/ManiphestTaskResultListView.php @@ -196,7 +196,7 @@ final class ManiphestTaskResultListView extends ManiphestView { array( 'href' => '#', 'mustcapture' => true, - 'class' => 'grey button', + 'class' => 'button button-grey', 'id' => 'batch-select-all', ), pht('Select All')); @@ -206,7 +206,7 @@ final class ManiphestTaskResultListView extends ManiphestView { array( 'href' => '#', 'mustcapture' => true, - 'class' => 'grey button', + 'class' => 'button button-grey', 'id' => 'batch-select-none', ), pht('Clear Selection')); @@ -224,7 +224,7 @@ final class ManiphestTaskResultListView extends ManiphestView { 'a', array( 'href' => '/maniphest/export/'.$saved_query->getQueryKey().'/', - 'class' => 'grey button', + 'class' => 'button button-grey', ), pht('Export to Excel')); diff --git a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php index a2d32e3360..afde9fee23 100644 --- a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php +++ b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php @@ -341,7 +341,7 @@ final class PhabricatorMetaMTAApplicationEmailPanel $button_edit = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('edit', $email->getID()), 'sigil' => 'workflow', ), @@ -350,7 +350,7 @@ final class PhabricatorMetaMTAApplicationEmailPanel $button_remove = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('delete', $email->getID()), 'sigil' => 'workflow', ), diff --git a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php index 06f26b2ee3..596f3decc9 100644 --- a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php +++ b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php @@ -87,7 +87,7 @@ final class PhabricatorOAuthServerAuthorizationsSettingsPanel 'a', array( 'href' => $this->getPanelURI('?revoke='.$authorization->getID()), - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'sigil' => 'workflow', ), pht('Revoke')); diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index b69faf5648..e5463b4cb4 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -124,7 +124,7 @@ final class PhabricatorOwnersPathsController 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'addpath', 'mustcapture' => true, ), diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 015dce1f40..411afd6f85 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -113,7 +113,7 @@ final class PassphraseCredentialControl extends AphrontFormControl { 'a', array( 'href' => '#', - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'passphrase-credential-add', 'mustcapture' => true, ), diff --git a/src/applications/pholio/view/PholioUploadedImageView.php b/src/applications/pholio/view/PholioUploadedImageView.php index 460b54c517..e583fa687b 100644 --- a/src/applications/pholio/view/PholioUploadedImageView.php +++ b/src/applications/pholio/view/PholioUploadedImageView.php @@ -120,7 +120,7 @@ final class PholioUploadedImageView extends AphrontView { return javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'pholio-drop-remove', ), 'X'); diff --git a/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php index fdc19dae44..dd611ecb7b 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php @@ -152,7 +152,7 @@ final class PhortuneCartCheckoutController $new_method = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => $payment_method_uri, ), pht('Add New Payment Method')); diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php index 7af8aa3456..185bbdbcc6 100644 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php +++ b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php @@ -123,7 +123,7 @@ final class PhortuneSubscriptionEditController extends PhortuneController { 'a', array( 'href' => $uri, - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Add Payment Method...')); diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php index a2e492c5be..435f5151d0 100644 --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -101,7 +101,7 @@ final class PhortuneOrderTableView extends AphrontView { 'a', array( 'href' => $cart->getCheckoutURI(), - 'class' => 'small green button', + 'class' => 'small button button-green', ), pht('Pay Now')), ); diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index 8ae9618bbd..b66a3b3094 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -103,7 +103,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1), - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht("\xC2\xAB Previous Change")); } else { @@ -111,7 +111,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '#', - 'class' => 'button grey disabled', + 'class' => 'button button-grey disabled', ), pht('Original Change')); } @@ -122,7 +122,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1), - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht("Next Change \xC2\xBB")); } else { @@ -130,7 +130,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '#', - 'class' => 'button grey disabled', + 'class' => 'button button-grey disabled', ), pht('Most Recent Change')); } @@ -200,7 +200,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '/phriction/edit/'.$document_id.'/', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Edit Current Version')); } @@ -210,7 +210,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '/phriction/edit/'.$document_id.'/?revert='.$version, - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Revert to Version %s...', $version)); } diff --git a/src/applications/policy/controller/PhabricatorPolicyEditController.php b/src/applications/policy/controller/PhabricatorPolicyEditController.php index 75701cc748..f211aa754d 100644 --- a/src/applications/policy/controller/PhabricatorPolicyEditController.php +++ b/src/applications/policy/controller/PhabricatorPolicyEditController.php @@ -199,7 +199,7 @@ final class PhabricatorPolicyEditController 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'create-rule', 'mustcapture' => true, ), diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php index 31249985e4..d628341dea 100644 --- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php @@ -63,7 +63,7 @@ final class PhabricatorEmailAddressesSettingsPanel $button_verify = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('verify', $email->getID()), 'sigil' => 'workflow', ), @@ -72,7 +72,7 @@ final class PhabricatorEmailAddressesSettingsPanel $button_make_primary = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('primary', $email->getID()), 'sigil' => 'workflow', ), @@ -81,7 +81,7 @@ final class PhabricatorEmailAddressesSettingsPanel $button_remove = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('delete', $email->getID()), 'sigil' => 'workflow', ), diff --git a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php index 77eb1c55d5..8f1a5c643c 100644 --- a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php @@ -69,7 +69,7 @@ final class PhabricatorMultiFactorSettingsPanel array( 'href' => $this->getPanelURI('?delete='.$factor->getID()), 'sigil' => 'workflow', - 'class' => 'small grey button', + 'class' => 'small button button-grey', ), pht('Remove')), ); diff --git a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php index a468ed53d0..e643f2ee08 100644 --- a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php @@ -58,7 +58,7 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel { $button = phutil_tag( 'a', array( - 'class' => 'small grey button disabled', + 'class' => 'small button button-grey disabled', ), pht('Current')); } else { @@ -67,7 +67,7 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel { 'a', array( 'href' => '/auth/session/terminate/'.$session->getID().'/', - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'sigil' => 'workflow', ), pht('Terminate')); diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php index d2e0be4a53..c2659d5226 100644 --- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php @@ -30,7 +30,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { 'a', array( 'href' => '/auth/token/revoke/'.$token->getID().'/', - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'sigil' => 'workflow', ), pht('Revoke')); @@ -38,7 +38,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { $button = javelin_tag( 'a', array( - 'class' => 'small grey button disabled', + 'class' => 'small button button-grey disabled', ), pht('Revoke')); } diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index aed5d5bf08..fe58123f03 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -16,63 +16,11 @@ final class PHUIButtonExample extends PhabricatorUIExample { $request = $this->getRequest(); $user = $request->getUser(); - $colors = array('', 'green', 'grey', 'disabled'); - $sizes = array('', 'small'); - $tags = array('a', 'button'); - - // phutil_tag - - $column = array(); - foreach ($tags as $tag) { - foreach ($colors as $color) { - foreach ($sizes as $key => $size) { - $class = implode(' ', array($color, $size)); - - if ($tag == 'a') { - $class .= ' button'; - } - - $column[$key][] = phutil_tag( - $tag, - array( - 'class' => $class, - ), - phutil_utf8_ucwords($size.' '.$color.' '.$tag)); - - $column[$key][] = hsprintf('

'); - } - } - } - - $column3 = array(); - foreach ($colors as $color) { - $caret = phutil_tag('span', array('class' => 'caret'), ''); - $column3[] = phutil_tag( - 'a', - array( - 'class' => $color.' button dropdown', - ), - array( - phutil_utf8_ucwords($color.' Dropdown'), - $caret, - )); - $column3[] = hsprintf('

'); - } - - $layout1 = id(new AphrontMultiColumnView()) - ->addColumn($column[0]) - ->addColumn($column[1]) - ->addColumn($column3) - ->setFluidLayout(true) - ->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM); - // PHUIButtonView - $colors = array( null, PHUIButtonView::GREEN, PHUIButtonView::GREY, - PHUIButtonView::DISABLED, ); $sizes = array(null, PHUIButtonView::SMALL); $column = array(); @@ -223,24 +171,21 @@ final class PHUIButtonExample extends PhabricatorUIExample { // Set it and forget it - $head1 = id(new PHUIHeaderView()) - ->setHeader('phutil_tag'); - $head2 = id(new PHUIHeaderView()) - ->setHeader('PHUIButtonView'); + ->setHeader('PHUIButtonView') + ->addClass('ml'); $head3 = id(new PHUIHeaderView()) - ->setHeader(pht('Icon Buttons')); + ->setHeader(pht('Icon Buttons')) + ->addClass('ml'); $head4 = id(new PHUIHeaderView()) - ->setHeader(pht('Simple Buttons')); + ->setHeader(pht('Simple Buttons')) + ->addClass('ml'); $head5 = id(new PHUIHeaderView()) - ->setHeader(pht('Big Icon Buttons')); - - $wrap1 = id(new PHUIBoxView()) - ->appendChild($layout1) - ->addMargin(PHUI::MARGIN_LARGE); + ->setHeader(pht('Big Icon Buttons')) + ->addClass('ml'); $wrap2 = id(new PHUIBoxView()) ->appendChild($layout2) @@ -259,8 +204,6 @@ final class PHUIButtonExample extends PhabricatorUIExample { ->addMargin(PHUI::MARGIN_LARGE); return array( - $head1, - $wrap1, $head2, $wrap2, $head3, diff --git a/src/applications/uiexample/examples/PHUIColorPalletteExample.php b/src/applications/uiexample/examples/PHUIColorPalletteExample.php index a8910b930d..ea8ac538be 100644 --- a/src/applications/uiexample/examples/PHUIColorPalletteExample.php +++ b/src/applications/uiexample/examples/PHUIColorPalletteExample.php @@ -101,7 +101,7 @@ final class PHUIColorPalletteExample extends PhabricatorUIExample { 'a', array( 'href' => 'http://color.hailpixel.com/#'.implode(',', $url), - 'class' => 'button grey mlb', + 'class' => 'button button-grey mlb', ), pht('Color Palette')); diff --git a/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php b/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php index 5d15d884cc..4f39df6065 100644 --- a/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php @@ -24,7 +24,7 @@ final class PhabricatorNotificationUIExample extends PhabricatorUIExample { 'a', array( 'sigil' => 'notification-example', - 'class' => 'button green', + 'class' => 'button button-green', ), pht('Show Notification')); diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index 4616393813..12bec92843 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -248,7 +248,7 @@ final class AphrontDialogView 'a', array( 'href' => $this->cancelURI, - 'class' => 'button grey', + 'class' => 'button button-grey', 'name' => '__cancel__', 'sigil' => 'jx-workflow-button', 'meta' => $meta, diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 6f343bf00e..0b0e608048 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -324,7 +324,8 @@ final class AphrontFormPolicyControl extends AphrontFormControl { javelin_tag( 'a', array( - 'class' => 'grey button dropdown has-icon has-text policy-control', + 'class' => 'button button-grey dropdown has-icon has-text '. + 'policy-control', 'href' => '#', 'mustcapture' => true, 'sigil' => 'policy-control', diff --git a/src/view/form/control/PHUIFormIconSetControl.php b/src/view/form/control/PHUIFormIconSetControl.php index 459bff08fa..96b1ad99b8 100644 --- a/src/view/form/control/PHUIFormIconSetControl.php +++ b/src/view/form/control/PHUIFormIconSetControl.php @@ -30,7 +30,7 @@ final class PHUIFormIconSetControl $classes = array(); $classes[] = 'button'; - $classes[] = 'grey'; + $classes[] = 'button-grey'; if ($is_disabled) { $classes[] = 'disabled'; diff --git a/src/view/layout/AphrontListFilterView.php b/src/view/layout/AphrontListFilterView.php index 7a6be9c3d2..f764964934 100644 --- a/src/view/layout/AphrontListFilterView.php +++ b/src/view/layout/AphrontListFilterView.php @@ -44,7 +44,7 @@ final class AphrontListFilterView extends AphrontView { $hide_action = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'reveal-content', 'id' => $hide_action_id, 'href' => $this->showHideHref, @@ -65,7 +65,7 @@ final class AphrontListFilterView extends AphrontView { $show_action = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'reveal-content', 'style' => 'display: none;', 'href' => '#', diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index 2e269bd9af..10ce5b5876 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -4,6 +4,7 @@ final class PHUIButtonView extends AphrontTagView { const GREEN = 'green'; const GREY = 'grey'; + const BLUE = 'blue'; const DISABLED = 'disabled'; const SMALL = 'small'; @@ -159,7 +160,7 @@ final class PHUIButtonView extends AphrontTagView { $classes[] = 'button'; if ($this->color) { - $classes[] = $this->color; + $classes[] = 'button-'.$this->color; } if ($this->size) { @@ -188,10 +189,10 @@ final class PHUIButtonView extends AphrontTagView { switch ($this->getButtonType()) { case self::BUTTONTYPE_DEFAULT: - // Nothing special for default buttons. + $classes[] = 'phui-button-default'; break; case self::BUTTONTYPE_SIMPLE: - $classes[] = 'simple'; + $classes[] = 'phui-button-simple'; break; } diff --git a/src/view/phui/PHUIHovercardView.php b/src/view/phui/PHUIHovercardView.php index b99fc8153e..f6cfc45a35 100644 --- a/src/view/phui/PHUIHovercardView.php +++ b/src/view/phui/PHUIHovercardView.php @@ -173,7 +173,7 @@ final class PHUIHovercardView extends AphrontTagView { foreach ($this->actions as $action) { $options = array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => $action['uri'], ); diff --git a/webroot/rsrc/css/phui/button/phui-button-simple.css b/webroot/rsrc/css/phui/button/phui-button-simple.css index fa0a8d11b2..4bc6daee23 100644 --- a/webroot/rsrc/css/phui/button/phui-button-simple.css +++ b/webroot/rsrc/css/phui/button/phui-button-simple.css @@ -6,55 +6,55 @@ /* - Basic -------------------------------------------------------------------*/ -button.simple, -input[type="submit"].simple, -a.simple, -a.simple:visited { +button.phui-button-simple, +input[type="submit"].phui-button-simple, +a.phui-button-simple, +a.phui-button-simple:visited { background: #fff; color: {$bluetext}; border: 1px solid {$lightblueborder}; } -button.simple .phui-icon-view, -input[type="submit"].simple .phui-icon-view, -a.simple .phui-icon-view, -a.simple:visited .phui-icon-view { +button.phui-button-simple .phui-icon-view, +input[type="submit"].phui-button-simple .phui-icon-view, +a.phui-button-simple .phui-icon-view, +a.phui-button-simple:visited .phui-icon-view { color: {$lightbluetext}; } -a.button.simple:hover, -button.simple:hover { +a.button.phui-button-simple:hover, +button.phui-button-simple:hover { border-color: {$blueborder}; background-image: none; background-color: #fff; transition: 0s; } -a.simple.current { +a.phui-button-simple.current { background: {$lightblue}; } /* - Red --------------------------------------------------------------------*/ -button.simple.red, -input[type="submit"].simple.red, -a.simple.red, -a.simple.red:visited { +button.phui-button-simple.button-red, +input[type="submit"].phui-button-simple.button-red, +a.phui-button-simple.button-red, +a.phui-button-simple.button-red:visited { background: {$sh-redbackground}; color: {$redtext}; border: 1px solid {$sh-redborder}; } -button.simple.red .phui-icon-view, -input[type="submit"].simple.red .phui-icon-view, -a.simple.red .phui-icon-view, -a.simple.red:visited .phui-icon-view { +button.phui-button-simple.button-red .phui-icon-view, +input[type="submit"].phui-button-simple.button-red .phui-icon-view, +a.phui-button-simple.button-red .phui-icon-view, +a.phui-button-simple.button-red:visited .phui-icon-view { color: {$redtext}; } -a.button.simple.red:hover, -button.simple.red:hover { +a.button.phui-button-simple.button-red:hover, +button.phui-button-simple.button-red:hover { border-color: {$sh-redtext}; background-image: none; background-color: {$sh-redbackground}; @@ -63,24 +63,24 @@ button.simple.red:hover { /* - Green ------------------------------------------------------------------*/ -button.simple.green, -input[type="submit"].simple.green, -a.simple.green, -a.simple.green:visited { +button.phui-button-simple.button-green, +input[type="submit"].phui-button-simple.button-green, +a.phui-button-simple.button-green, +a.phui-button-simple.button-green:visited { background: {$sh-greenbackground}; color: {$greentext}; border: 1px solid {$sh-greenborder}; } -button.simple.green .phui-icon-view, -input[type="submit"].simple.green .phui-icon-view, -a.simple.green .phui-icon-view, -a.simple.green:visited .phui-icon-view { +button.phui-button-simple.button-green .phui-icon-view, +input[type="submit"].phui-button-simple.button-green .phui-icon-view, +a.phui-button-simple.button-green .phui-icon-view, +a.phui-button-simple.button-green:visited .phui-icon-view { color: {$greentext}; } -a.button.simple.green:hover, -button.simple.green:hover { +a.button.phui-button-simple.button-green:hover, +button.phui-button-simple.button-green:hover { border-color: {$sh-greentext}; background-image: none; background-color: {$sh-greenbackground}; @@ -89,24 +89,24 @@ button.simple.green:hover { /* - Yellow -----------------------------------------------------------------*/ -button.simple.yellow, -input[type="submit"].simple.yellow, -a.simple.yellow, -a.simple.yellow:visited { +button.phui-button-simple.button-yellow, +input[type="submit"].phui-button-simple.button-yellow, +a.phui-button-simple.button-yellow, +a.phui-button-simple.button-yellow:visited { background-color: {$sh-yellowbackground}; color: {$sh-yellowtext}; border: 1px solid {$sh-yellowborder}; } -button.simple.yellow .phui-icon-view, -input[type="submit"].simple.yellow .phui-icon-view, -a.simple.yellow .phui-icon-view, -a.simple.yellow:visited .phui-icon-view { +button.phui-button-simple.button-yellow .phui-icon-view, +input[type="submit"].phui-button-simple.button-yellow .phui-icon-view, +a.phui-button-simple.button-yellow .phui-icon-view, +a.phui-button-simple.button-yellow:visited .phui-icon-view { color: {$sh-yellowicon}; } -a.button.simple.yellow:hover, -button.simple.yellow:hover { +a.button.phui-button-simple.button-yellow:hover, +button.phui-button-simple.button-yellow:hover { border-color: {$sh-yellowtext}; background-image: none; background-color: {$sh-yellowbackground}; @@ -116,16 +116,16 @@ button.simple.yellow:hover { /* - Misc -------------------------------------------------------------------*/ -a.button.simple .phui-icon-view { +a.button.phui-button-simple .phui-icon-view { border: none; } -a.button.simple.phuix-dropdown-open { +a.button.phui-button-simple.phuix-dropdown-open { background-color: #fff; color: {$blue}; box-shadow: none; } -a.button.simple.phuix-dropdown-open:hover .phui-icon-view { +a.button.phui-button-simple.phuix-dropdown-open:hover .phui-icon-view { color: {$blue}; } diff --git a/webroot/rsrc/css/phui/button/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css index d8183febd2..69718808c3 100644 --- a/webroot/rsrc/css/phui/button/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -49,13 +49,13 @@ input[type="submit"] { button .phui-icon-view, a.button .phui-icon-view, -button.green .phui-icon-view, -a.button.green .phui-icon-view { +button.button-green .phui-icon-view, +a.button.button-green .phui-icon-view { color: white; } -button.grey .phui-icon-view, -a.button.grey .phui-icon-view { +button.button-grey .phui-icon-view, +a.button.button-grey .phui-icon-view { color: {$darkbluetext}; } @@ -68,18 +68,18 @@ a.icon:visited { text-indent: 29px; } -button.green, -a.green.button, -a.green.button:visited { +button.button-green, +a.button-green.button, +a.button-green.button:visited { background-color: {$green}; border-color: {$green}; background-image: linear-gradient(to bottom, #23BB5B, #139543); } -button.grey, -input[type="submit"].grey, -a.grey, -a.grey:visited { +button.button-grey, +input[type="submit"].button-grey, +a.button-grey, +a.button-grey:visited { background-color: #F7F7F9; background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); border: 1px solid rgba({$alphablue}, 0.3); @@ -108,15 +108,15 @@ button:hover { transition: 0.1s; } -a.button.grey:hover, -button.grey:hover { +a.button.button-grey:hover, +button.button-grey:hover { background-image: linear-gradient(to bottom, #ffffff, #eeebec); border-color: rgba({$alphablue}, 0.4); transition: 0.1s; } -a.button.green:hover, -button.green:hover { +a.button.button-green:hover, +button.button-green:hover { border-color: #127336; background-color: #0DAD48; background-image: linear-gradient(to bottom, #23BB5B, #178841); @@ -244,7 +244,7 @@ a.policy-control .phui-button-text { margin-top: 6px; } -.grey.dropdown .caret { +.button-grey.dropdown .caret { border-top-color: #000; } diff --git a/webroot/rsrc/js/phuix/PHUIXButtonView.js b/webroot/rsrc/js/phuix/PHUIXButtonView.js index e060cf0e9a..e87db88fe4 100644 --- a/webroot/rsrc/js/phuix/PHUIXButtonView.js +++ b/webroot/rsrc/js/phuix/PHUIXButtonView.js @@ -35,10 +35,10 @@ JX.install('PHUIXButtonView', { var node = this.getNode(); if (this._color) { - JX.DOM.alterClass(node, this._color, false); + JX.DOM.alterClass(node, 'button-' + this._color, false); } this._color = color; - JX.DOM.alterClass(node, this._color, true); + JX.DOM.alterClass(node, 'button-' + this._color, true); return this; }, @@ -50,7 +50,7 @@ JX.install('PHUIXButtonView', { var node = this.getNode(); var is_simple = (this._buttonType == self.BUTTONTYPE_SIMPLE); - JX.DOM.alterClass(node, 'simple', is_simple); + JX.DOM.alterClass(node, 'phui-button-simple', is_simple); return this; }, From cb85be81d8193fbbbb41c4cf7cc89d1c0501c2b0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 6 Jun 2017 14:04:09 +0000 Subject: [PATCH 237/543] Add checkbox images for remarkup Summary: Minor, adds some weightier checkbox styles for use in Remarkup. Test Plan: Test a task, Phriction, various remarkup list styles. {F4990161} Reviewers: epriestley Reviewed By: epriestley Subscribers: cspeckmim, Korvin Differential Revision: https://secure.phabricator.com/D18080 --- resources/celerity/map.php | 8 +++-- webroot/rsrc/css/core/remarkup.css | 29 +++++++++++++++--- .../rsrc/image/controls/checkbox-checked.png | Bin 0 -> 514 bytes .../image/controls/checkbox-unchecked.png | Bin 0 -> 328 bytes 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 webroot/rsrc/image/controls/checkbox-checked.png create mode 100644 webroot/rsrc/image/controls/checkbox-unchecked.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0745c41d4d..6dfa36b3bd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '1a935531', + 'core.pkg.css' => '176b5104', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', @@ -115,7 +115,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '23beb330', - 'rsrc/css/core/remarkup.css' => 'd1a5e11e', + 'rsrc/css/core/remarkup.css' => '509fb36e', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -298,6 +298,8 @@ return array( 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', + 'rsrc/image/controls/checkbox-checked.png' => 'ad6441ea', + 'rsrc/image/controls/checkbox-unchecked.png' => '8eb1f0ae', 'rsrc/image/d5d8e1.png' => '0c2a1497', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', @@ -793,7 +795,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => 'd1a5e11e', + 'phabricator-remarkup-css' => '509fb36e', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 0c07c145a8..98529b9434 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -122,17 +122,38 @@ .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item, .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-unchecked-item { list-style: none; - margin-left: -18px; + margin-left: -20px; + position: relative; + padding-left: 22px; } .phabricator-remarkup .remarkup-list-with-checkmarks input { - margin-right: 4px; - opacity: 1; + display: none; +} + +.phabricator-remarkup .remarkup-list-with-checkmarks + .remarkup-list-item::before { + height: 16px; + width: 16px; + background-size: 16px; + display: inline-block; + content: ''; + position: absolute; + top: 3px; + left: 0; +} + +.remarkup-list-with-checkmarks .remarkup-checked-item::before { + background-image: url(rsrc/image/controls/checkbox-checked.png); +} + +.remarkup-list-with-checkmarks .remarkup-unchecked-item::before { + background-image: url(rsrc/image/controls/checkbox-unchecked.png); } .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item { - text-decoration: line-through; color: {$lightgreytext}; + text-decoration: line-through; } .phabricator-remarkup ul.remarkup-list ol.remarkup-list, diff --git a/webroot/rsrc/image/controls/checkbox-checked.png b/webroot/rsrc/image/controls/checkbox-checked.png new file mode 100644 index 0000000000000000000000000000000000000000..9e93e82a373045bb5b4f726f4493006522674eba GIT binary patch literal 514 zcmV+d0{#7oP)n3pyU9-lD!V}05*6N z5^pWKNTk}tRuumeE&Z)Vj7IOxd?PUu{eB{5Hd0zIaawR!_&5QCuRtN{+zraxiT-U8mKq*BShG?x%FQAmd z7=zlMq6zZL(GHK5!`c8YW4CwU#tZU-e}*-vwS; zAd`rqXiXx6$8o$99NzgQjN=#~M7{@x!{J`=cMAZFMx%}GlL;IOoaNs>5&bm+j}`J4 zlII`As`66;*T6#s@CV>p0{1`%_yIhf0Q~gA>Hzn@0i*G+3o|a>YXATM07*qoM6N<$ Eg1uqZivR!s literal 0 HcmV?d00001 diff --git a/webroot/rsrc/image/controls/checkbox-unchecked.png b/webroot/rsrc/image/controls/checkbox-unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..e60350d77099dd164b96d039643a9eb4dcb66f44 GIT binary patch literal 328 zcmV-O0k{5%P)4184UvXq58x3n5h^`{dI7cASh(5{?qX%MS{g5)cd!!;9D$lFfdVTFmt?b{ zAd$spzGRwj{>=MMGSf)YRFMPR;}S<`+ieyY;{}iShGwyOh*xxX3@xXP(_-D=0@KLv z&Fh9&)5RNF5$^U3U*{ONOp;vC|fI}R|k|e3t zy$G%9U|rd8b}Oolwq=4q5C{T6AP5A3AP@wCK!jDf$n(55*nB>(R*MK9ZW{b^ zX0w?r%c}Lbk)~-M4?8DrmwtpNeEtLc3(xB}aE{R);Uk=3wf+TDoM3==ENg%+i?as> aYYIQml{{z<<^5>@0000 Date: Tue, 6 Jun 2017 09:33:55 -0700 Subject: [PATCH 238/543] Rescope disabled icon colors in phui-oi Summary: This is a little too agressive, it's meant to only color direct icons, not icons inside tags if the object item is disabled. Test Plan: Check closed tasks and see icon colors inside tags, check a disabled project, see disabled color. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18084 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/object-item/phui-oi-list-view.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6dfa36b3bd..4482214fbe 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '176b5104', + 'core.pkg.css' => 'd88bfb44', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', @@ -135,7 +135,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '43752968', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -860,7 +860,7 @@ return array( 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '43752968', + 'phui-oi-list-view-css' => 'bf094950', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index f6d1589a9a..5ddedfa24a 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -426,7 +426,7 @@ ul.phui-oi-icons { } .phui-oi.phui-oi-disabled .phui-oi-attribute, -.phui-oi.phui-oi-disabled .phui-oi-attribute .phui-icon-view { +.phui-oi.phui-oi-disabled .phui-oi-attribute > .phui-icon-view { color: {$lightgreytext}; } From ece651255c9e8974c97f9d36082e19f6f0e0af00 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 6 Jun 2017 10:20:45 -0700 Subject: [PATCH 239/543] Optimize mobile layout of DiffusionHistoryView Summary: Little nits and spacing changes to viewing diffusion commit history on phones. Test Plan: Review in Chrome, iOS Simulator. {F4990749} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18085 --- resources/celerity/map.php | 4 +- .../controller/DiffusionHistoryController.php | 3 +- .../diffusion/diffusion-history.css | 53 +++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4482214fbe..35913087f9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => 'cc283766', + 'rsrc/css/application/diffusion/diffusion-history.css' => '6870e8c1', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -569,7 +569,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => 'cc283766', + 'diffusion-history-css' => '6870e8c1', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 8ee6c848a7..b8a1877ddf 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -69,7 +69,8 @@ final class DiffusionHistoryController extends DiffusionController { return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($view); + ->appendChild($view) + ->addClass('diffusion-history-view'); } private function buildHeader(DiffusionRequest $drequest) { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index b90977373f..2d0aea3b10 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -2,6 +2,8 @@ * @provides diffusion-history-css */ +/* - List Styles ------------------------------------------------------------*/ + .diffusion-history-list .phui-oi-link { color: #000; font-size: {$biggerfontsize}; @@ -27,3 +29,54 @@ .diffusion-history-list .diffusion-differential-tag { margin-left: 4px; } + + +/* - Phone Style ------------------------------------------------------------*/ + +.device-phone.diffusion-history-view .phui-two-column-view + .phui-two-column-footer .phui-object-box { + border-color: {$thinblueborder}; +} + +.device-phone.diffusion-history-view .phui-two-column-view + .phui-two-column-footer .phui-header-view { + text-align: center; +} + +.device-phone.diffusion-history-view .phui-two-column-content { + padding: 0; + margin: 0 -4px; +} + +.device-phone.diffusion-history-view .phui-oi-attribute-spacer { + display: none; +} + +.device-phone.diffusion-history-view .phui-oi-attribute { + display: block; + margin: 0 0 4px 0; +} + +.device-phone.diffusion-history-view .phui-oi-image { + height: 36px; + width: 36px; + margin-top: 10px; +} + +.device-phone.diffusion-history-view .phui-oi-with-image .phui-oi-content-box { + margin-left: 44px; +} + +.device-phone.diffusion-history-view .phui-oi-col2.phui-oi-side-column { + padding-bottom: 10px; +} + +.device-phone.diffusion-history-view .diffusion-history-list .button.has-icon + .phui-button-text { + margin: 0; +} + +.device-phone.diffusion-history-view .diffusion-history-list .button.has-icon + .phui-icon-view { + display: none; +} From b7f147ea0f8c6bdcc13c9ee21554cb644e2a1dde Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 6 Jun 2017 11:00:47 -0700 Subject: [PATCH 240/543] Clean up user profile commit list view Summary: Porting over a fix that we could miss the tail end of commits. Also use the new tag borderless option. Test Plan: Review various commit pages in profile. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18086 --- .../diffusion/view/DiffusionCommitListView.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionCommitListView.php b/src/applications/diffusion/view/DiffusionCommitListView.php index f1d1ebc8e4..4d4bcc1020 100644 --- a/src/applications/diffusion/view/DiffusionCommitListView.php +++ b/src/applications/diffusion/view/DiffusionCommitListView.php @@ -94,18 +94,10 @@ final class DiffusionCommitListView extends AphrontView { $handles = $viewer->loadHandles($phids); $cur_date = 0; - $list = null; - $header = null; $view = array(); foreach ($this->commits as $commit) { - $new_date = date('Ymd', $commit->getEpoch()); - if ($cur_date != $new_date) { - if ($list) { - $view[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - } + $new_date = phabricator_date($commit->getEpoch(), $viewer); + if ($cur_date !== $new_date) { $date = ucfirst( phabricator_relative_date($commit->getEpoch(), $viewer)); $header = id(new PHUIHeaderView()) @@ -113,6 +105,11 @@ final class DiffusionCommitListView extends AphrontView { $list = id(new PHUIObjectItemListView()) ->setFlush(true) ->addClass('diffusion-history-list'); + + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); } $commit_phid = $commit->getPHID(); @@ -146,6 +143,7 @@ final class DiffusionCommitListView extends AphrontView { ->setName($commit_name) ->setType(PHUITagView::TYPE_SHADE) ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) ->setSlimShady(true); $item = id(new PHUIObjectItemView()) From d89c24a460bd1e8733999a7c6332fbcd37d40b4d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 6 Jun 2017 15:29:57 -0700 Subject: [PATCH 241/543] Shrink padding on readmes on phones in Diffusion Summary: Padding is ridiculously large on mobile devices, shrink it. Test Plan: Review in simulator. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18087 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/application/diffusion/diffusion-readme.css | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 35913087f9..1b00aee0ad 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', 'rsrc/css/application/diffusion/diffusion-history.css' => '6870e8c1', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', @@ -571,7 +571,7 @@ return array( 'differential-table-of-contents-css' => 'ae4b7a55', 'diffusion-history-css' => '6870e8c1', 'diffusion-icons-css' => 'a6a1e2ba', - 'diffusion-readme-css' => '18bd3910', + 'diffusion-readme-css' => '419dd5b6', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', diff --git a/webroot/rsrc/css/application/diffusion/diffusion-readme.css b/webroot/rsrc/css/application/diffusion/diffusion-readme.css index b8e20af5cb..0dd5cdd858 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-readme.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-readme.css @@ -14,6 +14,10 @@ padding: 24px 32px; } +.device .diffusion-readme-view .phui-document-container { + padding: 8px 16px; +} + .diffusion-readme-view .phabricator-remarkup-toc { display: none; } From fd0cac0d459cc1219d6fce3c230ba1ed853f1e2c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 6 Jun 2017 15:47:47 -0700 Subject: [PATCH 242/543] Use normalsized font sizes in AphrontTable Summary: Updates to use the standard 13px size we use everywhere else, cleans up a few mobile/display bugs, adds a hover state for `tr`. Test Plan: Review Diffusion, Daemons, Almanac, People Logs, anything else? {F4991070} {F4991071} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18088 --- resources/celerity/map.php | 22 ++++++++-------- webroot/rsrc/css/aphront/table-view.css | 26 +++++++------------ .../diffusion/behavior-commit-graph.js | 2 +- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1b00aee0ad..262b33fe9c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,13 +9,13 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd88bfb44', + 'core.pkg.css' => 'f1bbed88', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', 'differential.pkg.js' => '889ab0ab', 'diffusion.pkg.css' => 'b93d9b8c', - 'diffusion.pkg.js' => '84c8f8fd', + 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', @@ -32,7 +32,7 @@ return array( 'rsrc/css/aphront/notification.css' => '3f6c89c9', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc', - 'rsrc/css/aphront/table-view.css' => '34cf86b4', + 'rsrc/css/aphront/table-view.css' => 'a3aa6910', 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', 'rsrc/css/aphront/tooltip.css' => '173b9431', 'rsrc/css/aphront/typeahead-browse.css' => '4f82e510', @@ -406,7 +406,7 @@ return array( 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '49ae8328', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '75b83cbb', 'rsrc/js/application/diffusion/behavior-diffusion-browse-file.js' => '054a0f0b', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', @@ -542,7 +542,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '34cf86b4', + 'aphront-table-view-css' => 'a3aa6910', 'aphront-tokenizer-control-css' => '15d5ff71', 'aphront-tooltip-css' => '173b9431', 'aphront-typeahead-control-css' => '8a84cc7d', @@ -620,7 +620,7 @@ return array( 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', - 'javelin-behavior-diffusion-commit-graph' => '49ae8328', + 'javelin-behavior-diffusion-commit-graph' => '75b83cbb', 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', @@ -1224,11 +1224,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '49ae8328' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '4b3c4443' => array( 'phuix-icon-view', ), @@ -1445,6 +1440,11 @@ return array( 'javelin-install', 'javelin-workboard-card', ), + '75b83cbb' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 2de08095dd..2238f5a041 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -24,7 +24,6 @@ .aphront-table-notice { padding: 12px 16px; - font-size: {$normalfontsize}; color: {$darkbluetext}; border-bottom: 1px solid {$thinblueborder}; } @@ -37,9 +36,16 @@ background: {$lightgreybackground}; } +.device-desktop .aphront-table-view tr:hover { + background: {$bluebackground}; +} + +.device-desktop .aphront-table-view tr.no-data:hover { + background: inherit; +} + .aphront-table-view th { font-weight: bold; - font-size: {$normalfontsize}; white-space: nowrap; color: {$bluetext}; text-shadow: 0 1px 0 white; @@ -78,6 +84,7 @@ th.aphront-table-view-sortable-selected { .aphront-table-view td { white-space: nowrap; vertical-align: middle; + color: {$darkbluetext}; } .aphront-table-down-sort { @@ -115,12 +122,10 @@ th.aphront-table-view-sortable-selected { .aphront-table-view th { padding: 8px 10px; - font-size: {$normalfontsize}; } .aphront-table-view td { padding: 8px 10px; - font-size: {$smallerfontsize}; } .device-tablet .aphront-table-view td, @@ -128,22 +133,12 @@ th.aphront-table-view-sortable-selected { padding: 6px; } -.device-tablet .aphront-table-view td + td, -.device-phone .aphront-table-view td + td { - padding-left: 0px; -} - .device-tablet .aphront-table-view th, .device-phone .aphront-table-view th { padding: 6px; overflow: hidden; } -.device-tablet .aphront-table-view th + th, -.device-phone .aphront-table-view th + th { - padding-left: 0px; -} - .aphront-table-view td.sorted-column { background: {$lightbluebackground}; } @@ -196,7 +191,7 @@ th.aphront-table-view-sortable-selected { } .aphront-table-view td.wrap { - white-space: normal; + white-space: normal; } .aphront-table-view td.prewrap { @@ -282,7 +277,6 @@ span.single-display-line-content { text-align: center; color: {$lightgreytext}; font-style: italic; - font-size: {$normalfontsize}; } .aphront-table-view td.thumb img { diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index 317a132240..309f972324 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -53,7 +53,7 @@ JX.behavior('diffusion-commit-graph', function(config) { return (col * cell) + (cell / 2); }; - var h = 32; + var h = 34; var w = cell * config.count; var canvas = JX.$N('canvas', {width: w, height: h}); From 0700bb2aca6785af6fe7ee21fd971ead9b6c9073 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 6 Jun 2017 16:53:14 -0700 Subject: [PATCH 243/543] Add a selected color state to PHUIPagerView Summary: This probably got nuked at some point, but the class is there so let's use it. Test Plan: Browse a directory with 800 files. See pager change color. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18089 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-pager.css | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 262b33fe9c..ea03c78d2d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'f1bbed88', + 'core.pkg.css' => '7e4901ea', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', @@ -168,7 +168,7 @@ return array( 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', 'rsrc/css/phui/phui-list.css' => '12eb8ce6', 'rsrc/css/phui/phui-object-box.css' => '9cff003c', - 'rsrc/css/phui/phui-pager.css' => '77d8a794', + 'rsrc/css/phui/phui-pager.css' => 'edcbc226', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => '2dc7993f', 'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863', @@ -862,7 +862,7 @@ return array( 'phui-oi-flush-ui-css' => '9d9685d6', 'phui-oi-list-view-css' => 'bf094950', 'phui-oi-simple-ui-css' => 'a8beebea', - 'phui-pager-css' => '77d8a794', + 'phui-pager-css' => 'edcbc226', 'phui-pinboard-view-css' => '2495140e', 'phui-property-list-view-css' => '2dc7993f', 'phui-remarkup-preview-css' => '54a34863', diff --git a/webroot/rsrc/css/phui/phui-pager.css b/webroot/rsrc/css/phui/phui-pager.css index 590b8514ce..462ef2ad22 100644 --- a/webroot/rsrc/css/phui/phui-pager.css +++ b/webroot/rsrc/css/phui/phui-pager.css @@ -6,3 +6,9 @@ clear: both; text-align: center; } + +.phui-pager-view a.button.current, +.phui-pager-view a.button.current:hover { + border-color: {$sh-orangetext}; + color: {$sh-orangetext}; +} From 7c955d795e1ba4d0b88b865f9af517a37c9b6db4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 7 Jun 2017 13:27:26 +0000 Subject: [PATCH 244/543] Remove badge support from PHUIHeaderView Summary: These are now unused. Test Plan: grep, remove uiexamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18090 --- resources/celerity/map.php | 6 +++--- .../uiexample/examples/PHUIBoxExample.php | 17 ++++------------- .../examples/PHUIHovercardUIExample.php | 10 ---------- src/view/phui/PHUIHeaderView.php | 16 +--------------- webroot/rsrc/css/phui/phui-header-view.css | 8 -------- 5 files changed, 8 insertions(+), 49 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ea03c78d2d..1e4721cbd5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '7e4901ea', + 'core.pkg.css' => 'ab24402f', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', @@ -157,7 +157,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => '73edce66', + 'rsrc/css/phui/phui-header-view.css' => 'e7de7ee2', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '4c46b6ba', @@ -843,7 +843,7 @@ return array( 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => '73edce66', + 'phui-header-view-css' => 'e7de7ee2', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', diff --git a/src/applications/uiexample/examples/PHUIBoxExample.php b/src/applications/uiexample/examples/PHUIBoxExample.php index 4a551de707..f77ac3afd4 100644 --- a/src/applications/uiexample/examples/PHUIBoxExample.php +++ b/src/applications/uiexample/examples/PHUIBoxExample.php @@ -68,21 +68,10 @@ final class PHUIBoxExample extends PhabricatorUIExample { ->setText(pht('Such Wow')) ->addClass(PHUI::MARGIN_SMALL_RIGHT); - $badge1 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-bug') - ->setHeader(pht('Bugmeister')); - - $badge2 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-heart') - ->setHeader(pht('Funder')) - ->setQuality(PhabricatorBadgesQuality::UNCOMMON); - $header = id(new PHUIHeaderView()) ->setHeader(pht('Fancy Box')) ->addActionLink($button) - ->setSubheader(pht('Much Features')) - ->addBadge($badge1) - ->addBadge($badge2); + ->setSubheader(pht('Much Features')); $obj4 = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -116,7 +105,9 @@ final class PHUIBoxExample extends PhabricatorUIExample { return phutil_tag( 'div', - array(), + array( + 'class' => 'ml', + ), array( $head1, $wrap1, diff --git a/src/applications/uiexample/examples/PHUIHovercardUIExample.php b/src/applications/uiexample/examples/PHUIHovercardUIExample.php index 8cdec56d68..b88fdc6639 100644 --- a/src/applications/uiexample/examples/PHUIHovercardUIExample.php +++ b/src/applications/uiexample/examples/PHUIHovercardUIExample.php @@ -55,14 +55,6 @@ final class PHUIHovercardUIExample extends PhabricatorUIExample { ->addTag($tag)); $elements[] = $panel; - $badge1 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-book') - ->setHeader(pht('Documenter')); - - $badge2 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-star') - ->setHeader(pht('Contributor')); - $user_handle = $this->createBasicDummyHandle( 'gwashington', PhabricatorPeopleUserPHIDType::TYPECONST, @@ -75,8 +67,6 @@ final class PHUIHovercardUIExample extends PhabricatorUIExample { ->addField(pht('Status'), pht('Available')) ->addField(pht('Member since'), '30. February 1750') ->addAction(pht('Send a Message'), '/dev/null') - ->addBadge($badge1) - ->addBadge($badge2) ->setUser($user)); $elements[] = $panel; diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index ab576c64d5..dc836f3d3a 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -21,7 +21,6 @@ final class PHUIHeaderView extends AphrontTagView { private $policyObject; private $epoch; private $actionItems = array(); - private $badges = array(); private $href; private $actionList; private $actionListID; @@ -46,11 +45,6 @@ final class PHUIHeaderView extends AphrontTagView { return $this; } - public function addBadge(PHUIBadgeMiniView $badge) { - $this->badges[] = $badge; - return $this; - } - public function setImage($uri) { $this->image = $uri; return $this; @@ -341,21 +335,13 @@ final class PHUIHeaderView extends AphrontTagView { $header_content, )); - if ($this->subheader || $this->badges) { - $badges = null; - if ($this->badges) { - $badges = new PHUIBadgeBoxView(); - $badges->addItems($this->badges); - $badges->setCollapsed(true); - } - + if ($this->subheader) { $left[] = phutil_tag( 'div', array( 'class' => 'phui-header-subheader', ), array( - $badges, $this->subheader, )); } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index f0c37a1a75..87df1d0fc5 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -307,14 +307,6 @@ body .phui-header-shell.phui-bleed-header text-decoration: underline; } -.phui-header-subheader .phui-badge-flex-view { - display: inline; - margin-right: 4px; -} - -.phui-header-subheader .phui-badge-flex-view:after { - display: inline; -} /*** Profile Header ***********************************************************/ From 38904ea4ad6a3ab28d2b3d73526e7df9dce2d6da Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 7 Jun 2017 09:29:45 -0700 Subject: [PATCH 245/543] More button grey conversions Summary: Ran across a few straglers. Convert to the correct color. Test Plan: grep for profile-image-button, check profile image selection page Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18096 --- .../controller/ConpherenceRoomPictureController.php | 2 +- .../files/controller/PhabricatorFileComposeController.php | 4 ++-- .../controller/PhabricatorPeopleProfilePictureController.php | 2 +- .../controller/blog/PhameBlogProfilePictureController.php | 2 +- .../controller/merchant/PhortuneMerchantPictureController.php | 2 +- .../PhabricatorProjectBoardBackgroundController.php | 2 +- .../controller/PhabricatorProjectEditPictureController.php | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php index 6a39481377..fbf3def9cd 100644 --- a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php +++ b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php @@ -132,7 +132,7 @@ final class ConpherenceRoomPictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 7afd99330a..b06e855617 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -82,7 +82,7 @@ final class PhabricatorFileComposeController $buttons[] = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip compose-select-color', 'style' => 'margin: 0 8px 8px 0', 'meta' => array( @@ -102,7 +102,7 @@ final class PhabricatorFileComposeController $icons[] = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip compose-select-icon', 'style' => 'margin: 0 8px 8px 0', 'meta' => array( diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index cff833aa51..5cab924255 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -190,7 +190,7 @@ final class PhabricatorPeopleProfilePictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php index 04b41dbb79..4e69034253 100644 --- a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php @@ -133,7 +133,7 @@ final class PhameBlogProfilePictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index 469887172f..cbd122f682 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -133,7 +133,7 @@ final class PhortuneMerchantPictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index be4049bb73..b229f59ecb 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -138,7 +138,7 @@ final class PhabricatorProjectBoardBackgroundController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $option['name'], diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 95d3bbd855..5060d0a203 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -134,7 +134,7 @@ final class PhabricatorProjectEditPictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], @@ -285,7 +285,7 @@ final class PhabricatorProjectEditPictureController $default_button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Use Icon and Color'), From fb9d036e574fe50e0aff321f817672ffc4b52f36 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 7 Jun 2017 13:30:12 -0700 Subject: [PATCH 246/543] Show task duplicates as related objects in Maniphest and migrate old duplicates Summary: Does the UI work that's part of T12234 and adds migrations for both of the old-style duplicate transactions. Test Plan: - Started with a clean DB. - Checked out really old code that marks tasks as dupes using comments. - Made a bunch of tasks and closed some as dupes. Made a bunch of additional comments. - Checked out D10427 and did a `storage upgrade`. - Made a bunch more new tasks and dupes. - Snapshotted DB. - Ran migration repeatedly until all expected edges showed up in the `phabricator_maniphest.edge`table. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T12234 Differential Revision: https://secure.phabricator.com/D18037 --- .../autopatches/20170528.maniphestdupes.php | 68 +++++++++++++++++++ .../ManiphestTaskDetailController.php | 28 ++++++++ 2 files changed, 96 insertions(+) create mode 100644 resources/sql/autopatches/20170528.maniphestdupes.php diff --git a/resources/sql/autopatches/20170528.maniphestdupes.php b/resources/sql/autopatches/20170528.maniphestdupes.php new file mode 100644 index 0000000000..aea8ae93d2 --- /dev/null +++ b/resources/sql/autopatches/20170528.maniphestdupes.php @@ -0,0 +1,68 @@ +getTransactionType(); + + if ($txn_type == 'mergedinto') { + // dupe handling as implemented in D10427, which creates a specific txn + $add_edges[] = array( + 'src' => $txn->getObjectPHID(), + 'dst' => $txn->getNewValue(), + ); + } else if ($txn_type == 'status' && $txn->getNewValue() == 'duplicate') { + // dupe handling as originally implemented, which just changes the status + // and adds a comment + $src_phid = $txn->getObjectPHID(); + + // get all the comment transactions associated with this task + $viewer = PhabricatorUser::getOmnipotentUser(); + $comment_txns = id(new ManiphestTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($src_phid)) + ->needComments(true) + ->execute(); + + // check each comment, looking for the "Merged Into" message + foreach ($comment_txns as $comment_txn) { + if ($comment_txn->hasComment()) { + $comment = $comment_txn->getComment()->getContent(); + $pattern = '/^\xE2\x9C\x98 Merged into T(\d+)\.$/'; + $matches = array(); + + if (preg_match($pattern, $comment, $matches)) { + $dst_task = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withIDs(array($matches[1])) + ->executeOne(); + + if ($dst_task) { + $dst_phid = $dst_task->getPHID(); + $add_edges[] = array( + 'src' => $src_phid, + 'dst' => $dst_phid, + ); + } + } + } + } + } +} + +if ($add_edges) { + foreach ($add_edges as $edge) { + $src_phid = $edge['src']; + $dst_phid = $edge['dst']; + + $type = ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST; + try { + $editor = id(new PhabricatorEdgeEditor()) + ->addEdge($src_phid, $type, $dst_phid) + ->save(); + } catch (PhabricatorEdgeCycleException $ex) { + // Some earlier or later merge made this invalid, just skip it. + } + } +} diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 92fe703f91..ecfb723f97 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -36,6 +36,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ManiphestTaskHasMockEdgeType::EDGECONST, PhabricatorObjectMentionedByObjectEdgeType::EDGECONST, PhabricatorObjectMentionsObjectEdgeType::EDGECONST, + ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST, ); $phid = $task->getPHID(); @@ -159,6 +160,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $related_tabs[] = $this->newMocksTab($task, $query); $related_tabs[] = $this->newMentionsTab($task, $query); + $related_tabs[] = $this->newDuplicatesTab($task, $query); $tab_view = null; @@ -553,6 +555,32 @@ final class ManiphestTaskDetailController extends ManiphestController { ->appendChild($view); } + private function newDuplicatesTab( + ManiphestTask $task, + PhabricatorEdgeQuery $edge_query) { + + $in_type = ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST; + $in_phids = $edge_query->getDestinationPHIDs(array(), array($in_type)); + + $viewer = $this->getViewer(); + $in_handles = $viewer->loadHandles($in_phids); + $in_handles = $this->getCompleteHandles($in_handles); + + $view = new PHUIPropertyListView(); + + if (!count($in_handles)) { + return null; + } + + $view->addProperty( + pht('Duplicates Merged Here'), $in_handles->renderList()); + + return id(new PHUITabView()) + ->setName(pht('Duplicates')) + ->setKey('duplicates') + ->appendChild($view); + } + private function getCompleteHandles(PhabricatorHandleList $handles) { $phids = array(); From 345d80b40d9b4d894eb413e4ddf1a9dd46c7a194 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 7 Jun 2017 13:35:46 -0700 Subject: [PATCH 247/543] Clean up Dark Console broken table css Summary: Fixes T12808 Test Plan: Enable Dark Console. Hover Hover Hover. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12808 Differential Revision: https://secure.phabricator.com/D18099 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/aphront/dark-console.css | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1e4721cbd5..33e8f8ad19 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -25,7 +25,7 @@ return array( 'rsrc/audio/basic/tap.mp3' => 'fc2fd796', 'rsrc/audio/basic/ting.mp3' => '17660001', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => '53798a6d', + 'rsrc/css/aphront/dark-console.css' => 'f7b071f1', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -537,7 +537,7 @@ return array( 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => '53798a6d', + 'aphront-dark-console-css' => 'f7b071f1', 'aphront-dialog-view-css' => '685c7e2d', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css index e1ddc5a16d..81e888ba46 100644 --- a/webroot/rsrc/css/aphront/dark-console.css +++ b/webroot/rsrc/css/aphront/dark-console.css @@ -3,12 +3,12 @@ */ .dark-console { - background: #444444; - color: #eeeeee; - width: 100%; - font-family: "Verdana"; - font-size: 11px; - position: relative; + background: #444444; + color: #eeeeee; + width: 100%; + font-family: "Verdana"; + font-size: 11px; + position: relative; } .dark-console a { @@ -74,6 +74,7 @@ .dark-console-requests a.dark-console-request:hover, .dark-console-tabs a.dark-console-tab:hover { background: #1188cc; + text-decoration: none; } .dark-console-tabs a.dark-console-tab { @@ -107,6 +108,12 @@ .dark-console .aphront-table-view td { font-size: 11px; + color: #eee; +} + +.dark-console .aphront-table-view tr:hover, +.dark-console .aphront-table-view tr.alt:hover { + background: #333; } .dark-console .aphront-table-view td.header { From 9ef726a22bbc09a3b384585d7331d1d73bfb85c4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 7 Jun 2017 13:48:19 -0700 Subject: [PATCH 248/543] Fix object selector button color Summary: Should be button-grey Test Plan: Use object selector on a diff for a task Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18100 --- resources/celerity/map.php | 18 +++++++++--------- .../rsrc/js/core/behavior-object-selector.js | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 33e8f8ad19..3dccc41cb5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', - 'differential.pkg.js' => '889ab0ab', + 'differential.pkg.js' => '52e2a4a7', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -496,7 +496,7 @@ return array( 'rsrc/js/core/behavior-lightbox-attachments.js' => '560f41da', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', - 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', + 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '947753e0', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', @@ -658,7 +658,7 @@ return array( 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 'javelin-behavior-phabricator-nav' => '947753e0', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', - 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', + 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', @@ -1457,6 +1457,12 @@ return array( 'javelin-reactor', 'javelin-util', ), + '77c1f0b0' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-request', + 'javelin-util', + ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', @@ -2032,12 +2038,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'e0ec7f2f' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-request', - 'javelin-util', - ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/core/behavior-object-selector.js b/webroot/rsrc/js/core/behavior-object-selector.js index 686f0f8820..722cfdd562 100644 --- a/webroot/rsrc/js/core/behavior-object-selector.js +++ b/webroot/rsrc/js/core/behavior-object-selector.js @@ -137,7 +137,9 @@ JX.behavior('phabricator-object-selector', function(config) { var select_object_button = JX.$N( 'a', - {href: '#', sigil: 'object-attacher', className: 'button small grey'}, + {href: '#', + sigil: 'object-attacher', + className: 'button small button-grey'}, attach ? 'Select' : 'Remove'); var cells = [ From 8692d673c8ab1ebec062f662aab17023b65cd061 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 7 Jun 2017 19:04:23 -0700 Subject: [PATCH 249/543] Fix minor inline comment header button behaviors Summary: Fixes T12806. Ref T12733. - Don't count synthetic (lint) comments as anything. - When you begin writing an inline then cancel it, don't count it as anything. - When we would show "0 / X", just show "X". Test Plan: - Viewed a diff with synthetic comments, no button. - Wrote, then cancelled an inline. No "X comments". - Clicked / unlicked "Done", saw "X" -> "1 / X". Reviewers: chad Reviewed By: chad Maniphest Tasks: T12806, T12733 Differential Revision: https://secure.phabricator.com/D18103 --- .../view/PHUIDiffInlineCommentDetailView.php | 11 +++--- .../js/application/diff/DiffChangesetList.js | 37 +++++++++++++++---- .../rsrc/js/application/diff/DiffInline.js | 6 +++ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index a42efa932a..46853e185c 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -107,6 +107,11 @@ final class PHUIDiffInlineCommentDetailView break; } + $is_synthetic = false; + if ($inline->getSyntheticAuthor()) { + $is_synthetic = true; + } + $metadata = array( 'id' => $inline->getID(), 'phid' => $inline->getPHID(), @@ -120,6 +125,7 @@ final class PHUIDiffInlineCommentDetailView 'isDraft' => $inline->isDraft(), 'isFixed' => $is_fixed, 'isGhost' => $inline->getIsGhost(), + 'isSynthetic' => $is_synthetic, ); $sigil = 'differential-inline-comment'; @@ -136,11 +142,6 @@ final class PHUIDiffInlineCommentDetailView $links = array(); - $is_synthetic = false; - if ($inline->getSyntheticAuthor()) { - $is_synthetic = true; - } - $draft_text = null; if (!$is_synthetic) { // This display is controlled by CSS diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 400b828a9a..b476b8c168 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1352,8 +1352,16 @@ JX.install('DiffChangesetList', { continue; } + if (inline.isSynthetic()) { + continue; + } + if (inline.isEditing()) { unsaved.push(inline); + } else if (!inline.getID()) { + // These are new comments which have been cancelled, and do not + // count as anything. + continue; } else if (inline.isDraft()) { unsubmitted.push(inline); } else if (!inline.isDone()) { @@ -1395,13 +1403,28 @@ JX.install('DiffChangesetList', { } if (done.length || undone.length) { - done_button.setText([ - done.length, - ' / ', - (done.length + undone.length), - ' ', - pht('Comments') - ]); + // If you haven't marked any comments as "Done", we just show text + // like "3 Comments". If you've marked at least one done, we show + // "1 / 3 Comments". + + var done_text; + if (done.length) { + done_text = [ + done.length, + ' / ', + (done.length + undone.length), + ' ', + pht('Comments') + ]; + } else { + done_text = [ + undone.length, + ' ', + pht('Comments') + ]; + } + + done_button.setText(done_text); JX.DOM.show(done_button.getNode()); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 6d897c27ae..baee53a7a4 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -34,6 +34,7 @@ JX.install('DiffInline', { _isFixed: null, _isEditing: false, _isNew: false, + _isSynthetic: false, bindToRow: function(row) { this._row = row; @@ -71,6 +72,7 @@ JX.install('DiffInline', { this._isDraft = data.isDraft; this._isFixed = data.isFixed; this._isGhost = data.isGhost; + this._isSynthetic = data.isSynthetic; this._changesetID = data.changesetID; this._isNew = false; @@ -97,6 +99,10 @@ JX.install('DiffInline', { return this._isDeleted; }, + isSynthetic: function() { + return this._isSynthetic; + }, + bindToRange: function(data) { this._displaySide = data.displaySide; this._number = parseInt(data.number, 10); From c3f905af6c7eaf61b6223441dca8d4a39e7fa73b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 7 Jun 2017 19:11:32 -0700 Subject: [PATCH 250/543] Coniferous roots. --- resources/celerity/map.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3dccc41cb5..1c0fb7fb3c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '1ccbf3a9', - 'differential.pkg.js' => '52e2a4a7', + 'differential.pkg.js' => 'b7504037', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -396,8 +396,8 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'd498bddb', - 'rsrc/js/application/diff/DiffChangesetList.js' => '0db8cdca', - 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', + 'rsrc/js/application/diff/DiffChangesetList.js' => '29bbc02c', + 'rsrc/js/application/diff/DiffInline.js' => '20553f71', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -775,8 +775,8 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'd498bddb', - 'phabricator-diff-changeset-list' => '0db8cdca', - 'phabricator-diff-inline' => '1d17130f', + 'phabricator-diff-changeset-list' => '29bbc02c', + 'phabricator-diff-inline' => '20553f71', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -959,10 +959,6 @@ return array( 'javelin-dom', 'javelin-router', ), - '0db8cdca' => array( - 'javelin-install', - 'phuix-button-view', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1020,9 +1016,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '1d17130f' => array( - 'javelin-dom', - ), '1e911d0f' => array( 'javelin-stratcom', 'javelin-request', @@ -1046,6 +1039,9 @@ return array( 'javelin-install', 'javelin-dom', ), + '20553f71' => array( + 'javelin-dom', + ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1071,6 +1067,10 @@ return array( 'javelin-install', 'javelin-util', ), + '29bbc02c' => array( + 'javelin-install', + 'phuix-button-view', + ), '2ae077e1' => array( 'javelin-behavior', 'javelin-dom', From 8ef9490f66a3813d015a95078d711d1f4c997110 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 8 Jun 2017 07:00:57 -0700 Subject: [PATCH 251/543] Add a retroactive migration to expand the `contentHash` field Summary: See D18037. The migration there may cause us to write new file records as a side effect. Ideally, we would rewrite that migration to not ever have this kind of side effect. However, that would make it much more complicated, and it's already very complicated. Instead, retroactively expand the size of this field before `storage adjust` does it, so it has the right size by the time we hit the migration in D18037. Test Plan: @chad, can you `arc patch` this and see if it works? It's possible that it will get us about five lines deeper and then we'll just hit another similar exception, and that this isn't really a viable way forward. Reviewers: chad, amckinley Reviewed By: amckinley Subscribers: amckinley, chad Differential Revision: https://secure.phabricator.com/D18107 --- .../sql/autopatches/20170404.files.retroactive-content-hash.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resources/sql/autopatches/20170404.files.retroactive-content-hash.sql diff --git a/resources/sql/autopatches/20170404.files.retroactive-content-hash.sql b/resources/sql/autopatches/20170404.files.retroactive-content-hash.sql new file mode 100644 index 0000000000..7c4eb0f013 --- /dev/null +++ b/resources/sql/autopatches/20170404.files.retroactive-content-hash.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + CHANGE contentHash contentHash BINARY(64); From e26cd3ffe0f9d656535c85a66085f21ab4e612ec Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 8 Jun 2017 05:38:42 -0700 Subject: [PATCH 252/543] Give "x committed " feed stories an explicitly higher action strength than other transactions Summary: Fixes T12811. The issue here //appears// to be that both the "alice committed rXYZabc" and "Herald added projects..." actions have the same (default) strength and end up applying in arbitrary order, and probably got shuffled around as this transitioned to Modular transactions. Give "alice committed rXYZabc" an explicitly higher action strength. Test Plan: - Wrote an "Always, add project X" Herald rule for commits. - Ran `bin/repository reparse --herald ...`. - Saw an "alice committed rXYZabc" story instead of a "Herald added projects: X" story. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12811 Differential Revision: https://secure.phabricator.com/D18104 --- .../audit/storage/PhabricatorAuditTransaction.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php index d09bdb76ce..ad96edb3a4 100644 --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -49,6 +49,17 @@ final class PhabricatorAuditTransaction return $blocks; } + public function getActionStrength() { + $type = $this->getTransactionType(); + + switch ($type) { + case self::TYPE_COMMIT: + return 3.0; + } + + return parent::getActionStrength(); + } + public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); From d5163d014345e3370bfa45aef8bb13ba5bd696b4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 8 Jun 2017 05:53:39 -0700 Subject: [PATCH 253/543] Write patterns to "git grep" on stdin instead of passing them with "-e" Summary: Fixes T12807. Some shells may apparently mangle/strip UTF8 characters? Just dodge this whole problem by sending the pattern over stdin rather than actually figuring out the particulars. Related tasks, like T7339 and T5554, discuss finding broader fixes for this class of issue, and this definitely isn't exactly a fully legitimate fix, but in many cases (as here) we can reasonably just avoid the problem rather than actually fixing it, at least for a long time. Test Plan: Searched for emoji and non-emoji locally, but this worked fine (on OSX) for me before the patch too. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12807 Differential Revision: https://secure.phabricator.com/D18105 --- .../conduit/DiffusionSearchQueryConduitAPIMethod.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index e9b8f8d15e..389f931c0f 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -57,11 +57,14 @@ final class DiffusionSearchQueryConduitAPIMethod $results = array(); $future = $repository->getLocalCommandFuture( // NOTE: --perl-regexp is available only with libpcre compiled in. - 'grep --extended-regexp --null -n --no-color -e %s %s -- %s', - $grep, + 'grep --extended-regexp --null -n --no-color -f - %s -- %s', $drequest->getStableCommit(), $path); + // NOTE: We're writing the pattern on stdin to avoid issues with UTF8 + // being mangled by the shell. See T12807. + $future->write($grep); + $binary_pattern = '/Binary file [^:]*:(.+) matches/'; $lines = new LinesOfALargeExecFuture($future); From 3400f24c8b53a742b6dd39a9c244f45c51db85ac Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 8 Jun 2017 06:18:48 -0700 Subject: [PATCH 254/543] Send permanent dameon failures to the log, even when not running in verbose mode Summary: Fixes T12803. An install is having difficulty diagnosing mail failures, and one component is that permanent task failures aren't reaching the log. It's reasonable to send these to the log even when "phd.verbose" is off. See T12803 for a rough review of when we generate these failrues today. Test Plan: - Faked some exceptions. - Got a result in the log (P2058) with `phd.verbose` turned off. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12803 Differential Revision: https://secure.phabricator.com/D18106 --- .../metamta/PhabricatorMetaMTAWorker.php | 17 ++++++++++++----- .../workers/PhabricatorTaskmasterDaemon.php | 12 ++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/applications/metamta/PhabricatorMetaMTAWorker.php b/src/applications/metamta/PhabricatorMetaMTAWorker.php index af655f4e0f..dcc6a8dc5e 100644 --- a/src/applications/metamta/PhabricatorMetaMTAWorker.php +++ b/src/applications/metamta/PhabricatorMetaMTAWorker.php @@ -13,10 +13,6 @@ final class PhabricatorMetaMTAWorker protected function doWork() { $message = $this->loadMessage(); - if (!$message) { - throw new PhabricatorWorkerPermanentFailureException( - pht('Unable to load message!')); - } if ($message->getStatus() != PhabricatorMailOutboundStatus::STATUS_QUEUE) { return; @@ -32,7 +28,18 @@ final class PhabricatorMetaMTAWorker private function loadMessage() { $message_id = $this->getTaskData(); - return id(new PhabricatorMetaMTAMail())->load($message_id); + $message = id(new PhabricatorMetaMTAMail()) + ->load($message_id); + + if (!$message) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load mail message (with ID "%s") while preparing to '. + 'deliver it.', + $message_id)); + } + + return $message; } public function renderForDisplay(PhabricatorUser $viewer) { diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index 57a69843a4..c2276843db 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -23,11 +23,15 @@ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { $ex = $task->getExecutionException(); if ($ex) { if ($ex instanceof PhabricatorWorkerPermanentFailureException) { - $this->log( + // NOTE: Make sure these reach the daemon log, even when not + // running in "phd.verbose" mode. See T12803 for discussion. + $log_exception = new PhutilProxyException( pht( - 'Task %d was cancelled: %s', - $id, - $ex->getMessage())); + 'Task "%s" encountered a permanent failure and was '. + 'cancelled.', + $id), + $ex); + phlog($log_exception); } else if ($ex instanceof PhabricatorWorkerYieldException) { $this->log(pht('Task %s yielded.', $id)); } else { From 0d1654446be6de43b2bd12fcc247db0dc1a46464 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 9 Jun 2017 22:17:36 +0000 Subject: [PATCH 255/543] Clean up spacing on diff-banner Summary: Adds spacing to the buttons, line-height for aligning text vertically. Test Plan: Leave comments on a diff. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18110 --- resources/celerity/map.php | 12 ++++++------ .../css/application/differential/changeset-view.css | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1c0fb7fb3c..2209dffea7 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,7 +12,7 @@ return array( 'core.pkg.css' => 'ab24402f', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '1ccbf3a9', + 'differential.pkg.css' => '4e99863c', 'differential.pkg.js' => 'b7504037', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'c3f44655', + 'rsrc/css/application/differential/changeset-view.css' => 'c72dba88', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -562,7 +562,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'c3f44655', + 'differential-changeset-view-css' => 'c72dba88', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -1872,9 +1872,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - 'c3f44655' => array( - 'phui-inline-comment-view-css', - ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1896,6 +1893,9 @@ return array( 'javelin-stratcom', 'javelin-util', ), + 'c72dba88' => array( + 'phui-inline-comment-view-css', + ), 'c7ccd872' => array( 'phui-fontkit-css', ), diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 33dcea1213..bfde532c44 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -392,13 +392,14 @@ tr.differential-inline-loading { top: 0; left: 0; right: 0; - background: rgba(255, 255, 255, 0.95); + background: #fff; box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); border-bottom: 1px solid {$lightgreyborder}; - padding: 12px 18px; + padding: 8px 18px; vertical-align: middle; font-weight: bold; font-size: {$biggerfontsize}; + line-height: 28px; } .diff-banner .phui-icon-view { @@ -409,6 +410,10 @@ tr.differential-inline-loading { color: {$greytext}; } +.diff-banner-buttons .button { + margin-left: 8px; +} + .diff-banner-has-unsaved, .diff-banner-has-unsubmitted { background: {$sh-yellowbackground}; From 83a89166ee339758c556ef41fd6fe20c5e3da747 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 12 Jun 2017 07:51:16 -0700 Subject: [PATCH 256/543] Add profile images to Repositories Summary: Builds out some images to use to identify repositories. Fixes T12825. Test Plan: Try setting custom, built in, and null images. {F4998175} {F4998192} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12825 Differential Revision: https://secure.phabricator.com/D18116 --- externals/octicons/LICENSE | 21 ++ externals/octicons/README.md | 194 ++++++++++++++ resources/builtin/repo/building.png | Bin 0 -> 1538 bytes resources/builtin/repo/cloud.png | Bin 0 -> 2821 bytes resources/builtin/repo/code.png | Bin 0 -> 4346 bytes resources/builtin/repo/commit.png | Bin 0 -> 3028 bytes resources/builtin/repo/database.png | Bin 0 -> 4716 bytes resources/builtin/repo/desktop.png | Bin 0 -> 1688 bytes resources/builtin/repo/gears.png | Bin 0 -> 5903 bytes resources/builtin/repo/globe.png | Bin 0 -> 5820 bytes resources/builtin/repo/locked.png | Bin 0 -> 2888 bytes resources/builtin/repo/microchip.png | Bin 0 -> 2456 bytes resources/builtin/repo/mobile.png | Bin 0 -> 1905 bytes resources/builtin/repo/repo.png | Bin 0 -> 1950 bytes resources/builtin/repo/servers.png | Bin 0 -> 1800 bytes .../20170612.repository.image.01.sql | 2 + src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 2 + .../DiffusionRepositoryController.php | 4 +- ...sionRepositoryProfilePictureController.php | 246 ++++++++++++++++++ .../diffusion/request/DiffusionRequest.php | 1 + .../query/PhabricatorRepositoryQuery.php | 36 +++ .../PhabricatorRepositorySearchEngine.php | 6 +- .../storage/PhabricatorRepository.php | 17 ++ 24 files changed, 528 insertions(+), 3 deletions(-) create mode 100644 externals/octicons/LICENSE create mode 100644 externals/octicons/README.md create mode 100644 resources/builtin/repo/building.png create mode 100644 resources/builtin/repo/cloud.png create mode 100644 resources/builtin/repo/code.png create mode 100644 resources/builtin/repo/commit.png create mode 100644 resources/builtin/repo/database.png create mode 100644 resources/builtin/repo/desktop.png create mode 100644 resources/builtin/repo/gears.png create mode 100644 resources/builtin/repo/globe.png create mode 100644 resources/builtin/repo/locked.png create mode 100644 resources/builtin/repo/microchip.png create mode 100644 resources/builtin/repo/mobile.png create mode 100644 resources/builtin/repo/repo.png create mode 100644 resources/builtin/repo/servers.png create mode 100644 resources/sql/autopatches/20170612.repository.image.01.sql create mode 100644 src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php diff --git a/externals/octicons/LICENSE b/externals/octicons/LICENSE new file mode 100644 index 0000000000..4cf2020ce7 --- /dev/null +++ b/externals/octicons/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2016 GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/externals/octicons/README.md b/externals/octicons/README.md new file mode 100644 index 0000000000..84edd84dce --- /dev/null +++ b/externals/octicons/README.md @@ -0,0 +1,194 @@ +# GitHub Octicons + +[![NPM version](https://img.shields.io/npm/v/octicons.svg)](https://www.npmjs.org/package/octicons) +[![Build Status](https://travis-ci.org/primer/octicons.svg?branch=master)](https://travis-ci.org/primer/octicons) + +> Octicons are a scalable set of icons handcrafted with <3 by GitHub. + +## Install + +**NOTE:** The compiled files are located in `/build/`. This directory is located in the published npm package. Which means you can access it when you `npm install octicons`. You can also build this directory by following the [building octicons directions](#building-octicons). The files in the `/lib/` directory are the raw source files and are not compiled or optimized. + +#### NPM + +This repository is distributed with [npm][npm]. After [installing npm][install-npm], you can install `octicons` with this command. + +``` +$ npm install --save octicons +``` + +## Usage + +For all the usages, we recommend using the CSS located in `./build/octicons.css`. This is some simple CSS to normalize the icons and inherit colors. + +### Spritesheet + +With a [SVG sprite icon system](https://css-tricks.com/svg-sprites-use-better-icon-fonts/) you can include the sprite sheet located `./build/sprite.octicons.svg` after you [build the icons](#building-octicons) or from the npm package. There is a demo of how to use the spritesheet in the build directory also. + +### Node + +After installing `npm install octicons` you can access the icons like this. + +```js +var octicons = require("octicons") +octicons.alert +// { keywords: [ 'warning', 'triangle', 'exclamation', 'point' ], +// path: '', +// height: '16', +// width: '16', +// symbol: 'alert', +// options: +// { version: '1.1', +// width: '16', +// height: '16', +// viewBox: '0 0 16 16', +// class: 'octicon octicon-alert', +// 'aria-hidden': 'true' }, +// toSVG: [Function] } +``` + +There will be a key for every icon, with `keywords` and `svg`. + +#### `octicons.alert.symbol` + +Returns the string of the symbol name + +```js +octicons.x.symbol +// "x" +``` + +#### `octicons.person.path` + +Path returns the string representation of the path of the icon. + +```js +octicons.x.path +// +``` + +#### `octicons.issue.options` + +This is a json object of all the `options` that will be added to the output tag. + +```js +octicons.x.options +// { version: '1.1', width: '12', height: '16', viewBox: '0 0 12 16', class: 'octicon octicon-x', 'aria-hidden': 'true' } +``` + +#### `octicons.alert.width` + +Width is the icon's true width. Based on the svg view box width. _Note, this doesn't change if you scale it up with size options, it only is the natural width of the icon_ + +#### `octicons.alert.height` + +Height is the icon's true height. Based on the svg view box height. _Note, this doesn't change if you scale it up with size options, it only is the natural height of the icon_ + +#### `keywords` + +Returns an array of keywords for the icon. The data [comes from the octicons repository](https://github.com/primer/octicons/blob/master/lib/data.json). Consider contributing more aliases for the icons. + +```js +octicons.x.keywords +// ["remove", "close", "delete"] +``` + +#### `octicons.alert.toSVG()` + +Returns a string of the svg tag + +```js +octicons.x.toSVG() +// +``` + +The `.toSVG()` method accepts an optional `options` object. This is used to add CSS classnames, a11y options, and sizing. + +##### class + +Add more CSS classes to the `` tag. + +```js +octicons.x.toSVG({ "class": "close" }) +// +``` + +##### aria-label + +Add accessibility `aria-label` to the icon. + +```js +octicons.x.toSVG({ "aria-label": "Close the window" }) +// +``` + +##### width & height + +Size the SVG icon larger using `width` & `height` independently or together. + +```js +octicons.x.toSVG({ "width": 45 }) +// +``` + +#### `octicons.alert.toSVGUse()` + +Returns a string of the svg tag with the `` tag, for use with the spritesheet located in the /build/ directory. + +```js +octicons.x.toSVGUse() +// +``` + +### Ruby + +If your environment is Ruby on Rails, we have a [octicons_helper](https://github.com/primer/octicons_helper) gem available that renders SVG in your page. The octicons_helper uses the [octicons_gem](https://github.com/primer/octicons_gem) to do the computing and reading of the SVG files. + +### Jekyll + +For jekyll, there's a [jekyll-octicons](https://github.com/primer/jekyll-octicons) plugin available. This works exactly like the octicons_helper. + +## Changing, adding, or deleting icons + +1. Open the [Sketch document][sketch-document] in `/lib/`. Each icon exists as an artboard within our master Sketch document. If you’re adding an icon, duplicate one of the artboards and add your shapes to it. Be sure to give your artboard a name that makes sense. +2. Once you’re happy with your icon set, choose File > Export… +3. Choose all the artboards you’d like to export and then press “Export” +4. Export to `/lib/svg/` + +You’ll next need to build your Octicons. + +## Building Octicons + +All the files you need will be in the `/build/` directory already, but if you’ve made changes to the `/lib/` directory and need to regenerate, follow these steps: + +1. Open the Octicons directory in Terminal +2. `npm install` to install all dependencies for the project. +3. Run the command `npm run build`. This will run the grunt task to build the SVGs, placing them in the `/build/` directory. + +## Publishing + +If you have access to publish this repository, these are the steps to publishing. If you need access, contact [#design-systems](https://github.slack.com/archives/design-systems). + +1. Update the [CHANGELOG.md](./CHANGELOG.md) with relevant version number and any updates made to the repository. +2. `npm version ` Run [npm version](https://docs.npmjs.com/cli/version) inputing the relevant version type. The versioning is [semver](http://semver.org/), so version appropriately based on what has changed. +3. `npm publish` This will publish the new version to npmjs.org +4. `git push && git push --tags` Push all these changes to origin. + +## License + +(c) 2012-2016 GitHub, Inc. + +When using the GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos). + +_SVG License:_ [SIL OFL 1.1](http://scripts.sil.org/OFL) +Applies to all SVG files + +_Code License:_ [MIT](./LICENSE) +Applies to all other files + +[primer]: https://github.com/primer/primer +[docs]: http://primercss.io/ +[npm]: https://www.npmjs.com/ +[install-npm]: https://docs.npmjs.com/getting-started/installing-node +[sass]: http://sass-lang.com/ +[sketch-document]: https://github.com/primer/octicons/blob/master/lib/octicons-master.sketch diff --git a/resources/builtin/repo/building.png b/resources/builtin/repo/building.png new file mode 100644 index 0000000000000000000000000000000000000000..dd39fb8f140f158dc8676adebb509e0771069baa GIT binary patch literal 1538 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAV(pc#T@@PYLE#mW->l;=XhIZt`MUp>)S4AaHG%f2fPF*n`d< z5w4GY?>Rf_gr2xQa{TJEMQ6h%O(TQq(#ra0hT6s&-h$otbPJkW{aAL&^uJb4T(4() zLh97@)5dKK3@nD8E{-7;jBoE;Ez@=vaC>OAz$!t;7zA!7`rZ4ld5CT2G3~eKvUE$c z-@o4VYu%ff^(rn15MsBt^7p%1$Ki9%fW|<$V68A-9IL*hFcWXYrSlPcs~S zy1!{Ctx+vD(OH&Syya2R7L{e8)gpq^X0;tLS)z7+ah&PV?y2=A3xLdhUI&@Dfi3_O zS!V9ddi7rGD&^MRUfi+4EM{NJ9TWFxJ@?y5w>G*9Ccb^O`IwPmsm{yI-(vd8j!x;F z2~?F5-#tri>YOaQj6ilh_x4GbUu?1L;_xmrpY?U$(pJrrxvySCywAR8QTZ?<@#V>j z>z)gqe^8moh!GHwz%e|K+HjhoJg#V#0dq|I;+K0CR~AqC$54L1c1+t64r-9S~?d;xYRNb39pq&PuyZx?rVdG%SlX8!9}maFc~&#gAs zTjTt>Ox>Y#&)u6>(|c2e41V7$Fn?u!saI%&?C(75wQHN}SLVvj>HFhdU&I0nbxZbZ z6Ij-S`>w3be-R&bU)rkn|uy3;J51xOk*MGelIzQ|McX!>_6&oTZM%-T!em_hlY3NfXtyJ~?KUzy)`BtZwgK~Ag`|}_-K?Jxc Zzqw3tgRE1!2C$%F@O1TaS?83{1OV)qoLm3^ literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/cloud.png b/resources/builtin/repo/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..7c355b0adc84896144f15afcc0c1fd8f8a346122 GIT binary patch literal 2821 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/code.png b/resources/builtin/repo/code.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e56be76276860087e987694d3397d22689ef74 GIT binary patch literal 4346 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 Hs}cVIiSwi` literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/database.png b/resources/builtin/repo/database.png new file mode 100644 index 0000000000000000000000000000000000000000..2132b8504126bdfd63592d76dce49b971b272d56 GIT binary patch literal 4716 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/desktop.png b/resources/builtin/repo/desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..adc14c782ca05eea1fd332cabfd64eb3e69bb1d2 GIT binary patch literal 1688 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAm>wnPlzj!n_b*HibhTd?7h#Q z4vh6>B|(0{3~aLIsZ;k|e>#O#%z4@WrO9WLTy_sv5?@}zu+-@F&j^W=zMz8J)@ zZjHJ|!7H`A=SIeJPbVaveE8?V2MNiKpQQdODz&x$oSpLi-reIzW@cyF+bMesojM^H z7jyIwi}u@hTG79vYHK<>N{T{W>bP#@((Hfno!zf~#ojt!1wCCy#^n?K&AR>h>)H*E zpQv_SJIL+t#T@E$^HP9(^_N>rxhq$d#;?D@#M0r*z`(rT)5S5Qg7M8kR>lSc28Ip0 zzw-NV$ZTsh2l@sA8fFx&T-)#eDtFqPFpFbue6h*~0*4($LKj5NGB7qazWYDw<<*n7 zE8}t})QPBnh*RuS*}EZvsWr%Q$`q~83oF7@rbfxSyfQ7Ep4s4|EaE;>?ZpVWZ(N) zcl%%d3P1kq@6CnD+ANI<42!}xycOVNUzPBmcV>LqdEYy~C;NZWPF`z!&1}ndx$N~l zTfax-X54>v>{i0TCRT1Sorn!!#^u#=EH-uf?o+94Ps^a!JzB)kfE>i#DEH zy6$R`TC!`*{6(>XoDrYd5Dv!WykEv^#DcHqDc7ECX8*eDkOs$s;O*kUx3{h3-~R7WbMdc6Wn-_{&?WEjxU!uTwZ;xvNZ6aoxqOuQ>{v*WGpLh<1N* zqbr(2Fxuz1*l~d;X0p3`4a(nLT3h{H@CD=Ds*V6*xr-~0`wP5zX}s&pA6BJ`pIv-= zt9m`|U0Ari)}-<i9(uUYWW+UCZt2B!U+A3D}CigG=46jaqa)VTBL6pfv{ zzr8er*gpGB3tqBF=-S+cKB7I=alSKOJ>HOO)T(xPi)p_`NHF*5R~n)Ghm$sImhNOM zGYwk9nRaxR=Cw-dM8;TbZ2wX{M!ka?O$z_HB()ATqUQ<-*#S`?`dz1 z>xIl_rX@k^Hgmg`2IuLXd9^Tt|4rA|pjGB`th`g#BW literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/gears.png b/resources/builtin/repo/gears.png new file mode 100644 index 0000000000000000000000000000000000000000..c08e733e0019e8e8bbea774c4c3407c84dc1ca78 GIT binary patch literal 5903 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 D9mo~D literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/globe.png b/resources/builtin/repo/globe.png new file mode 100644 index 0000000000000000000000000000000000000000..bdc379aa78ed5faeeec9acaac99ec011b7d1e54d GIT binary patch literal 5820 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 VPWA6h>8SpT$6@eJ4US)5`xCiLet`f0 literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/mobile.png b/resources/builtin/repo/mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb5dc21e0b01bcee97adc44e213b97ec14a9f79 GIT binary patch literal 1905 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/servers.png b/resources/builtin/repo/servers.png new file mode 100644 index 0000000000000000000000000000000000000000..2b9f2c3d85b52dfe01e2b29216dfea90b14cc915 GIT binary patch literal 1800 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxV1_maU0G|+7AUC_XcNC4}5U5km zm<3D+Yf6Irf*D@@`1b$fk=D|J_LIM+n{o46%>8}A%JKR(f&C|nO3IVA?qp`&Ta|h3 zPPL71-J%W7T*9|!PH1ed>8hR7w(gOZ;%dF#@GRqQW2XS)fCEWT)WV?E!VeVf_-7}`!;c($*;cXGdp`3z0dinL={yOuC+ zn!ncgpMA^SS#v^#B(6-^!oqndooC1K??#nJ^AqN+Ijn!W!%qDY0|N`Yr;B4q1>@Vh z&1J?u60Q#~r`g@!mJ3C@PTcpfGWJYQWqteRxlozl9&L3dpkJ|qt~k$36|X6KTRL5w zZmv#J3T%xyq9nGl!&N6qup7t>o47J+ZB?(-)$D6ZVQO1nUs=0+|CRg;8w59A%Dm&^ zIm=9WTKwlKuZxRP<&w5$-CdS@aBFrxQ|XHpMpmWgYS-v#vBmn%$orbI^1ki!ja4fo zwwNVWomv=vF8OPiVMcP(mx;mgNtPy4gYA=UMtz-rxuNmMCZ0*h6_O&^PA7A0)Ny`l z*b^~L@6!uMozn%CJqpuOm)N)op1$NRx41J^(m2fT@hXASW^sPPJR5U_txqoeza-{( z*6O5y!!0v)+BMa#Z(Qbenal60?~IqWsWUHXJl~jhZkFxx2hYwKH$Kzf5T#nde=q%^ z($a`I21hl zNk(s0%<)}Ik_v9N>@@P7<|kY%mt8hJF_`P!D^uYYyH4#YoVef8X#rzcMRoQIO49x0bN%H?z&VNT6| zyvid-1QKup(>s}!4Fhi)Uwp9Zux1a6j#WGQzE@ggTsZu?w{z;n#Yd~ZPS5RMFRZ>G9VPW}5#-neXU zXKLaxy#v-ezt;Qf+|Stlbdln;waey&86-ddkT^%i4j53sLKOFBKG>Xdx$n8nwcq+{ zSBu}dF0OXLFf#A+woT`^XL@b?xLa}B_5S%@Mc<|?hDj|A)?ari{?~QcoyP>UoBulB zzVhSJvRnLpYaT*#+*E!@-qTK0ni%lS%S$;>32VLu33gn4|Mv33ga74<_kp;cu6{1- HoD!M 'applications/diffusion/management/DiffusionRepositoryManagementPanel.php', 'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php', 'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php', + 'DiffusionRepositoryProfilePictureController' => 'applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php', 'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php', 'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php', 'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php', @@ -5820,6 +5821,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryManagementPanel' => 'Phobject', 'DiffusionRepositoryPath' => 'Phobject', 'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel', + 'DiffusionRepositoryProfilePictureController' => 'DiffusionController', 'DiffusionRepositoryRef' => 'Phobject', 'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 8fef05bd88..c07c89159b 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -140,6 +140,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { $this->getEditRoutePattern('edit/') => 'DiffusionCommitEditController', ), + 'picture/(?P[0-9]\d*)/' + => 'DiffusionRepositoryProfilePictureController', ), ); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 91b33bcaaa..72b4d105d2 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -274,7 +274,9 @@ final class DiffusionRepositoryController extends DiffusionController { ->setHeader($repository->getName()) ->setUser($viewer) ->setPolicyObject($repository) - ->setHeaderIcon('fa-code'); + ->setProfileHeader(true) + ->setImage($repository->getProfileImageURI()) + ->setImageEditURL('/diffusion/picture/'.$repository->getID().'/'); if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php b/src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php new file mode 100644 index 0000000000..e6256989b5 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php @@ -0,0 +1,246 @@ +getViewer(); + $id = $request->getURIData('id'); + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->needURIs(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$repository) { + return new Aphront404Response(); + } + + $supported_formats = PhabricatorFile::getTransformableImageFormats(); + $e_file = true; + $errors = array(); + $done_uri = $repository->getURI(); + + if ($request->isFormPost()) { + $phid = $request->getStr('phid'); + $is_default = false; + if ($phid == PhabricatorPHIDConstants::PHID_VOID) { + $phid = null; + $is_default = true; + } else if ($phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + } else { + if ($request->getFileExists('picture')) { + $file = PhabricatorFile::newFromPHPUpload( + $_FILES['picture'], + array( + 'authorPHID' => $viewer->getPHID(), + 'canCDN' => true, + )); + } else { + $e_file = pht('Required'); + $errors[] = pht( + 'You must choose a file when uploading a new profile picture.'); + } + } + + if (!$errors && !$is_default) { + if (!$file->isTransformableImage()) { + $e_file = pht('Not Supported'); + $errors[] = pht( + 'This server only supports these image formats: %s.', + implode(', ', $supported_formats)); + } else { + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); + } + } + + if (!$errors) { + if ($is_default) { + $repository->setProfileImagePHID(null); + } else { + $repository->setProfileImagePHID($xformed->getPHID()); + $xformed->attachToObject($repository->getPHID()); + } + $repository->save(); + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + } + + $title = pht('Edit Picture'); + + $form = id(new PHUIFormLayoutView()) + ->setUser($viewer); + + $default_image = PhabricatorFile::loadBuiltin( + $viewer, 'repo/code.png'); + + $images = array(); + + $current = $repository->getProfileImagePHID(); + $has_current = false; + if ($current) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current)) + ->execute(); + if ($files) { + $file = head($files); + if ($file->isTransformableImage()) { + $has_current = true; + $images[$current] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Current Picture'), + ); + } + } + } + + $builtins = array( + 'repo/building.png', + 'repo/cloud.png', + 'repo/commit.png', + 'repo/database.png', + 'repo/desktop.png', + 'repo/gears.png', + 'repo/globe.png', + 'repo/locked.png', + 'repo/microchip.png', + 'repo/mobile.png', + 'repo/repo.png', + 'repo/servers.png', + ); + foreach ($builtins as $builtin) { + $file = PhabricatorFile::loadBuiltin($viewer, $builtin); + $images[$file->getPHID()] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Builtin Image'), + ); + } + + $images[PhabricatorPHIDConstants::PHID_VOID] = array( + 'uri' => $default_image->getBestURI(), + 'tip' => pht('Default Picture'), + ); + + require_celerity_resource('people-profile-css'); + Javelin::initBehavior('phabricator-tooltips', array()); + + $buttons = array(); + foreach ($images as $phid => $spec) { + $style = null; + if (isset($spec['style'])) { + $style = $spec['style']; + } + $button = javelin_tag( + 'button', + array( + 'class' => 'button-grey profile-image-button', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $spec['tip'], + 'size' => 300, + ), + ), + phutil_tag( + 'img', + array( + 'height' => 50, + 'width' => 50, + 'src' => $spec['uri'], + ))); + + $button = array( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'phid', + 'value' => $phid, + )), + $button, + ); + + $button = phabricator_form( + $viewer, + array( + 'class' => 'profile-image-form', + 'method' => 'POST', + ), + $button); + + $buttons[] = $button; + } + + if ($has_current) { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Current Picture')) + ->setValue(array_shift($buttons))); + } + + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Use Picture')) + ->setValue($buttons)); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $upload_form = id(new AphrontFormView()) + ->setUser($viewer) + ->setEncType('multipart/form-data') + ->appendChild( + id(new AphrontFormFileControl()) + ->setName('picture') + ->setLabel(pht('Upload Picture')) + ->setError($e_file) + ->setCaption( + pht('Supported formats: %s', implode(', ', $supported_formats)))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($done_uri) + ->setValue(pht('Upload Picture'))); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Repository Picture')) + ->setHeaderIcon('fa-camera-retro'); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($repository->getName(), $repository->getURI()); + $crumbs->addTextCrumb(pht('Edit Picture')); + $crumbs->setBorder(true); + + $upload_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Upload New Picture')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($upload_form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + $upload_box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } +} diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index ab08025349..1a46d43adf 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -143,6 +143,7 @@ abstract class DiffusionRequest extends Phobject { $query = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withIdentifiers(array($identifier)) + ->needProfileImage(true) ->needURIs(true); if ($need_edit) { diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index c7cfb4df5c..85b2ea0f0d 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -36,6 +36,7 @@ final class PhabricatorRepositoryQuery private $needCommitCounts; private $needProjectPHIDs; private $needURIs; + private $needProfileImage; public function withIDs(array $ids) { $this->ids = $ids; @@ -160,6 +161,11 @@ final class PhabricatorRepositoryQuery return $this; } + public function needProfileImage($need) { + $this->needProfileImage = $need; + return $this; + } + public function getBuiltinOrders() { return array( 'committed' => array( @@ -374,6 +380,36 @@ final class PhabricatorRepositoryQuery } } + if ($this->needProfileImage) { + $default = null; + + $file_phids = mpull($repositories, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + foreach ($repositories as $repository) { + $file = idx($files, $repository->getProfileImagePHID()); + if (!$file) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'repo/code.png'); + } + $file = $default; + } + $repository->attachProfileImageFile($file); + } + } + return $repositories; } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 78f29fad1a..f321a112d9 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -15,7 +15,8 @@ final class PhabricatorRepositorySearchEngine return id(new PhabricatorRepositoryQuery()) ->needProjectPHIDs(true) ->needCommitCounts(true) - ->needMostRecentCommits(true); + ->needMostRecentCommits(true) + ->needProfileImage(true); } protected function buildCustomSearchFields() { @@ -165,7 +166,8 @@ final class PhabricatorRepositorySearchEngine ->setObject($repository) ->setHeader($repository->getName()) ->setObjectName($repository->getMonogram()) - ->setHref($repository->getURI()); + ->setHref($repository->getURI()) + ->setImageURI($repository->getProfileImageURI()); $commit = $repository->getMostRecentCommit(); if ($commit) { diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 889bb734f3..a0d4c6c7b5 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -57,6 +57,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO protected $viewPolicy; protected $editPolicy; protected $pushPolicy; + protected $profileImagePHID; protected $versionControlSystem; protected $details = array(); @@ -69,6 +70,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO private $mostRecentCommit = self::ATTACHABLE; private $projectPHIDs = self::ATTACHABLE; private $uris = self::ATTACHABLE; + private $profileImageFile = self::ATTACHABLE; public static function initializeNewRepository(PhabricatorUser $actor) { @@ -110,6 +112,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'credentialPHID' => 'phid?', 'almanacServicePHID' => 'phid?', 'localPath' => 'text128?', + 'profileImagePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'callsign' => array( @@ -478,6 +481,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } } + public function getProfileImageURI() { + return $this->getProfileImageFile()->getBestURI(); + } + + public function attachProfileImageFile(PhabricatorFile $file) { + $this->profileImageFile = $file; + return $this; + } + + public function getProfileImageFile() { + return $this->assertAttached($this->profileImageFile); + } + + /* -( Remote Command Execution )------------------------------------------- */ From 6cb326e58e41fb283956fc9d379c8e7bfb6450cf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 12 Jun 2017 09:24:57 -0700 Subject: [PATCH 257/543] Add a background to repository icons Summary: I think these are a hair nicer in the actual UI. Test Plan: Revisit /diffusion/ Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18118 --- resources/builtin/repo/building.png | Bin 1538 -> 2566 bytes resources/builtin/repo/cloud.png | Bin 2821 -> 1904 bytes resources/builtin/repo/code.png | Bin 4346 -> 2680 bytes resources/builtin/repo/commit.png | Bin 3028 -> 2103 bytes resources/builtin/repo/database.png | Bin 4716 -> 3428 bytes resources/builtin/repo/desktop.png | Bin 1688 -> 1193 bytes resources/builtin/repo/gears.png | Bin 5903 -> 4209 bytes resources/builtin/repo/globe.png | Bin 5820 -> 4171 bytes resources/builtin/repo/locked.png | Bin 2888 -> 2092 bytes resources/builtin/repo/microchip.png | Bin 2456 -> 1841 bytes resources/builtin/repo/mobile.png | Bin 1905 -> 1411 bytes resources/builtin/repo/repo.png | Bin 1950 -> 1590 bytes resources/builtin/repo/servers.png | Bin 1800 -> 1400 bytes 13 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/builtin/repo/building.png b/resources/builtin/repo/building.png index dd39fb8f140f158dc8676adebb509e0771069baa..1638c4174dcca9d964625cbd884c1b48414438e0 100644 GIT binary patch literal 2566 zcmZ`)c{Cf?9;RyASIe|KrL?qK)vBt|YAw_0Fs-GQ>9lIAP^vuIDk+U!iKtLZ7GjA| zLM#yy5;Rm1BqFgUu|+J2eP7Z}r`5-q_s2Uk?|tXH_jk`dzjJ=)e)o6p2fbr&DR)Tq zkc5PUoVC@>yAl$Tx_jfG)b5UbQM9Fm#J;dQwhp&Ge*Cz;zR}&o#T3;>fpcDjBVrQr zipv|=y+dNLc$7btlwSPgMG7JZKRdU;9U6~{&z+fFSY2I*B)?6D7r$@qZtLhl=HPP* zh`?}o3KI7$Bo!D24-AC`yi5-YgSXIFf(cPg9WCq?YH?|W)yoTwK?y~3jPAj;wT-p) zjjgTiU2Jad1~H!4oR(3VkW%L3sn&(?HRg`Pf=)Sp%WwLt$whG8f;{&W=mOkQ-u)^|;G5~WQvGanI`6ZR?# zoRlw|nIGd%v3huksYSsN$g0{F6sBr?LKqD}M}l)ivkMbbGw`embYTrFs~qy?Ef|`= zyu7-yx<+RX42?`+%j)Bk3lP~AKQon6BqR=4S>H5s2$fvwg#<~N{ZqPHqXew4?X%Eq z2AYhzb~>X#^Tck${ug{_l~GQfgy#XPI=x?iQ7+TaVTW=c;gRL~J(mD@-l0vdbVQY| z2B5m9Yl9=bc)

^dM8$1*dJ3iO&}_mk@Sv2*X@Kn%%|B0fuX31(6j@qEP*x=A*Q1 zcJoNy4EUc5c}p?7yx|!xrRaf=&%wrfSE#a+SXm;#$4CD1Wn}gqCKa331tedM^F{s! z(jnWXb-wBVv&N2bV%gi%J2uljDvgO)dH#p4OrukU6zK5P6CgfK-+n% z1hHlg8>8q(3QydxpVDyI%pBn9 zce%O~6lbWDp9zvmT%=0X*x_`wX`w+dLZB5I&UBl?pJ}$mob6YzyIr1kMe!bd1$DKU zNk@7gy$8?JfyK7p0PF3XI_mXzYEc*rR>djp<~(5S$?MuZHj_h|#crIf8`-%J+zJCJ zUi0O4HquEM{QB44@VG7^B1ohpIqWin&xnPHl&cxryvjn_h=uT_Nj;eE=|IsmNU68BZd`n*bcSoM3Hl)eF2#8XGKbox%>oXJ z za+@DL(9pK$-g`q(KgY1c-4^OSTKBW2xZ+gYl*b*te4pcezwX!l-;$l;rgmNS1#tq! zfmrJwOg@ff1Ki~ejRm$V3He)}zUwjBPy+kO&zEC@5be;A*cX53-JNyqW;{Ns!@E|} zdFx@X_o6b$|fE|7pwi$~gjRAV5d2<&Sl-IfjzM~vRVAM6pAw?z$ z_TsX7Ukfc3?dE**@hLzf&Q>T2Ju6HpRqku)^as&Z7}QT*Q!8iJfcgB;I-?kK^b>hl z=Y2|Rxau13H^;$k-fx&4DzW->%3kM$#;!h%R}NKW7K!QpW}@R6mX5hn=9ogpnT=y7 zx}6X`VZ3SSpj+O7V10oc6}ZxIa$y7{^q$_Q)2SWQ&pHP-R{mn1<-A;_jA~YGU8l??No)Ax;vi5gQdj7 zGJoTu9Wl+)Q7q$fU)NLNoFiv)e{*^52S0ra=?uB~ zZn$dFuwfwrl}K<>p zv=cwxlzV>m_^;TCfCYDt-J`+Bm2YqY`>^OQk%Vbz+V|0|okMGzw$NY>)57IT#Yal9 zMSy2c#T@@PYLE#mW->l;=XhIZt`MUp>)S4AaHG%f2fPF*n`d< z5w4GY?>Rf_gr2xQa{TJEMQ6h%O(TQq(#ra0hT6s&-h$otbPJkW{aAL&^uJb4T(4() zLh97@)5dKK3@nD8E{-7;jBoE;Ez@=vaC>OAz$!t;7zA!7`rZ4ld5CT2G3~eKvUE$c z-@o4VYu%ff^(rn15MsBt^7p%1$Ki9%fW|<$V68A-9IL*hFcWXYrSlPcs~S zy1!{Ctx+vD(OH&Syya2R7L{e8)gpq^X0;tLS)z7+ah&PV?y2=A3xLdhUI&@Dfi3_O zS!V9ddi7rGD&^MRUfi+4EM{NJ9TWFxJ@?y5w>G*9Ccb^O`IwPmsm{yI-(vd8j!x;F z2~?F5-#tri>YOaQj6ilh_x4GbUu?1L;_xmrpY?U$(pJrrxvySCywAR8QTZ?<@#V>j z>z)gqe^8moh!GHwz%e|K+HjhoJg#V#0dq|I;+K0CR~AqC$54L1c1+t64r-9S~?d;xYRNb39pq&PuyZx?rVdG%SlX8!9}maFc~&#gAs zTjTt>Ox>Y#&)u6>(|c2e41V7$Fn?u!saI%&?C(75wQHN}SLVvj>HFhdU&I0nbxZbZ z6Ij-S`>w3be-R&bU)rkn|uy3;J51xOk*MGelIzQ|McX!>_6&oTZM%-T!em_hlY3NfXtyJ~?KUzy)`BtZwgK~Ag`|}_-K?Jxc Zzqw3tgRE1!2C$%F@O1TaS?83{1OV)qoLm3^ diff --git a/resources/builtin/repo/cloud.png b/resources/builtin/repo/cloud.png index 7c355b0adc84896144f15afcc0c1fd8f8a346122..be998915c9349387f171d66757fcd8d841b33459 100644 GIT binary patch delta 1889 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 diff --git a/resources/builtin/repo/code.png b/resources/builtin/repo/code.png index f7e56be76276860087e987694d3397d22689ef74..3080e9df8862a3dc50f98b10309ad8e830a834b3 100644 GIT binary patch literal 2680 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 UO>b&6G5`Po07*qoM6N<$g0GBq#sB~S literal 3028 zcmeHJ`#Tei7azT$)hadA$~|EvVpeXMTSjvmb1lm?wkBy4Aw?!KCYOW}BDcw9MpAB( zCdI}`uF+gaNO)a(^?ZMO|Ag=Jp68tBJfF|`;XKdhIp;Yi#@f;p1cU$q0077gX^aK{ z1Wx}6kzG5CW!_y=06>syjj}V@f%sdL|NK7%{+|?Ry+R$2Z-oCG6-=}8yKbBX2&d>6`4fXfb#;NTR zfcX?|{a%=!d>dqROxC+$?eC3&`wGft>GhY@_n`9Z;6(1P7?Xq-Zni5mfoF==A6KOw zlzBO5@Ygt{QAP2?(s^IY#emBF&A03PKpCe`Jm?;g*I;yg8kK6N=f^|Bli1jYPluH{ zkfqGsgiZb2*H^Y*c?q*R({jo~IVqUnNJFhl5qdTyc*)~Bb8Qu2ccOF=Z!)i!?Q1P~ zC)DC{n3ha-K9}|Jkkd_VXp^T0)g1i<{6*sZ#a)VxKL7yX*Jj2Bc0mGjU6F}@w!Zut z;VbjiZ&$tB51>b7u{+GaO*dvCd!*qvLJb^m?6GL9p7P1_*1BPZU6~!8q<4GeVpZ=v zF=VUBAC`r>w%qEjNNw= z@})bS3Ez8|JL#*=+THEUPtGCY-oMtd%~fjq)-@Q!rsmd`wclbTm-BJeGnzOd@0D@Y zm-a}$_V5q2*q9$&`jh8(y&F)y&)noyDfq$Xz8d9D%rM)HcZ17}ddGqCR9vMvSvd?>d%uZ)iC%O_%9llsnL z60%&Ka~y8)w||DoZLsBNwqD#4jK98f;co!V8FbwE63DO6szi3(w>qbe0fA?7@%g4D zqHc_qb{h!8{8C1oeR!KDnR)bBVNl7wzv3!lci~68O0@}~7T>9=#%JC@hwl2jO zsnU`z((O4*90OVsRIEf=62mMoq!)19my>?H(g4ymhl1eFNrV{4mpvG~ zEU~Grx6!=l6roPMUh8wWtj)ydlY6229*c(}{2HR}wrz)3heKCfign9*NKNrDZc{pj zL(jR1&mpVBio=)Ek%&k=sjW*#^>r;2h@9`h4cnv+3k^*qVtHT4Q!MBAN4;RYT>m`jl} zBp1=v+G+5s@ZFNV<7=}rq5h@N*0}uPA4em!cR+sJ za)T<~XuL>cdO{8%IuxQ{O}l-n^{z6U4?Mm$Lr zz2%A;cf4jKg9*h`JP7wZr(yA=J&&)P$t9Gs9AupG69?Mr(#0zI9JswAQlf-Vil>&{ z_OkGZx)M!nvV~DFD{2$RN_L)ZNT=INNLyyW}9V|RR(n8bBAr4Gj)u2wumP8>PV-F>CX>xUu zPtxn!1~veddOdJ%9hf?2;79OS6g`2jbQ*D_R2h`L?>T2bsYoq;?@HO>pAQ(*Jj9Lw zVxWAswzzGu%;%KOJMW=tAhstZH?P9yl^&I+tl}WLr+@WhJzhiUYLHxeWvXBn_Vuri z^-<-lH4^wB&}_i^oop>?lgSsN8Kww+^>`xWUNMZ|HtoxL#w$z+xVH^P|<>r^`zgt=l5i ziCkHw3ELyIp90Y7<)`~u*^=f?PEqlTQqk^YQh{w9LhJJsH6rO*rf=A^$c-CHQwefJ z!_&zO<;-6$=3@2 zz}AJZI;2cp_HBznrfxohEA=0A4ywpV-GJ<}7$lfH8Y(&vc4C!np50)eXferFPr12*W&ca5cRlQ|H)P?rjqlY)mR;3k}yOO^pL|a6RXr*{gi9^6Og>Jjn zisqBfEUeG)>*Mvs#OGDIr#yaLJ+S#yQH_(~Cj1#g4`3o+i)=q3bw*2^u~S=^w29hJGi{pV{$Mi`OXD)b Hs}cVIiSwi` diff --git a/resources/builtin/repo/database.png b/resources/builtin/repo/database.png index 2132b8504126bdfd63592d76dce49b971b272d56..651a2d5bf7c6d9591f22f1053d94cfbb810a929b 100644 GIT binary patch delta 3426 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 diff --git a/resources/builtin/repo/desktop.png b/resources/builtin/repo/desktop.png index adc14c782ca05eea1fd332cabfd64eb3e69bb1d2..0513d3876f9a8631a3196e03d67b7f33943da7ba 100644 GIT binary patch literal 1193 zcmcJNc}&^|9L0ZBC#X1p*>qJ&;vrHqHKOvVZrIZ}Q&z>yv!nOQ;XT*vP^N z003jpJMQ-Z0AzD=20F)$Pk9pLIJ|&*KLBgBTKR^OFI?{K8>dt=H7X%D8c7f=tSl`{M9X3xUxXo6X*B!Tyy{urVq9{5dNzeb zWt3I4v~{s3IP;Sn{?5)WGNrJlzH39Fij2p%b`J0FYd4i@RC)yog%emW6wxQg8A2OT(jFwfgP&6tBB)tgCm7(K^`N+8-X9qf%|n%q>iD z1*KF*5xHsW?e@?JcaS}uiKFs`D}>jy@K~%^A|qEaSJtFq(bBB>Vx{e0e}Yj-v`Vr{aZF8 zpyM~aC)Tr@x4qkDEuoQ_4o`BBlP<|?~8+WmB1 zqd<)aW{TjT!;fw0COM8=o?$?+h2w37Tj(PLk(v?P<0^BFy2tKBY#9?xREeLN4l>Su z!MXEJ|2`t{^g1|%%r+m*v#zaqN(nKSmrY;e^m|OPfNUJv@cLyv55%~0th_t}eNhi6 z0GW+zCM_RzAKnu@_ed!V^M(x(vh9j?wI-mS#Uo_`l4 zO%@AM;XX#Tsy#nRZ}-(YFFSr3-G9c?iKXutKCSat!zC9JgFeg$P8W3@`}R-<%8Vq+ zZUD^Rv~qW?!3swe<4xdqGciabB=?4#aW2H4Fv5M>_8slYpK@9NC@=BrX;?)>L){Z{ z?v)^KH;sMC?r%o9JM|B-)8-^aOL9S#N3#ZO?Sf{VB|mI7PM`3FUvRL6!asDdg~81o zoGMwNRkLvrG|>;i@W90&7_PXx){Gd^=UO2rxX8_sQ|V6rv8ox;Sj+i^6cAD7y^0FB zn$EFu(SbnM3oiRCx#jv=*v~KTz89th9IFqHz|CCK~Q_o^&zJ2(7B%W)-aPZfhU92d-ub#xc0s2{u@w4 XAkhwXBkr1^{G{*%`?%M+!BhSL@kn$l literal 1688 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAm>wnPlzj!n_b*HibhTd?7h#Q z4vh6>B|(0{3~aLIsZ;k|e>#O#%z4@WrO9WLTy_sv5?@}zu+-@F&j^W=zMz8J)@ zZjHJ|!7H`A=SIeJPbVaveE8?V2MNiKpQQdODz&x$oSpLi-reIzW@cyF+bMesojM^H z7jyIwi}u@hTG79vYHK<>N{T{W>bP#@((Hfno!zf~#ojt!1wCCy#^n?K&AR>h>)H*E zpQv_SJIL+t#T@E$^HP9(^_N>rxhq$d#;?D@#M0r*z`(rT)5S5Qg7M8kR>lSc28Ip0 zzw-NV$ZTsh2l@sA8fFx&T-)#eDtFqPFpFbue6h*~0*4($LKj5NGB7qazWYDw<<*n7 zE8}t})QPBnh*RuS*}EZvsWr%Q$`q~83oF7@rbfxSyfQ7Ep4s4|EaE;>?ZpVWZ(N) zcl%%d3P1kq@6CnD+ANI<42!}xycOVNUzPBmcV>LqdEYy~C;NZWPF`z!&1}ndx$N~l zTfax-X54>v>{i0TCRT1Sorn!!#^u#=EH-uf?o+94Ps^a!JzB)kfE>i#DEH zy6$R`TC!`*{6(>XoDrYd5Dv!WykEv^#DcHqDc7ECX8*eDkOs$s;O*kUx3{h3-~R7WbMdc6Wn-_{&?WEjxU!uTwZ;xvNZ6aoxqOuQ>{v*WGpLh<1N* zqbr(2Fxuz1*l~d;X0p3`4a(nLT3h{H@CD=Ds*V6*xr-~0`wP5zX}s&pA6BJ`pIv-= zt9m`|U0Ari)}-<i9(uUYWW+UCZt2B!U+A3D}CigG=46jaqa)VTBL6pfv{ zzr8er*gpGB3tqBF=-S+cKB7I=alSKOJ>HOO)T(xPi)p_`NHF*5R~n)Ghm$sImhNOM zGYwk9nRaxR=Cw-dM8;TbZ2wX{M!ka?O$z_HB()ATqUQ<-*#S`?`dz1 z>xIl_rX@k^Hgmg`2IuLXd9^Tt|4rA|pjGB`th`g#BW diff --git a/resources/builtin/repo/gears.png b/resources/builtin/repo/gears.png index c08e733e0019e8e8bbea774c4c3407c84dc1ca78..85d8392636b1748428647f20590d21b37ee98c10 100644 GIT binary patch literal 4209 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 D9mo~D diff --git a/resources/builtin/repo/globe.png b/resources/builtin/repo/globe.png index bdc379aa78ed5faeeec9acaac99ec011b7d1e54d..d30feee429c2b16c7b682c7ec885706af98d8491 100644 GIT binary patch literal 4171 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 literal 5820 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 literal 2888 zcmeHJX*3&H8%`}lZC?$4fq*bkbDb_{2^K?GS`eL9CTeJ9FlIXXZQS`~96W_q_Kx?|aX^_dWM{o*y^C!rVxZPl68s z00S4NLoi@=>Gb{`ucU96uTdjq zt7&?_#P1{X${ZL5D&R_cVwwc*YQC$v@|ACGd2xRGSNc%j&xYqLf=JPlesv!|QNUQp z`iJA<6F<#`N1=Z!zwQ*#-EeHv^=RkYJco?3#QV^;u^Xzdy>+`RF*cyjuU2oSg=|dw zCWzp3AI(_ZF}{7?#2-;vJNmNf#babjOJk--`&RQK=5x-6$*_BV!2kNxetxMlvN!*fA_j*OIrLe&(eX4u;9^3pAr8QSHA zmBkYDNi=3UZD4@-cuPVeCs-*PYM+lEEa!#QD+e;e<+`YpPxGl0yl#Q(U$d*L)o&W( zog^l0ct!h%KzafKx>pV4C8jzK$ho7Rb!@_-OyWGz_qkIqCFeIAHdwR`!z9XFq)am9 ziU28hEN<#)Kc3BsTRlm(cy25%Bc6%6D1W|?%~a>jUeR%15%q$IuoJMtGIj1ehRa+9W zg|WMVT-ZdjjquTv-F@za;ie-ZU^h#(v2-eWK`29xawMWcDq)7^xLW_7@GqWp{;uNL zNT~>oeEu^@cBah3Zh_Wjc0-!`GX(9#aOM`aMbobB&y6xeYP7j+M?Fqq;r@cS9kMO)$cEJb3N};|J zuC5n06b7{}8_vfRHdD?{vT9`fz@`>EvdP0U^4vIo@vY+$qcwJ>PH%$uBb<1I=8q<@ zeNZk_yKDlb4xj(C=e`Kq%gUsOi~gi!8G(YYRQzTlY|rt(M=BG~EDOz?IPWoy)M>+L zScW>cq;y9jR+%I8;3R}>bJU4YMHjog9< zAV3Dx&vf*+%bVV@SG}Z4ehD3xVxBH%dOy>oDMCszrmLa3;PQ-%gM+M5Uixh64cVuv zI!<-H397i*igVGtVqll3v}51#NBY0u3Wpu!szuJSF#+r4HL`j}jgvqht<@?hQi)j> zYR%4Rpv;2otFh*?v#hA#@j~ z-}eAzaPS2EoYR~W?g{KEV;1eU>AO(j_a==wZWy-t6T~_aHQF3AbKu^?yPw_My=6KwNJDyN3_}#fH>N0!G^i|uJ=?h+Od zWCjWHDR|jr&xsD?zgB9e-s;-K_4htM+DbIiD|lgA%a`_A7_BE=K-2p@I}ep?ro&Xg zPbGg>?oYWe=YrNi6-GCCsof(mZAPgUYl|%s3#$lee zN*qUavFwNl6nJyy6-=I8q8qUFIo-9IulKif7t&EQCH*ciM9z9i#$yfBQcdzmw<1kg ze-s)YPM&MS@QvZp%&_)c4VC%u=g6LX;02%Jx^JYI?i?GW;C>l^(;FXk7PnEhOlWP#Ps6-yS?>UHI+ zdTo7URi&O0&*u~o;EAP(Wc1^N5@Z>vu#^<@q%^O%zNWsNL}jF8;k4QK?Og*stkF)!5WRb_mp$6uJIo#!Pe{WgrejjGs~*K5BVvovva8D~ zsnR9I=B9RYbF;Rgy^7FQ+t2||EEyi-3#P=Q?(1ac<<=qy45#pPrarm?6T=5>Xr$oqs zN@emH(fr)JOtz#L9_91+ua=fq3wZtc9~h zQmCuubKgSE39uS{v%`%iI_8VIbxvgAq4`B^R;rw;{yLY^Fh#T3y8h@Mn-=sGH2t3C_T2if*l5QPjm*llEqA}C{w9eUio0_j zBKTyy1!-pCZJ!obyyxpDQR{iy9EZfJ#7E6sGkjWa=~P&V?u?;7%640T8S+cUagUQpc9>i5um7DX?$~eP96382 z1|9Y;HTUwe)Ptpyyeh3RqEKVf()7q7f(0tEobIVocf{{J| zT5RtcngH&{J+k(utbbiWffJSTV3s!-6V%`hxpXEZ-%k_6Xm)yKu8(nTB{SI*c1O)&9 literal 2456 zcmeHHSx}RQ7LFjO>;xN744XeoB&=Z#F;yglv=9_9tc3z4?1DlC3W!w-H$ngtk^&-B z*=`V!fM|mapb{dh8cK@nn_&sWvP2pdEq!u2W9L5Jm!A2~_C1~X&Y43xi^nR;9hC!t zK#DjQCl3&4AN(&rD80wvE3RNcAgPSAu3nhEHada)FaA~F|Dr(pMD*s~2@WNC;5|VH zP7%wyN5?0ITC+UN!k&JhAq?AAuYCUIuI=yjpH2Sf&aVCT!@NIGkmUr^3Kvy>onYX& znuao+_nJR-ukJ;z{I%bPWBdAlvi|SbZQ~geLe!QxLbBl-y?O2on6o16=v0!GDJ{v@ zH*j>uoYp&{4m~!~aR1lJZwvQ1+^YW3>g3zScRrDRF?hweCia5`W_wvt@v9rpEsWqt zwPpqLkA6;bt*ya&F&?|^mzJVj?OFOo7j~kHJDA;Eb?6K9j!okea?13aw&=Q?HBl6?R*VZKF!oac}d72MtVv zk%;tWRr+op*m!yI_ibG^R@ zxgre2_nVu0p=NrP)67nNDFR3{c2~G%4Cc!4!`M;$dyUb(WW(>0j*ZJZ!FE1hyS0OM zf4NJ3eBr|O0VAk!$7DXj{%Yl#yn>w4bY>O|YK*eVR6C}61Ovt(s2WwF980PlB?5&Y zc_Rr@2pm8nMr%de;0g+h#u()SrU%8Ls+tr-8?YPcnzo@_^SXd_pU-HkS(&v9QD6qv&CSh3 z%tsNRLmJ-|92S>wYYj-?cpVEs3@K3q79q)XgiuQ^i~3|19w(DL-6k6LSBjK4$K zli7|LWnD9`z5T`8?K-%FYj-US@+rXKANQ37C*EU2K1G*Kr|J6ZLei^-;QMZ z2yF{AG^m|(<>s0u>d|V4nqOa_;SWvG6=lmi9ZQ=yFRiWbI;{M(Qq)Rm~ zS2f%IUcM1j_pu5~`mBYezx zP12Fm0=V8Al08El_^Dlynjy21q)TWsBwe+@r4eI2^dRWwF_TB8$)<+}XL)ao!v{+3 z*{P)hCQrT0LLnffCCjPx^6r*cvv=u1P(kvp_lE7%i`z)bf&qeb9!W4qurOdKi+yiK zsb;5v#}v^gr@fYImvk5+a?D&M)EmiPec(LcVurfT{hp)k2=Dwf>gv&(`SDuGFlevv z^p81WvR-jXDJCH~MVXc-Hi}cazrZ8|BljhN*@hmW)10Ue^RBQipu(mht#!0sQB++1^ukcpw zBN~lXfsfJ>&MD2BP7r=*4{(KEyITlztw4Sk%s|GG~z?(V7vb!WRR#2}xRXMMFe%?&Iu zLtmUfJbx>0Pt_IM8oMO)va${%51JI87W)OaNrJTFI=FyQ*RlV!r;oh0$5@i<5d_=i z;v^z$!%Uw8CHDN>n0q+fzt@YIS(rqH>D@Pkz18>Z{EBzV(({aF-h;U9xgydh-L>-0#R4CB#{ddivn!jnycQqk3akG)s(sr VPWA6h>8SpT$6@eJ4US)5`xCiLet`f0 diff --git a/resources/builtin/repo/mobile.png b/resources/builtin/repo/mobile.png index ddb5dc21e0b01bcee97adc44e213b97ec14a9f79..b4ec5ee3f17bf73acc177ea2e7b344e99523e284 100644 GIT binary patch literal 1411 zcmchW{ZkSK7{@WJ3}0y4t5(`8+w$5joo#yAT(*3#ZD!`QEUkI@Rw|{W_!g)IN+MvO zi1-SkA|k$!7J?=wiIhnAZlIVU3OKJeXw_}qZGXVNchBATd4BlZJ@>tD-symomLOXY z006M`_VWB005EaX{B)Bc`LWKw4xb_rouAsC*H!Y ztgJlZi}0ll@i#HiiD*HeI2~Eq(>F9VGh2YCVequ+nOV``2s9N3O~Z-ABmCY${$p`h z_mih$*~FwG7FrN@qhMl6k(*zUS5Vo})yM4;E^3yV+xYCZo^(WsWOy_dn*VHgw2s+D zu3(cZ*>^Bii7@Qld(}LF=x$-vtt_H?QB%))R8!BbVQ`bv3G=ViX}3u&?Y;e?;l3x& z9`{R9vx!YD-HVG$Og1kjDZf`JiAh3JYg#GQoRJrkQrX1PvNj{9RHxHxwK{5TYk4)N zS2)Dt@S8aNv}|Glt`zNDdq z%DNhfq5uFunzyG%V6w?;UgrDI02vemZ7Xff2m6m^k%v&*)@5x2^!tIFoD$!Os~369kG zo9K84c`$B*vZl^wyOeY$-AM0-HOmJtK!f9@QO2ab{>%FW{=ZGjgaGHVFu)9{Ym?B+wo9MvX<(-cEe@(@eEul1 zleTT$?22mU`^+cX-)lcsRkSId9qZOBYdm`6j41VH=uzmd>3zS@6mHm-PCYiq9x$cmL+t&fct)9f1a8h(97zj% zbI0=VIc$~o+Z=!GSAO`=ax)KFIIFTBlN`m0{1QVaIu7kOuXOwdyTA5$c^u2s@^fc@ zyn`tMFSM4GhaWkY<+4eB!1vl3P-z5aGlYC-Q->hY>g{#wQ((}kT*x|uXD(y|B3@vF zfOiXYhV_+j;~w(YdK)wA!FVsjZgRMpt=6VMWVOM3bqu^SC2wAH%*+Vhhy}Z9#|~8U zR%HqU^iqd96OYV|PByhep{xmZM7qt{-}gzsdsE!DLyYV3i4k%mf{PyF_PNxpm3R2M zU8C8;4X&Z}Lsnym-d*BM7@&T<0+?h^v@4?k6K?I0ll&x^ZE#CN_$!c_dyp^$d3vF^ zVaS~x_TbvCv`QW0h!e)Wbxs0w>csBcMiRBGU;nXu-y0jz6#BmeT5?d3Yu!0TAByRH MBEYlZhw!Yw03mzW^#A|> literal 1905 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAm?R(Plzj!n_b*HiiUp(_&KBi zBYSa4kY6wZyOL>S^Og%wK5F_d{`=pc^gVaOo7eRL$NX!*RkO}u5m>o`bIodwh4ULj zf$Y>J&mIY# z$GAgFckliZo$b5k918yO?BV^dCl&Yv-qb{3GlwYpMz`!!a)5S5Qg7M9@*2*MC0hR|xYYVP_ z?&cQb54_{~f9Vk}kAi0(-^L$$)e$ASbV`tyCXi6ud^Bsb^_+EE3$5Omtv;F~w79T( zk6lj7`PYTr6Mrx}<^&i#5MRvEs(fQZhK@>`fPaI7k|x^(uVyDJ!Y*y?(PRlvJqn^f|tC;g~DtxH3e{PSnTS ze$i5{psU7-#e7lXPR~AZZCRJ!ayplHOIG=zr*cQ+T23o>ENwY0+)>$j+PLFtt2gHn zsY9%1vZ6!Qq-AsY>LgTmUFAC|;*_f^cHzZ|5G!6?v0b(^z1FWj>aFA0S znBQ{6InS+4yQV4>)19_8x@3D^=;m901!HZet<4LQ7W>>M=Dd31*YzjiA^0nMyWDo2 z&Ljh#`sC%gx3>Y2<+faDbE|icgTE^G#%4d=*;20jGdHg;v9K_)&NJy{WE9`$$jH0f z^GkITYb_m;GH-rpY(0Bf==920%^M1ztvOPp^GfE;oc#(LWp?i1=X=vVHR78d>%oPm z!&RoO-FUj-(Wjmxo1T@5`o(^p5)l@&@Zqi_n@W_Wq!v9_oz^=0LWy@$qy?YvVWHD& ztOaKY^?T?{zI~z4J!$5h*1l??(@*S5?o5wR+kC-Ld!vt>!*S+E8m&=AosShut_Zv9 zUJ3G*`FG)=uZ(Gcw`gI{I;YEvj~^)%%37(?cS-%imzIb>3#To&-eoPLGL37+(j`+Y zb}d}lX5n>u!L%l}q|WxX^dmtbJV&1hdIz)2soLkE^E2RVqfAm~Yp~&wNt3ysrU;+s z?sYspiTCTCIm+`2ExvSB7+qSp;QEm#f{fOJYmZFf5>-_byu6D$XHTcnvexYZLH30^ zD}4?eYH?R9Jn9@+c;s8&uU{V&{z;shZReF!7!bzuG4RvxLnluj`p$jC{boS8i;AYI zmJ}B^uX1ZkyHlp)$|XGy1Xis3#H^`!K%Q;G&xs-rUNbvubo_RyK6Cl7tlP(%Tc>>s zSi73L=gE)htAe+v`-RT6;9J~z@Bi1^Z`Zy5e7x-JpYQ8#->-Yq-zMP1q1b{Gn9c$L$vCoaA(s>Zs(7Qh7D0Ohk7Y$}5CRNrqu;ig~m( zdtzHQd5j^i%`>wxLt-|u&Bm;5H{H3nZk^8k&iTK-@Av)w-|v6^U+Q@mM^ocX#sC1o z6!hCER{%iIa(S#W(0N>N@s7H~)cG^+4qB~tetw~=M@XU7$0j`vi_HnYn_Gx`*2aA) znVN2D>3)d71%+iq#iQxWro}}qmROaV`BXMNgTXTXNGY6^D=VM3AaV$kQ`4b$vYT6b z7_8>S#YL%XCN?SmW_X5RaFj0`;WT&Eak%6PHkMG8g(hX>67Js1zYj0D9gT!%;nT89 z6CM;MJuE_D%0%LESOg*|wfJ!{ZD{yaNL1FXXk=bd#h_>`I5LY>(~|v&ob#B1Cs!BY zD$5ug4E7n7-q6)6q_dit)vXH)3v+X7wR&EuR4puM5|T01>~=J!oItHDc)}>bGb<_^ z<#Oee^o>;ZMxmTzR=?m2#Myad9PxQxVFj_QuC}2A`H0fm(Z_D&<(JTN^Qonj8gAD> zM<;)5Trw$<)i?2iBQxXg7bHF`ii|_OnUTvC%Fb>9EF$yOxTKFSs;O^pZtJ}rgN(ah zpis=co{&B%Wetj6Wul0kJ%Ztp*ALRM5pgI+)r*$)zH}sE{PmP_PIWUJ(c3>%#%Q>c zkSCj-C6(93Cu4*|V>7b~UiUx^yS*6Ce2{^CR8-N<>+ctcRVuY$aO5Rlq|s;wgd;Bp zhO_d>ef^?LG-*zyE`C}m92!kde^OG)nvlp$esG5Y04so?Q}*t6^ky5Gi3WOWSJ2pj zgDKY5S|6Nq=;6etF)MU`lUWxP}F7!vg zVHR_1tiW#qB=pvV_(z^TsP)0Ab=4=IczqNrvc{3N}=&7pY5; z`azVHFoQJw`mc)3PHmC8;E?*1o3ZpQM?UNMB*l`pv<+;P?gnrgOVK!`pCO_U3>T^1n9^ln`@w!c%Hlphgn*3o5Jzzpr zw&r=$F?JeuvIP2mAKVgFElNafgV_@#B*U;P$#yo4moHkKgB=hSW)rxb5rGmCO4@mA zcQ?o&yxMi>H46<6z5ZjDmJm1y-6|o4nwS=O04uKmfhxi|&Eg#)ANtqvj(Z#*iSc*ybmBuC z2STEXhwS?>hgR=#xLDk~TPRIOMp?1=Eeo4G(f~IaPiFlJqxm Cm`s%b literal 1950 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAm>ehPlzj!n_b*Hibg^RFiB_B z0poi~NswPK1FuH>_K%< zi@Pjd<70c^$t=MHza5@aKEL5)`k^dwWPaw*Q+Fp0~nD5k_&(qf4{e%_8CYAp1=vcV;ckzZ>bvj2beiWV=J=aDw z>E)N1Yc`&n$GvgO$C_&q;!ZZPXKt)|8Pu`-boi#DyQ*|ut}M*ixb8lOV5xERn&|6J zE_cs~z4q%^x%+0)U2elt&FD416_?4SbbnR&6U#4eTNQI|o=vToRP{XFYuz8p_pX_` z?XJ6X^7^cq->m+`=AY```n4$IP2|Oh{L|f~FJyHhHY6NuV&xWtGPKp!Onz^#V#TS= zoBjIu^d;xYLkzARdKBaz`YI|;Q|$GNT~Vb{_7OT^)&9CO_gkYz z#Oe}YuxVRP_`xrg6S+F#WaE+g3m?vI?fSY?`OoXH>sn!-1pj;HqlN7n`P-Y>Rjr#e zdLGA{JQcp}skjZ8f+ufXySgIxee*Gy@>Rdm^3Q*-ZVLXNSS~G@eNK|))rT>QUg?+v+f*Dj(YVD2ZC$P23? ze1cfkdL2<&dSR81&nJecsrJInAr7m(7AN_(gsHDOB6EJ#!cgC}GZyFZeA%V+><0hY zSKB)@yQVvD@76vlV7}n)BQ`_lHv(U})sL`PPx5HLl6|r!f0D=K?b527=hn7=sr>(N zamu=5%Tzs9Ke_U3PsC}*?DPe9rY_2P@25L&`?=iJGa_u+{vUj}`fa%%%dCpHDW!8R zJUaKs&{X2;Ig!pyelvUzzwG-d(Y+CZcKI2G-H6z@`0j2A?U*$Yyc>=Qh4(zoVYS&L zp=}Z7&voDC6>rzk2>y?kXX*Tp=(*Z_-|F|DB*SaYe=_T*mFpb4)wTca%da363D@_Y zwiQeEZ43UNyB(;K&AhtID%tSbwW9@Bcb=B(-k8+>Fwyt@{f!dZVa(y%rt&BMyc_-v zNO|Tk)~ubWU~dq{-^BC9a9VEf+|IG)-z;#pH73LV-}%ruF)6Cb70Gsmk0AE diff --git a/resources/builtin/repo/servers.png b/resources/builtin/repo/servers.png index 2b9f2c3d85b52dfe01e2b29216dfea90b14cc915..749c3c1a854ec23b69dfb319b8c70fdbf7b50570 100644 GIT binary patch delta 1355 zcmV-R1+@Bz4)_X?BYy$8P)t-s_xJaYm8gi0qib`GbbFL>d6IX2mUw@cbbOSHlBKn| z#^mMb@$vHV^7HEJ?Zd{;nxL_5b&tNn%+uA|e}$ZIc#+%P;_&hEkCv&0i=pD<=j7(; z>+J5JrL}H$klEYdgo~h&m#VF@z`eoC}jcs*~ zhK-`^?eCeNud4QPn^YpK@!LPHy*4W+A z)7sF}+1lLUm!7Z7&DH4X>~40C$jj4zhMlXhzQDxIb9$4Xq_p7T=HcSzvbV&Jm8g1w znSzO*gNdJ^)!Vwg z$nEa$@9*(*dy|ovs>#gM*V*2AfR~Anr1bRlr>whugPNYBw0wh_eT18GdXkNlr_|To z&Cl1dwZeyuqV@Im_4W0Gil2&*rLeWa=;`ag!_AD7rhnk!<)*8;mYuG%xWlNfysEFg z-{9q!ov+W)*pr&9=I83Nw!?CHlI7;-Zsp|`un<>%_g$b)P~7yqly zN&o-=0FxyH5Ptvwz~4blZS|>%K9W?U=p%JW^{Ep}EV0BAODwU(DyhDqv9U?4H2U=) zFtE9~rL|40O4mDfyMYsO z7HeW;V^WgB84yM0idEXsc~NA(Sfw>C zh$0KcDy?Br>R4he9uq~Dh*jFsWl?DP3bD$V6otkoVwJISRTOCZIRD&PousfPdRm+0 zE7tUNgV$$Z!^R|yP0`)v276X7wrq_qv$h%kdbfRE{9(sVOKByIy9V#xvuFI831;O# zK`gPv%72hpVu>Y|SYE)*-rh;oXK(Zo0002kS6hAdMRrUy#y_#d5=$(x#1c!a{Ra*n z93oa4hYk;qh$Baj?G&qm<0qoSl#{uOwRGkFl^GaPpQKT{Ai6n~w^+3chA+rKoK8|W zbGG<^E>E$}MK+ElDV&d<1~xcHDW@h1T@b6B!+%kzWv5uBZ8;l7&WTmp&WkQtQ^#6) zNvzbdF8j<%A8SiX6uBZ+8CRpwm^!h_xDtgXf1JO;Z%}@V%@(OMcTwFt$lM8nJQKpJEG9%L&YlNMilzu^5v8a zd^I*n;lrNj?#P3@O*BJ449!4Ody>YsucOP&_x7w_)wf26@!uH#DyjMQyXNNRF(Kdo zU{+4!enVs9j~-Zfx+7Nh#1cy^vBdIDX{Kswt4~dnAp;Z|0001F;%5swwqA~^j$!}+ N002ovPDHLkV1hla^#K3? literal 1800 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxV1_maU0G|+7AUC_XcNC4}5U5km zm<3D+Yf6Irf*D@@`1b$fk=D|J_LIM+n{o46%>8}A%JKR(f&C|nO3IVA?qp`&Ta|h3 zPPL71-J%W7T*9|!PH1ed>8hR7w(gOZ;%dF#@GRqQW2XS)fCEWT)WV?E!VeVf_-7}`!;c($*;cXGdp`3z0dinL={yOuC+ zn!ncgpMA^SS#v^#B(6-^!oqndooC1K??#nJ^AqN+Ijn!W!%qDY0|N`Yr;B4q1>@Vh z&1J?u60Q#~r`g@!mJ3C@PTcpfGWJYQWqteRxlozl9&L3dpkJ|qt~k$36|X6KTRL5w zZmv#J3T%xyq9nGl!&N6qup7t>o47J+ZB?(-)$D6ZVQO1nUs=0+|CRg;8w59A%Dm&^ zIm=9WTKwlKuZxRP<&w5$-CdS@aBFrxQ|XHpMpmWgYS-v#vBmn%$orbI^1ki!ja4fo zwwNVWomv=vF8OPiVMcP(mx;mgNtPy4gYA=UMtz-rxuNmMCZ0*h6_O&^PA7A0)Ny`l z*b^~L@6!uMozn%CJqpuOm)N)op1$NRx41J^(m2fT@hXASW^sPPJR5U_txqoeza-{( z*6O5y!!0v)+BMa#Z(Qbenal60?~IqWsWUHXJl~jhZkFxx2hYwKH$Kzf5T#nde=q%^ z($a`I21hl zNk(s0%<)}Ik_v9N>@@P7<|kY%mt8hJF_`P!D^uYYyH4#YoVef8X#rzcMRoQIO49x0bN%H?z&VNT6| zyvid-1QKup(>s}!4Fhi)Uwp9Zux1a6j#WGQzE@ggTsZu?w{z;n#Yd~ZPS5RMFRZ>G9VPW}5#-neXU zXKLaxy#v-ezt;Qf+|Stlbdln;waey&86-ddkT^%i4j53sLKOFBKG>Xdx$n8nwcq+{ zSBu}dF0OXLFf#A+woT`^XL@b?xLa}B_5S%@Mc<|?hDj|A)?ari{?~QcoyP>UoBulB zzVhSJvRnLpYaT*#+*E!@-qTK0ni%lS%S$;>32VLu33gn4|Mv33ga74<_kp;cu6{1- HoD!M Date: Mon, 12 Jun 2017 18:00:04 +0000 Subject: [PATCH 258/543] Add a tooltip method to PHUIIconView Summary: We seem to use these a lot. Makes the code cleaner. Test Plan: UIExamples. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18114 --- .../uiexample/examples/PHUIIconExample.php | 3 ++- src/view/phui/PHUIIconView.php | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/applications/uiexample/examples/PHUIIconExample.php b/src/applications/uiexample/examples/PHUIIconExample.php index 35395fbff0..81778b0fd4 100644 --- a/src/applications/uiexample/examples/PHUIIconExample.php +++ b/src/applications/uiexample/examples/PHUIIconExample.php @@ -159,7 +159,8 @@ final class PHUIIconExample extends PhabricatorUIExample { ->setIcon($icon) ->setBackground('bg-blue') ->setHref('#') - ->addClass('mmr'); + ->addClass('mmr') + ->setTooltip($icon); } $layout_cicons = id(new PHUIBoxView()) diff --git a/src/view/phui/PHUIIconView.php b/src/view/phui/PHUIIconView.php index d6b1ad0ccd..8cc61ba2eb 100644 --- a/src/view/phui/PHUIIconView.php +++ b/src/view/phui/PHUIIconView.php @@ -18,6 +18,7 @@ final class PHUIIconView extends AphrontTagView { private $iconFont; private $iconColor; private $iconBackground; + private $tooltip; public function setHref($href) { $this->href = $href; @@ -60,6 +61,11 @@ final class PHUIIconView extends AphrontTagView { return $this; } + public function setTooltip($text) { + $this->tooltip = $text; + return $this; + } + protected function getTagName() { $tag = 'span'; if ($this->href) { @@ -100,11 +106,24 @@ final class PHUIIconView extends AphrontTagView { $this->appendChild($this->text); } + $sigil = null; + $meta = array(); + if ($this->tooltip) { + Javelin::initBehavior('phabricator-tooltips'); + require_celerity_resource('aphront-tooltip-css'); + $sigil = 'has-tooltip'; + $meta = array( + 'tip' => $this->tooltip, + ); + } + return array( 'href' => $this->href, 'style' => $style, 'aural' => false, 'class' => $classes, + 'sigil' => $sigil, + 'meta' => $meta, ); } From 0610b86f57d02c2c2ca11b5bf6cab5afbed997e8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Jun 2017 07:44:38 -0700 Subject: [PATCH 259/543] Allow Herald rules to apply "only the first time" to Calendar events Summary: Fixes T12821. (Some events only happen once, so the default behavior doesn't let you pick this rule, and objects must opt into it by saying "yeah, I support multiple edits".) Test Plan: Note "only the first time" selected: {F4999093} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12821 Differential Revision: https://secure.phabricator.com/D18117 --- .../herald/PhabricatorCalendarEventHeraldAdapter.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php b/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php index 49d5f38959..1fc3a56317 100644 --- a/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php +++ b/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php @@ -49,6 +49,13 @@ final class PhabricatorCalendarEventHeraldAdapter extends HeraldAdapter { } } + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::EVERY, + HeraldRepetitionPolicyConfig::FIRST, + ); + } + public function getHeraldName() { return $this->getObject()->getMonogram(); } From 4b15871ced9e66d16de04989c4ea656eff0536d0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Jun 2017 11:19:37 -0700 Subject: [PATCH 260/543] Fix specification of `ttl.relative` via LFS Summary: Fixes T12828. This API was tightened up D17616, but I missed this callsite. Test Plan: I've just got a good feeling in my bones. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12828 Differential Revision: https://secure.phabricator.com/D18119 --- .../files/uploadsource/PhabricatorFileUploadSource.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index cda07d590b..5e72156091 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -208,11 +208,17 @@ abstract class PhabricatorFileUploadSource } private function getNewFileParameters() { - return array( + $parameters = array( 'name' => $this->getName(), - 'ttl.relative' => $this->getRelativeTTL(), 'viewPolicy' => $this->getViewPolicy(), ); + + $ttl = $this->getRelativeTTL(); + if ($ttl !== null) { + $parameters['ttl.relative'] = $ttl; + } + + return $parameters; } private function getChunkEngine() { From 283a95d2aae0fd4d2b3a4c926ab18273f8b80e62 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 12 Jun 2017 11:24:17 -0700 Subject: [PATCH 261/543] Build a page for viewing all inline comments Summary: Adds a very basic list of all inline comments, threaded, and their status. Kept this a little simpler than the mock, mostly because sorting here feels a little strange given threads would be all over the place. Not sure sorted is needed in practice anyways. I'd probably lean towards just adding a JS checkbox to hide certain rows if needed in the future. Test Plan: Test various commenting structures: - Leave Comment - Update Diff - Leave new comment - Reply to comment - Reply to comment as revision author - Mark items as done - Update diff again {F4996915} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D18112 --- src/__phutil_library_map__.php | 4 + .../storage/PhabricatorAuditInlineComment.php | 8 + .../base/controller/PhabricatorController.php | 6 +- .../PhabricatorDifferentialApplication.php | 2 + .../DifferentialRevisionInlinesController.php | 190 ++++++++++++++++++ .../DifferentialRevisionViewController.php | 6 + .../parser/DifferentialChangesetParser.php | 65 +----- .../storage/DifferentialInlineComment.php | 7 + .../PhabricatorInlineCommentInterface.php | 3 + .../diff/view/PHUIDiffInlineThreader.php | 66 ++++++ 10 files changed, 292 insertions(+), 65 deletions(-) create mode 100644 src/applications/differential/controller/DifferentialRevisionInlinesController.php create mode 100644 src/infrastructure/diff/view/PHUIDiffInlineThreader.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d63b99d4a2..5d917db2c0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -547,6 +547,7 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', 'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', + 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', 'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', @@ -1738,6 +1739,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentTableScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php', 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', + 'PHUIDiffInlineThreader' => 'infrastructure/diff/view/PHUIDiffInlineThreader.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', 'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php', @@ -5516,6 +5518,7 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', + 'DifferentialRevisionInlinesController' => 'DifferentialController', 'DifferentialRevisionLandController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', @@ -6872,6 +6875,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentTableScaffold' => 'AphrontView', 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentView' => 'AphrontView', + 'PHUIDiffInlineThreader' => 'Phobject', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffRevealIconView' => 'AphrontView', 'PHUIDiffTableOfContentsItemView' => 'AphrontView', diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php index b330368d1b..2534384f61 100644 --- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php +++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php @@ -331,6 +331,14 @@ final class PhabricatorAuditInlineComment return $this->isGhost; } + public function getDateModified() { + return $this->proxy->getDateModified(); + } + + public function getDateCreated() { + return $this->proxy->getDateCreated(); + } + /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index c4d1b47c40..1ecfdaf557 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -483,8 +483,10 @@ abstract class PhabricatorController extends AphrontController { // NOTE: Applications (objects of class PhabricatorApplication) can't // currently be set here, although they don't need any of the extensions // anyway. This should probably work differently than it does, though. - if ($object instanceof PhabricatorLiskDAO) { - $action_list->setObject($object); + if ($object) { + if ($object instanceof PhabricatorLiskDAO) { + $action_list->setObject($object); + } } $curtain = id(new PHUICurtainView()) diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index 1b6443a9e0..403e60c8a9 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -77,6 +77,8 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { => 'DifferentialDiffCreateController', 'operation/(?P[1-9]\d*)/' => 'DifferentialRevisionOperationController', + 'inlines/(?P[1-9]\d*)/' + => 'DifferentialRevisionInlinesController', ), 'comment/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialCommentPreviewController', diff --git a/src/applications/differential/controller/DifferentialRevisionInlinesController.php b/src/applications/differential/controller/DifferentialRevisionInlinesController.php new file mode 100644 index 0000000000..c5eb063e99 --- /dev/null +++ b/src/applications/differential/controller/DifferentialRevisionInlinesController.php @@ -0,0 +1,190 @@ +getViewer(); + $id = $request->getURIData('id'); + + $revision = id(new DifferentialRevisionQuery()) + ->withIDs(array($id)) + ->setViewer($viewer) + ->needDiffIDs(true) + ->executeOne(); + if (!$revision) { + return new Aphront404Response(); + } + + $revision_monogram = $revision->getMonogram(); + $revision_uri = $revision->getURI(); + $revision_title = $revision->getTitle(); + + $query = id(new DifferentialInlineCommentQuery()) + ->setViewer($viewer) + ->needHidden(true) + ->withRevisionPHIDs(array($revision->getPHID())); + $inlines = $query->execute(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($revision_monogram, $revision_uri); + $crumbs->addTextCrumb(pht('Inline Comments')); + $crumbs->setBorder(true); + + $content = $this->renderInlineTable($revision, $inlines); + $header = $this->buildHeader($revision); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($content); + + return $this->newPage() + ->setTitle( + array( + "{$revision_monogram} {$revision_title}", + pht('Inlines'), + )) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildHeader(DifferentialRevision $revision) { + $viewer = $this->getViewer(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-chevron-left') + ->setHref($revision->getURI()) + ->setText(pht('Back to Revision')); + + return id(new PHUIHeaderView()) + ->setHeader($revision->getTitle()) + ->setUser($viewer) + ->setHeaderIcon('fa-cog') + ->addActionLink($button); + } + + private function renderInlineTable( + DifferentialRevision $revision, + array $inlines) { + + $viewer = $this->getViewer(); + $inlines = id(new PHUIDiffInlineThreader()) + ->reorderAndThreadCommments($inlines); + + $handle_phids = array(); + $changeset_ids = array(); + foreach ($inlines as $inline) { + $handle_phids[] = $inline->getAuthorPHID(); + $changeset_ids[] = $inline->getChangesetID(); + } + $handles = $viewer->loadHandles($handle_phids); + $handles = iterator_to_array($handles); + + if ($changeset_ids) { + $changesets = id(new DifferentialChangesetQuery()) + ->setViewer($viewer) + ->withIDs($changeset_ids) + ->execute(); + $changesets = mpull($changesets, null, 'getID'); + } else { + $changesets = array(); + } + + $current_changeset = head($revision->getDiffIDs()); + + $rows = array(); + foreach ($inlines as $inline) { + $status_icons = array(); + + $c_id = $inline->getChangesetID(); + $d_id = $changesets[$c_id]->getDiffID(); + + if ($d_id == $current_changeset) { + $diff_id = phutil_tag('strong', array(), pht('Current')); + } else { + $diff_id = pht('Diff %d', $d_id); + } + + $reviewer = $handles[$inline->getAuthorPHID()]->renderLink(); + $now = PhabricatorTime::getNow(); + $then = $inline->getDateModified(); + $datetime = phutil_format_relative_time($now - $then); + + $comment_href = $revision->getURI().'#inline-'.$inline->getID(); + $comment = phutil_tag( + 'a', + array( + 'href' => $comment_href, + ), + $inline->getContent()); + + $state = $inline->getFixedState(); + if ($state == PhabricatorInlineCommentInterface::STATE_DONE) { + $status_icons[] = id(new PHUIIconView()) + ->setIcon('fa-check green') + ->addClass('mmr'); + } else if ($inline->getReplyToCommentPHID() && + $inline->getAuthorPHID() == $revision->getAuthorPHID()) { + $status_icons[] = id(new PHUIIconView()) + ->setIcon('fa-commenting-o blue') + ->addClass('mmr'); + } else { + $status_icons[] = id(new PHUIIconView()) + ->setIcon('fa-circle-o grey') + ->addClass('mmr'); + } + + + if ($inline->getReplyToCommentPHID()) { + $reply_icon = id(new PHUIIconView()) + ->setIcon('fa-reply mmr darkgrey'); + $comment = array($reply_icon, $comment); + } + + $rows[] = array( + $diff_id, + $status_icons, + $reviewer, + AphrontTableView::renderSingleDisplayLine($comment), + $datetime, + ); + } + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + pht('Diff'), + pht('Status'), + pht('Reviewer'), + pht('Comment'), + pht('Created'), + )); + $table->setColumnClasses( + array( + '', + '', + '', + 'wide', + 'right', + )); + $table->setColumnVisibility( + array( + true, + true, + true, + true, + true, + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Inline Comments')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 69e45b377d..0f3675f8f6 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -574,6 +574,12 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-indent') + ->setHref("/differential/revision/inlines/{$revision_id}/") + ->setName(pht('List Inline Comments'))); + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-upload') diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index c745d8e42a..88303cf420 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1045,7 +1045,8 @@ final class DifferentialChangesetParser extends Phobject { } } - $this->comments = $this->reorderAndThreadComments($this->comments); + $this->comments = id(new PHUIDiffInlineThreader()) + ->reorderAndThreadCommments($this->comments); foreach ($this->comments as $comment) { $final = $comment->getLineNumber() + @@ -1617,68 +1618,6 @@ final class DifferentialChangesetParser extends Phobject { return array($old_back, $new_back); } - private function reorderAndThreadComments(array $comments) { - $comments = msort($comments, 'getID'); - - // Build an empty map of all the comments we actually have. If a comment - // is a reply but the parent has gone missing, we don't want it to vanish - // completely. - $comment_phids = mpull($comments, 'getPHID'); - $replies = array_fill_keys($comment_phids, array()); - - // Now, remove all comments which are replies, leaving only the top-level - // comments. - foreach ($comments as $key => $comment) { - $reply_phid = $comment->getReplyToCommentPHID(); - if (isset($replies[$reply_phid])) { - $replies[$reply_phid][] = $comment; - unset($comments[$key]); - } - } - - // For each top level comment, add the comment, then add any replies - // to it. Do this recursively so threads are shown in threaded order. - $results = array(); - foreach ($comments as $comment) { - $results[] = $comment; - $phid = $comment->getPHID(); - $descendants = $this->getInlineReplies($replies, $phid, 1); - foreach ($descendants as $descendant) { - $results[] = $descendant; - } - } - - // If we have anything left, they were cyclic references. Just dump - // them in a the end. This should be impossible, but users are very - // creative. - foreach ($replies as $phid => $comments) { - foreach ($comments as $comment) { - $results[] = $comment; - } - } - - return $results; - } - - private function getInlineReplies(array &$replies, $phid, $depth) { - $comments = idx($replies, $phid, array()); - unset($replies[$phid]); - - $results = array(); - foreach ($comments as $comment) { - $results[] = $comment; - $descendants = $this->getInlineReplies( - $replies, - $comment->getPHID(), - $depth + 1); - foreach ($descendants as $descendant) { - $results[] = $descendant; - } - } - - return $results; - } - private function getOffset(array $map, $line) { if (!$map) { return null; diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php index bdc231671f..cbe05663db 100644 --- a/src/applications/differential/storage/DifferentialInlineComment.php +++ b/src/applications/differential/storage/DifferentialInlineComment.php @@ -255,6 +255,13 @@ final class DifferentialInlineComment return $this; } + public function getDateModified() { + return $this->proxy->getDateModified(); + } + + public function getDateCreated() { + return $this->proxy->getDateCreated(); + } /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ diff --git a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php index 13bf3ad83b..5c2bafdc86 100644 --- a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php +++ b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php @@ -60,4 +60,7 @@ interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface { public function supportsHiding(); public function isHidden(); + public function getDateModified(); + public function getDateCreated(); + } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineThreader.php b/src/infrastructure/diff/view/PHUIDiffInlineThreader.php new file mode 100644 index 0000000000..5209f1f359 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffInlineThreader.php @@ -0,0 +1,66 @@ + $comment) { + $reply_phid = $comment->getReplyToCommentPHID(); + if (isset($replies[$reply_phid])) { + $replies[$reply_phid][] = $comment; + unset($comments[$key]); + } + } + + // For each top level comment, add the comment, then add any replies + // to it. Do this recursively so threads are shown in threaded order. + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $phid = $comment->getPHID(); + $descendants = $this->getInlineReplies($replies, $phid, 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + // If we have anything left, they were cyclic references. Just dump + // them in a the end. This should be impossible, but users are very + // creative. + foreach ($replies as $phid => $comments) { + foreach ($comments as $comment) { + $results[] = $comment; + } + } + + return $results; + } + + private function getInlineReplies(array &$replies, $phid, $depth) { + $comments = idx($replies, $phid, array()); + unset($replies[$phid]); + + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $descendants = $this->getInlineReplies( + $replies, + $comment->getPHID(), + $depth + 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + return $results; + } +} From df6ad07566949c2b0b590e6b890edd538ad81281 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 13 Jun 2017 11:02:12 -0700 Subject: [PATCH 262/543] Add DiffusionBranchListView for browsing branches Summary: Adds a new DiffusionBranchListView which replaces the BranchTable when browsing all branches in Diffusion. Has all the same capabilities, but is easier to read, adds a Compare button, and plays nicely on mobile. It does take up more space, but I think that's generally OK here since we expect our branches to not be heaping piles of intern revert branches. Test Plan: Follow a few repositories with branches, like Phabricator and KDE's Krita. View layouts on mobile, tablet, desktop. Try out new compare button. {F4996207} Reviewers: epriestley Reviewed By: epriestley Subscribers: avivey, Korvin Maniphest Tasks: T12824 Differential Revision: https://secure.phabricator.com/D18113 --- resources/celerity/map.php | 4 +- src/__phutil_library_map__.php | 2 + .../DiffusionBranchTableController.php | 9 +- .../view/DiffusionBranchListView.php | 149 ++++++++++++++++++ .../diffusion/view/DiffusionView.php | 4 +- .../diffusion/diffusion-history.css | 17 ++ 6 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 src/applications/diffusion/view/DiffusionBranchListView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2209dffea7..e5903df96e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '6870e8c1', + 'rsrc/css/application/diffusion/diffusion-history.css' => '4540f568', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -569,7 +569,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '6870e8c1', + 'diffusion-history-css' => '4540f568', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '419dd5b6', 'diffusion-source-css' => '750add59', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5d917db2c0..5d88f4665c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -613,6 +613,7 @@ phutil_register_library_map(array( 'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php', 'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php', 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', + 'DiffusionBranchListView' => 'applications/diffusion/view/DiffusionBranchListView.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', @@ -5584,6 +5585,7 @@ phutil_register_library_map(array( 'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBlameQuery' => 'DiffusionQuery', 'DiffusionBlockHeraldAction' => 'HeraldAction', + 'DiffusionBranchListView' => 'DiffusionView', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index e5e033f416..7f8ae11f57 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -48,7 +48,7 @@ final class DiffusionBranchTableController extends DiffusionController { ->withRepository($repository) ->execute(); - $table = id(new DiffusionBranchTableView()) + $list = id(new DiffusionBranchListView()) ->setUser($viewer) ->setBranches($branches) ->setCommits($commits) @@ -57,7 +57,7 @@ final class DiffusionBranchTableController extends DiffusionController { $content = id(new PHUIObjectBoxView()) ->setHeaderText($repository->getName()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table) + ->setTable($list) ->setPager($pager); } @@ -84,10 +84,7 @@ final class DiffusionBranchTableController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } } diff --git a/src/applications/diffusion/view/DiffusionBranchListView.php b/src/applications/diffusion/view/DiffusionBranchListView.php new file mode 100644 index 0000000000..77540bb8c3 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionBranchListView.php @@ -0,0 +1,149 @@ +branches = $branches; + return $this; + } + + public function setCommits(array $commits) { + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); + $this->commits = mpull($commits, null, 'getCommitIdentifier'); + return $this; + } + + public function render() { + $drequest = $this->getDiffusionRequest(); + $current_branch = $drequest->getBranch(); + $repository = $drequest->getRepository(); + $commits = $this->commits; + $viewer = $this->getUser(); + require_celerity_resource('diffusion-history-css'); + + $buildables = $this->loadBuildables($commits); + $have_builds = false; + + $can_close_branches = ($repository->isHg()); + + Javelin::initBehavior('phabricator-tooltips'); + + $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Autoclose'); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list') + ->addClass('diffusion-branch-list'); + + foreach ($this->branches as $branch) { + $build_view = null; + $button_bar = new PHUIButtonBarView(); + $commit = idx($commits, $branch->getCommitIdentifier()); + if ($commit) { + $details = $commit->getSummary(); + $datetime = phabricator_datetime($commit->getEpoch(), $viewer); + + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable) { + $status = $buildable->getBuildableStatus(); + $icon = HarbormasterBuildable::getBuildableStatusIcon($status); + $color = HarbormasterBuildable::getBuildableStatusColor($status); + $name = HarbormasterBuildable::getBuildableStatusName($status); + $build_view = id(new PHUIButtonView()) + ->setTag('a') + ->setText($name) + ->setIcon($icon) + ->setColor($color) + ->setHref('/'.$buildable->getMonogram()) + ->addClass('mmr') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); + } + } else { + $datetime = null; + $details = null; + } + + if ($repository->supportsBranchComparison()) { + $compare_uri = $drequest->generateURI( + array( + 'action' => 'compare', + 'head' => $branch->getShortName(), + )); + $can_compare = ($branch->getShortName() != $current_branch); + if ($can_compare) { + $button_bar->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-balance-scale') + ->setToolTip(pht('Compare')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setWorkflow(true) + ->setHref($compare_uri)); + } + } + + $fields = $branch->getRawFields(); + $closed = idx($fields, 'closed'); + if ($closed) { + $status = pht('Closed'); + } else { + $status = pht('Open'); + } + + $browse_href = $drequest->generateURI( + array( + 'action' => 'browse', + 'branch' => $branch->getShortName(), + )); + + $button_bar->addButton( + id(new PHUIButtonView()) + ->setIcon('fa-code') + ->setHref($browse_href) + ->setTag('a') + ->setTooltip(pht('Browse')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)); + + $commit_link = $repository->getCommitURI( + $branch->getCommitIdentifier()); + + $commit_name = $repository->formatCommitName( + $branch->getCommitIdentifier(), $local = true); + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setHref($commit_link) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + $subhead = array($commit_tag, ' ', $details); + + $item = id(new PHUIObjectItemView()) + ->setHeader($branch->getShortName()) + ->setHref($drequest->generateURI( + array( + 'action' => 'history', + 'branch' => $branch->getShortName(), + ))) + ->setSubhead($subhead) + ->setSideColumn(array( + $build_view, + $button_bar, + )); + + if ($branch->getShortName() == $repository->getDefaultBranch()) { + $item->setStatusIcon('fa-code-fork', pht('Default Branch')); + } + $item->addAttribute(array($datetime)); + + $list->addItem($item); + + } + return $list; + + } +} diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 1d89a9dbd7..a51fbb2fd2 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -116,10 +116,10 @@ abstract class DiffusionView extends AphrontView { if ($button) { return id(new PHUIButtonView()) - ->setText(pht('Browse')) + ->setTag('a') ->setIcon('fa-code') ->setHref($href) - ->setTag('a') + ->setToolTip(pht('Browse')) ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index 2d0aea3b10..f4a51f7b56 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -30,6 +30,23 @@ margin-left: 4px; } +/* - Branch Styles ----------------------------------------------------------*/ + +.diffusion-branch-list .phui-oi-attribute a { + color: {$darkbluetext}; +} + +.diffusion-branch-list .phui-oi-attribute-spacer { + visibility: hidden; +} + +.diffusion-branch-list .phui-oi-subhead { + color: {$bluetext}; +} + +.diffusion-branch-list .phui-oi-subhead .phui-tag-view { + margin-right: 4px; +} /* - Phone Style ------------------------------------------------------------*/ From 6f7b31fbf8a57d1eb8e95a5d774ab7f4a6a9eb95 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 13 Jun 2017 11:16:57 -0700 Subject: [PATCH 263/543] Add a DiffusionTagListView Summary: Moves DiffusionTagsListView to uhhh, list. Separates out table view which is still in use now, implements mobile friendly UI for tags. Test Plan: Review KDE's Krita repository locally with lots of tags, desktop and mobile. {F4997708} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12824 Differential Revision: https://secure.phabricator.com/D18115 --- src/__phutil_library_map__.php | 2 + .../DiffusionRepositoryController.php | 2 +- .../controller/DiffusionTagListController.php | 25 ++-- .../view/DiffusionBranchListView.php | 13 +- .../view/DiffusionHistoryListView.php | 14 +- .../diffusion/view/DiffusionTagListView.php | 138 +++++++++-------- .../diffusion/view/DiffusionTagTableView.php | 140 ++++++++++++++++++ .../diffusion/view/DiffusionView.php | 15 +- 8 files changed, 246 insertions(+), 103 deletions(-) create mode 100644 src/applications/diffusion/view/DiffusionTagTableView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5d88f4665c..9ed3195e0b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -892,6 +892,7 @@ phutil_register_library_map(array( 'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php', 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', + 'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php', 'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php', 'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php', @@ -5866,6 +5867,7 @@ phutil_register_library_map(array( 'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery', 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', + 'DiffusionTagTableView' => 'DiffusionView', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 72b4d105d2..e341b87972 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -492,7 +492,7 @@ final class DiffusionRepositoryController extends DiffusionController { ->needCommitData(true) ->execute(); - $view = id(new DiffusionTagListView()) + $view = id(new DiffusionTagTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setTags($tags) diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index df3b356f5d..5e765ddcb6 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -64,17 +64,21 @@ final class DiffusionTagListController extends DiffusionController { ->needCommitData(true) ->execute(); - $view = id(new DiffusionTagListView()) + $tag_list = id(new DiffusionTagListView()) ->setTags($tags) ->setUser($viewer) ->setCommits($commits) ->setDiffusionRequest($drequest); - $phids = $view->getRequiredHandlePHIDs(); + $phids = $tag_list->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); - $view->setHandles($handles); + $tag_list->setHandles($handles); - $content = $view; + $content = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getDisplayName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($tag_list) + ->setPager($pager); } $crumbs = $this->buildCrumbs( @@ -84,17 +88,9 @@ final class DiffusionTagListController extends DiffusionController { )); $crumbs->setBorder(true); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($repository->getDisplayName()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($view) - ->setPager($pager); - $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $box, - )); + ->setFooter($content); return $this->newPage() ->setTitle( @@ -103,7 +99,8 @@ final class DiffusionTagListController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($view); + ->appendChild($view) + ->addClass('diffusion-history-view'); } } diff --git a/src/applications/diffusion/view/DiffusionBranchListView.php b/src/applications/diffusion/view/DiffusionBranchListView.php index 77540bb8c3..8d1df82dc9 100644 --- a/src/applications/diffusion/view/DiffusionBranchListView.php +++ b/src/applications/diffusion/view/DiffusionBranchListView.php @@ -48,18 +48,7 @@ final class DiffusionBranchListView extends DiffusionView { $buildable = idx($buildables, $commit->getPHID()); if ($buildable) { - $status = $buildable->getBuildableStatus(); - $icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $color = HarbormasterBuildable::getBuildableStatusColor($status); - $name = HarbormasterBuildable::getBuildableStatusName($status); - $build_view = id(new PHUIButtonView()) - ->setTag('a') - ->setText($name) - ->setIcon($icon) - ->setColor($color) - ->setHref('/'.$buildable->getMonogram()) - ->addClass('mmr') - ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); + $build_view = $this->renderBuildable($buildable, 'button'); } } else { $datetime = null; diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 9d6727a357..9dfa46cbef 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -119,19 +119,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { if ($show_builds) { $buildable = idx($buildables, $commit->getPHID()); if ($buildable !== null) { - $status = $buildable->getBuildableStatus(); - $icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $color = HarbormasterBuildable::getBuildableStatusColor($status); - $name = HarbormasterBuildable::getBuildableStatusName($status); - $build_view = id(new PHUIButtonView()) - ->setTag('a') - ->setText($name) - ->setIcon($icon) - ->setColor($color) - ->setHref('/'.$buildable->getMonogram()) - ->addClass('mmr') - ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) - ->addClass('diffusion-list-build-status'); + $build_view = $this->renderBuildable($buildable, 'button'); } } diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index df59925522..f9030581e3 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -29,36 +29,28 @@ final class DiffusionTagListView extends DiffusionView { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $viewer = $this->getViewer(); + require_celerity_resource('diffusion-history-css'); $buildables = $this->loadBuildables($this->commits); - $has_builds = false; - $rows = array(); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list'); foreach ($this->tags as $tag) { $commit = idx($this->commits, $tag->getCommitIdentifier()); + $button_bar = new PHUIButtonBarView(); - $tag_link = phutil_tag( - 'a', + $tag_href = $drequest->generateURI( array( - 'href' => $drequest->generateURI( - array( - 'action' => 'browse', - 'commit' => $tag->getName(), - )), - ), - $tag->getName()); + 'action' => 'history', + 'commit' => $tag->getName(), + )); - $commit_link = phutil_tag( - 'a', + $commit_href = $drequest->generateURI( array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $tag->getCommitIdentifier(), - )), - ), - $repository->formatCommitName( - $tag->getCommitIdentifier())); + 'action' => 'commit', + 'commit' => $tag->getCommitIdentifier(), + )); $author = null; if ($commit && $commit->getAuthorPHID()) { @@ -69,6 +61,15 @@ final class DiffusionTagListView extends DiffusionView { $author = self::renderName($tag->getAuthor()); } + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $author_name = phutil_tag( + 'strong', + array( + 'class' => 'diffusion-history-author-name', + ), + $author); + $authored = pht('%s on %s.', $author_name, $committed); + $description = null; if ($tag->getType() == 'git/tag') { // In Git, a tag may be a "real" tag, or just a reference to a commit. @@ -83,58 +84,71 @@ final class DiffusionTagListView extends DiffusionView { } } - $build = null; + $build_view = null; if ($commit) { $buildable = idx($buildables, $commit->getPHID()); if ($buildable) { - $build = $this->renderBuildable($buildable); - $has_builds = true; + $build_view = $this->renderBuildable($buildable, 'button'); } } - $history = $this->linkTagHistory($tag->getName()); + if ($repository->supportsBranchComparison()) { + $compare_uri = $drequest->generateURI( + array( + 'action' => 'compare', + 'head' => $tag->getName(), + )); - $rows[] = array( - $history, - $tag_link, - $commit_link, - $build, - $author, - $description, - $viewer->formatShortDateTime($tag->getEpoch()), - ); - } + $button_bar->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-balance-scale') + ->setToolTip(pht('Compare')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setWorkflow(true) + ->setHref($compare_uri)); + } - $table = id(new AphrontTableView($rows)) - ->setHeaders( + $commit_name = $repository->formatCommitName( + $tag->getCommitIdentifier(), $local = true); + + $browse_href = $drequest->generateURI( array( - null, - pht('Tag'), - pht('Commit'), - null, - pht('Author'), - pht('Description'), - pht('Created'), - )) - ->setColumnClasses( - array( - 'nudgeright', - 'pri', - '', - '', - '', - 'wide', - 'right', - )) - ->setColumnVisibility( - array( - true, - true, - true, - $has_builds, + 'action' => 'browse', + 'commit' => $tag->getName(), )); - return $table->render(); + $button_bar->addButton( + id(new PHUIButtonView()) + ->setTooltip(pht('Browse')) + ->setIcon('fa-code') + ->setHref($browse_href) + ->setTag('a') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)); + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setHref($commit_href) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + + $item = id(new PHUIObjectItemView()) + ->setHeader($tag->getName()) + ->setHref($tag_href) + ->addAttribute(array($commit_tag)) + ->addAttribute($description) + ->addAttribute($authored) + ->setSideColumn(array( + $build_view, + $button_bar, + )); + + $list->addItem($item); + } + + return $list; } } diff --git a/src/applications/diffusion/view/DiffusionTagTableView.php b/src/applications/diffusion/view/DiffusionTagTableView.php new file mode 100644 index 0000000000..59a06353ab --- /dev/null +++ b/src/applications/diffusion/view/DiffusionTagTableView.php @@ -0,0 +1,140 @@ +tags = $tags; + return $this; + } + + public function setCommits(array $commits) { + $this->commits = mpull($commits, null, 'getCommitIdentifier'); + return $this; + } + + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + public function getRequiredHandlePHIDs() { + return array_filter(mpull($this->commits, 'getAuthorPHID')); + } + + public function render() { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $viewer = $this->getViewer(); + + $buildables = $this->loadBuildables($this->commits); + $has_builds = false; + + $rows = array(); + foreach ($this->tags as $tag) { + $commit = idx($this->commits, $tag->getCommitIdentifier()); + + $tag_link = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'browse', + 'commit' => $tag->getName(), + )), + ), + $tag->getName()); + + $commit_link = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $tag->getCommitIdentifier(), + )), + ), + $repository->formatCommitName( + $tag->getCommitIdentifier())); + + $author = null; + if ($commit && $commit->getAuthorPHID()) { + $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); + } else if ($commit && $commit->getCommitData()) { + $author = self::renderName($commit->getCommitData()->getAuthorName()); + } else { + $author = self::renderName($tag->getAuthor()); + } + + $description = null; + if ($tag->getType() == 'git/tag') { + // In Git, a tag may be a "real" tag, or just a reference to a commit. + // If it's a real tag, use the message on the tag, since this may be + // unique data which isn't otherwise available. + $description = $tag->getDescription(); + } else { + if ($commit) { + $description = $commit->getSummary(); + } else { + $description = $tag->getDescription(); + } + } + + $build = null; + if ($commit) { + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable) { + $build = $this->renderBuildable($buildable); + $has_builds = true; + } + } + + $history = $this->linkTagHistory($tag->getName()); + + $rows[] = array( + $history, + $tag_link, + $commit_link, + $build, + $author, + $description, + $viewer->formatShortDateTime($tag->getEpoch()), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + null, + pht('Tag'), + pht('Commit'), + null, + pht('Author'), + pht('Description'), + pht('Created'), + )) + ->setColumnClasses( + array( + 'nudgeright', + 'pri', + '', + '', + '', + 'wide', + 'right', + )) + ->setColumnVisibility( + array( + true, + true, + true, + $has_builds, + )); + + return $table->render(); + } + +} diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index a51fbb2fd2..d058dc5cec 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -190,7 +190,8 @@ abstract class DiffusionView extends AphrontView { } final protected function renderBuildable( - HarbormasterBuildable $buildable) { + HarbormasterBuildable $buildable, + $type = null) { $status = $buildable->getBuildableStatus(); Javelin::initBehavior('phabricator-tooltips'); @@ -198,6 +199,18 @@ abstract class DiffusionView extends AphrontView { $color = HarbormasterBuildable::getBuildableStatusColor($status); $name = HarbormasterBuildable::getBuildableStatusName($status); + if ($type == 'button') { + return id(new PHUIButtonView()) + ->setTag('a') + ->setText($name) + ->setIcon($icon) + ->setColor($color) + ->setHref('/'.$buildable->getMonogram()) + ->addClass('mmr') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->addClass('diffusion-list-build-status'); + } + return id(new PHUIIconView()) ->setIcon($icon.' '.$color) ->addSigil('has-tooltip') From 21d16c723613b9612bb13cda4827e981c9dcb6b2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 13 Jun 2017 10:03:28 -0700 Subject: [PATCH 264/543] Fix cancel button on inline comment view Summary: Switch over to PHUIButtonView Test Plan: Cancel, Edit, Submit new inline diff comment. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18120 --- .../diff/view/PHUIDiffInlineCommentEditView.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php index a7c10b5b48..7ad3b1f1a6 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php @@ -121,14 +121,13 @@ final class PHUIDiffInlineCommentEditView private function renderBody() { $buttons = array(); - $buttons[] = phutil_tag('button', array(), pht('Save Draft')); - $buttons[] = javelin_tag( - 'button', - array( - 'sigil' => 'inline-edit-cancel', - 'class' => 'grey', - ), - pht('Cancel')); + $buttons[] = id(new PHUIButtonView()) + ->setText(pht('Save Draft')); + + $buttons[] = id(new PHUIButtonView()) + ->setText(pht('Cancel')) + ->setColor(PHUIButtonView::GREY) + ->addSigil('inline-edit-cancel'); $title = phutil_tag( 'div', From e1850b3c4e4efec5d8c4a3b9423ba0f3a8d65ece Mon Sep 17 00:00:00 2001 From: Mukunda Modell Date: Mon, 12 Jun 2017 11:00:39 -0500 Subject: [PATCH 265/543] Allow dashboard panels to be found by monogram Summary: Just add the monogram to the datasource's `name` field so that it will match when typing Wnn in the typeahead field. Test Plan: Tested locally on my dev phab. Try searching for a panel by monogram in the 'Add existing panel' dialog on the 'arrange workboard' interface. Previously: typing W123 showed no results. After this change: typing W123 finds the panel W123 Reviewers: chad, epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18121 --- .../typeahead/PhabricatorDashboardPanelDatasource.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php index 958883d34e..d534d32adc 100644 --- a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php @@ -42,8 +42,7 @@ final class PhabricatorDashboardPanelDatasource $properties = $panel->getProperties(); $result = id(new PhabricatorTypeaheadResult()) - ->setName($panel->getName()) - ->setDisplayName($monogram.' '.$panel->getName()) + ->setName($monogram.' '.$panel->getName()) ->setPHID($id) ->setIcon($impl->getIcon()) ->addAttribute($type_text); From 3d70db9eb5d052a8eff5b7c8f5e85b19e12a72cd Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Jun 2017 12:10:39 -0700 Subject: [PATCH 266/543] Queue a worker task to send mail only after committing the mail transaction Summary: Fixes T12844. This code is misleading: the daemon insert is happening on a different connection, and is not inside the transaction on the Mail connection. What actually happens is this: - (Connection A) `BEGIN` - (Connection A) `INSERT INTO mail ...` - (Connection B) `INSERT INTO worker ...` <-- This is a different connection, and it is NOT in a transaction! - There's a race window here: the worker row is globally visible but the mail row is still isolated inside the transaction. - (Connection A) `COMMIT` - Now we're clear: the mail row is globally visible. Change this code to reflect what's actually happening. This means that if the worker row insert fails for some reason, we'll now throw with a mail row written to the database. But this is fine: it doesn't send on its own (so it can't cause mail loops or anything) and it can be re-queued with `bin/mail resend` if necessary without too much trouble. Test Plan: See T12844 for particulars. Made some comments on tasks, saw the daemons send mail. Reviewers: chad, amckinley, jmeador Reviewed By: jmeador Maniphest Tasks: T12844 Differential Revision: https://secure.phabricator.com/D18124 --- .../metamta/storage/PhabricatorMetaMTAMail.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index f2ed8c7721..20b1482036 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -372,16 +372,16 @@ final class PhabricatorMetaMTAMail } $editor->save(); - // Queue a task to send this mail. - $mailer_task = PhabricatorWorker::scheduleTask( - 'PhabricatorMetaMTAWorker', - $this->getID(), - array( - 'priority' => PhabricatorWorker::PRIORITY_ALERTS, - )); - $this->saveTransaction(); + // Queue a task to send this mail. + $mailer_task = PhabricatorWorker::scheduleTask( + 'PhabricatorMetaMTAWorker', + $this->getID(), + array( + 'priority' => PhabricatorWorker::PRIORITY_ALERTS, + )); + return $result; } From 8008ade9af46d417aec187f4213118d220077b32 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 14 Jun 2017 14:42:14 -0700 Subject: [PATCH 267/543] Use keywords instead of ints to update task priority in ManiphestEditEngine Summary: Fixes T12124. Changes `ManiphestEditEngine` to populate the select using priority keywords instead of the integer value. Marks `maniphest.querystatuses` as frozen. Adds a new Conduit method for fetching potential task statuses. Test Plan: Created tasks and changed their priorities, observed that transactions in the DB still have the same type (integers as strings). Invoked `maniphest.update` with `priority => '90'` and observed that it still works. Invoked `maniphest.edit` with `priority => 'unbreak'` and observed that it now works. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T12124 Differential Revision: https://secure.phabricator.com/D18111 --- .../sql/autopatches/20170614.taskstatus.sql | 4 ++ src/__phutil_library_map__.php | 2 + .../__tests__/ManiphestTaskTestCase.php | 10 +++- .../bulk/ManiphestTaskEditBulkJobType.php | 5 ++ .../command/ManiphestPriorityEmailCommand.php | 16 ++---- .../conduit/ManiphestConduitAPIMethod.php | 4 +- ...ManiphestQueryStatusesConduitAPIMethod.php | 10 ++++ .../ManiphestStatusSearchConduitAPIMethod.php | 52 +++++++++++++++++++ .../constants/ManiphestTaskPriority.php | 43 ++++++++++++++- .../constants/ManiphestTaskStatus.php | 14 ++--- .../__tests__/ManiphestTaskStatusTestCase.php | 9 +++- .../ManiphestSubpriorityController.php | 5 +- .../maniphest/editor/ManiphestEditEngine.php | 32 ++++++------ .../ManiphestTaskPriorityHeraldAction.php | 7 ++- ...bricatorManiphestTaskTestDataGenerator.php | 5 +- .../maniphest/storage/ManiphestTask.php | 10 +++- .../ManiphestTaskPriorityTransaction.php | 13 +++++ .../PhabricatorProjectMoveController.php | 5 +- 18 files changed, 198 insertions(+), 48 deletions(-) create mode 100644 resources/sql/autopatches/20170614.taskstatus.sql create mode 100644 src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php diff --git a/resources/sql/autopatches/20170614.taskstatus.sql b/resources/sql/autopatches/20170614.taskstatus.sql new file mode 100644 index 0000000000..2543632093 --- /dev/null +++ b/resources/sql/autopatches/20170614.taskstatus.sql @@ -0,0 +1,4 @@ +/* Extend from 12 characters to 64. */ + +ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task + CHANGE status status VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9ed3195e0b..bca514a06e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1507,6 +1507,7 @@ phutil_register_library_map(array( 'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php', 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php', + 'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php', 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', 'ManiphestSubtypesConfigOptionsType' => 'applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php', 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', @@ -6607,6 +6608,7 @@ phutil_register_library_map(array( 'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand', + 'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestSubpriorityController' => 'ManiphestController', 'ManiphestSubtypesConfigOptionsType' => 'PhabricatorConfigJSONOptionType', 'ManiphestTask' => array( diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php index 1e571190d7..58190f6a89 100644 --- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php +++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php @@ -194,11 +194,14 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $dst, $is_after); + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head($keyword_map[$pri]); + $xactions = array(); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) - ->setNewValue($pri); + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) @@ -217,11 +220,14 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $target_priority, $is_end); + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head($keyword_map[$pri]); + $xactions = array(); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) - ->setNewValue($pri); + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) diff --git a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php index 0e11530b51..1b083d88f9 100644 --- a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php +++ b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php @@ -285,6 +285,11 @@ final class ManiphestTaskEditBulkJobType '=' => array_fuse($value), )); break; + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $value)); + $xaction->setNewValue($keyword); + break; default: $xaction->setNewValue($value); break; diff --git a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php index 5d7bbb1eea..ef966a50da 100644 --- a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php @@ -49,18 +49,8 @@ final class ManiphestPriorityEmailCommand array $argv) { $xactions = array(); - $target = phutil_utf8_strtolower(head($argv)); - $priority = null; - - $keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); - foreach ($keywords as $key => $words) { - foreach ($words as $word) { - if ($word == $target) { - $priority = $key; - break; - } - } - } + $keyword = phutil_utf8_strtolower(head($argv)); + $priority = ManiphestTaskPriority::getTaskPriorityFromKeyword($keyword); if ($priority === null) { return array(); @@ -72,7 +62,7 @@ final class ManiphestPriorityEmailCommand $xactions[] = $object->getApplicationTransactionTemplate() ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) - ->setNewValue($priority); + ->setNewValue($keyword); return $xactions; } diff --git a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php index 5a6a8cea33..640e30fee5 100644 --- a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php @@ -99,7 +99,9 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Priority set to invalid value.')); } - $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $priority; + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $priority)); + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $keyword; } $owner_phid = $request->getValue('ownerPHID'); diff --git a/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php index 971be8820b..28afaa4fe1 100644 --- a/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php @@ -33,4 +33,14 @@ final class ManiphestQueryStatusesConduitAPIMethod return $results; } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "maniphest.status.search" instead.'); + } + } diff --git a/src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php new file mode 100644 index 0000000000..7185cde96f --- /dev/null +++ b/src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php @@ -0,0 +1,52 @@ +'; + } + + public function getRequiredScope() { + return self::SCOPE_ALWAYS; + } + + protected function execute(ConduitAPIRequest $request) { + $config = PhabricatorEnv::getEnvConfig('maniphest.statuses'); + $results = array(); + foreach ($config as $code => $status) { + $stripped_status = array( + 'name' => $status['name'], + 'value' => $code, + 'closed' => !empty($status['closed']), + ); + + if (isset($status['special'])) { + $stripped_status['special'] = $status['special']; + } + + $results[] = $stripped_status; + } + + return array('data' => $results); + } + +} diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index dd2ab69c69..8bc682f7e4 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -2,6 +2,8 @@ final class ManiphestTaskPriority extends ManiphestConstants { + const UNKNOWN_PRIORITY_KEYWORD = '!!unknown!!'; + /** * Get the priorities and their full descriptions. * @@ -105,6 +107,18 @@ final class ManiphestTaskPriority extends ManiphestConstants { return 'fa-arrow-right'; } + public static function getTaskPriorityFromKeyword($keyword) { + $map = self::getTaskPriorityKeywordsMap(); + + foreach ($map as $priority => $keywords) { + if (in_array($keyword, $keywords)) { + return $priority; + } + } + + return null; + } + public static function isDisabledPriority($priority) { $config = idx(self::getConfig(), $priority, array()); return idx($config, 'disabled', false); @@ -116,6 +130,18 @@ final class ManiphestTaskPriority extends ManiphestConstants { return $config; } + private static function isValidPriorityKeyword($keyword) { + if (!strlen($keyword) || strlen($keyword) > 64) { + return false; + } + + // Alphanumeric, but not exclusively numeric + if (!preg_match('/^(?![0-9]*$)[a-zA-Z0-9]+$/', $keyword)) { + return false; + } + return true; + } + public static function validateConfiguration($config) { if (!is_array($config)) { throw new Exception( @@ -147,9 +173,24 @@ final class ManiphestTaskPriority extends ManiphestConstants { 'name' => 'string', 'short' => 'optional string', 'color' => 'optional string', - 'keywords' => 'optional list', + 'keywords' => 'list', 'disabled' => 'optional bool', )); + + $keywords = $value['keywords']; + foreach ($keywords as $keyword) { + if (!self::isValidPriorityKeyword($keyword)) { + throw new Exception( + pht( + 'Key "%s" is not a valid priority keyword. Priority keywords '. + 'must be 1-64 alphanumeric characters and cannot be '. + 'exclusively digits. For example, "%s" or "%s" are '. + 'reasonable choices.', + $keyword, + 'low', + 'critical')); + } + } } } diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index 6781fb7724..53d2e1afe3 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -232,16 +232,17 @@ final class ManiphestTaskStatus extends ManiphestConstants { * @task validate */ public static function isValidStatusConstant($constant) { - if (strlen($constant) > 12) { + if (!strlen($constant) || strlen($constant) > 64) { return false; } - if (!preg_match('/^[a-z0-9]+\z/', $constant)) { + + // Alphanumeric, but not exclusively numeric + if (!preg_match('/^(?![0-9]*$)[a-zA-Z0-9]+$/', $constant)) { return false; } return true; } - /** * @task validate */ @@ -250,10 +251,9 @@ final class ManiphestTaskStatus extends ManiphestConstants { if (!self::isValidStatusConstant($key)) { throw new Exception( pht( - 'Key "%s" is not a valid status constant. Status constants must '. - 'be 1-12 characters long and contain only lowercase letters (a-z) '. - 'and digits (0-9). For example, "%s" or "%s" are reasonable '. - 'choices.', + 'Key "%s" is not a valid status constant. Status constants '. + 'must be 1-64 alphanumeric characters and cannot be exclusively '. + 'digits. For example, "%s" or "%s" are reasonable choices.', $key, 'open', 'closed')); diff --git a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php index 20f4c438a7..140c86d6dd 100644 --- a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php +++ b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php @@ -10,10 +10,15 @@ final class ManiphestTaskStatusTestCase extends PhabricatorTestCase { 'duplicate2' => true, '' => false, - 'longlonglonglong' => false, + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' => + false, '.' => false, - 'ABCD' => false, + ' ' => false, + 'ABCD' => true, 'a b c ' => false, + '1' => false, + '111' => false, + '11a' => true, ); foreach ($map as $input => $expect) { diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php index e91cc65d4a..8869b6a327 100644 --- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php +++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php @@ -40,11 +40,14 @@ final class ManiphestSubpriorityController extends ManiphestController { $is_end = false); } + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $pri)); + $xactions = array(); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) - ->setNewValue($pri); + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 52491ce6e6..acd27fc908 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -215,7 +215,7 @@ EODOCS ->setConduitTypeDescription(pht('New task priority constant.')) ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) - ->setValue($object->getPriority()) + ->setValue($object->getPriorityKeyword()) ->setOptions($priority_map) ->setCommentActionLabel(pht('Change Priority')), ); @@ -289,29 +289,29 @@ EODOCS private function getTaskPriorityMap(ManiphestTask $task) { $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); + $priority_keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); $current_priority = $task->getPriority(); + $results = array(); + + foreach ($priority_map as $priority => $priority_name) { + $disabled = ManiphestTaskPriority::isDisabledPriority($priority); + if ($disabled && !($priority == $current_priority)) { + continue; + } + + $keyword = head(idx($priority_keywords, $priority)); + $results[$keyword] = $priority_name; + } // If the current value isn't a legitimate one, put it in the dropdown - // anyway so saving the form doesn't cause a side effects. + // anyway so saving the form doesn't cause any side effects. if (idx($priority_map, $current_priority) === null) { - $priority_map[$current_priority] = pht( + $results[ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD] = pht( '', $current_priority); } - foreach ($priority_map as $priority => $priority_name) { - // Always keep the current priority. - if ($priority == $current_priority) { - continue; - } - - if (ManiphestTaskPriority::isDisabledPriority($priority)) { - unset($priority_map[$priority]); - continue; - } - } - - return $priority_map; + return $results; } protected function newEditResponse( diff --git a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php index ff8420544d..2e2db4b62d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php @@ -39,12 +39,15 @@ final class ManiphestTaskPriorityHeraldAction return; } + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $priority)); + $xaction = $adapter->newTransaction() ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) - ->setNewValue($priority); + ->setNewValue($keyword); $adapter->queueTransaction($xaction); - $this->logEffect(self::DO_PRIORITY, $priority); + $this->logEffect(self::DO_PRIORITY, $keyword); } public function getHeraldActionStandardType() { diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php index 2d6a6807ce..3fc1957b4f 100644 --- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php +++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php @@ -100,7 +100,10 @@ final class PhabricatorManiphestTaskTestDataGenerator } public function generateTaskPriority() { - return array_rand(ManiphestTaskPriority::getTaskPriorityMap()); + $pri = array_rand(ManiphestTaskPriority::getTaskPriorityMap()); + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $pri)); + return $keyword; } public function generateTaskSubPriority() { diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 7cf9d3353f..3b312fc9cc 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -79,7 +79,7 @@ final class ManiphestTask extends ManiphestDAO ), self::CONFIG_COLUMN_SCHEMA => array( 'ownerPHID' => 'phid?', - 'status' => 'text12', + 'status' => 'text64', 'priority' => 'uint32', 'title' => 'sort', 'originalTitle' => 'text', @@ -245,6 +245,14 @@ final class ManiphestTask extends ManiphestDAO ); } + public function getPriorityKeyword() { + $priority = $this->getPriority(); + $map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $default = array(ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD); + $keywords = idx($map, $priority, $default); + return head($keywords); + } + private function comparePriorityTo(ManiphestTask $other) { $upri = $this->getPriority(); $vpri = $other->getPriority(); diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php index f7d58911ba..4e505668d1 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -12,6 +12,19 @@ final class ManiphestTaskPriorityTransaction return $object->getPriority(); } + public function generateNewValue($object, $value) { + // `$value` is supposed to be a keyword, but if the priority + // assigned to a task has been removed from the config, + // no such keyword will be available. Other edits to the task + // should still be allowed, even if the priority is no longer + // valid, so treat this as a no-op. + if ($value === ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD) { + return $object->getPriority(); + } + + return (string)ManiphestTaskPriority::getTaskPriorityFromKeyword($value); + } + public function applyInternalEffects($object, $value) { $object->setPriority($value); } diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index 801acbb90e..29b70cfafc 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -153,11 +153,14 @@ final class PhabricatorProjectMoveController break; } + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $pri)); + $xactions = array(); if ($pri !== null) { $xactions[] = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) - ->setNewValue($pri); + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) ->setTransactionType( ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) From 887bd2d66ef078c73a08e81e4160921ee127a678 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Jun 2017 04:17:01 -0700 Subject: [PATCH 268/543] In the UI, rename "Hide Inline" to "Collapse Inline" Summary: Ref T12733. This paves the way for a separate "hide" operation which completely hides things. (I didn't extend this to the server side because that would require schema changes and the new "hide" state is client-only.) Test Plan: Collapsed and expanded inlines, viewed tooltips. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18126 --- resources/celerity/map.php | 50 +++++++++---------- .../view/DifferentialChangesetListView.php | 12 ++--- .../view/PHUIDiffInlineCommentDetailView.php | 2 +- .../diff/view/PHUIDiffRevealIconView.php | 2 +- .../rsrc/js/application/diff/DiffChangeset.js | 2 +- .../js/application/diff/DiffChangesetList.js | 46 +++++++---------- .../rsrc/js/application/diff/DiffInline.js | 25 +++++----- 7 files changed, 66 insertions(+), 73 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e5903df96e..a706777a59 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4e99863c', - 'differential.pkg.js' => 'b7504037', + 'differential.pkg.js' => 'ee50e5ae', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -395,9 +395,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'd498bddb', - 'rsrc/js/application/diff/DiffChangesetList.js' => '29bbc02c', - 'rsrc/js/application/diff/DiffInline.js' => '20553f71', + 'rsrc/js/application/diff/DiffChangeset.js' => '94f81a34', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'fc6e482d', + 'rsrc/js/application/diff/DiffInline.js' => 'a386f83c', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -774,9 +774,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'd498bddb', - 'phabricator-diff-changeset-list' => '29bbc02c', - 'phabricator-diff-inline' => '20553f71', + 'phabricator-diff-changeset' => '94f81a34', + 'phabricator-diff-changeset-list' => 'fc6e482d', + 'phabricator-diff-inline' => 'a386f83c', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1039,9 +1039,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '20553f71' => array( - 'javelin-dom', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1067,10 +1064,6 @@ return array( 'javelin-install', 'javelin-util', ), - '29bbc02c' => array( - 'javelin-install', - 'phuix-button-view', - ), '2ae077e1' => array( 'javelin-behavior', 'javelin-dom', @@ -1619,6 +1612,17 @@ return array( 'javelin-resource', 'javelin-routable', ), + '94f81a34' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', @@ -1663,6 +1667,9 @@ return array( 'javelin-install', 'javelin-dom', ), + 'a386f83c' => array( + 'javelin-dom', + ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1986,17 +1993,6 @@ return array( 'javelin-uri', 'javelin-util', ), - 'd498bddb' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', @@ -2158,6 +2154,10 @@ return array( 'javelin-behavior-device', 'phabricator-keyboard-shortcut', ), + 'fc6e482d' => array( + 'javelin-install', + 'phuix-button-view', + ), 'fc91ab6c' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 25f762ba66..4b9f7c6b5f 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -257,15 +257,15 @@ final class DifferentialChangesetListView extends AphrontView { 'You must select a comment to mark done.' => pht('You must select a comment to mark done.'), - 'Hide or show inline comment.' => - pht('Hide or show inline comment.'), + 'Collapse or expand inline comment.' => + pht('Collapse or expand inline comment.'), 'You must select a comment to hide.' => pht('You must select a comment to hide.'), - 'Jump to next inline comment, including hidden comments.' => - pht('Jump to next inline comment, including hidden comments.'), - 'Jump to previous inline comment, including hidden comments.' => - pht('Jump to previous inline comment, including hidden comments.'), + 'Jump to next inline comment, including collapsed comments.' => + pht('Jump to next inline comment, including collapsed comments.'), + 'Jump to previous inline comment, including collapsed comments.' => + pht('Jump to previous inline comment, including collapsed comments.'), 'This file content has been collapsed.' => pht('This file content has been collapsed.'), diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 46853e185c..6d360cd595 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -265,7 +265,7 @@ final class PHUIDiffInlineCommentDetailView if (!$this->preview && $this->canHide()) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') - ->setTooltip(pht('Hide Comment')) + ->setTooltip(pht('Collapse')) ->setIcon('fa-times') ->addSigil('hide-inline') ->setMustCapture(true); diff --git a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php index 8ca3eae2c1..dca19725d8 100644 --- a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php +++ b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php @@ -8,7 +8,7 @@ final class PHUIDiffRevealIconView extends AphrontView { ->addSigil('has-tooltip') ->setMetadata( array( - 'tip' => pht('Show Hidden Comment'), + 'tip' => pht('Expand'), 'align' => 'E', 'size' => 275, )); diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index c697ba5a6c..739df030af 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -446,7 +446,7 @@ JX.install('DiffChangeset', { type: block.type, changeset: this, target: inline, - hidden: inline.isHidden(), + collapsed: inline.isCollapsed(), deleted: !inline.getID() && !inline.isEditing(), nodes: { begin: block.items[jj], diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index b476b8c168..03f3ae9c38 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -19,11 +19,11 @@ JX.install('DiffChangesetList', { var onmenu = JX.bind(this, this._ifawake, this._onmenu); JX.Stratcom.listen('click', 'differential-view-options', onmenu); - var onhide = JX.bind(this, this._ifawake, this._onhide); - JX.Stratcom.listen('click', 'hide-inline', onhide); + var oncollapse = JX.bind(this, this._ifawake, this._oncollapse, true); + JX.Stratcom.listen('click', 'hide-inline', oncollapse); - var onreveal = JX.bind(this, this._ifawake, this._onreveal); - JX.Stratcom.listen('click', 'reveal-inline', onreveal); + var onexpand = JX.bind(this, this._ifawake, this._oncollapse, false); + JX.Stratcom.listen('click', 'reveal-inline', onexpand); var onedit = JX.bind(this, this._ifawake, this._onaction, 'edit'); JX.Stratcom.listen( @@ -158,11 +158,11 @@ JX.install('DiffChangesetList', { label = pht('Jump to previous inline comment.'); this._installJumpKey('p', label, -1, 'comment'); - label = pht('Jump to next inline comment, including hidden comments.'); + label = pht('Jump to next inline comment, including collapsed comments.'); this._installJumpKey('N', label, 1, 'comment', true); label = pht( - 'Jump to previous inline comment, including hidden comments.'); + 'Jump to previous inline comment, including collapsed comments.'); this._installJumpKey('P', label, -1, 'comment', true); label = pht('Hide or show the current file.'); @@ -183,8 +183,8 @@ JX.install('DiffChangesetList', { label = pht('Mark or unmark selected inline comment as done.'); this._installKey('w', label, this._onkeydone); - label = pht('Hide or show inline comment.'); - this._installKey('q', label, this._onkeyhide); + label = pht('Collapse or expand inline comment.'); + this._installKey('q', label, this._onkeycollapse); }, isAsleep: function() { @@ -261,12 +261,12 @@ JX.install('DiffChangesetList', { .register(); }, - _installJumpKey: function(key, label, delta, filter, show_hidden) { + _installJumpKey: function(key, label, delta, filter, show_collapsed) { filter = filter || null; var options = { filter: filter, - hidden: show_hidden + collapsed: show_collapsed }; var handler = JX.bind(this, this._onjumpkey, delta, options); @@ -424,16 +424,16 @@ JX.install('DiffChangesetList', { this._warnUser(pht('You must select a file to hide or show.')); }, - _onkeyhide: function() { + _onkeycollapse: function() { var cursor = this._cursorItem; if (cursor) { if (cursor.type == 'comment') { var inline = cursor.target; - if (inline.canHide()) { + if (inline.canCollapse()) { this.setFocus(null); - inline.setHidden(!inline.isHidden()); + inline.setCollapsed(!inline.isCollapsed()); return; } } @@ -455,7 +455,7 @@ JX.install('DiffChangesetList', { var state = this._getSelectionState(); var filter = options.filter || null; - var hidden = options.hidden || false; + var collapsed = options.collapsed || false; var wrap = options.wrap || false; var attribute = options.attribute || null; @@ -507,10 +507,10 @@ JX.install('DiffChangesetList', { } } - // If the item is hidden, don't select it when iterating with jump + // If the item is collapsed, don't select it when iterating with jump // keys. It can still potentially be selected in other ways. - if (!hidden) { - if (items[cursor].hidden) { + if (!collapsed) { + if (items[cursor].collapsed) { continue; } } @@ -851,20 +851,12 @@ JX.install('DiffChangesetList', { menu.open(); }, - _onhide: function(e) { - this._onhidereveal(e, true); - }, - - _onreveal: function(e) { - this._onhidereveal(e, false); - }, - - _onhidereveal: function(e, is_hide) { + _oncollapse: function(is_collapse, e) { e.kill(); var inline = this._getInlineForEvent(e); - inline.setHidden(is_hide); + inline.setCollapsed(is_collapse); }, _onresize: function() { diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index baee53a7a4..692d5644a0 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -14,7 +14,6 @@ JX.install('DiffInline', { _phid: null, _changesetID: null, _row: null, - _hidden: false, _number: null, _length: null, _displaySide: null, @@ -30,6 +29,7 @@ JX.install('DiffInline', { _changeset: null, + _isCollapsed: false, _isDraft: null, _isFixed: null, _isEditing: false, @@ -41,7 +41,7 @@ JX.install('DiffInline', { var row_data = JX.Stratcom.getData(row); row_data.inline = this; - this._hidden = row_data.hidden || false; + this._isCollapsed = row_data.hidden || false; // TODO: Get smarter about this once we do more editing, this is pretty // hacky. @@ -225,7 +225,7 @@ JX.install('DiffInline', { return true; }, - canHide: function() { + canCollapse: function() { if (!JX.DOM.scry(this._row, 'a', 'hide-inline').length) { return false; } @@ -254,20 +254,18 @@ JX.install('DiffInline', { this._id = null; this._phid = null; - this._hidden = false; + this._isCollapsed = false; this._originalText = null; return row; }, - setHidden: function(hidden) { - this._hidden = hidden; - - JX.DOM.alterClass(this._row, 'inline-hidden', this._hidden); + setCollapsed: function(collapsed) { + this._isCollapsed = collapsed; var op; - if (hidden) { + if (collapsed) { op = 'hide'; } else { op = 'show'; @@ -280,11 +278,12 @@ JX.install('DiffInline', { .setHandler(JX.bag) .start(); + this._redraw(); this._didUpdate(true); }, - isHidden: function() { - return this._hidden; + isCollapsed: function() { + return this._isCollapsed; }, toggleDone: function() { @@ -703,11 +702,13 @@ JX.install('DiffInline', { _redraw: function() { var is_invisible = (this._isInvisible || this._isDeleted); - var is_loading = (this._isLoading); + var is_loading = this._isLoading; + var is_collapsed = this._isCollapsed; var row = this._row; JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); + JX.DOM.alterClass(row, 'inline-hidden', is_collapsed); }, _removeRow: function(row) { From 3be36783b3776ce73f3ec4650f810946895f5a5f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Jun 2017 04:28:21 -0700 Subject: [PATCH 269/543] Consider inline comments with draft checkmarks as "unsubmitted" Summary: Ref T12733. When a revision has unsubmitted checkmarks: - Color the banner yellow. - Show them in the "X unsubmitted" count. - Make the "X unsubmitted" button cycle between all drafts (written but unpublished comments) and "draft done" (checked but unsubmitted "Done" checkbox comments). Test Plan: - Checked a "Done" box, saw "1 unsubmitted" and yellow banner. - Clicked "5 unsubmitted" repeatedly, saw it cycle through all unsubmitted comments and checkboxes. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18127 --- resources/celerity/map.php | 62 +++++++++---------- .../view/PHUIDiffInlineCommentDetailView.php | 9 +++ .../differential/changeset-view.css | 3 +- .../rsrc/js/application/diff/DiffChangeset.js | 2 +- .../js/application/diff/DiffChangesetList.js | 29 ++++++--- .../rsrc/js/application/diff/DiffInline.js | 7 +++ 6 files changed, 72 insertions(+), 40 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a706777a59..03112ea938 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => 'ab24402f', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '4e99863c', - 'differential.pkg.js' => 'ee50e5ae', + 'differential.pkg.css' => '4ec4a37a', + 'differential.pkg.js' => '3442216b', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'c72dba88', + 'rsrc/css/application/differential/changeset-view.css' => 'b5e6be7f', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -395,9 +395,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => '94f81a34', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'fc6e482d', - 'rsrc/js/application/diff/DiffInline.js' => 'a386f83c', + 'rsrc/js/application/diff/DiffChangeset.js' => 'cdc5fa19', + 'rsrc/js/application/diff/DiffChangesetList.js' => '4ca11264', + 'rsrc/js/application/diff/DiffInline.js' => '27b6d01f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -562,7 +562,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'c72dba88', + 'differential-changeset-view-css' => 'b5e6be7f', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -774,9 +774,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => '94f81a34', - 'phabricator-diff-changeset-list' => 'fc6e482d', - 'phabricator-diff-inline' => 'a386f83c', + 'phabricator-diff-changeset' => 'cdc5fa19', + 'phabricator-diff-changeset-list' => '4ca11264', + 'phabricator-diff-inline' => '27b6d01f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1056,6 +1056,9 @@ return array( 'phabricator-drag-and-drop-file-upload', 'javelin-workboard-board', ), + '27b6d01f' => array( + 'javelin-dom', + ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1231,6 +1234,10 @@ return array( 'javelin-uri', 'phabricator-notification', ), + '4ca11264' => array( + 'javelin-install', + 'phuix-button-view', + ), '4d863052' => array( 'javelin-dom', 'javelin-util', @@ -1612,17 +1619,6 @@ return array( 'javelin-resource', 'javelin-routable', ), - '94f81a34' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', @@ -1667,9 +1663,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'a386f83c' => array( - 'javelin-dom', - ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1807,6 +1800,9 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b5e6be7f' => array( + 'phui-inline-comment-view-css', + ), 'b6993408' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1900,9 +1896,6 @@ return array( 'javelin-stratcom', 'javelin-util', ), - 'c72dba88' => array( - 'phui-inline-comment-view-css', - ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -1963,6 +1956,17 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'cdc5fa19' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'd0a99ab4' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2154,10 +2158,6 @@ return array( 'javelin-behavior-device', 'phabricator-keyboard-shortcut', ), - 'fc6e482d' => array( - 'javelin-install', - 'phuix-button-view', - ), 'fc91ab6c' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 6d360cd595..27a489a705 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -107,6 +107,14 @@ final class PHUIDiffInlineCommentDetailView break; } + $is_draft_done = false; + switch ($inline->getFixedState()) { + case PhabricatorInlineCommentInterface::STATE_DRAFT: + case PhabricatorInlineCommentInterface::STATE_UNDRAFT: + $is_draft_done = true; + break; + } + $is_synthetic = false; if ($inline->getSyntheticAuthor()) { $is_synthetic = true; @@ -126,6 +134,7 @@ final class PHUIDiffInlineCommentDetailView 'isFixed' => $is_fixed, 'isGhost' => $inline->getIsGhost(), 'isSynthetic' => $is_synthetic, + 'isDraftDone' => $is_draft_done, ); $sigil = 'differential-inline-comment'; diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index bfde532c44..bf532d0eb3 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -415,7 +415,8 @@ tr.differential-inline-loading { } .diff-banner-has-unsaved, -.diff-banner-has-unsubmitted { +.diff-banner-has-unsubmitted, +.diff-banner-has-draft-done { background: {$sh-yellowbackground}; } diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index 739df030af..f73dba0967 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -454,7 +454,7 @@ JX.install('DiffChangeset', { }, attributes: { unsaved: inline.isEditing(), - unsubmitted: inline.isDraft(), + anyDraft: inline.isDraft() || inline.isDraftDone(), undone: (is_saved && !inline.isDone()), done: (is_saved && inline.isDone()) } diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 03f3ae9c38..8200d54a7a 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1332,6 +1332,7 @@ JX.install('DiffChangesetList', { var changesets = this._changesets; var unsaved = []; var unsubmitted = []; + var draft_done = []; var undone = []; var done = []; @@ -1356,10 +1357,18 @@ JX.install('DiffChangesetList', { continue; } else if (inline.isDraft()) { unsubmitted.push(inline); - } else if (!inline.isDone()) { - undone.push(inline); } else { - done.push(inline); + // NOTE: Unlike other states, an inline may be marked with a + // draft checkmark and still be a "done" or "undone" comment. + if (inline.isDraftDone()) { + draft_done.push(inline); + } + + if (!inline.isDone()) { + undone.push(inline); + } else { + done.push(inline); + } } } } @@ -1374,6 +1383,11 @@ JX.install('DiffChangesetList', { 'diff-banner-has-unsubmitted', !!unsubmitted.length); + JX.DOM.alterClass( + node, + 'diff-banner-has-draft-done', + !!draft_done.length); + var pht = this.getTranslations(); var unsaved_button = this._getUnsavedButton(); var unsubmitted_button = this._getUnsubmittedButton(); @@ -1386,9 +1400,10 @@ JX.install('DiffChangesetList', { JX.DOM.hide(unsaved_button.getNode()); } - if (unsubmitted.length) { - unsubmitted_button.setText( - unsubmitted.length + ' ' + pht('Unsubmitted')); + if (unsubmitted.length || draft_done.length) { + var any_draft_count = unsubmitted.length + draft_done.length; + + unsubmitted_button.setText(any_draft_count + ' ' + pht('Unsubmitted')); JX.DOM.show(unsubmitted_button.getNode()); } else { JX.DOM.hide(unsubmitted_button.getNode()); @@ -1523,7 +1538,7 @@ JX.install('DiffChangesetList', { var options = { filter: 'comment', wrap: true, - attribute: 'unsubmitted' + attribute: 'anyDraft' }; this._onjumpkey(1, options); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 692d5644a0..eb473a3075 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -31,6 +31,7 @@ JX.install('DiffInline', { _isCollapsed: false, _isDraft: null, + _isDraftDone: null, _isFixed: null, _isEditing: false, _isNew: false, @@ -73,6 +74,7 @@ JX.install('DiffInline', { this._isFixed = data.isFixed; this._isGhost = data.isGhost; this._isSynthetic = data.isSynthetic; + this._isDraftDone = data.isDraftDone; this._changesetID = data.changesetID; this._isNew = false; @@ -103,6 +105,10 @@ JX.install('DiffInline', { return this._isSynthetic; }, + isDraftDone: function() { + return this._isDraftDone; + }, + bindToRange: function(data) { this._displaySide = data.displaySide; this._number = parseInt(data.number, 10); @@ -321,6 +327,7 @@ JX.install('DiffInline', { JX.DOM.alterClass(comment, 'inline-state-is-draft', response.draftState); this._isFixed = response.isChecked; + this._isDraftDone = !!response.draftState; this._didUpdate(); }, From b3b30dde6ac99786a77f903823ebf259db6e3adc Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Jun 2017 04:51:06 -0700 Subject: [PATCH 270/543] Add options for hidding inlines to the Differential header banner Summary: Fixes T8909. Ref T12733. UI attempts to follow the mock, but is a bit rough since PHUIXButtonView without text in this menu gets weird spacing, we don't have circular buttons yet, and PHUIXActionView without an icon also gets odd spacing. Test Plan: {F5003125} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733, T8909 Differential Revision: https://secure.phabricator.com/D18128 --- resources/celerity/map.php | 50 +-- .../view/DifferentialChangesetListView.php | 6 + .../rsrc/js/application/diff/DiffChangeset.js | 1 + .../js/application/diff/DiffChangesetList.js | 287 +++++++++++++++--- .../rsrc/js/application/diff/DiffInline.js | 20 +- 5 files changed, 294 insertions(+), 70 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 03112ea938..a2f711c594 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4ec4a37a', - 'differential.pkg.js' => '3442216b', + 'differential.pkg.js' => 'a55a2c13', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -395,9 +395,9 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'cdc5fa19', - 'rsrc/js/application/diff/DiffChangesetList.js' => '4ca11264', - 'rsrc/js/application/diff/DiffInline.js' => '27b6d01f', + 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'd442be4a', + 'rsrc/js/application/diff/DiffInline.js' => '1bfa31c7', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -774,9 +774,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'cdc5fa19', - 'phabricator-diff-changeset-list' => '4ca11264', - 'phabricator-diff-inline' => '27b6d01f', + 'phabricator-diff-changeset' => '99abf4cd', + 'phabricator-diff-changeset-list' => 'd442be4a', + 'phabricator-diff-inline' => '1bfa31c7', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1016,6 +1016,9 @@ return array( 'javelin-request', 'javelin-uri', ), + '1bfa31c7' => array( + 'javelin-dom', + ), '1e911d0f' => array( 'javelin-stratcom', 'javelin-request', @@ -1056,9 +1059,6 @@ return array( 'phabricator-drag-and-drop-file-upload', 'javelin-workboard-board', ), - '27b6d01f' => array( - 'javelin-dom', - ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1234,10 +1234,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - '4ca11264' => array( - 'javelin-install', - 'phuix-button-view', - ), '4d863052' => array( 'javelin-dom', 'javelin-util', @@ -1626,6 +1622,17 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), + '99abf4cd' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1956,17 +1963,6 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), - 'cdc5fa19' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'd0a99ab4' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -1991,6 +1987,10 @@ return array( 'd254d646' => array( 'javelin-util', ), + 'd442be4a' => array( + 'javelin-install', + 'phuix-button-view', + ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 4b9f7c6b5f..6aa9020a95 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -279,6 +279,12 @@ final class DifferentialChangesetListView extends AphrontView { 'Unsaved' => pht('Unsaved'), 'Unsubmitted' => pht('Unsubmitted'), 'Comments' => pht('Comments'), + + 'Hide "Done" Inlines' => pht('Hide "Done" Inlines'), + 'Hide Collapsed Inlines' => pht('Hide Collapsed Inlines'), + 'Hide Older Inlines' => pht('Hide Older Inlines'), + 'Hide All Inlines' => pht('Hide All Inlines'), + 'Show All Inlines' => pht('Show All Inlines'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js index f73dba0967..72eeae294a 100644 --- a/webroot/rsrc/js/application/diff/DiffChangeset.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -446,6 +446,7 @@ JX.install('DiffChangeset', { type: block.type, changeset: this, target: inline, + hidden: inline.isHidden(), collapsed: inline.isCollapsed(), deleted: !inline.getID() && !inline.isEditing(), nodes: { diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 8200d54a7a..25f5d57698 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -117,6 +117,10 @@ JX.install('DiffChangesetList', { _doneButton: null, _doneMode: null, + _dropdownMenu: null, + _menuButton: null, + _menuItems: null, + sleep: function() { this._asleep = true; @@ -458,6 +462,7 @@ JX.install('DiffChangesetList', { var collapsed = options.collapsed || false; var wrap = options.wrap || false; var attribute = options.attribute || null; + var show = options.show || false; var cursor = state.cursor; var items = state.items; @@ -529,6 +534,15 @@ JX.install('DiffChangesetList', { } } + // If this item is a hidden inline but we're clicking a button which + // selects inlines of a particular type, make it visible again. + if (items[cursor].hidden) { + if (!show) { + continue; + } + items[cursor].target.setHidden(false); + } + // Otherwise, we've found a valid item to select. break; } @@ -1314,6 +1328,13 @@ JX.install('DiffChangesetList', { }, _redrawBanner: function() { + // If the inline comment menu is open and we've done a redraw, close it. + // In particular, this makes it close when you scroll the document: + // otherwise, it stays open but the banner moves underneath it. + if (this._dropdownMenu) { + this._dropdownMenu.close(); + } + var node = this._getBannerNode(); var changeset = this._getVisibleChangeset(); @@ -1329,49 +1350,13 @@ JX.install('DiffChangesetList', { return; } - var changesets = this._changesets; - var unsaved = []; - var unsubmitted = []; - var draft_done = []; - var undone = []; - var done = []; + var inlines = this._getInlinesByType(); - for (var ii = 0; ii < changesets.length; ii++) { - var inlines = changesets[ii].getInlines(); - for (var jj = 0; jj < inlines.length; jj++) { - var inline = inlines[jj]; - - if (inline.isDeleted()) { - continue; - } - - if (inline.isSynthetic()) { - continue; - } - - if (inline.isEditing()) { - unsaved.push(inline); - } else if (!inline.getID()) { - // These are new comments which have been cancelled, and do not - // count as anything. - continue; - } else if (inline.isDraft()) { - unsubmitted.push(inline); - } else { - // NOTE: Unlike other states, an inline may be marked with a - // draft checkmark and still be a "done" or "undone" comment. - if (inline.isDraftDone()) { - draft_done.push(inline); - } - - if (!inline.isDone()) { - undone.push(inline); - } else { - done.push(inline); - } - } - } - } + var unsaved = inlines.unsaved; + var unsubmitted = inlines.unsubmitted; + var undone = inlines.undone; + var done = inlines.done; + var draft_done = inlines.draftDone; JX.DOM.alterClass( node, @@ -1392,6 +1377,7 @@ JX.install('DiffChangesetList', { var unsaved_button = this._getUnsavedButton(); var unsubmitted_button = this._getUnsubmittedButton(); var done_button = this._getDoneButton(); + var menu_button = this._getMenuButton(); if (unsaved.length) { unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved')); @@ -1457,7 +1443,8 @@ JX.install('DiffChangesetList', { var buttons_list = [ unsaved_button.getNode(), unsubmitted_button.getNode(), - done_button.getNode() + done_button.getNode(), + menu_button.getNode() ]; var buttons_view = JX.$N('div', buttons_attrs, buttons_list); @@ -1470,6 +1457,104 @@ JX.install('DiffChangesetList', { document.body.appendChild(node); }, + _getInlinesByType: function() { + var changesets = this._changesets; + var unsaved = []; + var unsubmitted = []; + var undone = []; + var done = []; + var draft_done = []; + + var visible_done = []; + var visible_collapsed = []; + var visible_ghosts = []; + var visible = []; + var hidden = []; + + for (var ii = 0; ii < changesets.length; ii++) { + var inlines = changesets[ii].getInlines(); + var inline; + var jj; + for (jj = 0; jj < inlines.length; jj++) { + inline = inlines[jj]; + + if (inline.isDeleted()) { + continue; + } + + if (inline.isSynthetic()) { + continue; + } + + if (inline.isEditing()) { + unsaved.push(inline); + } else if (!inline.getID()) { + // These are new comments which have been cancelled, and do not + // count as anything. + continue; + } else if (inline.isDraft()) { + unsubmitted.push(inline); + } else { + // NOTE: Unlike other states, an inline may be marked with a + // draft checkmark and still be a "done" or "undone" comment. + if (inline.isDraftDone()) { + draft_done.push(inline); + } + + if (!inline.isDone()) { + undone.push(inline); + } else { + done.push(inline); + } + } + } + + for (jj = 0; jj < inlines.length; jj++) { + inline = inlines[jj]; + if (inline.isDeleted()) { + continue; + } + + if (inline.isEditing()) { + continue; + } + + if (inline.isHidden()) { + hidden.push(inline); + continue; + } + + visible.push(inline); + + if (inline.isDone()) { + visible_done.push(inline); + } + + if (inline.isCollapsed()) { + visible_collapsed.push(inline); + } + + if (inline.isGhost()) { + visible_ghosts.push(inline); + } + } + } + + return { + unsaved: unsaved, + unsubmitted: unsubmitted, + undone: undone, + done: done, + draftDone: draft_done, + visibleDone: visible_done, + visibleGhosts: visible_ghosts, + visibleCollapsed: visible_collapsed, + visible: visible, + hidden: hidden + }; + + }, + _getUnsavedButton: function() { if (!this._unsavedButton) { var button = new JX.PHUIXButtonView() @@ -1520,12 +1605,126 @@ JX.install('DiffChangesetList', { return this._doneButton; }, + + _getMenuButton: function() { + if (!this._menuButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-gear') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var dropdown = new JX.PHUIXDropdownMenu(button.getNode()); + this._menuItems = {}; + + var list = new JX.PHUIXActionListView(); + dropdown.setContent(list.getNode()); + + var map = { + hideDone: { + type: 'done' + }, + hideCollapsed: { + type: 'collapsed' + }, + hideGhosts: { + type: 'ghosts' + }, + hideAll: { + type: 'all' + }, + showAll: { + type: 'show' + } + }; + + for (var k in map) { + var spec = map[k]; + + var handler = JX.bind(this, this._onhideinlines, spec.type); + var item = new JX.PHUIXActionView() + .setHandler(handler); + + list.addItem(item); + this._menuItems[k] = item; + } + + dropdown.listen('open', JX.bind(this, this._ondropdown)); + + this._menuButton = button; + this._dropdownMenu = dropdown; + } + + return this._menuButton; + }, + + _ondropdown: function() { + var inlines = this._getInlinesByType(); + var items = this._menuItems; + var pht = this.getTranslations(); + + items.hideDone + .setName(pht('Hide "Done" Inlines')) + .setDisabled(!inlines.visibleDone.length); + + items.hideCollapsed + .setName(pht('Hide Collapsed Inlines')) + .setDisabled(!inlines.visibleCollapsed.length); + + items.hideGhosts + .setName(pht('Hide Older Inlines')) + .setDisabled(!inlines.visibleGhosts.length); + + items.hideAll + .setName(pht('Hide All Inlines')) + .setDisabled(!inlines.visible.length); + + items.showAll + .setName(pht('Show All Inlines')) + .setDisabled(!inlines.hidden.length); + }, + + _onhideinlines: function(type, e) { + this._dropdownMenu.close(); + e.prevent(); + + var inlines = this._getInlinesByType(); + + // Clear the selection state since we end up in a weird place if the + // user hides the selected inline. + this._setSelectionState(null); + + var targets; + var mode = true; + switch (type) { + case 'done': + targets = inlines.visibleDone; + break; + case 'collapsed': + targets = inlines.visibleCollapsed; + break; + case 'ghosts': + targets = inlines.visibleGhosts; + break; + case 'all': + targets = inlines.visible; + break; + case 'show': + targets = inlines.hidden; + mode = false; + break; + } + + for (var ii = 0; ii < targets.length; ii++) { + targets[ii].setHidden(mode); + } + }, + _onunsavedclick: function(e) { e.kill(); var options = { filter: 'comment', wrap: true, + show: true, attribute: 'unsaved' }; @@ -1538,6 +1737,7 @@ JX.install('DiffChangesetList', { var options = { filter: 'comment', wrap: true, + show: true, attribute: 'anyDraft' }; @@ -1550,6 +1750,7 @@ JX.install('DiffChangesetList', { var options = { filter: 'comment', wrap: true, + show: true, attribute: this._doneMode }; diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index eb473a3075..1c93959b32 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -36,6 +36,7 @@ JX.install('DiffInline', { _isEditing: false, _isNew: false, _isSynthetic: false, + _isHidden: false, bindToRow: function(row) { this._row = row; @@ -109,6 +110,14 @@ JX.install('DiffInline', { return this._isDraftDone; }, + isHidden: function() { + return this._isHidden; + }, + + isGhost: function() { + return this._isGhost; + }, + bindToRange: function(data) { this._displaySide = data.displaySide; this._number = parseInt(data.number, 10); @@ -207,6 +216,12 @@ JX.install('DiffInline', { return this; }, + setHidden: function(hidden) { + this._isHidden = hidden; + this._redraw(); + return this; + }, + canReply: function() { if (!this._hasAction('reply')) { return false; @@ -708,9 +723,10 @@ JX.install('DiffInline', { }, _redraw: function() { - var is_invisible = (this._isInvisible || this._isDeleted); + var is_invisible = + (this._isInvisible || this._isDeleted || this._isHidden); var is_loading = this._isLoading; - var is_collapsed = this._isCollapsed; + var is_collapsed = (this._isCollapsed && !this._isHidden); var row = this._row; JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); From 9b93697d52f40753799683ce461fcc02f45d7ef5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Jun 2017 05:31:42 -0700 Subject: [PATCH 271/543] Move "List Inline Comments" to the inline header dropdown menu Summary: See D18128. Ref T12733. Ref T8250. Test Plan: {F5003153} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733, T8250 Differential Revision: https://secure.phabricator.com/D18129 --- resources/celerity/map.php | 50 +++++++++---------- .../DifferentialRevisionViewController.php | 12 ++--- .../view/DifferentialChangesetListView.php | 13 +++++ .../js/application/diff/DiffChangesetList.js | 17 ++++++- .../differential/behavior-populate.js | 3 +- webroot/rsrc/js/phuix/PHUIXActionView.js | 10 ++++ 6 files changed, 72 insertions(+), 33 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a2f711c594..98d677d53c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => 'ab24402f', - 'core.pkg.js' => '1475bd91', + 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4ec4a37a', - 'differential.pkg.js' => 'a55a2c13', + 'differential.pkg.js' => 'd4ab0e81', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -396,12 +396,12 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'd442be4a', + 'rsrc/js/application/diff/DiffChangesetList.js' => '79de07c6', 'rsrc/js/application/diff/DiffInline.js' => '1bfa31c7', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', + 'rsrc/js/application/differential/behavior-populate.js' => '419998ab', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -526,7 +526,7 @@ return array( 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', - 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', + 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', 'rsrc/js/phuix/PHUIXButtonView.js' => 'a37126bd', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', @@ -616,7 +616,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '5e41c819', + 'javelin-behavior-differential-populate' => '419998ab', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -775,7 +775,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '99abf4cd', - 'phabricator-diff-changeset-list' => 'd442be4a', + 'phabricator-diff-changeset-list' => '79de07c6', 'phabricator-diff-inline' => '1bfa31c7', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -878,7 +878,7 @@ return array( 'phui-workcard-view-css' => 'cca5fa92', 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', - 'phuix-action-view' => 'b3465b9b', + 'phuix-action-view' => '442efd08', 'phuix-autocomplete' => 'f6699267', 'phuix-button-view' => 'a37126bd', 'phuix-dropdown-menu' => '8018ee50', @@ -1160,6 +1160,14 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), + '419998ab' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1174,6 +1182,11 @@ return array( 'javelin-workflow', 'javelin-workboard-controller', ), + '442efd08' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-util', + ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1335,14 +1348,6 @@ return array( 'phabricator-phtize', 'javelin-dom', ), - '5e41c819' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1471,6 +1476,10 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), + '79de07c6' => array( + 'javelin-install', + 'phuix-button-view', + ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1778,11 +1787,6 @@ return array( 'javelin-uri', 'javelin-request', ), - 'b3465b9b' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - ), 'b3a4b884' => array( 'javelin-behavior', 'phabricator-prefab', @@ -1987,10 +1991,6 @@ return array( 'd254d646' => array( 'javelin-util', ), - 'd442be4a' => array( - 'javelin-install', - 'phuix-button-view', - ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 0f3675f8f6..f19ff37360 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -281,6 +281,12 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setTitle(pht('Diff %s', $target->getID())) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + + $revision_id = $revision->getID(); + $inline_list_uri = "/revision/inlines/{$revision_id}/"; + $inline_list_uri = $this->getApplicationURI($inline_list_uri); + $changeset_view->setInlineListURI($inline_list_uri); + if ($repository) { $changeset_view->setRepository($repository); } @@ -574,12 +580,6 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-indent') - ->setHref("/differential/revision/inlines/{$revision_id}/") - ->setName(pht('List Inline Comments'))); - $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-upload') diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 6aa9020a95..2787356bd2 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -14,6 +14,7 @@ final class DifferentialChangesetListView extends AphrontView { private $standaloneURI; private $leftRawFileURI; private $rightRawFileURI; + private $inlineListURI; private $symbolIndexes = array(); private $repository; @@ -64,6 +65,15 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setInlineListURI($uri) { + $this->inlineListURI = $uri; + return $this; + } + + public function getInlineListURI() { + return $this->inlineListURI; + } + public function setRepository(PhabricatorRepository $repository) { $this->repository = $repository; return $this; @@ -208,6 +218,7 @@ final class DifferentialChangesetListView extends AphrontView { array( 'changesetViewIDs' => $ids, 'inlineURI' => $this->inlineURI, + 'inlineListURI' => $this->inlineListURI, 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show All Context' => pht('Show All Context'), @@ -285,6 +296,8 @@ final class DifferentialChangesetListView extends AphrontView { 'Hide Older Inlines' => pht('Hide Older Inlines'), 'Hide All Inlines' => pht('Hide All Inlines'), 'Show All Inlines' => pht('Show All Inlines'), + + 'List Inline Comments' => pht('List Inline Comments'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 25f5d57698..808527876c 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -88,7 +88,8 @@ JX.install('DiffChangesetList', { properties: { translations: null, - inlineURI: null + inlineURI: null, + inlineListURI: null }, members: { @@ -1649,6 +1650,20 @@ JX.install('DiffChangesetList', { dropdown.listen('open', JX.bind(this, this._ondropdown)); + var pht = this.getTranslations(); + + if (this.getInlineListURI()) { + list.addItem( + new JX.PHUIXActionView() + .setDivider(true)); + + list.addItem( + new JX.PHUIXActionView() + .setIcon('fa-link') + .setName(pht('List Inline Comments')) + .setHref(this.getInlineListURI())); + } + this._menuButton = button; this._dropdownMenu = dropdown; } diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index e5b0d5039d..0a4a7912e4 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -60,7 +60,8 @@ JX.behavior('differential-populate', function(config, statics) { var changeset_list = new JX.DiffChangesetList() .setTranslations(JX.phtize(config.pht)) - .setInlineURI(config.inlineURI); + .setInlineURI(config.inlineURI) + .setInlineListURI(config.inlineListURI); // Install and activate the current page. var page_id = JX.Quicksand.getCurrentPageID(); diff --git a/webroot/rsrc/js/phuix/PHUIXActionView.js b/webroot/rsrc/js/phuix/PHUIXActionView.js index d5d849e733..2db58dbdc4 100644 --- a/webroot/rsrc/js/phuix/PHUIXActionView.js +++ b/webroot/rsrc/js/phuix/PHUIXActionView.js @@ -16,6 +16,7 @@ JX.install('PHUIXActionView', { _label: false, _handler: null, _selected: false, + _divider: false, _iconNode: null, _nameNode: null, @@ -41,6 +42,15 @@ JX.install('PHUIXActionView', { return this; }, + setDivider: function(divider) { + this._divider = divider; + JX.DOM.alterClass( + this.getNode(), + 'phabricator-action-view-type-divider', + divider); + return this; + }, + setSelected: function(selected) { this._selected = selected; JX.DOM.alterClass( From 9540ed7ec2ee180dc6edd9bada92c0b18b9ead7b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 18 Jun 2017 08:50:50 +0200 Subject: [PATCH 272/543] Use visibility hidden on remarkup checkboxes Summary: Fixes T12850, marks it hidden with zero width so the element still exists in the DOM Test Plan: T12850 code, locally Reviewers: epriestley, amckinley, avivey Reviewed By: avivey Subscribers: Korvin Maniphest Tasks: T12850 Differential Revision: https://secure.phabricator.com/D18130 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 98d677d53c..3e80c99baa 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'ab24402f', + 'core.pkg.css' => '6d40b714', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4ec4a37a', @@ -115,7 +115,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '23beb330', - 'rsrc/css/core/remarkup.css' => '509fb36e', + 'rsrc/css/core/remarkup.css' => 'eb37bd0d', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -795,7 +795,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => '509fb36e', + 'phabricator-remarkup-css' => 'eb37bd0d', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 98529b9434..1de7ccf85a 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -128,7 +128,8 @@ } .phabricator-remarkup .remarkup-list-with-checkmarks input { - display: none; + visibility: hidden; + width: 0; } .phabricator-remarkup .remarkup-list-with-checkmarks From d0898116d8c265065481b970f4ea1fa58547562d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 18 Jun 2017 09:53:43 +0200 Subject: [PATCH 273/543] Add a graph view page to Diffusion Summary: Fixes T12840. This adds a parallel "graph" button next to history on home and on the history list page. I'll think more about better placement of how to get to this page with the upcoming redesign that's still sitting in Pholio. Test Plan: View History, View Graph, Try pager, go to a file, click view history, see no graph button. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12840 Differential Revision: https://secure.phabricator.com/D18131 --- src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 1 + .../controller/DiffusionController.php | 3 + .../controller/DiffusionGraphController.php | 108 ++++++++++++++++++ .../controller/DiffusionHistoryController.php | 28 +++-- .../DiffusionRepositoryController.php | 54 +++++---- .../storage/PhabricatorRepository.php | 2 + 7 files changed, 161 insertions(+), 37 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionGraphController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bca514a06e..f1fd9648cd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -729,6 +729,7 @@ phutil_register_library_map(array( 'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php', 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', + 'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', @@ -5706,6 +5707,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryClusterEngineLogInterface', ), 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGraphController' => 'DiffusionController', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryListView' => 'DiffusionHistoryView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index c07c89159b..dc1b120138 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -56,6 +56,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', 'history/(?P.*)' => 'DiffusionHistoryController', + 'graph/(?P.*)' => 'DiffusionGraphController', 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 52465da410..abebbace90 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -204,6 +204,9 @@ abstract class DiffusionController extends PhabricatorController { case 'history': $view_name = pht('History'); break; + case 'graph': + $view_name = pht('Graph'); + break; case 'browse': $view_name = pht('Browse'); break; diff --git a/src/applications/diffusion/controller/DiffusionGraphController.php b/src/applications/diffusion/controller/DiffusionGraphController.php new file mode 100644 index 0000000000..8428a8152d --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionGraphController.php @@ -0,0 +1,108 @@ +loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + $params = array( + 'commit' => $drequest->getCommit(), + 'path' => $drequest->getPath(), + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, + ); + + $history_results = $this->callConduitWithDiffusionRequest( + 'diffusion.historyquery', + $params); + $history = DiffusionPathChange::newFromConduit( + $history_results['pathChanges']); + + $history = $pager->sliceResults($history); + + $graph = id(new DiffusionHistoryTableView()) + ->setViewer($viewer) + ->setDiffusionRequest($drequest) + ->setHistory($history); + + $graph->loadRevisions(); + $show_graph = !strlen($drequest->getPath()); + if ($show_graph) { + $graph->setParents($history_results['parents']); + $graph->setIsHead(!$pager->getOffset()); + $graph->setIsTail(!$pager->getHasMorePages()); + } + + $header = $this->buildHeader($drequest); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'graph', + )); + $crumbs->setBorder(true); + + $title = array( + pht('Graph'), + $repository->getDisplayName(), + ); + + $graph_view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('History Graph')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($graph) + ->setPager($pager); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($graph_view); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + + $tag = $this->renderCommitHashTag($drequest); + $history_uri = $drequest->generateURI( + array( + 'action' => 'history', + )); + + $history_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('History')) + ->setHref($history_uri) + ->setIcon('fa-history'); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag) + ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeaderIcon('fa-code-fork') + ->addActionLink($history_button); + + return $header; + + } + +} diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index b8a1877ddf..c2f718f11e 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -77,24 +77,28 @@ final class DiffusionHistoryController extends DiffusionController { $viewer = $this->getViewer(); $tag = $this->renderCommitHashTag($drequest); - $browse_uri = $drequest->generateURI( - array( - 'action' => 'browse', - )); - - $browse_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Browse')) - ->setHref($browse_uri) - ->setIcon('fa-code'); + $show_graph = !strlen($drequest->getPath()); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($drequest->getRepository()) ->addTag($tag) ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) - ->setHeaderIcon('fa-clock-o') - ->addActionLink($browse_button); + ->setHeaderIcon('fa-clock-o'); + + if ($show_graph) { + $graph_uri = $drequest->generateURI( + array( + 'action' => 'graph', + )); + + $graph_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Graph')) + ->setHref($graph_uri) + ->setIcon('fa-code-fork'); + $header->addActionLink($graph_button); + } return $header; diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index e341b87972..362f78bd3f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -451,11 +451,11 @@ final class DiffusionRepositoryController extends DiffusionController { $header->setSubheader(pht('Showing %d branches.', $limit)); } - $button = new PHUIButtonView(); - $button->setText(pht('Show All')); - $button->setTag('a'); - $button->setIcon('fa-code-fork'); - $button->setHref($drequest->generateURI( + $button = id(new PHUIButtonView()) + ->setText(pht('Show All')) + ->setTag('a') + ->setIcon('fa-code-fork') + ->setHref($drequest->generateURI( array( 'action' => 'branches', ))); @@ -511,11 +511,11 @@ final class DiffusionRepositoryController extends DiffusionController { pht('Showing the %d most recent tags.', $tag_limit)); } - $button = new PHUIButtonView(); - $button->setText(pht('Show All Tags')); - $button->setTag('a'); - $button->setIcon('fa-tag'); - $button->setHref($drequest->generateURI( + $button = id(new PHUIButtonView()) + ->setText(pht('Show All Tags')) + ->setTag('a') + ->setIcon('fa-tag') + ->setHref($drequest->generateURI( array( 'action' => 'tags', ))); @@ -567,23 +567,30 @@ final class DiffusionRepositoryController extends DiffusionController { $history_table->setIsHead(true); - $icon = id(new PHUIIconView()) - ->setIcon('fa-list-alt'); - - $button = id(new PHUIButtonView()) - ->setText(pht('View History')) + $history = id(new PHUIButtonView()) + ->setText(pht('History')) ->setHref($drequest->generateURI( array( 'action' => 'history', ))) ->setTag('a') - ->setIcon($icon); + ->setIcon('fa-history'); + + $graph = id(new PHUIButtonView()) + ->setText(pht('Graph')) + ->setHref($drequest->generateURI( + array( + 'action' => 'graph', + ))) + ->setTag('a') + ->setIcon('fa-code-fork'); $panel = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Commits')) - ->addActionLink($button); + ->addActionLink($graph) + ->addActionLink($history); $panel->setHeader($header); $panel->setTable($history_table); @@ -672,14 +679,11 @@ final class DiffusionRepositoryController extends DiffusionController { $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()); - $icon = id(new PHUIIconView()) - ->setIcon('fa-folder-open'); - - $button = new PHUIButtonView(); - $button->setText(pht('Browse Repository')); - $button->setTag('a'); - $button->setIcon($icon); - $button->setHref($browse_uri); + $button = id(new PHUIButtonView()) + ->setText(pht('Browse')) + ->setTag('a') + ->setIcon('fa-code') + ->setHref($browse_uri); $header->addActionLink($button); $browse_panel->setHeader($header); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index a0d4c6c7b5..f8a434e42b 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -699,6 +699,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $action = idx($params, 'action'); switch ($action) { case 'history': + case 'graph': case 'browse': case 'change': case 'lastmodified': @@ -776,6 +777,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO switch ($action) { case 'change': case 'history': + case 'graph': case 'browse': case 'lastmodified': case 'tags': From cd19ddf111dc95a927488e9ae249b4911c88b08b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Jun 2017 12:37:00 -0700 Subject: [PATCH 274/543] Improve validation errors for changing task priorities Summary: Ref T12124. Currently, Conduit provides a fairly rough error message if you provide an invalid priority. Instead, provide a more tailored message. Also, block `!!unknown!!` except from web edits. Test Plan: Before: {F5007964} After: {F5007965} Also, changed a priority to `999` in the database, edited it with the normal web UI form, it let me make the edit without being forced to adjust the priority. Reviewers: amckinley, chad Reviewed By: amckinley Maniphest Tasks: T12124 Differential Revision: https://secure.phabricator.com/D18135 --- .../ManiphestTaskPriorityTransaction.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php index 4e505668d1..2ed7c4e15b 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -129,4 +129,50 @@ final class ManiphestTaskPriorityTransaction } } + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $content_source = $this->getEditor()->getContentSource(); + $is_web = ($content_source instanceof PhabricatorWebContentSource); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + // If this is a legitimate keyword like "low" or "high", this transaction + // is fine and apply normally. + $keyword = ManiphestTaskPriority::getTaskPriorityFromKeyword($value); + if ($keyword !== null) { + continue; + } + + // If this is the magic "don't change things" value for editing tasks + // with an obsolete priority constant in the database, let it through if + // this is a web edit. + if ($value === ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD) { + if ($is_web) { + continue; + } + } + + $keyword_list = array(); + foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $name) { + $keyword = ManiphestTaskPriority::getKeywordForTaskPriority($pri); + if ($keyword === null) { + continue; + } + $keyword_list[] = $keyword; + } + + $errors[] = $this->newInvalidError( + pht( + 'Task priority "%s" is not a valid task priority. Use a priority '. + 'keyword to choose a task priority: %s.', + $value, + implode(', ', $keyword_list)), + $xaction); + } + + return $errors; + } + } From 474d528c3b85ec6639056be16d7fae7b536bf3bc Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Jun 2017 12:15:26 -0700 Subject: [PATCH 275/543] Allow numeric constants to act as aliases for task priorities in the web UI ` controls, so if the value comes in with something we don't recognize we'll treat it like some other value. Then alias all the numeric constants -- and other keywords -- to the right constants. This ended up only affecting the `" EditEngine fields canonicalize saved defaults Summary: Ref T12124. After D18134 we accept either "25" or "low" via HTTP parameters and when the field renders as a control, but if the form has a default value for the field but locks or hides it we don't actually run through that logic. Canonicalize both when rendering the control and when using a raw saved default value. Test Plan: - Created a form with "Priority: Low". - Hid the "Priority" field. - Before patch: Tried to create a task, was rebuffed with a (now verbose and helpful, after D18135) error. - Applied patch: things worked. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12124 Differential Revision: https://secure.phabricator.com/D18142 --- .../editfield/PhabricatorSelectEditField.php | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/applications/transactions/editfield/PhabricatorSelectEditField.php b/src/applications/transactions/editfield/PhabricatorSelectEditField.php index b72cafc5d1..fa565dbe61 100644 --- a/src/applications/transactions/editfield/PhabricatorSelectEditField.php +++ b/src/applications/transactions/editfield/PhabricatorSelectEditField.php @@ -27,18 +27,13 @@ final class PhabricatorSelectEditField return $this->optionAliases; } + protected function getDefaultValueFromConfiguration($value) { + return $this->getCanonicalValue($value); + } + protected function getValueForControl() { $value = parent::getValueForControl(); - - $options = $this->getOptions(); - if (!isset($options[$value])) { - $aliases = $this->getOptionAliases(); - if (isset($aliases[$value])) { - $value = $aliases[$value]; - } - } - - return $value; + return $this->getCanonicalValue($value); } protected function newControl() { @@ -59,4 +54,16 @@ final class PhabricatorSelectEditField return new ConduitStringParameterType(); } + private function getCanonicalValue($value) { + $options = $this->getOptions(); + if (!isset($options[$value])) { + $aliases = $this->getOptionAliases(); + if (isset($aliases[$value])) { + $value = $aliases[$value]; + } + } + + return $value; + } + } From cdeba0f85b40e505d9b86fec8e5954507f0ba12e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 21 Jun 2017 22:05:29 +0200 Subject: [PATCH 279/543] New icons for repositories Summary: Had some made, they seem better than my attempt. Test Plan: review in sandbox. Reviewers: epriestley Reviewed By: epriestley Subscribers: avivey, Korvin Maniphest Tasks: T12859 Differential Revision: https://secure.phabricator.com/D18143 --- resources/builtin/repo/building.png | Bin 2566 -> 4249 bytes resources/builtin/repo/cloud.png | Bin 1904 -> 7360 bytes resources/builtin/repo/code.png | Bin 2680 -> 3956 bytes resources/builtin/repo/commit.png | Bin 2103 -> 6622 bytes resources/builtin/repo/database.png | Bin 3428 -> 10938 bytes resources/builtin/repo/desktop.png | Bin 1193 -> 2484 bytes resources/builtin/repo/gears.png | Bin 4209 -> 9870 bytes resources/builtin/repo/globe.png | Bin 4171 -> 14281 bytes resources/builtin/repo/locked.png | Bin 2092 -> 6430 bytes resources/builtin/repo/microchip.png | Bin 1841 -> 5924 bytes resources/builtin/repo/mobile.png | Bin 1411 -> 3879 bytes resources/builtin/repo/repo.png | Bin 1590 -> 3406 bytes resources/builtin/repo/servers.png | Bin 1400 -> 6414 bytes 13 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/builtin/repo/building.png b/resources/builtin/repo/building.png index 1638c4174dcca9d964625cbd884c1b48414438e0..d3ab581fe8584a1789948e9d965ebe286a354174 100644 GIT binary patch literal 4249 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 z za+@DL(9pK$-g`q(KgY1c-4^OSTKBW2xZ+gYl*b*te4pcezwX!l-;$l;rgmNS1#tq! zfmrJwOg@ff1Ki~ejRm$V3He)}zUwjBPy+kO&zEC@5be;A*cX53-JNyqW;{Ns!@E|} zdFx@X_o6b$|fE|7pwi$~gjRAV5d2<&Sl-IfjzM~vRVAM6pAw?z$ z_TsX7Ukfc3?dE**@hLzf&Q>T2Ju6HpRqku)^as&Z7}QT*Q!8iJfcgB;I-?kK^b>hl z=Y2|Rxau13H^;$k-fx&4DzW->%3kM$#;!h%R}NKW7K!QpW}@R6mX5hn=9ogpnT=y7 zx}6X`VZ3SSpj+O7V10oc6}ZxIa$y7{^q$_Q)2SWQ&pHP-R{mn1<-A;_jA~YGU8l??No)Ax;vi5gQdj7 zGJoTu9Wl+)Q7q$fU)NLNoFiv)e{*^52S0ra=?uB~ zZn$dFuwfwrl}K<>p zv=cwxlzV>m_^;TCfCYDt-J`+Bm2YqY`>^OQk%Vbz+V|0|okMGzw$NY>)57IT#Yal9 zMSy2 zj2Z?};*H-w@87rHS?jKK?>^`5z0Wz{-rp&)y3f@?WXxm)1Oy
gn{1cZV&4+#)2 z2@Fm2BOqXn)=*V83?SU;76svjM4T5M@Q1HOlF9KO|G(n9v*Z^-r!h}_fD+!?J1=Ov zDFUp6trtCHst3(&ZTG3Es0Z1I+K>x>*>I1#TG)j{Xj&`P+k(TwL#$1|h>1&f85$|# zCOLcxxE}F_@zyZ>E>V6PbG82H(7zb`sw=E(miviX>MU;!t>T^irm!%Yl>DfFkO}g> zrarO>u048w;2tfTZ4_=LJmo1QhXtxSMzcaMV_T>z?5*_EOuWtEu)!9lXB$-7{EQ+l zRV5IkAquIvQ@i2t83PFqPK5G5iTZs;C(Z)f1sNbG*@OWz6X^WLtb0sgn2qG}5cwyVfbo7<8>wHbn8wUo8ynlzmQ{z8LCFMg4ne$t$ z-RAzin*a1e8SVCw+OF;TH2g>HkNqEoEZLpb|2i%vF@2;{QlC7NB_EEKf7=O54|B;k zL*EtA@5Z5;Ei5piy_k}-v!X0&e%6Xidg2T(5a@fQzc1YC0ax0my#S?z{lKT%yWy&C zif4{}&xXfxXD_HDKsIkUa~WVbKLh@7?fd`oQb=m9)`wZ;C3%5 z_VO5Xvq%&6gGg*5bYokx0W`KWiCO8OQ4p$n(8%Bjp5E1JaMDfI3#tJh>L>C5!D=&2 zeOD?`M8nAWZBn$;aGinjD_snQv-3(X3B`=EXW|zMSI6zn>yh>+NfkM9qOL!q+q%%N zOR4du+dszF>z*`+$KL6%byu+nAqLFeZ)l^^lDEput9rVKCRuk23Oagd@qMF!_s)mG_3yCj6# zsJxK`*@o)L5O}KYH^xa>G87}FTuNG_US}asZ?UUzVK{B6UHp0)EPRIv`eM(>HdD83 z+RA{-o>cs-geXGuI&EyQJBrD~vjS?ZSUqiFxhc`O$H4Iu`T~~HK@}UFCwQ${fBswn z_gHW91FlhIf(xa$Kp#>G&8z$JTkMcyotQP1+zvL;6yf$p8`;*WiH7eCFwhKh8fJ=@ zgCEqLt{L2}5c(5V-B$DMw=32=Q5y)(-;;X&aE5&5^wvwz8I3Ji)UHiOO`IsIeA<Q^pXmDgeOuA^-KQ;8O}aRy3*FIawR^_7x)g4Dz=~kVqWfQo!9tCe}tt~Xk>)? zmg?R^rTzZ3EOWi~=0Xj%7n_tU!3 z<=h3bRY+dVm)cC~{t(n2HnH_gYF2>;54>ibqF;DMrGKp+GEr-nj!+w87}V~m{N&7P&HnX;ty%p8CpO+s1t0oB8BW~a zMdiv9tVw*XF9ISQnay-!n$F#B426ig(ioqCIDQ#OIz<`19bGjQ=bNcBTT{JwcGv2< zeO%0fot{O{avMgwVO5id?@Gnx^VFpV(u_J54_5;x}{P_F0Gp)mg z&5hb<6{l6>91>vwE;_Qh=+9lOr~f2?{9v`$llC95HpvJG?wEg(%EqQ#y`f_$HS4uJ zK417pJ}rC^^Fl;Oy^rijx*ov)+LQq!yEtx&>jI-N(5UY4WInHQs3_VYF z0V)t)-^}+MW8Qw5*Gw^ew=ppQN2v+~FY%`3Pnl#??sapYsi^*r%|c%uX(>5n#EKKE z6a+elhm9TRS)4f2n6+53;I#+EpJncO$AGI8C@Op3AC`xG_GsV-?Oe;`mD+ZV2V^fU zH&~Zm%B|8B99Kx*YdsgYf`Qx7)@F;Br&;=m6PP_oL{x^$QJ+T-wvRFVl0 z>{OfFl(@7SPrD{ta>1xCIyu?i`yyj%ZTbeeQt_#90Apw-Qu8F{&C~&1FF&Q{b+LwH z=yJjjJYt0400x~**$YbBq94hky+g#h+sQ$ub;78ZeWvcrH$&5<3m-ZHihlMA6WJcr z;DviG>z>ZF?J|b|!MJ%4)Z%B0)3Dd%h3Za4ksmol3+7F(vo8W2*&qnaFb`2}o3K(q zAgXJrysfgeg~u0LzrV=9u}QtfyDd2fngmH1tNXrZyRe>?-1!MH^>-R$c279H2^^N! z^#<08350AZ(_PT#l+E=EL}qIo>;1^w(y^&my&VGD>V%o^kIk{Z#eQ}> zgZzMaiIKnjWN$=Yc_k(|+@d9UIhBKv{fymXg4UhzFIbsq9RZBSf7!~={xeVpBBG0y zg?HmBG9w7$R(l~6 zgR*!yLX6(VV73&Bd?vt&-2qz7U~K5(--LeTjO=SEDCVwg4awyn z*&|;4Q!#5iuH_nJ)4f17!p3Qol2KZ&r7w!4KAH&(} zxt1r|R(?4^(|@}kQ|WO&TJ^n|S=_y;-R4e#^I>K7@NQQ!ovocmw2i_K{{|ZIyg%Do za@W5!%NxXqDX*YebdCDoGhRZ!MX?$i$V-)zR@wy=>gmHS)jam+9KdFOd<;bR$M^?kVPPMKA1kbh175y994DwZ=%N5HS zeej-tiDu{<2yK-3a8*uVyBpznYevDgqWKj< zjlAL$lYDb0>u}3KoLXS;kvyK0r{-M*RL=El&E8eDMkh9}`>l>m=qj=W@Fs_uckHxm zsPQ*b#_q?fQ6O6!HT}b=S@u$vy4Jrs?@AVrJ&zNA*wO^`J7y%Jkc6eoAMZ1G*u!oo zFVXhAUBCh&J$)~(knTxEZ{B1lceYE*8f3`5;>r0~L-shp^un?DJgHgBB6${}rqjg2 z`?pH&I(Rj3E7FyRR@unjJU_(LtAA9S3a8MRe8TNkEBuvDMkAvO=h9(M1&`aCO6g>`^W{)8<$qgYi8dNzJU*j z@-y07^M%A7&G?p>7VNJ8 zEO+(TKXlS{iXa!Sp;p1_9ZvO;3Z8c?y>fI~xmsZsgfk>;XVMRM;yHly+Nu%0qb%ye z+wGStJ$#K&Gnoh764W{$*t5|9^wWKU=!^JR*jNh)pZLQj5*`8wrbcZ1oqn;{!DQ64 zyli849yAX0kzcYveVdjDlw49hs}?VMn49~^E$-(}d$LhqZ;fF}(s+rDX8{@I4g1uI zSJI@_wCmaw$YvYZ9l3HdhL)UGj^d44Oo@3r7~01JF;kSI6uKq`OD)M=P9f-F(~MKzinQf_+dBrxvuPoJED(jDSJb%^-~U>d>w9A+)4~;uF?X+ysj+X|8zp}! z3=sHa1$h^_r1Jh&wPKHRiu~wiXkEX8(BiS3*r!5j4;V~ss$Xc1*pPelZ(1*3C4Q zA8Sw`$1U&b_f!=2zQ56-G!aLs z92wVX@qT=FomTtj+q_qP^A8_Wpbl9_Tr+9X1*^9EdLRQ}I9o9;T)NZZ=o`c+=X(-m z0@sR)tAEGYIZLIy0xVXS!0o`2863|D4PuBRD$Y}482EF^HAh#NFrRKP{X}8@Yd{c& z#=h27^sm)b8p3}6uWfEasWtnOsC?WJac#-?sqBI7gW=Jv zu$Xek8N;s^wkR6(>5fU2>nA0_cv=~hl?Xdn(_sl`#0!rfB(9L#NQ4+zQ$S1F8!4Ck zy=tWe?f|e+WjX+HcMm1*`sUD>oIgOi@^iczzhmZ+`bUM12tULuB<=a=5V%5C5q$cI z6&^l-=cyIElDrqYR>lS;w=?u^%PmW@PWtvn$8=PRe|k2`bTA0x`F0)g<~nBUz9#Id zn-^)^avxdL>Wl4h0AE|YiZ=?CgvCm1$a~sDZcUr_t>A)t)bmr}F_Dpg<;(yCjG87!1?pqk4RD>#8U>axP=E}o02wR>&A^uK0aY8< z2Atb4L5sr(9P#eXkgUK{)z06l=eD)L>Bc2;fiH_+30Fl`o6po}p&X~Sunuj1IiDs! z$xMH0y~kIL!l*E8KSr}ISg(c8jr|f8Q{LgDF(e%;5{UFI zG$ttNU<|f zW}XLL;{U&#%5S|NRX~b-R19vEHldW|z+Dq-_QAgtkRTiStY4dbp2zZ^+0eEC0a$vt z2qfH(SkV=7&GG8w87%b6o|+*z;q1Ei?t0mUqC7S9+@;;Ky6sd^5He&swT-I>hWby8 zJ_@&3Q|s%QkZr@{;51=Vd4Ee{yKm(6H`!e#i)jX9 zYYMm{=`kOOacx|Y4_(u^?{ z?%$_>RF;LF8$#0!DX=}xg^xJo;iUZoa@f+waAM+HDe6Ovtb)WQ+eh7YGa;pxrV$Jx z4aic%GWF_<_7iIOXRCCPK80 zA(nQfTk3;P49kJ4*CPRGj}@{ZmI2@D7prpk7~g=4h>4?l&dOF3;#R&T$K>wtO6D*a zC#RPQ*=Nyj{pCU}+nuTyAnl89vxKtBs|wG(i2if#cCVpoSoIwTkxPoS`+`_YdH5d7 zYmg#kQ5FOoy=VCVNl?Y94OO^Tu$_sCplf4jw^3VKlp7}Ak(yQN zrEQu8!YJdIyNb7`7j~>3u%~BCxhw1@RJ!L506B>b>F%I$`_|IPuh<&)J7a&$Vcl!FSuTA`JmjdYsL0HUGC+ho*v$^`6!@ar$^2l%<=19vZwxow8kLg$4M{F=#Y4+ZCHQn|w0^tk z_DUm3rW|$M=+HK^b5;3^@t-84AEX!d@k#Jdr%eiCXGFW)C_dV#V$M!;ShhKzxI2&H z_mD6n>xou-w*nyd_S2!5m>3TN^e%pfKv)AGM-o~>y9ea9wR`w2@A7EEI`pN6&4TSp zcXQ)DMxP~gcRzh$W>=b-I$=*^JlOx8h7Scx(1rgIYu#P8xEz4#1eA!g+ZKQd$R+d* zWn-|X__Kpy2;T$|HgH{Z2^eBJ0$yTCFr%Rymx&ptZm67cB_**(n;Q<=4rPJ2_r=LxxCjAz=t9YTMCD~R0ldIG4v474#n>vg{R zyL1G!oStRvH{w|4=YoD2==cchJlD?9HL@=GG*+CCwU{*2L(`z!9ZE-*6n7eP z7nsq)5cgh%i7rESIc8?cTk>S*NvY@LC_l~V`v(n!)3;iaUUw{h&ja;&u@KwflvDaJ z-g}_+JH^L3R9Z$(37tQ=CJ7$XE4+k}h+4euz7i3$oVVG0Ez9?{K$jy%+o!5oymA~e zu-yvVR$lrVhbTSeIy5QveJ8?4Vnq+8W%OkX$x6^u=@r!2=~K4bkt-!FnPRJ+9^1aQ*%xZMC=~cIMs&sknEpo3v@esadges5GVLpkE1C1fa#s}DKvB^%2JDZ47052r_`4bTAhu$)r}8_-v1J81;vHDuo%CQ7Er=sprZN_j%(aexd*R_uxao z@cuHFexFxyjD>9>ly7;&(-tQ5A|;>}Dm);8vq*VzjB=#Yl1?S#LH#maXIXcj5GkPM zCyMz8d7@T!mJkprE%5F~CPK|2)+9zG-X5YY5f>c=p}D3`iZK-xmQ~GeHn_A3F}~#J z)wm3o^HUS$f?2qiAl)+6W`S1 s#W(eM@&Bb>39mNezm_*||7(Ec?VitA*5I`B&84G;+H=(!6SCIA2c delta 1864 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 JPDHLkV1gFXczFN- diff --git a/resources/builtin/repo/database.png b/resources/builtin/repo/database.png index 651a2d5bf7c6d9591f22f1053d94cfbb810a929b..4c9ec543fac2ee25c52e25a150eeff7e23c9d596 100644 GIT binary patch literal 10938 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 literal 3428 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 MHsr2%ofkaqU+lM0FaQ7m diff --git a/resources/builtin/repo/gears.png b/resources/builtin/repo/gears.png index 85d8392636b1748428647f20590d21b37ee98c10..11fd42c827d3f9fc0ceee521ae77edfc4440b62b 100644 GIT binary patch literal 9870 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 delta 4205 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�jEtDT_bwFn>H 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 X<-)99u0>e$zcJ*blqBoKK_UMGVeoS- literal 4171 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 diff --git a/resources/builtin/repo/locked.png b/resources/builtin/repo/locked.png index eeef06acd35261a6c96933d850fe9347b6ed3657..80e320c26a1f1581a679dc8a090dec3ccac7f225 100644 GIT binary patch literal 6430 zcmb7p2T)Vnw|4*$B+`_k2!aKXE+Eo776j>FkRU`v=?I9y(5p1zD$=WTk^n(^mv}>! zA`p-+K|(JPLJ94iczxg8|Mz|KX5N{}%vooxv)6C0wf0`WofB!Of0vnwhY17%G3(sZ zHU@!c#Hc^UlfaYLez9I4(78DsZ4Faznw3^58sH&TV85{Xi>nQA`~SPtceZ<_n_Zqx zeyRUTz4WpqWMrnL#bmV3@jlaM(x=P6`e5*8z8z6qOooG-xD;T{fJi;M`RS@! z=GneRI)r1~b7IMEOK+dY9!-+k3yV49RcYd7=lZ0|8vgbxyO`< zZy}VL{;cJBippX0<74?iO!n4+7OzjC$jEl;n3LT3Qw909aiUpt z1)hT+JB;c?tjR^H_zQ!Xr2*F`&(UCm-Qc6_b0JoqI83M6gOuU5ruRCXhKEsFByF)$ zzQ-*JI65}211nQY4y5D-X#{*w6=VJB9#51mNx1iVjpl*Xf$a$(NMG%)Z?!k!Cku{- z8*xg-Pd|Ed))3;+DQ!`w1{GczvWmy>nFbVDGrb$8offz( zhKa|w{jB4p~-#yIr7twySzvmr+ zNqj@UJ(>+(ewz@_*m;rs@Dlh}h1lO$#CES{vA^6w%yqdAPAJC*dR^wru((-bwOLT- zwVgYDj^`EA$K#QIlI!1TYu-|P9K0U&ygNi4@$B)5lmxixy` z6pc#_#nG?)HR7!hi22lEz@TG@gkM`dj3%jdG=U3dwq49wJx>=s#+S(xTu~I0L!O&6 zsJ0%O&;-HeuDMYVYx!{C6RT(dh?MNZ*rMjVY;TT-XeaBXkw6dCrMFph7VKQnE91dv zi^W-8XpUposmFmTvJ$FK{7qsn+NL}}=0c4Q+TC?##tw%W*FE=z>prGgVnPi<1dd)7w%7|SDPs&e= z8SRKJvpuAcUb}CuEKz6g-#EFQNjM zOqxCt%o}toGs)3ky+RYhRUW<(i%-5&)pxJmfyw_z6>)1Qq&;S@pRw>nkw4DJ{=(2v z$96IOJt88x$$}9zv3F%WE?6E9IR!orAx?t!~KgWvgz zemAYTvOzbw=K9%}w2Q(D({Z@<-$Ghn-W$I0`Sq8Ur)yWx9zQX7I()5YuxxDOkpmvx`oUc#z%Hy2m&X#&L;9{^;y3!&VF?4OzRKN1URZs#+@*mc{F3SB+ax=` zJ&)8f#%QZmeu#?t?SngptUD&Gyqx+`h7D1ao{w|ZZ2XvgK3D>H1fsXl-|)#&Y3GEZ z@#asz_Rx4uT0W9dOPGp^jm3!U+zo4XF3a<=kJA0XJEW&uHrb{tJL^7r^1|2di3E_X zi{fSypUcF3!qLD4Gpx%MRgG1w(6(}l_G0O$4J&7rc+|S<{RKNWIbj;TM5pcT*26GW zF+>TuC;5x)ATfkhl}-Pop^uB32eZ=2>hXIXIvFTNuxj_5-E>1~=`@Gy$H`wYR$tou zH+$2B!s0Itxi%ct$?~uDiZwhyOZseLb$Eb{81E>m)vcFYX*zsCn_uCe@`ZH7YWRSm z!{E)>TV(Hlj9e@0hHGrMY*yAwZ_T|rC4_RW-oIvWXt4mX=XEJdYZi_KFt$$m$JZPc zh{KF=mL*mW)&m#H8U@o&=lWwTx|!ldtY;mrpc~?EJ!I$deT#2zEto8~z9y&i%22+7 zZ9hMf#~@9yac|}-eJFi^bH%d(Dd=v=>06!pPLofwg{D;n)8n&`FggP~1gmwLaoO8F ztU|&9VzplgcfQ?-ylNUQDRYG(bd&vt65?~gw5A}YsBU2C&VbVUn?MG{aIbZ&zpT!h zFIG?al3iweX)*UD3y#EJINvO--UDak1!Cl5J-gfn!aE7js;L0CZ;&tgGf>d(b_PXG zyW0K&EQ64&ZyZWL?UWGq%)59)2`ApwvQQ;ugAWLsDlT)c5}r;I(l6G4Y!pfzF_*fP zqq)ZWG8BZ;!Jsr8oa~8S4{Ul(_`nlN|8O+VByN*2B8g|J?z+trW$AoU75lK6PI4I| zSdjpIQC8^>oe1IgC8 zzLio$|J>ODIqB*-pYXz$rlr|5fIGFW;|@efSB09NW_)7!H#&Ehb9V3sm2ww>Rm_CZ zdJlJ@DX^tpJ2u^zEce&)eoDMFvrFslZ&Ku5&7Me%HW2}cLD2&dN^oDXl`LVOP5ISM z1(rmAKrueFs>Q4rk2*EW=hMPk=mVUy&oBv*2^lzVF<@x|_I8F-9<~X>UvkuzoKorVe2Q zvvFZ*Lpd*o(gPYAtR^H7LtyxG!^S0m-Vpd-TdDmSGzp~k&qJfrBSZ)|tI#JaQtjp{Zp6gf`|6O427vZ%M|2ifTKF<|ers1bfm+-5ip z${h3z#@z3N*EKFE*?=~3GPy&|6q`TK(Ja%@1u|?+2%rZH&wGCcWuJj@X7Rsxt+ru9 z^LZcP&t~qvSjCs!#c5Sg;aW4Qe3YG%9?+&Id6DiOU20$INa}Rzyha{A8WYw2ur|Hw zd@?but!Hijv21jEA8p?G9?u@QB`gE)8H$Pe((?WE{pHp8XEP}#7AY7|>U!!<^#BC` z!b>?gnd@kkHM+TAX^{qKc2bXTVVATZxEcSJ)^jUL$@o*?>^-+D=zVr`DU7^m?QB0d zB?Aot%$_r|o4eoe|Ejj9Z!RPnNNjnF35^G_4No;Z?vMsiYXwLnkTRfrx+MNnrlpqE zzx4l)d{5~=^r@DLTnd|Svf_M=X;u`xFURghVa|jKb3Pbxk(!-l4b998%%h|c^;RiI zyTGE=glIZnRD^CkrGJ{x@XC|)`;i$6@%3wSZjg}ZXT$wqA{$_Zvm3P8fLnl=AI4l3 z3pMjSInGUMU>>+s)dYA?c*3{L$eP`~*3_3A+4<&cL6Fw7BG0Vz_9=3Hbry$~$7O0G zePo}lB;79wjbAh&w+j#t;!-99#Nt^eRtBVUCHd-+uaZ-lTbt(w)|b5yOnIrll*fdHiOvT_$xYl*EmG&w{=15G7r%6I5V2XC zl8%r!VTJfF`;7%~?Ht|0#|t|!dx<62cZZX?1+Zs$dxCDPOl*E|Xt2vEV*LxCD#Pxy zcRks->_0X-n!zKtYrRnZ$MQ750(u35o4Xod$D#;^%G_B> z0?3+8TY}~n&MQj|e}Alf_7@irECFI-Y}y1q4eMeAnY48*<&`4Kxydraon_u~o~6_r zSqGG6y%Dx13G26sb_4b|0)D=RquQ#mTu|aW>GQYxP&s5N<9XICb^9jRgVQ!Gf=K_Je5A2E9_pF3w(! zNTHh|cjt;HK!v9{I^iU4kIF{D*_WtX^F>xAV~IN#uC9NJ{_0S_yVHIJOj8ekQ9 zMjlJ96V6q;y=yyQ4M-kxMEsqdN>CwO*-LGPYlR)f4^)iIcqIF1XE zbJ6*L;fOQ%>i96Fb1Bqe%eDRb8OCT+@}yl7vUxAO@mQ&Y!WVfakwA*HSQROwn|Be^ zF^j|8GqZlY6FXw1__M_yUoK>XHC9%YLRs0#vXGPF1jN+XxQdsY#D+u0DBNcS#c;D{ zE5>dk5!P&6>zMFvQM0B8v_r8o`P!#+Nsy~g#Zm6|jgF^bm(WiNk?Z0OrsP`=4UHka zIvONMiBQ2-K@8AWLe1)Ox}7Iq#$B^kfO&=YkI!UsaKzzLupSN3=(#p!AbKR@)wL9d z1~06hOF-Vl(>d{N7E5QI2g5lH1X47Bz8%crH6AK)1r60fQouj#S-u~XWTH~^sS&US zBGA#sgv_!C~T@eGClTYeF*L#L zLNvkrz=B5-@TvZ$wrL_@_~w)XOtJlPd_ou4w1q%s$$HLA1yp2fEdYxMA8*V3E1~NY zbjPq=Gx0Z?_o(VgRdxvPBTuHfXPlM#xD;|;G1c9GzN}S)!e7d%OPY2g-!hLdQNQz%yn>4^k;q{`kk)3XJa+BkHrMzTr^TD+n*nwrgWkcR5q5J@a1%ig4 z%#1!flvB;tZ?WlS=?u~+x?x&tG9`axfB2*PK;1L%#@*s(Y6-(7mE#ALDo(DHWks%2 z&f(s59q~C&>ZVGD-bPZMT!XsspH65oEHf|&9@-d<2B^>_^vCTmQ-5f=z0ZQrG#QbG z;4nC`df7|`m_gdz*t|8^SlztvF$m#v9yCzC%c{zt1}drT{cRGQ;i<}y zg-gBIc}>JL(=8)v8S70Q>kMj)-&bOoW&wP*ke0ffGFbyQ6vFBHqm~bTJNqu}`}gsW zO<7wbCf&Kc;o{nJ)MyH9s5~IcxkMU6MEc6~abEstD%Ct)`JLByiYp)UkB7l0)M?7^ zHi{m!&O=z#*A>-%&vI;iBk69b|J@H*(Fj27C|AkH2*E$o%$( zFRnFCci4YDS98@&1zz8-b;x<;S@-wJMdjUvJ5!BXMywuY>DsHgK^h`PMjGC9yoDFaItn-{s4K{WMK{ z!?XBeixB`)>o;DE9melcC)U_E<}5%@%u`yGejgvI*AdF!fTvjz+(2~%ucc<_d`KYt zi9Rt-*$v=#=40t?i$C=`3FCZI z2(W0G8qtb)PW8=cO^CrIG~utE2YL17(=qx}@pX|**{{p?+%BP&u_7!^&=`Jkxc*a@ zPqg+^;~n?W8UWc=vWm&C{Pas7?ERcm+nk`pZca)r=SyBi1~*%?vFyAnxW60g^@=SMm8^#UuV4tkxMeKx1qN6&Bp-y~>3>JU&M1_aRk z-%=?3e@gTdZM*?004hNtg$22|f?;M0}l4YnLHFR$4YnN);1pgOG CBUY>c literal 2092 zcmZ8h2~-l;8b+;V!m&-CQ%$M8sW;VRG-ac&vKhs1OgZ*V4;9ejA z0MN0qw!8)aXdK&E-)eoju}4K+1^_gD9j<^ct*x!CuBxPS^CI!o0B?ldKh)PR=;H`D z+@T@i_~g{AQn{p7uT4m1UUaa~7)r@=Qf^^QN+voyCO29G{j996 zyoyX{puXab$mZoEW0E#%Z)C!=N06Mrkc^1f{OH6&R8>t06078H~a z8k-sAmE?rf(o95EXAf7QSnOr<5YO;;gVV!f^2jZnlF8Y$?8-`f%dAueg*~rpq%Fvm zQHg~e40dx{cSs~6zqod4S_((v=w1Dxk%%Hx{o>*>j`%Vuy?k6Ula*inFe1B$H8?pf zO-w7Jv~@$`3iv}~Si%e8=tLF1b$DbvEGoC2OrKv+wA1>s^Rb*)!|n7wwOTzrD|-;0 zU0vIzQmLU?=mGvHiP9ODQt~tdRe^2p?c=ezL(D#2%Zr{8bVEF}B=%|X;?nZN@a*x4 z>C%eEM-e$oOUqr%S4nB*b22%-yFVxlmYV%MKBWX%N>r&-v`!Abu01)uoYBp#t)~ix z#Vj_zyLW&}?`v&mE-$a}1Y-nJ2O85fE1iEFoyQ*%t*)+$#-|{0$g)cEs7O*+UYC|r zaW6D8rvQtoX-!Nmt8b!B&CEr{Ba2ZDrO!z$4nO1xLb!)bVFWI zEs@mG(aDj^l{2%ld!eu=@yHBBRSpt&|8Z7RYgbglvpO<80a}`wi%}>S$HbGPW0L8a zxtQc4Vj~Tfi|HR2eo13x1ULlYx9sp$Ak zwkrUzImX7)0{mEGfm*MzNdo{NkON2FDcAUA&1Y;jO7+XIK33E-v;5`)5e7p1%mbhE zS6S8b6v2@9nbi9x#aDcJgTKwBdi#dqT_;CyB0sl&%j&^d?&MZe@^oMd! znB5f|5_4|Sqhsv#Ce^h|<}o%YHq|6s``BInGyU{i_5Qvx(on(Rt5YXV@+b2p5sbLzEH`1LitM172&V?{m>&E6mJU>#!Kddc{c=AIeZ^ z!T0)RoUA*T5h5zk{Z zB3XSX?Jc_ffHUzr`c!1eq4h96f_vQ;barHAVGW2MsM0rR4poP_g|;kL-43PeIc>(SK z+{mE1$|9IR9J+qqk#;zFDC~JtlA}2>$nBI43Z3jFx~qfUb(+x{wb%N+VM;jGq*-Eq z^oLN-l#07=ZvRq?whEK;b!pDN5F{|5Dc|%D6xO(r1l$?`%@3s*|IgF^`phN>$&g&w z&EeB;4xJ(7`ZIm5BBbs9m?MyF8!x%=$);pG3BRk{A*ybH%7+$*x>O4HNMoGAV(0~=OV0?pL@ z`jg^=d7lUu^4j!9&~4pRPw%f&;)TEX_ZtPC@*{{kPhG0lUq7Uc0|)LJ5k=6BVz@;= zd$=}QhzR!nM#i;9Glw3GC^_V_yKw@qTU4LDR!U)N_*tyq?n6%_A(GQ+Gf(410Xv+G zLEB`_mKUW7P15)+4w-I}a*T2?dXiydSczFdp5A5ZE^@zVPX3VdShfO`N8En^(ZF(< diff --git a/resources/builtin/repo/microchip.png b/resources/builtin/repo/microchip.png index 2e5aea3d0e76dfe48d602b28cd9eb115ada86a25..25da5810de071ac7f1eec17585d1be92e570a473 100644 GIT binary patch literal 5924 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 V(r7q5nC$xPP*zk|sFXJk`Y(oS%~}8e literal 1841 zcmZXUdo;C>l^(;FXk7PnEhOlWP#Ps6-yS?>UHI+ zdTo7URi&O0&*u~o;EAP(Wc1^N5@Z>vu#^<@q%^O%zNWsNL}jF8;k4QK?Og*stkF)!5WRb_mp$6uJIo#!Pe{WgrejjGs~*K5BVvovva8D~ zsnR9I=B9RYbF;Rgy^7FQ+t2||EEyi-3#P=Q?(1ac<<=qy45#pPrarm?6T=5>Xr$oqs zN@emH(fr)JOtz#L9_91+ua=fq3wZtc9~h zQmCuubKgSE39uS{v%`%iI_8VIbxvgAq4`B^R;rw;{yLY^Fh#T3y8h@Mn-=sGH2t3C_T2if*l5QPjm*llEqA}C{w9eUio0_j zBKTyy1!-pCZJ!obyyxpDQR{iy9EZfJ#7E6sGkjWa=~P&V?u?;7%640T8S+cUagUQpc9>i5um7DX?$~eP96382 z1|9Y;HTUwe)Ptpyyeh3RqEKVf()7q7f(0tEobIVocf{{J| zT5RtcngH&{J+k(utbbiWffJSTV3s!-6V%`hxpXEZ-%k_6Xm)yKu8(nTB{SI*c1O)&9 diff --git a/resources/builtin/repo/mobile.png b/resources/builtin/repo/mobile.png index b4ec5ee3f17bf73acc177ea2e7b344e99523e284..5c59ad9b078447669e9423ff8c32fcf596aff016 100644 GIT binary patch literal 3879 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 literal 1411 zcmchW{ZkSK7{@WJ3}0y4t5(`8+w$5joo#yAT(*3#ZD!`QEUkI@Rw|{W_!g)IN+MvO zi1-SkA|k$!7J?=wiIhnAZlIVU3OKJeXw_}qZGXVNchBATd4BlZJ@>tD-symomLOXY z006M`_VWB005EaX{B)Bc`LWKw4xb_rouAsC*H!Y ztgJlZi}0ll@i#HiiD*HeI2~Eq(>F9VGh2YCVequ+nOV``2s9N3O~Z-ABmCY${$p`h z_mih$*~FwG7FrN@qhMl6k(*zUS5Vo})yM4;E^3yV+xYCZo^(WsWOy_dn*VHgw2s+D zu3(cZ*>^Bii7@Qld(}LF=x$-vtt_H?QB%))R8!BbVQ`bv3G=ViX}3u&?Y;e?;l3x& z9`{R9vx!YD-HVG$Og1kjDZf`JiAh3JYg#GQoRJrkQrX1PvNj{9RHxHxwK{5TYk4)N zS2)Dt@S8aNv}|Glt`zNDdq z%DNhfq5uFunzyG%V6w?;UgrDI02vemZ7Xff2m6m^k%v&*)@5x2^!tIFoD$!Os~369kG zo9K84c`$B*vZl^wyOeY$-AM0-HOmJtK!f9@QO2ab{>%FW{=ZGjgaGHVFu)9{Ym?B+wo9MvX<(-cEe@(@eEul1 zleTT$?22mU`^+cX-)lcsRkSId9qZOBYdm`6j41VH=uzmd>3zS@6mHm-PCYiq9x$cmL+t&fct)9f1a8h(97zj% zbI0=VIc$~o+Z=!GSAO`=ax)KFIIFTBlN`m0{1QVaIu7kOuXOwdyTA5$c^u2s@^fc@ zyn`tMFSM4GhaWkY<+4eB!1vl3P-z5aGlYC-Q->hY>g{#wQ((}kT*x|uXD(y|B3@vF zfOiXYhV_+j;~w(YdK)wA!FVsjZgRMpt=6VMWVOM3bqu^SC2wAH%*+Vhhy}Z9#|~8U zR%HqU^iqd96OYV|PByhep{xmZM7qt{-}gzsdsE!DLyYV3i4k%mf{PyF_PNxpm3R2M zU8C8;4X&Z}Lsnym-d*BM7@&T<0+?h^v@4?k6K?I0ll&x^ZE#CN_$!c_dyp^$d3vF^ zVaS~x_TbvCv`QW0h!e)Wbxs0w>csBcMiRBGU;nXu-y0jz6#BmeT5?d3Yu!0TAByRH MBEYlZhw!Yw03mzW^#A|> diff --git a/resources/builtin/repo/repo.png b/resources/builtin/repo/repo.png index ca6c80ad7e52cb9c22f4a3065eff3c67ecd9a3e5..b3706f91bb800c41bf21d56402189ff834f95eb4 100644 GIT binary patch literal 3406 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 Z5J`)34G-@uUId3BXp_??9vizx{TGwBJTU+O literal 1590 zcmZuw3p5jY7$1tRQQceA>Gn9c$L$vCoaA(s>Zs(7Qh7D0Ohk7Y$}5CRNrqu;ig~m( zdtzHQd5j^i%`>wxLt-|u&Bm;5H{H3nZk^8k&iTK-@Av)w-|v6^U+Q@mM^ocX#sC1o z6!hCER{%iIa(S#W(0N>N@s7H~)cG^+4qB~tetw~=M@XU7$0j`vi_HnYn_Gx`*2aA) znVN2D>3)d71%+iq#iQxWro}}qmROaV`BXMNgTXTXNGY6^D=VM3AaV$kQ`4b$vYT6b z7_8>S#YL%XCN?SmW_X5RaFj0`;WT&Eak%6PHkMG8g(hX>67Js1zYj0D9gT!%;nT89 z6CM;MJuE_D%0%LESOg*|wfJ!{ZD{yaNL1FXXk=bd#h_>`I5LY>(~|v&ob#B1Cs!BY zD$5ug4E7n7-q6)6q_dit)vXH)3v+X7wR&EuR4puM5|T01>~=J!oItHDc)}>bGb<_^ z<#Oee^o>;ZMxmTzR=?m2#Myad9PxQxVFj_QuC}2A`H0fm(Z_D&<(JTN^Qonj8gAD> zM<;)5Trw$<)i?2iBQxXg7bHF`ii|_OnUTvC%Fb>9EF$yOxTKFSs;O^pZtJ}rgN(ah zpis=co{&B%Wetj6Wul0kJ%Ztp*ALRM5pgI+)r*$)zH}sE{PmP_PIWUJ(c3>%#%Q>c zkSCj-C6(93Cu4*|V>7b~UiUx^yS*6Ce2{^CR8-N<>+ctcRVuY$aO5Rlq|s;wgd;Bp zhO_d>ef^?LG-*zyE`C}m92!kde^OG)nvlp$esG5Y04so?Q}*t6^ky5Gi3WOWSJ2pj zgDKY5S|6Nq=;6etF)MU`lUWxP}F7!vg zVHR_1tiW#qB=pvV_(z^TsP)0Ab=4=IczqNrvc{3N}=&7pY5; z`azVHFoQJw`mc)3PHmC8;E?*1o3ZpQM?UNMB*l`pv<+;P?gnrgOVK!`pCO_U3>T^1n9^ln`@w!c%Hlphgn*3o5Jzzpr zw&r=$F?JeuvIP2mAKVgFElNafgV_@#B*U;P$#yo4moHkKgB=hSW)rxb5rGmCO4@mA zcQ?o&yxMi>H46<6z5ZjDmJm1y-6|o4nwS=O04uKmfhxi|&Eg#)ANtqvj(Z#*iSc*ybmBuC z2STEXhwS?>hgR=#xLDk~TPRIOMp?1=Eeo4G(f~IaPiFlJqxm Cm`s%b diff --git a/resources/builtin/repo/servers.png b/resources/builtin/repo/servers.png index 749c3c1a854ec23b69dfb319b8c70fdbf7b50570..3adb8a2fcdd46641672b1573a7e275a4b1c2e740 100644 GIT binary patch literal 6414 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 delta 1355 zcmV-R1+@B(GWZIR8Gi!+002&-en$WR0l832R7Lmq_m7pRh>oLcbB%O+lyP~IcYc<5 zf0uN8l#7z3wYkRR<>>M8^78WY>g(;p#?P9dv2AsazQWAY)!ToCoN#!N+uh>u@$!$B zsfCN7;^XJ!=IQI~?x3Z$Zg!B_+u($YpploVt+K$q!OP?1=YNiqr>U;IfQOx@t-H3m z#oyuOY;=unb&iIOqU`POnV+!k@9>+UvhMEiZ+DQGp08|lj^5wo_V)IIh@N?XnDg`W zue8Chv%%Kb-Og|x0snXTky1mHl?(gsK@pF5Vk(jE<%+%M}-g$tRiI1f8^!2B#yM2S2o};vUgPMJW zn{s-Rjg+U<*WJy}*Ri$2hmE53_4f7k^@EC^ijbwSwSU6s>FdD5&5V<#;Nj(_tGbq* zuCutqsII)Kuf5;k<(HkW&(YYEnylvM>aw=Oa(R;F=IQP2@8jj@g^Z!MyT;|`>c+{@ zoT9UJe3kX-ob3Pr1A0kBK~#7F?cIZRB?%YD(UVlvh}yPo+qP}n?hm6L&%E$By2!0j zrS9sz9|qwU|EtbQkwF%Jz~4blZS|>%K9W?U=p%JW^{Ep}EV0BAODwU(DyhDqv9U?4 zH2U=)FtE9~rL|40O4mDf zyMYsO7HeW;V^WgB84yM0idEXsc~NA( zSfw>Ch$0KcDy?Br>R4he9uq~Dh*jFsWl?DP3bD$V6otkoVwJISRTOCZIRD&PousfP zdRm+0E7tUNgV$$Z!^R|yP0`)v276X7wrq_qv$h%kdbfRE{9(sVOKByIy9V#xvuFI8 z31;O#K`gPv%8*!pVu>Y|SYE)*-rh;oXK(Zo0002kS6hAdMRrUy#y_#d5=$(x#1c!a z{Ra*n93oa4hYk;qh$Baj?G&qm<0qoSl#{uOwRGkFl^GaPpQKT{Ai6n~w^+3chA+rK zoK8|WbGG<^E>E$}MK+ElDV&d<1~xcHDW@h1T@b6B!%?VzWv5uBZ8;l7&WTmp&WkQt zQ^#6)NvzbdF8j<%A8SiX6uBZ+8CRpwm^!h_xDtgXf1JO;Z%}@V%@(OMcTwFt$lM8nJQKpJEG9%L&YlNMilzu z^5v8ad^I*n;lrNj?#P3@O*BJ449!4Ody>YsucOP&_x7w_)wf26@!uH#DyjMQyXNNR zG2j0mU{+4!enVs9j~-Zfx+7Nh#1cy^vBdIDX{Kswt4~dlK^GTf;%5swwqA~^j$!}+ N002ovPDHLkV1kWb_Z9#E From eb201fce3831d585b1879248eedcf225e295d1c6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 22 Jun 2017 17:24:49 +0200 Subject: [PATCH 280/543] Fix AA in repository images Summary: Updated Files, no more AA issue. Test Plan: look Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18144 --- resources/builtin/repo/commit.png | Bin 6622 -> 10313 bytes resources/builtin/repo/gears.png | Bin 9870 -> 15975 bytes resources/builtin/repo/locked.png | Bin 6430 -> 8956 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/builtin/repo/commit.png b/resources/builtin/repo/commit.png index 9ee209cf4b0548cb6276be21e75d2803e6c99528..e6d251f09538edda71b854ca34e8764e148bcc41 100644 GIT binary patch literal 10313 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 literal 6622 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Ν&aA~i5hbuH>_F$Fa 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 diff --git a/resources/builtin/repo/locked.png b/resources/builtin/repo/locked.png index 80e320c26a1f1581a679dc8a090dec3ccac7f225..34074731a097691b1f3178737637a44c756da279 100644 GIT binary patch literal 8956 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 EKf=AI#{d8T literal 6430 zcmb7p2T)Vnw|4*$B+`_k2!aKXE+Eo776j>FkRU`v=?I9y(5p1zD$=WTk^n(^mv}>! zA`p-+K|(JPLJ94iczxg8|Mz|KX5N{}%vooxv)6C0wf0`WofB!Of0vnwhY17%G3(sZ zHU@!c#Hc^UlfaYLez9I4(78DsZ4Faznw3^58sH&TV85{Xi>nQA`~SPtceZ<_n_Zqx zeyRUTz4WpqWMrnL#bmV3@jlaM(x=P6`e5*8z8z6qOooG-xD;T{fJi;M`RS@! z=GneRI)r1~b7IMEOK+dY9!-+k3yV49RcYd7=lZ0|8vgbxyO`< zZy}VL{;cJBippX0<74?iO!n4+7OzjC$jEl;n3LT3Qw909aiUpt z1)hT+JB;c?tjR^H_zQ!Xr2*F`&(UCm-Qc6_b0JoqI83M6gOuU5ruRCXhKEsFByF)$ zzQ-*JI65}211nQY4y5D-X#{*w6=VJB9#51mNx1iVjpl*Xf$a$(NMG%)Z?!k!Cku{- z8*xg-Pd|Ed))3;+DQ!`w1{GczvWmy>nFbVDGrb$8offz( zhKa|w{jB4p~-#yIr7twySzvmr+ zNqj@UJ(>+(ewz@_*m;rs@Dlh}h1lO$#CES{vA^6w%yqdAPAJC*dR^wru((-bwOLT- zwVgYDj^`EA$K#QIlI!1TYu-|P9K0U&ygNi4@$B)5lmxixy` z6pc#_#nG?)HR7!hi22lEz@TG@gkM`dj3%jdG=U3dwq49wJx>=s#+S(xTu~I0L!O&6 zsJ0%O&;-HeuDMYVYx!{C6RT(dh?MNZ*rMjVY;TT-XeaBXkw6dCrMFph7VKQnE91dv zi^W-8XpUposmFmTvJ$FK{7qsn+NL}}=0c4Q+TC?##tw%W*FE=z>prGgVnPi<1dd)7w%7|SDPs&e= z8SRKJvpuAcUb}CuEKz6g-#EFQNjM zOqxCt%o}toGs)3ky+RYhRUW<(i%-5&)pxJmfyw_z6>)1Qq&;S@pRw>nkw4DJ{=(2v z$96IOJt88x$$}9zv3F%WE?6E9IR!orAx?t!~KgWvgz zemAYTvOzbw=K9%}w2Q(D({Z@<-$Ghn-W$I0`Sq8Ur)yWx9zQX7I()5YuxxDOkpmvx`oUc#z%Hy2m&X#&L;9{^;y3!&VF?4OzRKN1URZs#+@*mc{F3SB+ax=` zJ&)8f#%QZmeu#?t?SngptUD&Gyqx+`h7D1ao{w|ZZ2XvgK3D>H1fsXl-|)#&Y3GEZ z@#asz_Rx4uT0W9dOPGp^jm3!U+zo4XF3a<=kJA0XJEW&uHrb{tJL^7r^1|2di3E_X zi{fSypUcF3!qLD4Gpx%MRgG1w(6(}l_G0O$4J&7rc+|S<{RKNWIbj;TM5pcT*26GW zF+>TuC;5x)ATfkhl}-Pop^uB32eZ=2>hXIXIvFTNuxj_5-E>1~=`@Gy$H`wYR$tou zH+$2B!s0Itxi%ct$?~uDiZwhyOZseLb$Eb{81E>m)vcFYX*zsCn_uCe@`ZH7YWRSm z!{E)>TV(Hlj9e@0hHGrMY*yAwZ_T|rC4_RW-oIvWXt4mX=XEJdYZi_KFt$$m$JZPc zh{KF=mL*mW)&m#H8U@o&=lWwTx|!ldtY;mrpc~?EJ!I$deT#2zEto8~z9y&i%22+7 zZ9hMf#~@9yac|}-eJFi^bH%d(Dd=v=>06!pPLofwg{D;n)8n&`FggP~1gmwLaoO8F ztU|&9VzplgcfQ?-ylNUQDRYG(bd&vt65?~gw5A}YsBU2C&VbVUn?MG{aIbZ&zpT!h zFIG?al3iweX)*UD3y#EJINvO--UDak1!Cl5J-gfn!aE7js;L0CZ;&tgGf>d(b_PXG zyW0K&EQ64&ZyZWL?UWGq%)59)2`ApwvQQ;ugAWLsDlT)c5}r;I(l6G4Y!pfzF_*fP zqq)ZWG8BZ;!Jsr8oa~8S4{Ul(_`nlN|8O+VByN*2B8g|J?z+trW$AoU75lK6PI4I| zSdjpIQC8^>oe1IgC8 zzLio$|J>ODIqB*-pYXz$rlr|5fIGFW;|@efSB09NW_)7!H#&Ehb9V3sm2ww>Rm_CZ zdJlJ@DX^tpJ2u^zEce&)eoDMFvrFslZ&Ku5&7Me%HW2}cLD2&dN^oDXl`LVOP5ISM z1(rmAKrueFs>Q4rk2*EW=hMPk=mVUy&oBv*2^lzVF<@x|_I8F-9<~X>UvkuzoKorVe2Q zvvFZ*Lpd*o(gPYAtR^H7LtyxG!^S0m-Vpd-TdDmSGzp~k&qJfrBSZ)|tI#JaQtjp{Zp6gf`|6O427vZ%M|2ifTKF<|ers1bfm+-5ip z${h3z#@z3N*EKFE*?=~3GPy&|6q`TK(Ja%@1u|?+2%rZH&wGCcWuJj@X7Rsxt+ru9 z^LZcP&t~qvSjCs!#c5Sg;aW4Qe3YG%9?+&Id6DiOU20$INa}Rzyha{A8WYw2ur|Hw zd@?but!Hijv21jEA8p?G9?u@QB`gE)8H$Pe((?WE{pHp8XEP}#7AY7|>U!!<^#BC` z!b>?gnd@kkHM+TAX^{qKc2bXTVVATZxEcSJ)^jUL$@o*?>^-+D=zVr`DU7^m?QB0d zB?Aot%$_r|o4eoe|Ejj9Z!RPnNNjnF35^G_4No;Z?vMsiYXwLnkTRfrx+MNnrlpqE zzx4l)d{5~=^r@DLTnd|Svf_M=X;u`xFURghVa|jKb3Pbxk(!-l4b998%%h|c^;RiI zyTGE=glIZnRD^CkrGJ{x@XC|)`;i$6@%3wSZjg}ZXT$wqA{$_Zvm3P8fLnl=AI4l3 z3pMjSInGUMU>>+s)dYA?c*3{L$eP`~*3_3A+4<&cL6Fw7BG0Vz_9=3Hbry$~$7O0G zePo}lB;79wjbAh&w+j#t;!-99#Nt^eRtBVUCHd-+uaZ-lTbt(w)|b5yOnIrll*fdHiOvT_$xYl*EmG&w{=15G7r%6I5V2XC zl8%r!VTJfF`;7%~?Ht|0#|t|!dx<62cZZX?1+Zs$dxCDPOl*E|Xt2vEV*LxCD#Pxy zcRks->_0X-n!zKtYrRnZ$MQ750(u35o4Xod$D#;^%G_B> z0?3+8TY}~n&MQj|e}Alf_7@irECFI-Y}y1q4eMeAnY48*<&`4Kxydraon_u~o~6_r zSqGG6y%Dx13G26sb_4b|0)D=RquQ#mTu|aW>GQYxP&s5N<9XICb^9jRgVQ!Gf=K_Je5A2E9_pF3w(! zNTHh|cjt;HK!v9{I^iU4kIF{D*_WtX^F>xAV~IN#uC9NJ{_0S_yVHIJOj8ekQ9 zMjlJ96V6q;y=yyQ4M-kxMEsqdN>CwO*-LGPYlR)f4^)iIcqIF1XE zbJ6*L;fOQ%>i96Fb1Bqe%eDRb8OCT+@}yl7vUxAO@mQ&Y!WVfakwA*HSQROwn|Be^ zF^j|8GqZlY6FXw1__M_yUoK>XHC9%YLRs0#vXGPF1jN+XxQdsY#D+u0DBNcS#c;D{ zE5>dk5!P&6>zMFvQM0B8v_r8o`P!#+Nsy~g#Zm6|jgF^bm(WiNk?Z0OrsP`=4UHka zIvONMiBQ2-K@8AWLe1)Ox}7Iq#$B^kfO&=YkI!UsaKzzLupSN3=(#p!AbKR@)wL9d z1~06hOF-Vl(>d{N7E5QI2g5lH1X47Bz8%crH6AK)1r60fQouj#S-u~XWTH~^sS&US zBGA#sgv_!C~T@eGClTYeF*L#L zLNvkrz=B5-@TvZ$wrL_@_~w)XOtJlPd_ou4w1q%s$$HLA1yp2fEdYxMA8*V3E1~NY zbjPq=Gx0Z?_o(VgRdxvPBTuHfXPlM#xD;|;G1c9GzN}S)!e7d%OPY2g-!hLdQNQz%yn>4^k;q{`kk)3XJa+BkHrMzTr^TD+n*nwrgWkcR5q5J@a1%ig4 z%#1!flvB;tZ?WlS=?u~+x?x&tG9`axfB2*PK;1L%#@*s(Y6-(7mE#ALDo(DHWks%2 z&f(s59q~C&>ZVGD-bPZMT!XsspH65oEHf|&9@-d<2B^>_^vCTmQ-5f=z0ZQrG#QbG z;4nC`df7|`m_gdz*t|8^SlztvF$m#v9yCzC%c{zt1}drT{cRGQ;i<}y zg-gBIc}>JL(=8)v8S70Q>kMj)-&bOoW&wP*ke0ffGFbyQ6vFBHqm~bTJNqu}`}gsW zO<7wbCf&Kc;o{nJ)MyH9s5~IcxkMU6MEc6~abEstD%Ct)`JLByiYp)UkB7l0)M?7^ zHi{m!&O=z#*A>-%&vI;iBk69b|J@H*(Fj27C|AkH2*E$o%$( zFRnFCci4YDS98@&1zz8-b;x<;S@-wJMdjUvJ5!BXMywuY>DsHgK^h`PMjGC9yoDFaItn-{s4K{WMK{ z!?XBeixB`)>o;DE9melcC)U_E<}5%@%u`yGejgvI*AdF!fTvjz+(2~%ucc<_d`KYt zi9Rt-*$v=#=40t?i$C=`3FCZI z2(W0G8qtb)PW8=cO^CrIG~utE2YL17(=qx}@pX|**{{p?+%BP&u_7!^&=`Jkxc*a@ zPqg+^;~n?W8UWc=vWm&C{Pas7?ERcm+nk`pZca)r=SyBi1~*%?vFyAnxW60g^@=SMm8^#UuV4tkxMeKx1qN6&Bp-y~>3>JU&M1_aRk z-%=?3e@gTdZM*?004hNtg$22|f?;M0}l4YnLHFR$4YnN);1pgOG CBUY>c From 519bec3e6f365a72cbfa60c8d63ffffda56f6884 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Jun 2017 10:23:47 -0700 Subject: [PATCH 281/543] Make searching by tags work in Phriction Summary: Fixes T12860. Some joins were being dropped because we didn't call `parent::...` Test Plan: - Tagged a document. - Searched for documents with that tag. - Before change: got all documents. - After change: got only tagged documents. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12860 Differential Revision: https://secure.phabricator.com/D18145 --- src/applications/phriction/query/PhrictionDocumentQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index 24e7fbbef5..2736d825b5 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -163,7 +163,7 @@ final class PhrictionDocumentQuery } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { - $joins = array(); + $joins = parent::buildJoinClauseParts($conn); if ($this->getOrderVector()->containsKey('updated')) { $content_dao = new PhrictionContent(); From bd3f441098a02f4e96947574af6c458d45b0d96d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Jun 2017 10:41:10 -0700 Subject: [PATCH 282/543] Modularize "bin/cache" purgers Summary: Ref T12859. This is an older command with a lot of hard-coded flags. Modularize cache purging in a modern way so it can be extended. Test Plan: Ran `bin/cache purge --trace` with various valid and invalid flags. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12859 Differential Revision: https://secure.phabricator.com/D18146 --- src/__phutil_library_map__.php | 10 ++ ...habricatorCacheManagementPurgeWorkflow.php | 142 ++++++------------ .../cache/purger/PhabricatorCachePurger.php | 30 ++++ .../PhabricatorChangesetCachePurger.php | 18 +++ .../purger/PhabricatorGeneralCachePurger.php | 18 +++ .../purger/PhabricatorRemarkupCachePurger.php | 18 +++ .../purger/PhabricatorUserCachePurger.php | 18 +++ 7 files changed, 161 insertions(+), 93 deletions(-) create mode 100644 src/applications/cache/purger/PhabricatorCachePurger.php create mode 100644 src/applications/cache/purger/PhabricatorChangesetCachePurger.php create mode 100644 src/applications/cache/purger/PhabricatorGeneralCachePurger.php create mode 100644 src/applications/cache/purger/PhabricatorRemarkupCachePurger.php create mode 100644 src/applications/cache/purger/PhabricatorUserCachePurger.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f1fd9648cd..d7b197028f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2171,6 +2171,7 @@ phutil_register_library_map(array( 'PhabricatorCacheManagementPurgeWorkflow' => 'applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php', 'PhabricatorCacheManagementWorkflow' => 'applications/cache/management/PhabricatorCacheManagementWorkflow.php', 'PhabricatorCacheMarkupGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php', + 'PhabricatorCachePurger' => 'applications/cache/purger/PhabricatorCachePurger.php', 'PhabricatorCacheSchemaSpec' => 'applications/cache/storage/PhabricatorCacheSchemaSpec.php', 'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php', 'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php', @@ -2320,6 +2321,7 @@ phutil_register_library_map(array( 'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php', 'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php', 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', + 'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php', 'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', 'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php', 'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php', @@ -2930,6 +2932,7 @@ phutil_register_library_map(array( 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php', 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php', 'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php', + 'PhabricatorGeneralCachePurger' => 'applications/cache/purger/PhabricatorGeneralCachePurger.php', 'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php', 'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php', 'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php', @@ -3748,6 +3751,7 @@ phutil_register_library_map(array( 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', 'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php', 'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php', + 'PhabricatorRemarkupCachePurger' => 'applications/cache/purger/PhabricatorRemarkupCachePurger.php', 'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php', 'PhabricatorRemarkupCowsayBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php', 'PhabricatorRemarkupCustomBlockRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomBlockRule.php', @@ -4202,6 +4206,7 @@ phutil_register_library_map(array( 'PhabricatorUserBadgesCacheType' => 'applications/people/cache/PhabricatorUserBadgesCacheType.php', 'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php', 'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php', + 'PhabricatorUserCachePurger' => 'applications/cache/purger/PhabricatorUserCachePurger.php', 'PhabricatorUserCacheType' => 'applications/people/cache/PhabricatorUserCacheType.php', 'PhabricatorUserCardView' => 'applications/people/view/PhabricatorUserCardView.php', 'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php', @@ -7366,6 +7371,7 @@ phutil_register_library_map(array( 'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow', 'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorCacheMarkupGarbageCollector' => 'PhabricatorGarbageCollector', + 'PhabricatorCachePurger' => 'Phobject', 'PhabricatorCacheSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorCacheSpec' => 'Phobject', @@ -7551,6 +7557,7 @@ phutil_register_library_map(array( 'PhabricatorCelerityApplication' => 'PhabricatorApplication', 'PhabricatorCelerityTestCase' => 'PhabricatorTestCase', 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', + 'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger', 'PhabricatorChangesetResponse' => 'AphrontProxyResponse', 'PhabricatorChatLogApplication' => 'PhabricatorApplication', 'PhabricatorChatLogChannel' => array( @@ -8253,6 +8260,7 @@ phutil_register_library_map(array( 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', 'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorGeneralCachePurger' => 'PhabricatorCachePurger', 'PhabricatorGestureUIExample' => 'PhabricatorUIExample', 'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream', 'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider', @@ -9204,6 +9212,7 @@ phutil_register_library_map(array( 'PhabricatorRegistrationProfile' => 'Phobject', 'PhabricatorReleephApplication' => 'PhabricatorApplication', 'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorRemarkupCachePurger' => 'PhabricatorCachePurger', 'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl', 'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule', @@ -9756,6 +9765,7 @@ phutil_register_library_map(array( 'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType', 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserCache' => 'PhabricatorUserDAO', + 'PhabricatorUserCachePurger' => 'PhabricatorCachePurger', 'PhabricatorUserCacheType' => 'Phobject', 'PhabricatorUserCardView' => 'AphrontTagView', 'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php index 91ab0cf83d..d40c0f35e6 100644 --- a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php +++ b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php @@ -6,119 +6,75 @@ final class PhabricatorCacheManagementPurgeWorkflow protected function didConstruct() { $this ->setName('purge') - ->setSynopsis(pht('Drop data from caches. APC-based caches can be '. - 'purged from the web interface.')) + ->setSynopsis(pht('Drop data from readthrough caches.')) ->setArguments( array( array( - 'name' => 'purge-all', - 'help' => pht('Purge all caches.'), + 'name' => 'all', + 'help' => pht('Purge all caches.'), ), array( - 'name' => 'purge-remarkup', - 'help' => pht('Purge the remarkup cache.'), - ), - array( - 'name' => 'purge-changeset', - 'help' => pht('Purge the Differential changeset cache.'), - ), - array( - 'name' => 'purge-general', - 'help' => pht('Purge the general cache.'), - ), - array( - 'name' => 'purge-user', - 'help' => pht('Purge the user cache.'), + 'name' => 'caches', + 'param' => 'keys', + 'help' => pht('Purge a specific set of caches.'), ), )); } public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); + $all_purgers = PhabricatorCachePurger::getAllPurgers(); - $purge_all = $args->getArg('purge-all'); - - $purge = array( - 'remarkup' => $purge_all || $args->getArg('purge-remarkup'), - 'changeset' => $purge_all || $args->getArg('purge-changeset'), - 'general' => $purge_all || $args->getArg('purge-general'), - 'user' => $purge_all || $args->getArg('purge-user'), - ); - - if (!array_filter($purge)) { - $list = array(); - foreach ($purge as $key => $ignored) { - $list[] = "'--purge-".$key."'"; - } + $is_all = $args->getArg('all'); + $key_list = $args->getArg('caches'); + if ($is_all && strlen($key_list)) { throw new PhutilArgumentUsageException( pht( - "Specify which cache or caches to purge, or use '%s'. Available ". - "caches are: %s. Use '%s' for more information.", - '--purge-all', - implode(', ', $list), - '--help')); + 'Specify either "--all" or "--caches", not both.')); + } else if (!$is_all && !strlen($key_list)) { + throw new PhutilArgumentUsageException( + pht( + 'Select caches to purge with "--all" or "--caches". Available '. + 'caches are: %s.', + implode(', ', array_keys($all_purgers)))); } - if ($purge['remarkup']) { - $console->writeOut(pht('Purging remarkup cache...')); - $this->purgeRemarkupCache(); - $console->writeOut("%s\n", pht('Done.')); + if ($is_all) { + $purgers = $all_purgers; + } else { + $key_list = preg_split('/[\s,]+/', $key_list); + $purgers = array(); + foreach ($key_list as $key) { + if (isset($all_purgers[$key])) { + $purgers[$key] = $all_purgers[$key]; + } else { + throw new PhutilArgumentUsageException( + pht( + 'Cache purger "%s" is not recognized. Available caches '. + 'are: %s.', + $key, + implode(', ', array_keys($all_purgers)))); + } + } + if (!$purgers) { + throw new PhutilArgumentUsageException( + pht( + 'When using "--caches", you must select at least one valid '. + 'cache to purge.')); + } } - if ($purge['changeset']) { - $console->writeOut(pht('Purging changeset cache...')); - $this->purgeChangesetCache(); - $console->writeOut("%s\n", pht('Done.')); + foreach ($purgers as $key => $purger) { + echo tsprintf( + "%s\n", + pht( + 'Purging "%s" cache...', + $key)); + + $purger->purgeCache(); } - if ($purge['general']) { - $console->writeOut(pht('Purging general cache...')); - $this->purgeGeneralCache(); - $console->writeOut("%s\n", pht('Done.')); - } - - if ($purge['user']) { - $console->writeOut(pht('Purging user cache...')); - $this->purgeUserCache(); - $console->writeOut("%s\n", pht('Done.')); - } - } - - private function purgeRemarkupCache() { - $conn_w = id(new PhabricatorMarkupCache())->establishConnection('w'); - - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - id(new PhabricatorMarkupCache())->getTableName()); - } - - private function purgeChangesetCache() { - $conn_w = id(new DifferentialChangeset())->establishConnection('w'); - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - DifferentialChangeset::TABLE_CACHE); - } - - private function purgeGeneralCache() { - $conn_w = id(new PhabricatorMarkupCache())->establishConnection('w'); - - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - 'cache_general'); - } - - private function purgeUserCache() { - $table = new PhabricatorUserCache(); - $conn_w = $table->establishConnection('w'); - - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - $table->getTableName()); + return 0; } } diff --git a/src/applications/cache/purger/PhabricatorCachePurger.php b/src/applications/cache/purger/PhabricatorCachePurger.php new file mode 100644 index 0000000000..0115b45d7e --- /dev/null +++ b/src/applications/cache/purger/PhabricatorCachePurger.php @@ -0,0 +1,30 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function getPurgerKey() { + return $this->getPhobjectClassConstant('PURGERKEY'); + } + + final public static function getAllPurgers() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getPurgerKey') + ->execute(); + } + +} diff --git a/src/applications/cache/purger/PhabricatorChangesetCachePurger.php b/src/applications/cache/purger/PhabricatorChangesetCachePurger.php new file mode 100644 index 0000000000..fd6ff9940a --- /dev/null +++ b/src/applications/cache/purger/PhabricatorChangesetCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + DifferentialChangeset::TABLE_CACHE); + } + +} diff --git a/src/applications/cache/purger/PhabricatorGeneralCachePurger.php b/src/applications/cache/purger/PhabricatorGeneralCachePurger.php new file mode 100644 index 0000000000..f2dea1e1e5 --- /dev/null +++ b/src/applications/cache/purger/PhabricatorGeneralCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + 'cache_general'); + } + +} diff --git a/src/applications/cache/purger/PhabricatorRemarkupCachePurger.php b/src/applications/cache/purger/PhabricatorRemarkupCachePurger.php new file mode 100644 index 0000000000..2c416fff36 --- /dev/null +++ b/src/applications/cache/purger/PhabricatorRemarkupCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + $table->getTableName()); + } + +} diff --git a/src/applications/cache/purger/PhabricatorUserCachePurger.php b/src/applications/cache/purger/PhabricatorUserCachePurger.php new file mode 100644 index 0000000000..64f631c96b --- /dev/null +++ b/src/applications/cache/purger/PhabricatorUserCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + $table->getTableName()); + } + +} From 224c4692ee0e1d11950c18b55d43d82feeed1905 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Jun 2017 10:52:40 -0700 Subject: [PATCH 283/543] Add a cache purger for builtin files Summary: Fixes T12859. Test Plan: - Loaded Diffusion builtin icons before recent updates, saw cached builtins. - Ran `bin/cache purge --caches builtin-file`. - Reloaded, saw new files. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12859 Differential Revision: https://secure.phabricator.com/D18147 --- src/__phutil_library_map__.php | 2 ++ ...habricatorCacheManagementPurgeWorkflow.php | 4 ++++ .../PhabricatorBuiltinFileCachePurger.php | 22 +++++++++++++++++++ .../files/query/PhabricatorFileQuery.php | 18 +++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d7b197028f..0384c4b5f8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2162,6 +2162,7 @@ phutil_register_library_map(array( 'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', + 'PhabricatorBuiltinFileCachePurger' => 'applications/cache/purger/PhabricatorBuiltinFileCachePurger.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', @@ -7362,6 +7363,7 @@ phutil_register_library_map(array( 'PhabricatorBoolEditField' => 'PhabricatorEditField', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', + 'PhabricatorBuiltinFileCachePurger' => 'PhabricatorCachePurger', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', diff --git a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php index d40c0f35e6..cfe42c918b 100644 --- a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php +++ b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php @@ -64,7 +64,11 @@ final class PhabricatorCacheManagementPurgeWorkflow } } + $viewer = $this->getViewer(); + foreach ($purgers as $key => $purger) { + $purger->setViewer($viewer); + echo tsprintf( "%s\n", pht( diff --git a/src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php b/src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php new file mode 100644 index 0000000000..a4bc682c02 --- /dev/null +++ b/src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php @@ -0,0 +1,22 @@ +getViewer(); + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withIsBuiltin(true) + ->execute(); + + $engine = new PhabricatorDestructionEngine(); + foreach ($files as $file) { + $engine->destroyObject($file); + } + } + +} diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index a2de5ca6ab..071ee03991 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -18,6 +18,7 @@ final class PhabricatorFileQuery private $isDeleted; private $needTransforms; private $builtinKeys; + private $isBuiltin; public function withIDs(array $ids) { $this->ids = $ids; @@ -54,6 +55,11 @@ final class PhabricatorFileQuery return $this; } + public function withIsBuiltin($is_builtin) { + $this->isBuiltin = $is_builtin; + return $this; + } + /** * Select files which are transformations of some other file. For example, * you can use this query to find previously generated thumbnails of an image @@ -416,6 +422,18 @@ final class PhabricatorFileQuery $this->builtinKeys); } + if ($this->isBuiltin !== null) { + if ($this->isBuiltin) { + $where[] = qsprintf( + $conn, + 'builtinKey IS NOT NULL'); + } else { + $where[] = qsprintf( + $conn, + 'builtinKey IS NULL'); + } + } + return $where; } From 58df1b7d3be21a12ccb93cea537ff8a92a6fd703 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 22 Jun 2017 21:10:48 +0200 Subject: [PATCH 284/543] Add a top level tab navigation option to PHUITwoColumnView Summary: Builds out a responsive tab bar system for PHUITwoColumnView pages Test Plan: Tested at mobile, tablet, and desktop breakpoints {F5012429} {F5012430} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18148 --- resources/celerity/map.php | 10 +-- src/view/phui/PHUITwoColumnView.php | 18 +++++ webroot/rsrc/css/phui/phui-list.css | 71 +++++++++++++++++++ .../rsrc/css/phui/phui-two-column-view.css | 12 ++++ 4 files changed, 106 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3e80c99baa..0619fc34f8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '6d40b714', + 'core.pkg.css' => '37dd219b', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4ec4a37a', @@ -166,7 +166,7 @@ return array( 'rsrc/css/phui/phui-info-view.css' => '6e217679', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', - 'rsrc/css/phui/phui-list.css' => '12eb8ce6', + 'rsrc/css/phui/phui-list.css' => 'dcafb463', 'rsrc/css/phui/phui-object-box.css' => '9cff003c', 'rsrc/css/phui/phui-pager.css' => 'edcbc226', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -177,7 +177,7 @@ return array( 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '93b084cf', 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', - 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', + 'rsrc/css/phui/phui-two-column-view.css' => '5b8cd553', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', @@ -854,7 +854,7 @@ return array( 'phui-inline-comment-view-css' => 'ffd1a542', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', - 'phui-list-view-css' => '12eb8ce6', + 'phui-list-view-css' => 'dcafb463', 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', @@ -872,7 +872,7 @@ return array( 'phui-tag-view-css' => '93b084cf', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => '313c7f22', - 'phui-two-column-view-css' => 'ce9fa0b7', + 'phui-two-column-view-css' => '5b8cd553', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 1c56886f7a..d0443280b6 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -10,6 +10,7 @@ final class PHUITwoColumnView extends AphrontTagView { private $header; private $subheader; private $footer; + private $tabs; private $propertySection = array(); private $curtain; @@ -42,6 +43,12 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setTabs(PHUIListView $tabs) { + $tabs->setType(PHUIListView::TABBAR_LIST); + $this->tabs = $tabs; + return $this; + } + public function setFooter($footer) { $this->footer = $footer; return $this; @@ -91,6 +98,10 @@ final class PHUITwoColumnView extends AphrontTagView { $classes[] = 'phui-two-column-fluid'; } + if ($this->tabs) { + $classes[] = 'with-tabs'; + } + if ($this->subheader) { $classes[] = 'with-subheader'; } @@ -124,6 +135,12 @@ final class PHUITwoColumnView extends AphrontTagView { 'phui-two-column-header', $this->header); } + $tabs = null; + if ($this->tabs) { + $tabs = phutil_tag_div( + 'phui-two-column-tabs', $this->tabs); + } + $subheader = null; if ($this->subheader) { $subheader = phutil_tag_div( @@ -137,6 +154,7 @@ final class PHUITwoColumnView extends AphrontTagView { ), array( $header, + $tabs, $subheader, $table, $footer, diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index e571e228d8..5df6f4a210 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -144,6 +144,77 @@ border: none; } +/* - Two Column View, Responsive Navigations ----------------------------------- + + Sets a two column page with a responsive, top navbar + +*/ + +.phui-list-view.phui-list-tabbar { + list-style: none; + overflow: hidden; +} + +.phui-list-view.phui-list-tabbar > li { + list-style: none; + float: left; + display: block; +} + +.phui-list-view.phui-list-tabbar > li > * { + display: block; +} + +.phui-list-tabbar .phui-list-item-href { + color: {$bluetext}; + padding: 8px 24px; + line-height: 24px; + font-weight: bold; + font-size: {$biggerfontsize}; + border-top: 4px solid #fff; +} + +.phui-list-tabbar .phui-list-item-selected .phui-list-item-href { + color: {$sky}; + border-bottom: 4px solid {$sky}; +} + +.phui-list-tabbar .phui-list-item-selected .phui-list-item-href + .phui-icon-view { + color: {$sky}; +} + +.device-desktop .phui-list-tabbar .phui-list-item-href:hover { + color: {$sky}; + border-bottom: 4px solid $fff; + text-decoration: none; +} + +.phui-list-tabbar .phui-list-item-icon { + height: 20px; + width: 20px; + display: none; + font-size: 20px; + text-align: center; +} + +.device-phone .phui-list-tabbar .phui-list-item-icon { + display: inline-block; +} + +.device-phone .phui-list-tabbar .phui-list-item-name { + display: none; +} + +.device-phone .phui-list-tabbar .phui-list-item-href { + padding: 8px 16px; +} + +.device-phone .phui-list-view.phui-list-navbar > li { + float: none; + border: none; +} + /* - Status Colors ------------------------------------------------------------- Colors for navbars diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index d910cf71a0..7728980268 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -12,6 +12,7 @@ margin-bottom: 12px; } +.phui-two-column-view.with-tabs .phui-two-column-header, .phui-two-column-view.with-subheader .phui-two-column-header { margin-bottom: 0; } @@ -168,6 +169,17 @@ margin: 0 8px; } +.phui-two-column-tabs { + padding: 0 32px; + margin-bottom: 32px; + background: #fff; + box-shadow: 0 1px 3px 0 rgba(0,0,0,0.2); +} + +.device-phone .phui-two-column-tabs { + padding: 0 12px; +} + /* Info View */ .phui-two-column-view .phui-info-view { From 219ae8b6c950df6cc4af91efbe9696b001ddfdbd Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Jun 2017 07:51:32 -0700 Subject: [PATCH 285/543] Remove old "Landing Strategy" code Summary: Fixes T12869. This is a very old, pre-Drydock chunk of code from D7486 and some followups. It does three things: - "Land to Hosted Git": Obsoleted by Drydock, has been commented out in HEAD for a very long time with no complaints. Disabled by D8719 in 2014. - "Land to Hosted Mercurial": Could be obsoleted by Drydock with a fairly small amount of work, but currently has no replacement. Unclear if this sees any real use. Not actually disabled at HEAD. - "Land to GitHub": Use GitHub OAuth credentials to land to GitHub. This is sort of theoretically useful and has no analog today. Disabled by D13022 in 2015. This stuff was largely disabled a long time ago and we haven't seen users hitting issues with it. This could all be moved to an extension today if anyone still relies on it. Test Plan: Grepped for removed classes, browsed Differential. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12869 Differential Revision: https://secure.phabricator.com/D18150 --- src/__phutil_library_map__.php | 12 -- .../PhabricatorDifferentialApplication.php | 8 - .../DifferentialRevisionLandController.php | 157 --------------- .../DifferentialGitHubLandingStrategy.php | 186 ------------------ .../DifferentialHostedGitLandingStrategy.php | 127 ------------ ...erentialHostedMercurialLandingStrategy.php | 106 ---------- ...erentialLandingActionMenuEventListener.php | 81 -------- .../landing/DifferentialLandingStrategy.php | 87 -------- 8 files changed, 764 deletions(-) delete mode 100644 src/applications/differential/controller/DifferentialRevisionLandController.php delete mode 100644 src/applications/differential/landing/DifferentialGitHubLandingStrategy.php delete mode 100644 src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php delete mode 100644 src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php delete mode 100644 src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php delete mode 100644 src/applications/differential/landing/DifferentialLandingStrategy.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0384c4b5f8..9deeb338f6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -453,13 +453,10 @@ phutil_register_library_map(array( 'DifferentialGetRevisionCommentsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionCommentsConduitAPIMethod.php', 'DifferentialGetRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php', 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', - 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 'DifferentialGitSVNIDCommitMessageField' => 'applications/differential/field/DifferentialGitSVNIDCommitMessageField.php', 'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php', 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', - 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', - 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', 'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php', 'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php', 'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php', @@ -472,8 +469,6 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php', 'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', - 'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php', - 'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php', 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', @@ -548,7 +543,6 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', 'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', - 'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', 'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php', @@ -5411,13 +5405,10 @@ phutil_register_library_map(array( 'DifferentialGetRevisionCommentsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetWorkingCopy' => 'Phobject', - 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialGitSVNIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialHarbormasterField' => 'DifferentialCustomField', 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', - 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', - 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'DifferentialHunk' => array( 'DifferentialDAO', @@ -5436,8 +5427,6 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', - 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener', - 'DifferentialLandingStrategy' => 'Phobject', 'DifferentialLegacyHunk' => 'DifferentialHunk', 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialHarbormasterField', @@ -5529,7 +5518,6 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialRevisionInlinesController' => 'DifferentialController', - 'DifferentialRevisionLandController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', 'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver', diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index 403e60c8a9..e259c2f112 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -41,12 +41,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { return "\xE2\x9A\x99"; } - public function getEventListeners() { - return array( - new DifferentialLandingActionMenuEventListener(), - ); - } - public function getOverview() { return pht( 'Differential is a **code review application** which allows '. @@ -69,8 +63,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { => 'DifferentialRevisionEditController', $this->getEditRoutePattern('attach/(?P[^/]+)/to/') => 'DifferentialRevisionEditController', - 'land/(?:(?P[1-9]\d*))/(?P[^/]+)/' - => 'DifferentialRevisionLandController', 'closedetails/(?P[^/]+)/' => 'DifferentialRevisionCloseDetailsController', 'update/(?P[1-9]\d*)/' diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php deleted file mode 100644 index 0e222370df..0000000000 --- a/src/applications/differential/controller/DifferentialRevisionLandController.php +++ /dev/null @@ -1,157 +0,0 @@ -getViewer(); - $revision_id = $request->getURIData('id'); - $strategy_class = $request->getURIData('strategy'); - - $revision = id(new DifferentialRevisionQuery()) - ->withIDs(array($revision_id)) - ->setViewer($viewer) - ->executeOne(); - if (!$revision) { - return new Aphront404Response(); - } - - if (is_subclass_of($strategy_class, 'DifferentialLandingStrategy')) { - $this->pushStrategy = newv($strategy_class, array()); - } else { - throw new Exception( - pht( - "Strategy type must be a valid class name and must subclass ". - "%s. '%s' is not a subclass of %s", - 'DifferentialLandingStrategy', - $strategy_class, - 'DifferentialLandingStrategy')); - } - - if ($request->isDialogFormPost()) { - $response = null; - $text = ''; - try { - $response = $this->attemptLand($revision, $request); - $title = pht('Success!'); - $text = pht('Revision was successfully landed.'); - } catch (Exception $ex) { - $title = pht('Failed to land revision'); - if ($ex instanceof PhutilProxyException) { - $text = hsprintf( - '%s:

%s
', - $ex->getMessage(), - $ex->getPreviousException()->getMessage()); - } else { - $text = phutil_tag('pre', array(), $ex->getMessage()); - } - $text = id(new PHUIInfoView()) - ->appendChild($text); - } - - if ($response instanceof AphrontDialogView) { - $dialog = $response; - } else { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle($title) - ->appendChild(phutil_tag('p', array(), $text)) - ->addCancelButton('/D'.$revision_id, pht('Done')); - } - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - $is_disabled = $this->pushStrategy->isActionDisabled( - $viewer, - $revision, - $revision->getRepository()); - if ($is_disabled) { - if (is_string($is_disabled)) { - $explain = $is_disabled; - } else { - $explain = pht('This action is not currently enabled.'); - } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht("Can't land revision")) - ->appendChild($explain) - ->addCancelButton('/D'.$revision_id); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - - $prompt = hsprintf('%s

%s', - pht( - 'This will squash and rebase revision %s, and push it to '. - 'the default / master branch.', - $revision_id), - pht('It is an experimental feature and may not work.')); - - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Land Revision %s?', $revision_id)) - ->appendChild($prompt) - ->setSubmitURI($request->getRequestURI()) - ->addSubmitButton(pht('Land it!')) - ->addCancelButton('/D'.$revision_id); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - private function attemptLand($revision, $request) { - $status = $revision->getStatus(); - if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { - throw new Exception(pht('Only Accepted revisions can be landed.')); - } - - $repository = $revision->getRepository(); - - if ($repository === null) { - throw new Exception(pht('Revision is not attached to a repository.')); - } - - $can_push = PhabricatorPolicyFilter::hasCapability( - $request->getUser(), - $repository, - DiffusionPushCapability::CAPABILITY); - - if (!$can_push) { - throw new Exception( - pht('You do not have permission to push to this repository.')); - } - - $lock = $this->lockRepository($repository); - - try { - $response = $this->pushStrategy->processLandRequest( - $request, - $revision, - $repository); - } catch (Exception $e) { - $lock->unlock(); - throw $e; - } - - $lock->unlock(); - - $looksoon = new ConduitCall( - 'diffusion.looksoon', - array( - 'repositories' => array($repository->getPHID()), - )); - $looksoon->setUser($request->getUser()); - $looksoon->execute(); - - return $response; - } - - private function lockRepository($repository) { - $lock_name = __CLASS__.':'.($repository->getPHID()); - $lock = PhabricatorGlobalLock::newLock($lock_name); - $lock->lock(); - return $lock; - } - -} diff --git a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php b/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php deleted file mode 100644 index b4ff727cbe..0000000000 --- a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php +++ /dev/null @@ -1,186 +0,0 @@ -getUser(); - $this->init($viewer, $repository); - - $workspace = $this->getGitWorkspace($repository); - - try { - id(new DifferentialHostedGitLandingStrategy()) - ->commitRevisionToWorkspace($revision, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException(pht('Failed to commit patch.'), $e); - } - - try { - $this->pushWorkspaceRepository($repository, $workspace); - } catch (Exception $e) { - // If it's a permission problem, we know more than git. - $dialog = $this->verifyRemotePermissions($viewer, $revision, $repository); - if ($dialog) { - return $dialog; - } - - // Else, throw what git said. - throw new PhutilProxyException( - pht('Failed to push changes upstream.'), - $e); - } - } - - /** - * Returns PhabricatorActionView or an array of PhabricatorActionView or null. - */ - public function createMenuItem( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - // TODO: This temporarily disables this action, because it doesn't work - // and is confusing to users. If you want to use it, comment out this line - // for now and we'll provide real support eventually. - return; - - $vcs = $repository->getVersionControlSystem(); - if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { - return; - } - - if ($repository->isHosted()) { - return; - } - - try { - // These throw when failing. - $this->init($viewer, $repository); - $this->findGitHubRepo($repository); - } catch (Exception $e) { - return; - } - - return $this->createActionView($revision, pht('Land to GitHub')) - ->setIcon('fa-cloud-upload'); - } - - public function pushWorkspaceRepository( - PhabricatorRepository $repository, - ArcanistRepositoryAPI $workspace) { - - $token = $this->getAccessToken(); - - $github_repo = $this->findGitHubRepo($repository); - - $remote = urisprintf( - 'https://%s:x-oauth-basic@%s/%s.git', - $token, - $this->provider->getProviderDomain(), - $github_repo); - - $workspace->execxLocal( - 'push %P HEAD:master', - new PhutilOpaqueEnvelope($remote)); - } - - private function init($viewer, $repository) { - $repo_uri = $repository->getRemoteURIObject(); - $repo_domain = $repo_uri->getDomain(); - - $this->account = id(new PhabricatorExternalAccountQuery()) - ->setViewer($viewer) - ->withUserPHIDs(array($viewer->getPHID())) - ->withAccountTypes(array('github')) - ->withAccountDomains(array($repo_domain)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$this->account) { - throw new Exception( - pht('No matching GitHub account found for %s.', $repo_domain)); - } - - $this->provider = PhabricatorAuthProvider::getEnabledProviderByKey( - $this->account->getProviderKey()); - if (!$this->provider) { - throw new Exception( - pht('GitHub provider for %s is not enabled.', $repo_domain)); - } - } - - private function findGitHubRepo(PhabricatorRepository $repository) { - $repo_uri = $repository->getRemoteURIObject(); - - $repo_path = $repo_uri->getPath(); - - if (substr($repo_path, -4) == '.git') { - $repo_path = substr($repo_path, 0, -4); - } - $repo_path = ltrim($repo_path, '/'); - - return $repo_path; - } - - private function getAccessToken() { - return $this->provider->getOAuthAccessToken($this->account); - } - - private function verifyRemotePermissions($viewer, $revision, $repository) { - $github_user = $this->account->getUsername(); - $github_repo = $this->findGitHubRepo($repository); - - $uri = urisprintf( - 'https://api.github.com/repos/%s/collaborators/%s', - $github_repo, - $github_user); - - $uri = new PhutilURI($uri); - $uri->setQueryParam('access_token', $this->getAccessToken()); - list($status, $body, $headers) = id(new HTTPSFuture($uri))->resolve(); - - // Likely status codes: - // 204 No Content: Has permissions. Token might be too weak. - // 404 Not Found: Not a collaborator. - // 401 Unauthorized: Token is bad/revoked. - - $no_permission = ($status->getStatusCode() == 404); - - if ($no_permission) { - throw new Exception( - pht( - "You don't have permission to push to this repository. ". - "Push permissions for this repository are managed on GitHub.")); - } - - $scopes = BaseHTTPFuture::getHeader($headers, 'X-OAuth-Scopes'); - if (strpos($scopes, 'public_repo') === false) { - $provider_key = $this->provider->getProviderKey(); - $refresh_token_uri = new PhutilURI("/auth/refresh/{$provider_key}/"); - $refresh_token_uri->setQueryParam('scope', 'public_repo'); - - return id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Stronger token needed')) - ->appendChild(pht( - 'In order to complete this action, you need a '. - 'stronger GitHub token.')) - ->setSubmitURI($refresh_token_uri) - ->addCancelButton('/D'.$revision->getId()) - ->setDisableWorkflowOnSubmit(true) - ->addSubmitButton(pht('Refresh Account Link')); - } - } -} diff --git a/src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php b/src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php deleted file mode 100644 index cc0cf97998..0000000000 --- a/src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php +++ /dev/null @@ -1,127 +0,0 @@ -getUser(); - $workspace = $this->getGitWorkspace($repository); - - try { - $this->commitRevisionToWorkspace($revision, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to commit patch.'), - $e); - } - - try { - $this->pushWorkspaceRepository($repository, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to push changes upstream.'), - $e); - } - } - - public function commitRevisionToWorkspace( - DifferentialRevision $revision, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $diff_id = $revision->loadActiveDiff()->getID(); - - $call = new ConduitCall( - 'differential.getrawdiff', - array( - 'diffID' => $diff_id, - )); - - $call->setUser($user); - $raw_diff = $call->execute(); - - $missing_binary = - "\nindex " - ."0000000000000000000000000000000000000000.." - ."0000000000000000000000000000000000000000\n"; - if (strpos($raw_diff, $missing_binary) !== false) { - throw new Exception(pht('Patch is missing content for a binary file')); - } - - $future = $workspace->execFutureLocal('apply --index -'); - $future->write($raw_diff); - $future->resolvex(); - - $workspace->reloadWorkingCopy(); - - $call = new ConduitCall( - 'differential.getcommitmessage', - array( - 'revision_id' => $revision->getID(), - )); - - $call->setUser($user); - $message = $call->execute(); - - $author = id(new PhabricatorUser())->loadOneWhere( - 'phid = %s', - $revision->getAuthorPHID()); - - $author_string = sprintf( - '%s <%s>', - $author->getRealName(), - $author->loadPrimaryEmailAddress()); - $author_date = $revision->getDateCreated(); - - $workspace->execxLocal( - '-c user.name=%s -c user.email=%s '. - 'commit --date=%s --author=%s '. - '--message=%s', - // -c will set the 'committer' - $user->getRealName(), - $user->loadPrimaryEmailAddress(), - $author_date, - $author_string, - $message); - } - - public function pushWorkspaceRepository( - PhabricatorRepository $repository, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $workspace->execxLocal('push origin HEAD:master'); - } - - public function createMenuItem( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - $vcs = $repository->getVersionControlSystem(); - if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { - return; - } - - if (!$repository->isHosted()) { - return; - } - - if (!$repository->isWorkingCopyBare()) { - return; - } - - // TODO: This temporarily disables this action, because it doesn't work - // and is confusing to users. If you want to use it, comment out this line - // for now and we'll provide real support eventually. - return; - - return $this->createActionView( - $revision, - pht('Land to Hosted Repository')); - } -} diff --git a/src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php b/src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php deleted file mode 100644 index 38f7160958..0000000000 --- a/src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php +++ /dev/null @@ -1,106 +0,0 @@ -getUser(); - - $workspace = $this->getMercurialWorkspace($repository); - - try { - $this->commitRevisionToWorkspace($revision, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException(pht('Failed to commit patch.'), $e); - } - - try { - $this->pushWorkspaceRepository($repository, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to push changes upstream.'), - $e); - } - } - - public function commitRevisionToWorkspace( - DifferentialRevision $revision, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $diff_id = $revision->loadActiveDiff()->getID(); - - $call = new ConduitCall( - 'differential.getrawdiff', - array( - 'diffID' => $diff_id, - )); - - $call->setUser($user); - $raw_diff = $call->execute(); - - $future = $workspace->execFutureLocal('patch --no-commit -'); - $future->write($raw_diff); - $future->resolvex(); - - $workspace->reloadWorkingCopy(); - - $call = new ConduitCall( - 'differential.getcommitmessage', - array( - 'revision_id' => $revision->getID(), - )); - - $call->setUser($user); - $message = $call->execute(); - - $author = id(new PhabricatorUser())->loadOneWhere( - 'phid = %s', - $revision->getAuthorPHID()); - - $author_string = sprintf( - '%s <%s>', - $author->getRealName(), - $author->loadPrimaryEmailAddress()); - $author_date = $revision->getDateCreated(); - - $workspace->execxLocal( - 'commit --date=%s --user=%s '. - '--message=%s', - $author_date.' 0', - $author_string, - $message); - } - - - public function pushWorkspaceRepository( - PhabricatorRepository $repository, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $workspace->execxLocal('push -b default'); - } - - public function createMenuItem( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - $vcs = $repository->getVersionControlSystem(); - if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL) { - return; - } - - if (!$repository->isHosted()) { - return; - } - - return $this->createActionView( - $revision, - pht('Land to Hosted Repository')); - } -} diff --git a/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php b/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php deleted file mode 100644 index 2cf3eaa4e5..0000000000 --- a/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php +++ /dev/null @@ -1,81 +0,0 @@ -listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS); - } - - public function handleEvent(PhutilEvent $event) { - switch ($event->getType()) { - case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: - $this->handleActionsEvent($event); - break; - } - } - - private function handleActionsEvent(PhutilEvent $event) { - $object = $event->getValue('object'); - if ($object instanceof DifferentialRevision) { - $this->renderRevisionAction($event); - } - } - - private function renderRevisionAction(PhutilEvent $event) { - $viewer = $event->getUser(); - - if (!$this->canUseApplication($viewer)) { - return null; - } - - $revision = $event->getValue('object'); - - $repository = $revision->getRepository(); - if ($repository === null) { - return null; - } - - if ($repository->canPerformAutomation()) { - $revision_id = $revision->getID(); - - $op = new DrydockLandRepositoryOperation(); - $barrier = $op->getBarrierToLanding($viewer, $revision); - - if ($barrier) { - $can_land = false; - } else { - $can_land = true; - } - - $action = id(new PhabricatorActionView()) - ->setName(pht('Land Revision')) - ->setIcon('fa-fighter-jet') - ->setHref("/differential/revision/operation/{$revision_id}/") - ->setWorkflow(true) - ->setDisabled(!$can_land); - - - $this->addActionMenuItems($event, $action); - } - - $strategies = id(new PhutilClassMapQuery()) - ->setAncestorClass('DifferentialLandingStrategy') - ->execute(); - - foreach ($strategies as $strategy) { - $action = $strategy->createMenuItem($viewer, $revision, $repository); - if ($action == null) { - continue; - } - if ($strategy->isActionDisabled($viewer, $revision, $repository)) { - $action->setDisabled(true); - } - $this->addActionMenuItems($event, $action); - } - } - -} diff --git a/src/applications/differential/landing/DifferentialLandingStrategy.php b/src/applications/differential/landing/DifferentialLandingStrategy.php deleted file mode 100644 index 2cec11fefe..0000000000 --- a/src/applications/differential/landing/DifferentialLandingStrategy.php +++ /dev/null @@ -1,87 +0,0 @@ -getId(); - return id(new PhabricatorActionView()) - ->setRenderAsForm(true) - ->setWorkflow(true) - ->setName($name) - ->setHref("/differential/revision/land/{$revision_id}/{$strategy}/"); - } - - /** - * Check if this action should be disabled, and explain why. - * - * By default, this method checks for push permissions, and for the - * revision being Accepted. - * - * @return False for "not disabled"; human-readable text explaining why, if - * it is disabled. - */ - public function isActionDisabled( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - $status = $revision->getStatus(); - if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { - return pht('Only Accepted revisions can be landed.'); - } - - if (!PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - DiffusionPushCapability::CAPABILITY)) { - return pht('You do not have permissions to push to this repository.'); - } - - return false; - } - - /** - * Might break if repository is not Git. - */ - protected function getGitWorkspace(PhabricatorRepository $repository) { - try { - return DifferentialGetWorkingCopy::getCleanGitWorkspace($repository); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to allocate a workspace.'), - $e); - } - } - - /** - * Might break if repository is not Mercurial. - */ - protected function getMercurialWorkspace(PhabricatorRepository $repository) { - try { - return DifferentialGetWorkingCopy::getCleanMercurialWorkspace( - $repository); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to allocate a workspace.'), - $e); - } - } - -} From 988a52cf1acdfdc43577c6a58d7f29b3c25548ec Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Jun 2017 07:34:29 -0700 Subject: [PATCH 286/543] Fix ambiguous URI parsing in Youtube Remarkup rule Summary: Fixes T12867. Also: - Simplify the code a little. - Stop mutating this on text/mobile -- there's no inherent value in the "youtu.be" link so I think this just changes the text the user wrote unnecessarily. Test Plan: {F5013804} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12867 Differential Revision: https://secure.phabricator.com/D18149 --- .../rule/PhabricatorYoutubeRemarkupRule.php | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php index bad8e0eacf..25422b970d 100644 --- a/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php @@ -2,34 +2,37 @@ final class PhabricatorYoutubeRemarkupRule extends PhutilRemarkupRule { - private $uri; - public function getPriority() { return 350.0; } public function apply($text) { - $this->uri = new PhutilURI($text); - - if ($this->uri->getDomain() && - preg_match('/(^|\.)youtube\.com$/', $this->uri->getDomain()) && - idx($this->uri->getQueryParams(), 'v')) { - return $this->markupYoutubeLink(); + try { + $uri = new PhutilURI($text); + } catch (Exception $ex) { + return $text; } - return $text; - } + $domain = $uri->getDomain(); + if (!preg_match('/(^|\.)youtube\.com\z/', $domain)) { + return $text; + } + + $params = $uri->getQueryParams(); + $v_param = idx($params, 'v'); + if (!strlen($v_param)) { + return $text; + } - public function markupYoutubeLink() { - $v = idx($this->uri->getQueryParams(), 'v'); $text_mode = $this->getEngine()->isTextMode(); $mail_mode = $this->getEngine()->isHTMLMailMode(); if ($text_mode || $mail_mode) { - return $this->getEngine()->storeText('http://youtu.be/'.$v); + return $text; } - $youtube_src = 'https://www.youtube.com/embed/'.$v; + $youtube_src = 'https://www.youtube.com/embed/'.$v_param; + $iframe = $this->newTag( 'div', array( @@ -45,6 +48,7 @@ final class PhabricatorYoutubeRemarkupRule extends PhutilRemarkupRule { 'frameborder' => 0, ), '')); + return $this->getEngine()->storeText($iframe); } From f704f905d26055fb37564e154cac77056e047169 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Jun 2017 15:02:22 -0700 Subject: [PATCH 287/543] Let PhabricatorSearchCheckboxesField survive saved query data with mismatched types Summary: Fixes T12851. This should fix the error I'm seeing, which is: * `Argument 1 passed to array_fuse() must be of the type array, boolean given` There may be a better way to patch this up than overriding the getValue() method, however. Test Plan: - Changed the default "Tags" filter to specify `true` instead of `array('self')`, then viewed that filter in the UI. - Before patch: fatal. - After patch: page loads. Note that `true` is not interpreted as `array('self')`, but the page isn't broken, which is a big improvement. Reviewers: #blessed_reviewers, 20after4, chad, amckinley Reviewed By: #blessed_reviewers, amckinley Subscribers: Korvin Maniphest Tasks: T12851 Differential Revision: https://secure.phabricator.com/D18132 --- .../search/field/PhabricatorSearchCheckboxesField.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/search/field/PhabricatorSearchCheckboxesField.php b/src/applications/search/field/PhabricatorSearchCheckboxesField.php index 5a552362bf..e1a72f4576 100644 --- a/src/applications/search/field/PhabricatorSearchCheckboxesField.php +++ b/src/applications/search/field/PhabricatorSearchCheckboxesField.php @@ -18,6 +18,14 @@ final class PhabricatorSearchCheckboxesField return array(); } + protected function didReadValueFromSavedQuery($value) { + if (!is_array($value)) { + return array(); + } + + return $value; + } + protected function getValueFromRequest(AphrontRequest $request, $key) { return $this->getListFromRequest($request, $key); } From a19859053300f7087615f305f0ec8cb2038f7d4c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Jun 2017 12:06:04 -0700 Subject: [PATCH 288/543] Degrade more gracefully when ProfileMenu dashboards fail to render Summary: Ref T12871. This replaces a dead end UI (user totally locked out) with one where the menu is still available, if the default menu item is one which generates a policy exception (e.g., because users can't see the dashboard). Really, we should do better than this and not select this item as the default item if the viewer can't see it, but there is currently no reliable way to test for "can the viewer see this item?" so this is a more involved change. I'm thinking we get this minor improvement into the release, then pursue a more detailed fix afterward. Test Plan: - Added a dashboard as the top item in the global menu. - Changed the dashboard to be visible to only user B. - Viewed Home as user A. - Before patch: entire page is a policy exception dialog. - After patch, things are better: {F5014179} Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12871 Differential Revision: https://secure.phabricator.com/D18152 --- .../search/engine/PhabricatorProfileMenuEngine.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index 4b24e67c6b..7b1e213d20 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -238,7 +238,14 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { case 'view': $navigation->selectFilter($selected_item->getDefaultMenuItemKey()); - $content = $this->buildItemViewContent($selected_item); + try { + $content = $this->buildItemViewContent($selected_item); + } catch (Exception $ex) { + $content = id(new PHUIInfoView()) + ->setTitle(pht('Unable to Render Dashboard')) + ->setErrors(array($ex->getMessage())); + } + $crumbs->addTextCrumb($selected_item->getDisplayName()); if (!$content) { return new Aphront404Response(); From d4632f4b78a0e112e14f25fe90fc789c22bfcce8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 06:45:42 -0700 Subject: [PATCH 289/543] Restore "Land Revision" action to UI Summary: This was accidentally caught in the crossfire in D18150. This is stable enough to formalize instead of adding with an event hook. Test Plan: Looked at a candidate revision, saw "Land Revision" appear in UI again. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18154 --- .../DifferentialRevisionViewController.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index f19ff37360..ab03ee6d81 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -615,6 +615,29 @@ final class DifferentialRevisionViewController extends DifferentialController { $curtain->addAction($relationship_submenu); } + $repository = $revision->getRepository(); + if ($repository && $repository->canPerformAutomation()) { + $revision_id = $revision->getID(); + + $op = new DrydockLandRepositoryOperation(); + $barrier = $op->getBarrierToLanding($viewer, $revision); + + if ($barrier) { + $can_land = false; + } else { + $can_land = true; + } + + $action = id(new PhabricatorActionView()) + ->setName(pht('Land Revision')) + ->setIcon('fa-fighter-jet') + ->setHref("/differential/revision/operation/{$revision_id}/") + ->setWorkflow(true) + ->setDisabled(!$can_land); + + $curtain->addAction($action); + } + return $curtain; } From e4785484176c317da088485b9bdf38f7689f3839 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 27 Jun 2017 13:21:12 +0200 Subject: [PATCH 290/543] Turn off spellcheck, etc, on main search input Summary: Ref T12872, turns off all these "helpful" fields. Test Plan: Type "phab" in main search and do not get a suggestion for "phablet". Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12872 Differential Revision: https://secure.phabricator.com/D18163 --- src/view/page/menu/PhabricatorMainMenuSearchView.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php index d21d23d0fd..0b2ca8ab10 100644 --- a/src/view/page/menu/PhabricatorMainMenuSearchView.php +++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php @@ -39,6 +39,9 @@ final class PhabricatorMainMenuSearchView extends AphrontView { 'name' => 'query', 'id' => $search_id, 'autocomplete' => 'off', + 'autocorrect' => 'off', + 'autocapitalize' => 'off', + 'spellcheck' => 'false', )); $target = javelin_tag( From d6450bf7d255fcd201fe66edf9fcb5e2c3b18e60 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 27 Jun 2017 12:32:21 +0200 Subject: [PATCH 291/543] New icons for Projects Summary: Updates the builtin images, leaves the old choose... icons for now. I'd like to automate this based on icon when creating a project. Test Plan: Visit edit picture page, pick a few. Purge cache, see new default image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18162 --- resources/builtin/project.png | Bin 411 -> 4350 bytes resources/builtin/projects/v3/briefcase.png | Bin 0 -> 4350 bytes resources/builtin/projects/v3/bug.png | Bin 0 -> 8151 bytes resources/builtin/projects/v3/calendar.png | Bin 0 -> 4330 bytes resources/builtin/projects/v3/cloud.png | Bin 0 -> 7539 bytes resources/builtin/projects/v3/creditcard.png | Bin 0 -> 2306 bytes resources/builtin/projects/v3/database.png | Bin 0 -> 11418 bytes resources/builtin/projects/v3/desktop.png | Bin 0 -> 2581 bytes .../builtin/projects/v3/experimental.png | Bin 0 -> 7579 bytes resources/builtin/projects/v3/flag.png | Bin 0 -> 6751 bytes resources/builtin/projects/v3/folder.png | Bin 0 -> 2237 bytes resources/builtin/projects/v3/lock.png | Bin 0 -> 7824 bytes resources/builtin/projects/v3/mail.png | Bin 0 -> 5777 bytes resources/builtin/projects/v3/mobile.png | Bin 0 -> 4209 bytes .../builtin/projects/v3/organization.png | Bin 0 -> 4252 bytes resources/builtin/projects/v3/people.png | Bin 0 -> 13654 bytes resources/builtin/projects/v3/servers.png | Bin 0 -> 6447 bytes resources/builtin/projects/v3/tag.png | Bin 0 -> 4161 bytes resources/builtin/projects/v3/trash.png | Bin 0 -> 9154 bytes resources/builtin/projects/v3/truck.png | Bin 0 -> 6294 bytes resources/builtin/projects/v3/umbrella.png | Bin 0 -> 9812 bytes ...habricatorProjectEditPictureController.php | 36 ++++++++++++++++-- 22 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 resources/builtin/projects/v3/briefcase.png create mode 100644 resources/builtin/projects/v3/bug.png create mode 100644 resources/builtin/projects/v3/calendar.png create mode 100644 resources/builtin/projects/v3/cloud.png create mode 100644 resources/builtin/projects/v3/creditcard.png create mode 100644 resources/builtin/projects/v3/database.png create mode 100644 resources/builtin/projects/v3/desktop.png create mode 100644 resources/builtin/projects/v3/experimental.png create mode 100644 resources/builtin/projects/v3/flag.png create mode 100644 resources/builtin/projects/v3/folder.png create mode 100644 resources/builtin/projects/v3/lock.png create mode 100644 resources/builtin/projects/v3/mail.png create mode 100644 resources/builtin/projects/v3/mobile.png create mode 100644 resources/builtin/projects/v3/organization.png create mode 100644 resources/builtin/projects/v3/people.png create mode 100644 resources/builtin/projects/v3/servers.png create mode 100644 resources/builtin/projects/v3/tag.png create mode 100644 resources/builtin/projects/v3/trash.png create mode 100644 resources/builtin/projects/v3/truck.png create mode 100644 resources/builtin/projects/v3/umbrella.png diff --git a/resources/builtin/project.png b/resources/builtin/project.png index 1e16790ea8c5764db8bfe86cd201f04dfc1f448d..c6abf5ed42461fd03eb1bbe26ba87735794c0e16 100644 GIT binary patch literal 4350 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 Woh~YmBAZJeTDUlRI5gM+r2hfTA_vR> literal 411 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 Woh~YmBAZJeTDUlRI5gM+r2hfTA_vR> literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/bug.png b/resources/builtin/projects/v3/bug.png new file mode 100644 index 0000000000000000000000000000000000000000..bb2948a93aba530003b47d34aefbbee65880e41e GIT binary patch literal 8151 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 iqiM4~&ZBSb)?NvJW`Afh;efRu!xi9!fL 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 aeHlNIb|xw5RrE>rHWp^qrns|^8~+7Qp8QP! literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/cloud.png b/resources/builtin/projects/v3/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..efb644001f07c851f7c1add1b5e6f003cb1d43c0 GIT binary patch literal 7539 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/creditcard.png b/resources/builtin/projects/v3/creditcard.png new file mode 100644 index 0000000000000000000000000000000000000000..d231c9437dd8e35311204ad85a8d203f1006ff46 GIT binary patch literal 2306 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 Z4# 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 Lt0U=zcg(*4DpclA literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/experimental.png b/resources/builtin/projects/v3/experimental.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb05ac100f76fae3e93d4ebe10c353da910e721 GIT binary patch literal 7579 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 X=ce!Bgb2th!R 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/truck.png b/resources/builtin/projects/v3/truck.png new file mode 100644 index 0000000000000000000000000000000000000000..3c903ea58bfd0613a2b9d86ae79992b81f60306a GIT binary patch literal 6294 zcmbVwcT`i|w`~##y(3ks^b$bny%<266d?vsh!jD3?_dDwQl*F>Ql$k5y<_N76_7}g z-ch89bhzRB#_zrR#=C#rGsZb%?X~A#d(E}y-s7ATWAIRuoP?PK0059{J%Abk06^iJ zn}`7C@efY$1^^hJYC%AA5T@{9y0H*3t?OThuJP zCIIfmDOy|jysEIhzLd{3d08#+i!pG7D3>I0nnEfMD-XtZ#Gf5i5(ky!H9FCrf1)?X zd8mcN%D9k#EW;5Pu+wa4{;mT6nc+kS+LNY2dH=dyKmhWKvgd?*K%AGe=rB4gl+?nd z8a4O0nn!nxzn)qEMt2O1-HUlbs40oCsv1U5eiU$blgooyfrCBPsXDztf(QLpx9U!{ zd?z&+d<52@j$Bq8_?_>DLhjMj?Eo`fWcjm_ZVRZi<+F&CXICk_)}*3B9EltDSppAVCg7k`hov<|iVJ~=b)In3pR`VnxN4KJo3J(bapFSNYyYYG)OQ6q1B8rJDx9lDh7f@;-kEG?~}rbY0$Y~R1h)zD=7WS z9douUcY}Muo1jDrLD*_x!syY3UOJvR@FUhbB#*7Bj-PTJGK~XBYKGBHx{1m2;08m( zjZpnhFsGYfuq%9Fd!W!u5Pco;$b78AtjyQ@1{mVpLU%Mc?*@O7*xJ@w{$>ah23tYs zRnc>BI|Q-8uW2eMVRPiDRm9T(I#6jqY55rNr)B<;p;aBSKARrE?IcBnP%T$QKgSGC z1rZ6`OP*=@+4`z*7-m>(IY5E*bu;*mQt;3t8loDBR_2{6uPAyn7SdUWho(! zSWxivfE^WXznZPV`I5PdzEBf(*?h!5sdWl{$u__`5)}R zyAT)ZAMCdG4g3Fh*xB`2XnXCj=Uim|NmbzH!+DaHlrC$w=j(1({Y&m_;y2f!ka%OG zlE}noqg5uM0!q6nvGF6zjMT;U!-Gh@X}8SraH-68DW*B8q85&zos#c;yL-|n<}3xB zc{1pZkj9K0UDo{ai923a8rgqGbjSai@;ZChy@1VJ1{|YqvF2pvdWz?J`8q@^1yy|r z!D7ZS%=&b~S*W~wJTuO*!aa(lgR)X%>p4Tgd>?8mRB3Dw!K)a8#pz!de(^>Qh24FB z9y2*U9YWS5#kX>5%54(`p+%uQj{zhK#Sh4TkFhWP*y~ySG?JFij|P--iLV%Ezd3fE z2cjXhx-H_8Q015|9XuU8QrEV3#2EtE=yPUE_1hW(v|Wn!O}hAKKq6hO_=?i*RhD5Hoi1fF0gxcYAgzsWD1RFGbk(fs1jTSaE}`l`Z+GN{XowifJgshPkwR}d4x3LBu+=^&n#gVgQrHhJc&{Lj@g z1R8oVH1GWxv|)p*IliA?e|#FPp4GE4_tw0y_)(MunimfpYicfB-GR8(M-m>sq?N{` zW1U8C7Mh$S`rL_}J5oc(b`=l-0DhSwGacVhd@uIYPu)RVEv?6VLX8=&v_-D@h(%J) ztOpc6=6s%S6Q%cT-p%P*Op2mx4Vka{kxBXzvsmQ-YdY`j_3kxRZIj*fGy685+yRng zz0Wc;^0u1B_DN@bkPrPV(r<0;N1Tk!#a}1=&e}>j^t^|cVJA1Ag5cfnpFG@_W@!Js zbmSB@kuB-LL_C>t&1ooWiMF#aK}eR$YdaYZPM5TdGxVB--P)I zW8~eWh&+6wK7&5Kl}WWuu98Cv{p-f;kFjUBgbjdj*1=A}Uy-0t`rn2wA@@lwbiZz} zp4L66s5zX^1EEJ>z)zc&wYP@9t63Kd9_Bn0B(3li1pL%7Au#rN8E^X*PMs_kwI>5XJqdMm;7ln);UE(86?B1btRpwCN zO3Nd?#wrWWa_i>e-)pgCc0FO7Fypj)LPB{f&z=P)RSa!nCxsZZ%D%^b_l@L@E;_YVx%u66`GOLiHH*-`~z~?-#EK<>9AV=>BN3BsTJTa3{X2?}c~@J()83 zK@EfZmv?w3l;xh9`hf$F?-`#h%>=>+M*tn)ro z83t5+p&5kx*Y!Wzr9}@SBMGHg%V6-OJe&2(#TXA!BHlnJ9`C7c&to2mCzex{p>)QG zG)-*v{jfr&O^$9MnvWZU#2}51L1p{>0JA=h z*`b+F`?&z8YB3|>bU-sOAS=#(>|5*dM^!06W z>-UG_TZfbQ!hbUFN=a=r9l$IO(;}|w6Xxdj6n6F574vq6C<2ZesJGqM#7sBtt7_v3 zabV<{wy3Nl1UkLb-+e+ZvCeqV^zj!K$d965XDo^JzdiN%%yu~+1TMG@iYoso)l&eNT-8<*HL`BZ!O9q*s;GFPXm=dF(&5MVI8 zH`H=Ueq#?_!~2@=xOT_YdA7lMwxeN3Ui>@GXxMeCbarf*-`MhQ_?cZO=h;$xby6Wc z;AJvj`Kb8oQ~OuZ)95y8MZT_mDC2}{to^Pz*T;UeX>VmRHOgXy(DRF4oiuI#OM%ue zSq)!NKDyiLI$IG%q^P0W#^~*_k%83YH|nLwrskQ}stSED%EmIRP`&?z#@&8!liq4& zYgSMEp{o+kAI8N3Gb8Zxt0lZ6Bia zpzuka)|1K+uq=^QF*xqIC>Z-CL4gnWjy} zKYOXm@E?PFl0sPgwWg9(`oIY({;IHuE7j7LRwnCp{O7)3n{*js((gIJJVq!Yx*HzU zSDp)H54)O%4O;s-vp7w*nut6+CX1b;8m;|gU!8B8mU97V^e+w<`bPeyq zd3cf1lvtwY#dAh{&yqK)_c$4n*(fYl{ItWqX!p3!-2{E&%{eBz`g$!+#I^;Gmn?^^ z1TlHu)woB~9%fYV97|{uS2@4?H+X(GARRAkO;~i#S@w=5-k)pmdY%0dwcN5wAs7}d z|7BVYl$b(&J>LoA0@a?!_6qeH0pkU0!zLvzeI9kTX!4)Nt`t;tw)PQ#*P9gIk?mj3 zHx1ATa|FqN63#UFJq@!4l=9BH&9g2>o+vE^$OZ;LT>W=p@Mq_e6<$ce36BF4!-nDV3 zN4N_?Z-Le0Za0aTJL9Wm+~yLW*$FES885lG;Sog#%Dnwua$Zg!l&uY~9Dm!NSf00B z>gsqDx)4dXSWDBP^b47D_0$}Z>a;=`m4metYI|)LV9QY|2J>@#ZRi7ZWrhP%O28Fx z6j#?TT&^(7dUz=>&4QpISB3JpMh4+ zTRKblPl><-&_bc`6|2++VkkB!FvdON+$Z1Q6+Y>o+A2TkQQNx{=B-7%lpkVBV(7}| zA(8hthaU&`E{}^&)taDxF{Tkn6pG8`DYbF68AzEWeUe(`0Qc5iq{8}PiV z!186LM?iJ)GrOHyL!IEbG#BCuY?S%ILs|=S_>M+`W1;NtMl@{umu&bXPB4 z1?$Y>XNboi-V7!f;3EzW>4| zQ!pFbm;*ut78h+A~?;uNH9Y zx>)Vn$A8G-?_g0yE7lZI*CzE6*1LUsl+PzqNgjHyvXH>-&5LPfV|h8;fd^tYtCZ9K zX$X|oPj`K609D`C{GrXlZS>I}f(p{NGfq3-4wACWtO%o1)Hag?2@Whl!Q1G0=Ojp) zadt=wVN5}T$>y@C@JC~Caxm^&=o|3jz=#ed_K2I(3b_Kx&*9(_Y#8Z_8W+?SK0Zi> zx!k{1Q9iyCmngK_yi=_Cr&k+H+BVqX9_&J!{P7g^T{DEh? ze}5r2hWR#wb^QHGUdB+w~1wyxwhw)53 zkvaVJF${dA`I^sYIcRy`okMrvyMfeNBlKtP^LJpG6Va$90nZ&ABt#>XgMwC|Usf%Z zHjAZ9p%*$WGWNT*7b6HCZ0>Q3V)Y?&51rA<{zLd0EbE>)qXiyvg$_K4V(}r~ zFP!0V6XA=LwK>-7Dee*dCY6m4n9g~it5wiF%8>)vQw$qCg6>LaJkeUFU6ytrr9-v3 z(T$}}nF2;*cgMP7c7_LEKUgorm14zzv zB$Vh5b0?5a2aAuz_?2>`Y~k`#R!A1m^Wxy8DUWGymf29+UC=wot|Qwg+7}H@$YU|} zlHZpdi)CEn>1*7uIu7p%*!5coFVR-e%r5$}U+%LFzxkPuNcwcd|JS=P2@QA#BqCLT zBhi3YU=CpwIG_teMbCkv#X0`}o*M|xeFOO)`*9<0`iV&Y>3;>jiHr+)GYkp1d|yJr T)Xwa6GryMlLujQMBJ95aCNPh4 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/umbrella.png b/resources/builtin/projects/v3/umbrella.png new file mode 100644 index 0000000000000000000000000000000000000000..98c7c1236545200fdb673445e3e4c055ab20d99c GIT binary patch literal 9812 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] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Builtin Image'), + ); + } + $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), - 'tip' => pht('No Picture'), + 'tip' => pht('Default Picture'), ); require_celerity_resource('people-profile-css'); @@ -200,7 +230,7 @@ final class PhabricatorProjectEditPictureController $compose_button = javelin_tag( 'button', array( - 'class' => 'grey', + 'class' => 'button-grey', 'id' => $launch_id, 'sigil' => 'icon-composer', ), @@ -227,7 +257,7 @@ final class PhabricatorProjectEditPictureController $form->appendChild( id(new AphrontFormMarkupControl()) - ->setLabel(pht('Quick Create')) + ->setLabel(pht('Custom')) ->setValue($compose_form)); $upload_form = id(new AphrontFormView()) From 03b6bdde193143d87da5ba45b41996c0a6f57221 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 08:45:31 -0700 Subject: [PATCH 292/543] Begin modularizing config options in a more modern way Summary: Ref T12845. Config options are "modular", but the modularity is very old, half-implemented, and doesn't use modern patterns. Half the types are hard-coded, while half the types are semi-modular but in a weird hacky way where you prefix the type with `custom:...`. The actual API is also weird and requires types to return a lot of `array($stuff, $thing, $other_thing, $more_stuff)` sorts of tuples. Instead: - Add a new replacement layer which uses modern modularity patterns and overrides the older stuff if available, so we can migrate things one at a time. - New layer uses a more modern API -- no `return array($thing, $other_thing, ...)`, and more modern building blocks (like AphrontHTTPParameterType). - New layer allows custom types to be deleted, which will ultimately let us deal with T12845. Then, convert the `'int'` type to use the new layer. Test Plan: - Set, edited, tried-to-change-in-an-invalid-way, and deleted an `'int'` option from the web UI. - Same from the CLI. - Edited `config.json` to have an invalid value, verified that the error was detected and config was repaired. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18155 --- src/__phutil_library_map__.php | 6 + .../PhabricatorConfigEditController.php | 69 ++++++++-- ...PhabricatorConfigManagementSetWorkflow.php | 128 +++++++++--------- .../PhabricatorApplicationConfigOptions.php | 25 ++-- .../config/option/PhabricatorConfigOption.php | 6 + .../config/type/PhabricatorConfigType.php | 115 ++++++++++++++++ .../config/type/PhabricatorIntConfigType.php | 36 +++++ .../config/type/PhabricatorTextConfigType.php | 21 +++ 8 files changed, 322 insertions(+), 84 deletions(-) create mode 100644 src/applications/config/type/PhabricatorConfigType.php create mode 100644 src/applications/config/type/PhabricatorIntConfigType.php create mode 100644 src/applications/config/type/PhabricatorTextConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9deeb338f6..6ad24c6af3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2448,6 +2448,7 @@ phutil_register_library_map(array( 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', + 'PhabricatorConfigType' => 'applications/config/type/PhabricatorConfigType.php', 'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php', 'PhabricatorConfigVersionController' => 'applications/config/controller/PhabricatorConfigVersionController.php', 'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php', @@ -2997,6 +2998,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', + 'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php', 'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', 'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php', @@ -4127,6 +4129,7 @@ phutil_register_library_map(array( 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', + 'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php', 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', @@ -7697,6 +7700,7 @@ phutil_register_library_map(array( 'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorConfigType' => 'Phobject', 'PhabricatorConfigValidationException' => 'Exception', 'PhabricatorConfigVersionController' => 'PhabricatorConfigController', 'PhabricatorConpherenceApplication' => 'PhabricatorApplication', @@ -8324,6 +8328,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', + 'PhabricatorIntConfigType' => 'PhabricatorTextConfigType', 'PhabricatorInternalSetting' => 'PhabricatorSetting', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', 'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -9659,6 +9664,7 @@ phutil_register_library_map(array( 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorTestWorker' => 'PhabricatorWorker', 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', + 'PhabricatorTextConfigType' => 'PhabricatorConfigType', 'PhabricatorTextEditField' => 'PhabricatorEditField', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 71b4499591..33e796200b 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -274,12 +274,58 @@ final class PhabricatorConfigEditController PhabricatorConfigOption $option, AphrontRequest $request) { + $type = $option->newOptionType(); + if ($type) { + $is_set = $type->isValuePresentInRequest($option, $request); + if ($is_set) { + $value = $type->readValueFromRequest($option, $request); + + $errors = array(); + try { + $canonical_value = $type->newValueFromRequestValue( + $option, + $value); + $type->validateStoredValue($canonical_value); + $xaction = $type->newTransaction($option, $canonical_value); + } catch (PhabricatorConfigValidationException $ex) { + $errors[] = $ex->getMessage(); + $xaction = null; + } + + return array( + $errors ? pht('Invalid') : null, + $errors, + $value, + $xaction, + ); + } else { + $delete_xaction = id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => true, + 'value' => null, + )); + + return array( + null, + array(), + null, + $delete_xaction, + ); + } + } + + // TODO: If we missed on the new `PhabricatorConfigType` map, fall back + // to the old semi-modular, semi-hacky way of doing things. + $xaction = new PhabricatorConfigTransaction(); $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); $e_value = null; $errors = array(); + if ($option->isCustomType()) { $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; @@ -301,14 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'int': - if (preg_match('/^-?[0-9]+$/', trim($value))) { - $set_value = (int)$value; - } else { - $e_value = pht('Invalid'); - $errors[] = pht('Value must be an integer.'); - } - break; case 'string': case 'enum': $set_value = (string)$value; @@ -390,6 +428,11 @@ final class PhabricatorConfigEditController PhabricatorConfigEntry $entry, $value) { + $type = $option->newOptionType(); + if ($type) { + return $type->newDisplayValue($option, $value); + } + if ($option->isCustomType()) { return $option->getCustomObject()->getDisplayValue( $option, @@ -398,7 +441,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'int': case 'string': case 'enum': case 'class': @@ -421,6 +463,14 @@ final class PhabricatorConfigEditController $display_value, $e_value) { + $type = $option->newOptionType(); + if ($type) { + return $type->newControls( + $option, + $display_value, + $e_value); + } + if ($option->isCustomType()) { $controls = $option->getCustomObject()->renderControls( $option, @@ -429,7 +479,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'int': case 'string': $control = id(new AphrontFormTextControl()); break; diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 4511865785..823f1db9b3 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -59,63 +59,47 @@ final class PhabricatorConfigManagementSetWorkflow $option = $options[$key]; - $type = $option->getType(); - switch ($type) { - case 'string': - case 'class': - case 'enum': - $value = (string)$value; - break; - case 'int': - if (!ctype_digit($value)) { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify an integer.", - $key, - $type)); - } - $value = (int)$value; - break; - case 'bool': - if ($value == 'true') { - $value = true; - } else if ($value == 'false') { - $value = false; - } else { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", - $key, - $type, - 'true', - 'false')); - } - break; - default: - $value = json_decode($value, true); - if (!is_array($value)) { - switch ($type) { - case 'set': - $command = csprintf( - './bin/config set %R %s', + $type = $option->newOptionType(); + if ($type) { + try { + $value = $type->newValueFromCommandLineValue( + $option, + $value); + } catch (PhabricatorConfigValidationException $ex) { + throw new PhutilArgumentUsageException($ex->getMessage()); + } + } else { + $type = $option->getType(); + switch ($type) { + case 'string': + case 'class': + case 'enum': + $value = (string)$value; + break; + case 'bool': + if ($value == 'true') { + $value = true; + } else if ($value == 'false') { + $value = false; + } else { + throw new PhutilArgumentUsageException( + pht( + "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", $key, - '{"value1": true, "value2": true}'); - - $message = sprintf( - "%s\n\n %s\n", - pht( - 'Config key "%s" is of type "%s". Specify it in JSON. '. - 'For example:', - $key, - $type), - $command); - break; - default: - if (preg_match('/^listgetArg('database'); if ($option->getLocked() && $use_database) { throw new PhutilArgumentUsageException( diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 34d74e88ab..39538a2af5 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -20,13 +20,24 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { return; } + $type = $option->newOptionType(); + if ($type) { + try { + $type->validateStoredValue($option, $value); + } catch (PhabricatorConfigValidationException $ex) { + throw $ex; + } catch (Exception $ex) { + // If custom validators threw exceptions other than validation + // exceptions, convert them to validation exceptions so we repair the + // configuration and raise an error. + throw new PhabricatorConfigValidationException($ex->getMessage()); + } + } + if ($option->isCustomType()) { try { return $option->getCustomObject()->validateOption($option, $value); } catch (Exception $ex) { - // If custom validators threw exceptions, convert them to configuation - // validation exceptions so we repair the configuration and raise - // an error. throw new PhabricatorConfigValidationException($ex->getMessage()); } } @@ -41,14 +52,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { $option->getKey())); } break; - case 'int': - if (!is_int($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type int, but value is not an integer.", - $option->getKey())); - } - break; case 'string': if (!is_string($value)) { throw new PhabricatorConfigValidationException( diff --git a/src/applications/config/option/PhabricatorConfigOption.php b/src/applications/config/option/PhabricatorConfigOption.php index d10c450192..11b8de2617 100644 --- a/src/applications/config/option/PhabricatorConfigOption.php +++ b/src/applications/config/option/PhabricatorConfigOption.php @@ -175,6 +175,12 @@ final class PhabricatorConfigOption return $this->type; } + public function newOptionType() { + $type_key = $this->getType(); + $type_map = PhabricatorConfigType::getAllTypes(); + return idx($type_map, $type_key); + } + public function isCustomType() { return !strncmp($this->getType(), 'custom:', 7); } diff --git a/src/applications/config/type/PhabricatorConfigType.php b/src/applications/config/type/PhabricatorConfigType.php new file mode 100644 index 0000000000..c467d6ceec --- /dev/null +++ b/src/applications/config/type/PhabricatorConfigType.php @@ -0,0 +1,115 @@ +getPhobjectClassConstant('TYPEKEY'); + } + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTypeKey') + ->execute(); + } + + public function isValuePresentInRequest( + PhabricatorConfigOption $option, + AphrontRequest $request) { + $http_type = $this->newHTTPParameterType(); + return $http_type->getExists($request, 'value'); + } + + public function readValueFromRequest( + PhabricatorConfigOption $option, + AphrontRequest $request) { + $http_type = $this->newHTTPParameterType(); + return $http_type->getValue($request, 'value'); + } + + abstract protected function newHTTPParameterType(); + + public function validateValue(PhabricatorConfigOption $option, $value) { + return array(); + } + + public function newTransaction( + PhabricatorConfigOption $option, + $value) { + + $xaction_value = $this->newTransactionValue($option, $value); + + return id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => false, + 'value' => $xaction_value, + )); + } + + protected function newTransactionValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + public function newControls( + PhabricatorConfigOption $option, + $value, + $error) { + + $control = $this->newControl($option) + ->setError($error) + ->setLabel(pht('Database Value')) + ->setName('value'); + + $value = $this->newControlValue($option, $value); + $control->setValue($value); + + return array( + $control, + ); + } + + abstract protected function newControl(PhabricatorConfigOption $option); + + protected function newControlValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + protected function newException($message) { + return new PhabricatorConfigValidationException($message); + } + + public function newValueFromRequestValue( + PhabricatorConfigOption $option, + $value) { + return $this->newCanonicalValue($option, $value); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + return $this->newCanonicalValue($option, $value); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + abstract public function validateStoredValue( + PhabricatorConfigOption $option, + $value); + +} diff --git a/src/applications/config/type/PhabricatorIntConfigType.php b/src/applications/config/type/PhabricatorIntConfigType.php new file mode 100644 index 0000000000..807104f1a5 --- /dev/null +++ b/src/applications/config/type/PhabricatorIntConfigType.php @@ -0,0 +1,36 @@ +newException( + pht( + 'Value for option "%s" must be an integer.', + $option->getKey())); + } + + return (int)$value; + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_int($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'an integer.', + $option->getKey(), + $this->getTypeKey())); + } + } + +} diff --git a/src/applications/config/type/PhabricatorTextConfigType.php b/src/applications/config/type/PhabricatorTextConfigType.php new file mode 100644 index 0000000000..fee3977487 --- /dev/null +++ b/src/applications/config/type/PhabricatorTextConfigType.php @@ -0,0 +1,21 @@ + Date: Mon, 26 Jun 2017 09:05:26 -0700 Subject: [PATCH 293/543] Convert "enum" and "string" config options to new modular option types Summary: Ref T12845. This moves the "enum" and "string" types to the new code. Test Plan: Set, deleted, and tried to set invalid values for various enum and string config values (header color, mail prefixes, etc) from the CLI and web. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18156 --- src/__phutil_library_map__.php | 4 ++ .../PhabricatorConfigEditController.php | 20 +-------- ...PhabricatorConfigManagementSetWorkflow.php | 2 - .../PhabricatorApplicationConfigOptions.php | 8 ---- .../PhabricatorMetaMTAConfigOptions.php | 6 +-- .../option/PhabricatorUIConfigOptions.php | 12 +++--- .../config/type/PhabricatorEnumConfigType.php | 43 +++++++++++++++++++ .../type/PhabricatorStringConfigType.php | 22 ++++++++++ .../config/type/PhabricatorTextConfigType.php | 6 +++ .../PhabricatorDifferentialConfigOptions.php | 5 ++- 10 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 src/applications/config/type/PhabricatorEnumConfigType.php create mode 100644 src/applications/config/type/PhabricatorStringConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6ad24c6af3..a0cfff88f3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2755,6 +2755,7 @@ phutil_register_library_map(array( 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', 'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', + 'PhabricatorEnumConfigType' => 'applications/config/type/PhabricatorEnumConfigType.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php', @@ -4069,6 +4070,7 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php', 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', + 'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -8044,6 +8046,7 @@ phutil_register_library_map(array( 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorEmojiTranslation' => 'PhutilTranslation', 'PhabricatorEmptyQueryException' => 'Exception', + 'PhabricatorEnumConfigType' => 'PhabricatorTextConfigType', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 'PhabricatorEpochEditField' => 'PhabricatorEditField', @@ -9605,6 +9608,7 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'Phobject', 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorStringConfigType' => 'PhabricatorTextConfigType', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 33e796200b..efdd64e92a 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -285,7 +285,7 @@ final class PhabricatorConfigEditController $canonical_value = $type->newValueFromRequestValue( $option, $value); - $type->validateStoredValue($canonical_value); + $type->validateStoredValue($option, $canonical_value); $xaction = $type->newTransaction($option, $canonical_value); } catch (PhabricatorConfigValidationException $ex) { $errors[] = $ex->getMessage(); @@ -347,10 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'string': - case 'enum': - $set_value = (string)$value; - break; case 'list': case 'list': $set_value = phutil_split_lines( @@ -441,8 +437,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'string': - case 'enum': case 'class': return $value; case 'bool': @@ -479,9 +473,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'string': - $control = id(new AphrontFormTextControl()); - break; case 'bool': $control = id(new AphrontFormSelectControl()) ->setOptions( @@ -491,15 +482,6 @@ final class PhabricatorConfigEditController 'false' => idx($option->getBoolOptions(), 1), )); break; - case 'enum': - $options = array_mergev( - array( - array('' => pht('(Use Default)')), - $option->getEnumOptions(), - )); - $control = id(new AphrontFormSelectControl()) - ->setOptions($options); - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 823f1db9b3..d0407291af 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -71,9 +71,7 @@ final class PhabricatorConfigManagementSetWorkflow } else { $type = $option->getType(); switch ($type) { - case 'string': case 'class': - case 'enum': $value = (string)$value; break; case 'bool': diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 39538a2af5..1742169fde 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -52,14 +52,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { $option->getKey())); } break; - case 'string': - if (!is_string($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type string, but value is not a string.", - $option->getKey())); - } - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index 556b1b169a..cc9732dc70 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -297,9 +297,9 @@ EODOC $this->newOption('metamta.user-address-format', 'enum', 'full') ->setEnumOptions( array( - 'short' => 'short', - 'real' => 'real', - 'full' => 'full', + 'short' => pht('Short'), + 'real' => pht('Real'), + 'full' => pht('Full'), )) ->setSummary(pht('Control how Phabricator renders user names in mail.')) ->setDescription($address_description) diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php index cef3fbd342..7b2823f6ba 100644 --- a/src/applications/config/option/PhabricatorUIConfigOptions.php +++ b/src/applications/config/option/PhabricatorUIConfigOptions.php @@ -21,12 +21,12 @@ final class PhabricatorUIConfigOptions public function getOptions() { $options = array( - 'blindigo' => 'blindigo', - 'red' => 'red', - 'blue' => 'blue', - 'green' => 'green', - 'indigo' => 'indigo', - 'dark' => 'dark', + 'blindigo' => pht('Blindigo'), + 'red' => pht('Red'), + 'blue' => pht('Blue'), + 'green' => pht('Green'), + 'indigo' => pht('Indigo'), + 'dark' => pht('Dark'), ); $example = <<newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + + $map = $option->getEnumOptions(); + if (!isset($map[$value])) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the current value ("%s") is not '. + 'among the set of valid values: %s.', + $option->getKey(), + $this->getTypeKey(), + $value, + implode(', ', array_keys($map)))); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $map = array( + '' => pht('(Use Default)'), + ) + $option->getEnumOptions(); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } + +} diff --git a/src/applications/config/type/PhabricatorStringConfigType.php b/src/applications/config/type/PhabricatorStringConfigType.php new file mode 100644 index 0000000000..da05b75780 --- /dev/null +++ b/src/applications/config/type/PhabricatorStringConfigType.php @@ -0,0 +1,22 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + } + +} diff --git a/src/applications/config/type/PhabricatorTextConfigType.php b/src/applications/config/type/PhabricatorTextConfigType.php index fee3977487..8e67d65b7a 100644 --- a/src/applications/config/type/PhabricatorTextConfigType.php +++ b/src/applications/config/type/PhabricatorTextConfigType.php @@ -10,6 +10,12 @@ abstract class PhabricatorTextConfigType return (bool)strlen($value); } + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + return (string)$value; + } + protected function newHTTPParameterType() { return new AphrontStringHTTPParameterType(); } diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index a3db26bef6..7ff886664f 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -260,7 +260,10 @@ EOHELP ->setDescription( pht('Format for inlined or attached patches.')) ->setEnumOptions( - array('unified' => 'unified', 'git' => 'git')), + array( + 'unified' => pht('Unified'), + 'git' => pht('Git'), + )), ); } From 467be5e53fe6f574082ab7c470834103b2647eef Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 09:22:40 -0700 Subject: [PATCH 294/543] Convert the "list" and "list" Config option types Summary: Ref T12845. This updates the "list" and "list" options. Test Plan: Set, deleted, and mangled options of these types from the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18157 --- src/__phutil_library_map__.php | 6 ++ .../PhabricatorConfigEditController.php | 22 ----- ...PhabricatorConfigManagementSetWorkflow.php | 25 +---- .../PhabricatorApplicationConfigOptions.php | 54 ---------- .../type/PhabricatorRegexListConfigType.php | 24 +++++ .../type/PhabricatorStringListConfigType.php | 8 ++ .../type/PhabricatorTextListConfigType.php | 98 +++++++++++++++++++ 7 files changed, 141 insertions(+), 96 deletions(-) create mode 100644 src/applications/config/type/PhabricatorRegexListConfigType.php create mode 100644 src/applications/config/type/PhabricatorStringListConfigType.php create mode 100644 src/applications/config/type/PhabricatorTextListConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a0cfff88f3..7753782858 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3746,6 +3746,7 @@ phutil_register_library_map(array( 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', + 'PhabricatorRegexListConfigType' => 'applications/config/type/PhabricatorRegexListConfigType.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', 'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php', 'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php', @@ -4071,6 +4072,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', 'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php', + 'PhabricatorStringListConfigType' => 'applications/config/type/PhabricatorStringListConfigType.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -4133,6 +4135,7 @@ phutil_register_library_map(array( 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', 'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php', 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', + 'PhabricatorTextListConfigType' => 'applications/config/type/PhabricatorTextListConfigType.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', @@ -9207,6 +9210,7 @@ phutil_register_library_map(array( 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', + 'PhabricatorRegexListConfigType' => 'PhabricatorTextListConfigType', 'PhabricatorRegistrationProfile' => 'Phobject', 'PhabricatorReleephApplication' => 'PhabricatorApplication', 'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -9609,6 +9613,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorStringConfigType' => 'PhabricatorTextConfigType', + 'PhabricatorStringListConfigType' => 'PhabricatorTextListConfigType', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', @@ -9670,6 +9675,7 @@ phutil_register_library_map(array( 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', 'PhabricatorTextConfigType' => 'PhabricatorConfigType', 'PhabricatorTextEditField' => 'PhabricatorEditField', + 'PhabricatorTextListConfigType' => 'PhabricatorTextConfigType', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', 'PhabricatorTimeGuard' => 'Phobject', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index efdd64e92a..0d4aea3252 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -347,20 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'list': - case 'list': - $set_value = phutil_split_lines( - $request->getStr('value'), - $retain_endings = false); - - foreach ($set_value as $key => $v) { - if (!strlen($v)) { - unset($set_value[$key]); - } - } - $set_value = array_values($set_value); - - break; case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; @@ -441,9 +427,6 @@ final class PhabricatorConfigEditController return $value; case 'bool': return $value ? 'true' : 'false'; - case 'list': - case 'list': - return implode("\n", nonempty($value, array())); case 'set': return implode("\n", nonempty(array_keys($value), array())); default: @@ -497,11 +480,6 @@ final class PhabricatorConfigEditController $control = id(new AphrontFormSelectControl()) ->setOptions($names); break; - case 'list': - case 'list': - $control = id(new AphrontFormTextAreaControl()) - ->setCaption(pht('Separate values with newlines.')); - break; case 'set': $control = id(new AphrontFormTextAreaControl()) ->setCaption(pht('Separate values with newlines or commas.')); diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index d0407291af..e1b54e4dcf 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -65,6 +65,7 @@ final class PhabricatorConfigManagementSetWorkflow $value = $type->newValueFromCommandLineValue( $option, $value); + $type->validateStoredValue($option, $value); } catch (PhabricatorConfigValidationException $ex) { throw new PhutilArgumentUsageException($ex->getMessage()); } @@ -109,26 +110,10 @@ final class PhabricatorConfigManagementSetWorkflow $command); break; default: - if (preg_match('/^list': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but value ". - "is not an array.", - $option->getKey())); - } - if ($value && array_keys($value) != range(0, count($value) - 1)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but the ". - "value is a map with unnatural keys.", - $option->getKey())); - } - foreach ($value as $v) { - $ok = @preg_match($v, ''); - if ($ok === false) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but the ". - "value '%s' is not a valid regular expression.", - $option->getKey(), - $v)); - } - } - break; - case 'list': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but value is not ". - "an array.", - $option->getKey())); - } - if ($value && array_keys($value) != range(0, count($value) - 1)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but the value is a ". - "map with unnatural keys.", - $option->getKey())); - } - foreach ($value as $v) { - if (!is_string($v)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but it contains one ". - "or more non-strings.", - $option->getKey())); - } - } - break; case 'wild': default: break; diff --git a/src/applications/config/type/PhabricatorRegexListConfigType.php b/src/applications/config/type/PhabricatorRegexListConfigType.php new file mode 100644 index 0000000000..b37b440c7e --- /dev/null +++ b/src/applications/config/type/PhabricatorRegexListConfigType.php @@ -0,0 +1,24 @@ +'; + + protected function validateStoredItem( + PhabricatorConfigOption $option, + $value) { + + $ok = @preg_match($value, ''); + if ($ok === false) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s" and must be set to a list of valid '. + 'regular expressions, but "%s" is not a valid regular expression.', + $option->getKey(), + $this->getTypeKey(), + $value)); + } + } + +} diff --git a/src/applications/config/type/PhabricatorStringListConfigType.php b/src/applications/config/type/PhabricatorStringListConfigType.php new file mode 100644 index 0000000000..70f4c04316 --- /dev/null +++ b/src/applications/config/type/PhabricatorStringListConfigType.php @@ -0,0 +1,8 @@ +'; + +} diff --git a/src/applications/config/type/PhabricatorTextListConfigType.php b/src/applications/config/type/PhabricatorTextListConfigType.php new file mode 100644 index 0000000000..20f5362ea6 --- /dev/null +++ b/src/applications/config/type/PhabricatorTextListConfigType.php @@ -0,0 +1,98 @@ +setCaption(pht('Separate values with newlines.')); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + + $value = phutil_split_lines($value, $retain_endings = false); + foreach ($value as $k => $v) { + if (!strlen($v)) { + unset($value[$k]); + } + } + + return array_values($value); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + + try { + $value = phutil_json_decode($value); + } catch (Exception $ex) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value you provided is not a '. + 'valid JSON list. When setting a list option from the command '. + 'line, specify the value in JSON. You may need to quote the '. + 'value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + + return $value; + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return implode("\n", $value); + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_array($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a list.', + $option->getKey(), + $this->getTypeKey())); + } + + $expect_key = 0; + foreach ($value as $k => $v) { + if (!is_string($v)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the item at index "%s" of the '. + 'list is not a string.', + $option->getKey(), + $this->getTypeKey(), + $k)); + } + + // Make sure this is a list with keys "0, 1, 2, ...", not a map with + // arbitrary keys. + if ($k != $expect_key) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value is not a list: it '. + 'is a map with unnatural or sparse keys.', + $option->getKey(), + $this->getTypeKey())); + } + $expect_key++; + + $this->validateStoredItem($option, $v); + } + } + + protected function validateStoredItem( + PhabricatorConfigOption $option, + $value) { + return; + } + +} From 72119e786c7985c56887f0060bee4192f74051c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 09:40:37 -0700 Subject: [PATCH 295/543] Convert "bool" config values to new modular system Summary: Ref T12845. Moves the "bool" values over. Test Plan: Set, deleted, and mangled bool values from CLI and web UI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18158 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigEditController.php | 25 -------- ...PhabricatorConfigManagementSetWorkflow.php | 15 ----- .../PhabricatorApplicationConfigOptions.php | 9 --- .../config/type/PhabricatorBoolConfigType.php | 62 +++++++++++++++++++ 5 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 src/applications/config/type/PhabricatorBoolConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7753782858..87ddd3da74 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2153,6 +2153,7 @@ phutil_register_library_map(array( 'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php', 'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php', 'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php', + 'PhabricatorBoolConfigType' => 'applications/config/type/PhabricatorBoolConfigType.php', 'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', @@ -7356,6 +7357,7 @@ phutil_register_library_map(array( 'PhabricatorBoardLayoutEngine' => 'Phobject', 'PhabricatorBoardRenderingEngine' => 'Phobject', 'PhabricatorBoardResponseEngine' => 'Phobject', + 'PhabricatorBoolConfigType' => 'PhabricatorTextConfigType', 'PhabricatorBoolEditField' => 'PhabricatorEditField', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 0d4aea3252..8c3779fdf4 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -350,20 +350,6 @@ final class PhabricatorConfigEditController case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; - case 'bool': - switch ($value) { - case 'true': - $set_value = true; - break; - case 'false': - $set_value = false; - break; - default: - $e_value = pht('Invalid'); - $errors[] = pht('Value must be boolean, "true" or "false".'); - break; - } - break; case 'class': if (!class_exists($value)) { $e_value = pht('Invalid'); @@ -425,8 +411,6 @@ final class PhabricatorConfigEditController switch ($type) { case 'class': return $value; - case 'bool': - return $value ? 'true' : 'false'; case 'set': return implode("\n", nonempty(array_keys($value), array())); default: @@ -456,15 +440,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'bool': - $control = id(new AphrontFormSelectControl()) - ->setOptions( - array( - '' => pht('(Use Default)'), - 'true' => idx($option->getBoolOptions(), 0), - 'false' => idx($option->getBoolOptions(), 1), - )); - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index e1b54e4dcf..40ee7b373e 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -75,21 +75,6 @@ final class PhabricatorConfigManagementSetWorkflow case 'class': $value = (string)$value; break; - case 'bool': - if ($value == 'true') { - $value = true; - } else if ($value == 'false') { - $value = false; - } else { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", - $key, - $type, - 'true', - 'false')); - } - break; default: $value = json_decode($value, true); if (!is_array($value)) { diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 229ecdfd16..4381ecb7aa 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -43,15 +43,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } switch ($option->getType()) { - case 'bool': - if ($value !== true && - $value !== false) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type bool, but value is not true or false.", - $option->getKey())); - } - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/type/PhabricatorBoolConfigType.php b/src/applications/config/type/PhabricatorBoolConfigType.php new file mode 100644 index 0000000000..840d8b4015 --- /dev/null +++ b/src/applications/config/type/PhabricatorBoolConfigType.php @@ -0,0 +1,62 @@ +newException( + pht( + 'Value for option "%s" of type "%s" must be either '. + '"true" or "false".', + $option->getKey(), + $this->getTypeKey())); + } + + return ($value === 'true'); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + + if ($value) { + return 'true'; + } else { + return 'false'; + } + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_bool($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a boolean.', + $option->getKey(), + $this->getTypeKey())); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $bool_map = $option->getBoolOptions(); + + $map = array( + '' => pht('(Use Default)'), + ) + array( + 'true' => idx($bool_map, 0), + 'false' => idx($bool_map, 1), + ); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } +} From 0afdabff00ae178aa3dbde2f9b504b223378d10f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 09:58:55 -0700 Subject: [PATCH 296/543] Convert 'class' config options to new validation Summary: Ref T12845. These options prompt the user to select from among concrete subclasses of some base class. Test Plan: Set, deleted and mangled these values from the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18159 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigEditController.php | 31 -------- ...PhabricatorConfigManagementSetWorkflow.php | 3 - .../PhabricatorApplicationConfigOptions.php | 15 ---- .../type/PhabricatorClassConfigType.php | 76 +++++++++++++++++++ 5 files changed, 78 insertions(+), 49 deletions(-) create mode 100644 src/applications/config/type/PhabricatorClassConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 87ddd3da74..61ac0a3b22 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2329,6 +2329,7 @@ phutil_register_library_map(array( 'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php', 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', + 'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php', 'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php', @@ -7575,6 +7576,7 @@ phutil_register_library_map(array( ), 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorClassConfigType' => 'PhabricatorTextConfigType', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorClusterDatabasesConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorClusterException' => 'Exception', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 8c3779fdf4..d3d5b3160a 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -350,20 +350,6 @@ final class PhabricatorConfigEditController case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; - case 'class': - if (!class_exists($value)) { - $e_value = pht('Invalid'); - $errors[] = pht('Class does not exist.'); - } else { - $base = $option->getBaseClass(); - if (!is_subclass_of($value, $base)) { - $e_value = pht('Invalid'); - $errors[] = pht('Class is not of valid type.'); - } else { - $set_value = $value; - } - } - break; default: $json = json_decode($value, true); if ($json === null && strtolower($value) != 'null') { @@ -409,8 +395,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'class': - return $value; case 'set': return implode("\n", nonempty(array_keys($value), array())); default: @@ -440,21 +424,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'class': - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass($option->getBaseClass()) - ->setConcreteOnly(true) - ->selectSymbolsWithoutLoading(); - $names = ipull($symbols, 'name', 'name'); - asort($names); - $names = array( - '' => pht('(Use Default)'), - ) + $names; - - $control = id(new AphrontFormSelectControl()) - ->setOptions($names); - break; case 'set': $control = id(new AphrontFormTextAreaControl()) ->setCaption(pht('Separate values with newlines or commas.')); diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 40ee7b373e..45fc9f8129 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -72,9 +72,6 @@ final class PhabricatorConfigManagementSetWorkflow } else { $type = $option->getType(); switch ($type) { - case 'class': - $value = (string)$value; - break; default: $value = json_decode($value, true); if (!is_array($value)) { diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 4381ecb7aa..07a765d94c 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -43,21 +43,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } switch ($option->getType()) { - case 'class': - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass($option->getBaseClass()) - ->setConcreteOnly(true) - ->selectSymbolsWithoutLoading(); - $names = ipull($symbols, 'name', 'name'); - if (empty($names[$value])) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' value must name a class extending '%s'.", - $option->getKey(), - $option->getBaseClass())); - } - break; case 'set': $valid = true; if (!is_array($value)) { diff --git a/src/applications/config/type/PhabricatorClassConfigType.php b/src/applications/config/type/PhabricatorClassConfigType.php new file mode 100644 index 0000000000..0ec5e0eb5e --- /dev/null +++ b/src/applications/config/type/PhabricatorClassConfigType.php @@ -0,0 +1,76 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + + $base = $option->getBaseClass(); + $map = $this->getClassOptions($option); + + try { + $ok = class_exists($value); + } catch (Exception $ex) { + $ok = false; + } + + if (!$ok) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not the '. + 'name of a known class. Valid selections are: %s.', + $option->getKey(), + $this->getTypeKey(), + implode(', ', array_keys($map)))); + } + + if (!isset($map[$value])) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the current value ("%s") is not '. + 'a known, concrete subclass of base class "%s". Valid selections '. + 'are: %s.', + $option->getKey(), + $this->getTypeKey(), + $value, + $base, + implode(', ', array_keys($map)))); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $map = array( + '' => pht('(Use Default)'), + ) + $this->getClassOptions($option); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } + + private function getClassOptions(PhabricatorConfigOption $option) { + $symbols = id(new PhutilSymbolLoader()) + ->setType('class') + ->setAncestorClass($option->getBaseClass()) + ->setConcreteOnly(true) + ->selectSymbolsWithoutLoading(); + + $map = ipull($symbols, 'name', 'name'); + asort($map); + + return $map; + } + +} From ec2af086254bc369b9af797facfca1ac728c7ab4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 10:26:04 -0700 Subject: [PATCH 297/543] Move 'set' config option type to new structure Summary: Ref T12845. This move 'set' options (a set of values). Test Plan: Set, deleted and mangled 'set' options from CLI and web UI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18160 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigEditController.php | 9 -- ...PhabricatorConfigManagementSetWorkflow.php | 15 --- .../PhabricatorApplicationConfigOptions.php | 18 ---- .../config/type/PhabricatorSetConfigType.php | 92 +++++++++++++++++++ 5 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 src/applications/config/type/PhabricatorSetConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 61ac0a3b22..ac19fcfbe5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3943,6 +3943,7 @@ phutil_register_library_map(array( 'PhabricatorSelectSetting' => 'applications/settings/setting/PhabricatorSelectSetting.php', 'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php', 'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php', + 'PhabricatorSetConfigType' => 'applications/config/type/PhabricatorSetConfigType.php', 'PhabricatorSetting' => 'applications/settings/setting/PhabricatorSetting.php', 'PhabricatorSettingsAccountPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php', 'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php', @@ -9469,6 +9470,7 @@ phutil_register_library_map(array( 'PhabricatorSelectSetting' => 'PhabricatorSetting', 'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorSetConfigType' => 'PhabricatorTextConfigType', 'PhabricatorSetting' => 'Phobject', 'PhabricatorSettingsAccountPanelGroup' => 'PhabricatorSettingsPanelGroup', 'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index d3d5b3160a..7b0227540a 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -347,9 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'set': - $set_value = array_fill_keys($request->getStrList('value'), true); - break; default: $json = json_decode($value, true); if ($json === null && strtolower($value) != 'null') { @@ -395,8 +392,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'set': - return implode("\n", nonempty(array_keys($value), array())); default: return PhabricatorConfigJSON::prettyPrintJSON($value); } @@ -424,10 +419,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'set': - $control = id(new AphrontFormTextAreaControl()) - ->setCaption(pht('Separate values with newlines or commas.')); - break; default: $control = id(new AphrontFormTextAreaControl()) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 45fc9f8129..b29ef8c8ba 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -76,21 +76,6 @@ final class PhabricatorConfigManagementSetWorkflow $value = json_decode($value, true); if (!is_array($value)) { switch ($type) { - case 'set': - $command = csprintf( - './bin/config set %R %s', - $key, - '{"value1": true, "value2": true}'); - - $message = sprintf( - "%s\n\n %s\n", - pht( - 'Config key "%s" is of type "%s". Specify it in JSON. '. - 'For example:', - $key, - $type), - $command); - break; default: $message = pht( 'Config key "%s" is of type "%s". Specify it in JSON.', diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 07a765d94c..89c9409aba 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -43,24 +43,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } switch ($option->getType()) { - case 'set': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a set, but value is not an array.", - $option->getKey())); - } - foreach ($value as $v) { - if ($v !== true) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a set, but array contains values other ". - "than 'true'.", - $option->getKey())); - } - } - break; case 'wild': default: break; diff --git a/src/applications/config/type/PhabricatorSetConfigType.php b/src/applications/config/type/PhabricatorSetConfigType.php new file mode 100644 index 0000000000..805ae50468 --- /dev/null +++ b/src/applications/config/type/PhabricatorSetConfigType.php @@ -0,0 +1,92 @@ +setCaption(pht('Separate values with newlines or commas.')); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + + $value = preg_split('/[\n,]+/', $value); + foreach ($value as $k => $v) { + if (!strlen($v)) { + unset($value[$k]); + } + $value[$k] = trim($v); + } + + return array_fill_keys($value, true); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + + try { + $value = phutil_json_decode($value); + } catch (Exception $ex) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value you provided is not a '. + 'valid JSON list: when providing a set from the command line, '. + 'specify it as a list of values in JSON. You may need to quote the '. + 'value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + + if ($value) { + if (array_keys($value) !== range(0, count($value) - 1)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", and should be specified on the '. + 'command line as a JSON list of values. You may need to quote '. + 'the value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + } + + return array_fill_keys($value, true); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return implode("\n", array_keys($value)); + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_array($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a list.', + $option->getKey(), + $this->getTypeKey())); + } + + foreach ($value as $k => $v) { + if ($v !== true) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value at index "%s" of the '. + 'list is not "true".', + $option->getKey(), + $this->getTypeKey(), + $k)); + } + } + } + +} From a14b82d4f466ec1efee5e97d771ac56622ea7112 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 27 Jun 2017 06:30:19 -0700 Subject: [PATCH 298/543] Move "wild" config types to new code Summary: Ref T12845. This is the last of the hard-coded types. These are mostly used for values which users don't directly edit, so it's largely OK that they aren't carefully validated. In some cases, it would be good to introduce a separate validator eventually. Test Plan: Edited, deleted and mangled these values via the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18164 --- src/__phutil_library_map__.php | 4 ++ .../PhabricatorConfigEditController.php | 66 ++++--------------- ...PhabricatorConfigManagementSetWorkflow.php | 1 + .../PhabricatorApplicationConfigOptions.php | 14 ++-- .../config/type/PhabricatorJSONConfigType.php | 38 +++++++++++ .../config/type/PhabricatorWildConfigType.php | 39 +++++++++++ .../config/PhabricatorUserConfigOptions.php | 2 +- 7 files changed, 104 insertions(+), 60 deletions(-) create mode 100644 src/applications/config/type/PhabricatorJSONConfigType.php create mode 100644 src/applications/config/type/PhabricatorWildConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ac19fcfbe5..e10b5fceef 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3010,6 +3010,7 @@ phutil_register_library_map(array( 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', + 'PhabricatorJSONConfigType' => 'applications/config/type/PhabricatorJSONConfigType.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', @@ -4263,6 +4264,7 @@ phutil_register_library_map(array( 'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php', 'PhabricatorWebServerSetupCheck' => 'applications/config/check/PhabricatorWebServerSetupCheck.php', 'PhabricatorWeekStartDaySetting' => 'applications/settings/setting/PhabricatorWeekStartDaySetting.php', + 'PhabricatorWildConfigType' => 'applications/config/type/PhabricatorWildConfigType.php', 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', @@ -8348,6 +8350,7 @@ phutil_register_library_map(array( 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', + 'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorJumpNavHandler' => 'Phobject', @@ -9839,6 +9842,7 @@ phutil_register_library_map(array( 'PhabricatorWebContentSource' => 'PhabricatorContentSource', 'PhabricatorWebServerSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorWeekStartDaySetting' => 'PhabricatorSelectSetting', + 'PhabricatorWildConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorWorker' => 'Phobject', 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 7b0227540a..6ab4ccc253 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -325,40 +325,14 @@ final class PhabricatorConfigEditController $e_value = null; $errors = array(); - if ($option->isCustomType()) { $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; } else { - $value = $request->getStr('value'); - if (!strlen($value)) { - $value = null; - - $xaction->setNewValue( - array( - 'deleted' => true, - 'value' => null, - )); - - return array($e_value, $errors, $value, $xaction); - } - - $type = $option->getType(); - $set_value = null; - - switch ($type) { - default: - $json = json_decode($value, true); - if ($json === null && strtolower($value) != 'null') { - $e_value = pht('Invalid'); - $errors[] = pht( - 'The given value must be valid JSON. This means, among '. - 'other things, that you must wrap strings in double-quotes.'); - } else { - $set_value = $json; - } - break; - } + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } if (!$errors) { @@ -389,13 +363,12 @@ final class PhabricatorConfigEditController $option, $entry, $value); - } else { - $type = $option->getType(); - switch ($type) { - default: - return PhabricatorConfigJSON::prettyPrintJSON($value); - } } + + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } private function renderControls( @@ -417,23 +390,10 @@ final class PhabricatorConfigEditController $display_value, $e_value); } else { - $type = $option->getType(); - switch ($type) { - default: - $control = id(new AphrontFormTextAreaControl()) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setCustomClass('PhabricatorMonospaced') - ->setCaption(pht('Enter value in JSON.')); - break; - } - - $control - ->setLabel(pht('Database Value')) - ->setError($e_value) - ->setValue($display_value) - ->setName('value'); - - $controls = array($control); + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } return $controls; diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index b29ef8c8ba..9f50d2eeed 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -70,6 +70,7 @@ final class PhabricatorConfigManagementSetWorkflow throw new PhutilArgumentUsageException($ex->getMessage()); } } else { + // NOTE: For now, this handles both "wild" values and custom types. $type = $option->getType(); switch ($type) { default: diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 89c9409aba..49cd9eb17e 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -24,6 +24,7 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { if ($type) { try { $type->validateStoredValue($option, $value); + $this->didValidateOption($option, $value); } catch (PhabricatorConfigValidationException $ex) { throw $ex; } catch (Exception $ex) { @@ -32,6 +33,8 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { // configuration and raise an error. throw new PhabricatorConfigValidationException($ex->getMessage()); } + + return; } if ($option->isCustomType()) { @@ -40,12 +43,11 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } catch (Exception $ex) { throw new PhabricatorConfigValidationException($ex->getMessage()); } - } - - switch ($option->getType()) { - case 'wild': - default: - break; + } else { + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } $this->didValidateOption($option, $value); diff --git a/src/applications/config/type/PhabricatorJSONConfigType.php b/src/applications/config/type/PhabricatorJSONConfigType.php new file mode 100644 index 0000000000..b1226c6e53 --- /dev/null +++ b/src/applications/config/type/PhabricatorJSONConfigType.php @@ -0,0 +1,38 @@ +newException( + pht( + 'Value for option "%s" (of type "%s") must be specified in JSON, '. + 'but input could not be decoded: %s', + $option->getKey(), + $this->getTypeKey(), + $ex->getMessage())); + } + + return $value; + } + + protected function newControl(PhabricatorConfigOption $option) { + return id(new AphrontFormTextAreaControl()) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) + ->setCustomClass('PhabricatorMonospaced') + ->setCaption(pht('Enter value in JSON.')); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return PhabricatorConfigJSON::prettyPrintJSON($value); + } + +} diff --git a/src/applications/config/type/PhabricatorWildConfigType.php b/src/applications/config/type/PhabricatorWildConfigType.php new file mode 100644 index 0000000000..3f278ca4e9 --- /dev/null +++ b/src/applications/config/type/PhabricatorWildConfigType.php @@ -0,0 +1,39 @@ +newException( + pht( + 'Value for option "%s" (of type "%s") must be specified in JSON, '. + 'but input could not be decoded. (Did you forget to quote a string?)', + $option->getKey(), + $this->getTypeKey())); + } + + return $value; + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + return; + } + +} diff --git a/src/applications/people/config/PhabricatorUserConfigOptions.php b/src/applications/people/config/PhabricatorUserConfigOptions.php index 79be912699..3ef736c8ca 100644 --- a/src/applications/people/config/PhabricatorUserConfigOptions.php +++ b/src/applications/people/config/PhabricatorUserConfigOptions.php @@ -43,7 +43,7 @@ final class PhabricatorUserConfigOptions $this->newOption('user.fields', $custom_field_type, $default) ->setCustomData(id(new PhabricatorUser())->getCustomFieldBaseClass()) ->setDescription(pht('Select and reorder user profile fields.')), - $this->newOption('user.custom-field-definitions', 'map', array()) + $this->newOption('user.custom-field-definitions', 'wild', array()) ->setDescription(pht('Add new simple fields to user profiles.')), $this->newOption('user.require-real-name', 'bool', true) ->setDescription(pht('Always require real name for user profiles.')) From 6984d239b02b035b9d49d23c0012351f0145075d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 27 Jun 2017 07:05:58 -0700 Subject: [PATCH 299/543] Convert Maniphest custom config to new config types Summary: Fixes T12870. Ref T12845. Technically, this addresses the core issue in T12845 too, but I'm going to convert the rest of the `custom:...` types before closing that. In particular, for T12870: - Validates that keywords are unique across priorities. - Fixes missing newline in documentation. - Updates documentation to note that keywords are now mandatory and must be unique across priorities. Test Plan: Edited, deleted and mangled all the Maniphest custom options (priorities, statuses, points, subtypes). Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12870, T12845 Differential Revision: https://secure.phabricator.com/D18165 --- src/__phutil_library_map__.php | 16 ++--- .../PhabricatorConfigEditController.php | 5 ++ .../ManiphestPointsConfigOptionType.php | 10 --- .../config/ManiphestPointsConfigType.php | 14 ++++ .../config/ManiphestPrioritiesConfigType.php | 14 ++++ .../ManiphestPriorityConfigOptionType.php | 10 --- .../ManiphestStatusConfigOptionType.php | 10 --- .../config/ManiphestStatusesConfigType.php | 14 ++++ .../ManiphestSubtypesConfigOptionsType.php | 10 --- .../config/ManiphestSubtypesConfigType.php | 14 ++++ .../PhabricatorManiphestConfigOptions.php | 71 ++++++++++--------- .../constants/ManiphestTaskPriority.php | 15 +++- 12 files changed, 120 insertions(+), 83 deletions(-) delete mode 100644 src/applications/maniphest/config/ManiphestPointsConfigOptionType.php create mode 100644 src/applications/maniphest/config/ManiphestPointsConfigType.php create mode 100644 src/applications/maniphest/config/ManiphestPrioritiesConfigType.php delete mode 100644 src/applications/maniphest/config/ManiphestPriorityConfigOptionType.php delete mode 100644 src/applications/maniphest/config/ManiphestStatusConfigOptionType.php create mode 100644 src/applications/maniphest/config/ManiphestStatusesConfigType.php delete mode 100644 src/applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php create mode 100644 src/applications/maniphest/config/ManiphestSubtypesConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e10b5fceef..1c2a647485 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1488,8 +1488,8 @@ phutil_register_library_map(array( 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', 'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php', - 'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php', - 'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php', + 'ManiphestPointsConfigType' => 'applications/maniphest/config/ManiphestPointsConfigType.php', + 'ManiphestPrioritiesConfigType' => 'applications/maniphest/config/ManiphestPrioritiesConfigType.php', 'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php', 'ManiphestPrioritySearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php', 'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php', @@ -1500,11 +1500,11 @@ phutil_register_library_map(array( 'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php', 'ManiphestSchemaSpec' => 'applications/maniphest/storage/ManiphestSchemaSpec.php', 'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php', - 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php', 'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php', + 'ManiphestStatusesConfigType' => 'applications/maniphest/config/ManiphestStatusesConfigType.php', 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', - 'ManiphestSubtypesConfigOptionsType' => 'applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php', + 'ManiphestSubtypesConfigType' => 'applications/maniphest/config/ManiphestSubtypesConfigType.php', 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', 'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php', 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', @@ -6603,8 +6603,8 @@ phutil_register_library_map(array( 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestNameIndex' => 'ManiphestDAO', - 'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType', - 'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'ManiphestPointsConfigType' => 'PhabricatorJSONConfigType', + 'ManiphestPrioritiesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand', 'ManiphestPrioritySearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', @@ -6615,11 +6615,11 @@ phutil_register_library_map(array( 'ManiphestReportController' => 'ManiphestController', 'ManiphestSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', - 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand', 'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', + 'ManiphestStatusesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestSubpriorityController' => 'ManiphestController', - 'ManiphestSubtypesConfigOptionsType' => 'PhabricatorConfigJSONOptionType', + 'ManiphestSubtypesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestTask' => array( 'ManiphestDAO', 'PhabricatorSubscribableInterface', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 6ab4ccc253..783fcb4dd3 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -290,6 +290,11 @@ final class PhabricatorConfigEditController } catch (PhabricatorConfigValidationException $ex) { $errors[] = $ex->getMessage(); $xaction = null; + } catch (Exception $ex) { + // NOTE: Some older validators throw bare exceptions. Purely in good + // taste, it would be nice to convert these at some point. + $errors[] = $ex->getMessage(); + $xaction = null; } return array( diff --git a/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php b/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php deleted file mode 100644 index f79050b4a1..0000000000 --- a/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php +++ /dev/null @@ -1,10 +0,0 @@ - array( 'name' => pht('Unbreak Now!'), + 'keywords' => array('unbreak'), 'short' => pht('Unbreak!'), 'color' => 'pink', - 'keywords' => array('unbreak'), ), 90 => array( 'name' => pht('Needs Triage'), + 'keywords' => array('triage'), 'short' => pht('Triage'), 'color' => 'violet', - 'keywords' => array('triage'), ), 80 => array( 'name' => pht('High'), + 'keywords' => array('high'), 'short' => pht('High'), 'color' => 'red', - 'keywords' => array('high'), ), 50 => array( 'name' => pht('Normal'), + 'keywords' => array('normal'), 'short' => pht('Normal'), 'color' => 'orange', - 'keywords' => array('normal'), ), 25 => array( 'name' => pht('Low'), + 'keywords' => array('low'), 'short' => pht('Low'), 'color' => 'yellow', - 'keywords' => array('low'), ), 0 => array( 'name' => pht('Wishlist'), + 'keywords' => array('wish', 'wishlist'), 'short' => pht('Wish'), 'color' => 'sky', - 'keywords' => array('wish', 'wishlist'), ), ); - $status_type = 'custom:ManiphestStatusConfigOptionType'; + $status_type = 'maniphest.statuses'; $status_defaults = array( 'open' => array( 'name' => pht('Open'), @@ -265,7 +265,7 @@ EOTEXT ); $fields_json = id(new PhutilJSON())->encodeFormatted($fields_example); - $points_type = 'custom:ManiphestPointsConfigOptionType'; + $points_type = 'maniphest.points'; $points_example_1 = array( 'enabled' => true, @@ -299,7 +299,7 @@ See the example below for a starting point. EOTEXT )); - $subtype_type = 'custom:ManiphestSubtypesConfigOptionsType'; + $subtype_type = 'maniphest.subtypes'; $subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT; $subtype_example = array( array( @@ -349,6 +349,32 @@ EOTEXT , $subtype_default_key)); + $priorities_description = $this->deformat(pht(<<.// List of unique keywords which identify + this priority, like "high" or "low". Each priority must have at least one + keyword and two priorities may not share the same keyword. + - `short` //Optional string.// Alternate shorter name, used in UIs where + there is less space available. + - `color` //Optional string.// Color for this priority, like "red" or + "blue". + - `disabled` //Optional bool.// Set to true to prevent users from choosing + this priority when creating or editing tasks. Existing tasks will not be + affected, and can be batch edited to a different priority or left to + eventually die out. + +You can choose the default priority for newly created tasks with +"maniphest.default-priority". +EOTEXT + )); + return array( $this->newOption('maniphest.custom-field-definitions', 'wild', array()) @@ -367,30 +393,7 @@ EOTEXT $priority_type, $priority_defaults) ->setSummary(pht('Configure Maniphest priority names.')) - ->setDescription( - pht( - 'Allows you to edit or override the default priorities available '. - 'in Maniphest, like "High", "Normal" and "Low". The configuration '. - 'should contain a map of priority constants to priority '. - 'specifications (see defaults below for examples).'. - "\n\n". - 'The keys you can define for a priority are:'. - "\n\n". - ' - `name` Name of the priority.'."\n". - ' - `short` Alternate shorter name, used in UIs where there is '. - ' not much space available.'."\n". - ' - `color` A color for this priority, like "red" or "blue".'. - ' - `keywords` An optional list of keywords which can '. - ' be used to select this priority when using `!priority` '. - ' commands in email.'."\n". - ' - `disabled` Optional boolean to prevent users from choosing '. - ' this priority when creating or editing tasks. Existing '. - ' tasks will be unaffected, and can be batch edited to a '. - ' different priority or left to eventually die out.'. - "\n\n". - 'You can choose which priority is the default for newly created '. - 'tasks with `%s`.', - 'maniphest.default-priority')), + ->setDescription($priorities_description), $this->newOption('maniphest.statuses', $status_type, $status_defaults) ->setSummary(pht('Configure Maniphest task statuses.')) ->setDescription($status_description) diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index 86c7adbcb7..d559299af9 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -203,6 +203,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { $config)); } + $all_keywords = array(); foreach ($config as $key => $value) { if (!ctype_digit((string)$key)) { throw new Exception( @@ -223,9 +224,9 @@ final class ManiphestTaskPriority extends ManiphestConstants { $value, array( 'name' => 'string', + 'keywords' => 'list', 'short' => 'optional string', 'color' => 'optional string', - 'keywords' => 'list', 'disabled' => 'optional bool', )); @@ -242,6 +243,18 @@ final class ManiphestTaskPriority extends ManiphestConstants { 'low', 'critical')); } + + if (isset($all_keywords[$keyword])) { + throw new Exception( + pht( + 'Two different task priorities ("%s" and "%s") have the same '. + 'keyword ("%s"). Keywords must uniquely identify priorities.', + $value['name'], + $all_keywords[$keyword], + $keyword)); + } + + $all_keywords[$keyword] = $value['name']; } } } From b46e2bb4cc6f458a362946620ee8a86e97a07712 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 27 Jun 2017 09:50:57 -0700 Subject: [PATCH 300/543] Convert cluster/projects config options to newer modular structure Summary: Ref T12845. Converts the cluster and project config options to the new stuff; this is mostly just shifting boilerplate around. Test Plan: Edited, deleted, and mangled these options from the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18166 --- src/__phutil_library_map__.php | 20 ++++++------ .../PhabricatorClusterConfigOptions.php | 4 +-- .../PhabricatorNotificationConfigOptions.php | 2 +- .../config/type/PhabricatorConfigType.php | 4 --- ...bricatorNotificationServersConfigType.php} | 32 +++++++++---------- ...abricatorProjectColorsConfigOptionType.php | 10 ------ .../PhabricatorProjectColorsConfigType.php | 14 ++++++++ .../PhabricatorProjectConfigOptions.php | 4 +-- ...habricatorProjectIconsConfigOptionType.php | 10 ------ .../PhabricatorProjectIconsConfigType.php | 14 ++++++++ .../PhabricatorSearchManagementWorkflow.php | 2 +- ...PhabricatorClusterDatabasesConfigType.php} | 24 +++++++------- ...=> PhabricatorClusterSearchConfigType.php} | 17 ++++------ 13 files changed, 77 insertions(+), 80 deletions(-) rename src/applications/notification/config/{PhabricatorNotificationServersConfigOptionType.php => PhabricatorNotificationServersConfigType.php} (85%) delete mode 100644 src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php create mode 100644 src/applications/project/config/PhabricatorProjectColorsConfigType.php delete mode 100644 src/applications/project/config/PhabricatorProjectIconsConfigOptionType.php create mode 100644 src/applications/project/config/PhabricatorProjectIconsConfigType.php rename src/infrastructure/cluster/config/{PhabricatorClusterDatabasesConfigOptionType.php => PhabricatorClusterDatabasesConfigType.php} (82%) rename src/infrastructure/cluster/config/{PhabricatorClusterSearchConfigOptionType.php => PhabricatorClusterSearchConfigType.php} (86%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1c2a647485..095759a351 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2331,13 +2331,13 @@ phutil_register_library_map(array( 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', - 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php', + 'PhabricatorClusterDatabasesConfigType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php', 'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php', 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php', 'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php', 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php', 'PhabricatorClusterNoHostForRoleException' => 'infrastructure/cluster/exception/PhabricatorClusterNoHostForRoleException.php', - 'PhabricatorClusterSearchConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php', + 'PhabricatorClusterSearchConfigType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php', 'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php', 'PhabricatorClusterStrandedException' => 'infrastructure/cluster/exception/PhabricatorClusterStrandedException.php', 'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php', @@ -3199,7 +3199,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationQuery' => 'applications/notification/query/PhabricatorNotificationQuery.php', 'PhabricatorNotificationSearchEngine' => 'applications/notification/query/PhabricatorNotificationSearchEngine.php', 'PhabricatorNotificationServerRef' => 'applications/notification/client/PhabricatorNotificationServerRef.php', - 'PhabricatorNotificationServersConfigOptionType' => 'applications/notification/config/PhabricatorNotificationServersConfigOptionType.php', + 'PhabricatorNotificationServersConfigType' => 'applications/notification/config/PhabricatorNotificationServersConfigType.php', 'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php', 'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php', 'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php', @@ -3614,7 +3614,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', - 'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php', + 'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', 'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php', @@ -3652,7 +3652,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectIconTransaction' => 'applications/project/xaction/PhabricatorProjectIconTransaction.php', - 'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php', + 'PhabricatorProjectIconsConfigType' => 'applications/project/config/PhabricatorProjectIconsConfigType.php', 'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', @@ -7581,13 +7581,13 @@ phutil_register_library_map(array( 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorClassConfigType' => 'PhabricatorTextConfigType', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorClusterDatabasesConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorClusterDatabasesConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorClusterException' => 'Exception', 'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException', 'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException', 'PhabricatorClusterNoHostForRoleException' => 'Exception', - 'PhabricatorClusterSearchConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorClusterSearchConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorClusterServiceHealthRecord' => 'Phobject', 'PhabricatorClusterStrandedException' => 'PhabricatorClusterException', 'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField', @@ -8552,7 +8552,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNotificationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorNotificationServerRef' => 'Phobject', - 'PhabricatorNotificationServersConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorNotificationServersConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorNotificationStatusView' => 'AphrontTagView', 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory', @@ -9065,7 +9065,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectCardView' => 'AphrontTagView', 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', - 'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', 'PhabricatorApplicationTransactionInterface', @@ -9116,7 +9116,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectIconTransaction' => 'PhabricatorProjectTransactionType', - 'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectIconsConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php index c3636c31e0..ca4153a8b2 100644 --- a/src/applications/config/option/PhabricatorClusterConfigOptions.php +++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php @@ -20,7 +20,7 @@ final class PhabricatorClusterConfigOptions } public function getOptions() { - $databases_type = 'custom:PhabricatorClusterDatabasesConfigOptionType'; + $databases_type = 'cluster.databases'; $databases_help = $this->deformat(pht(<<deformat(pht(<<deformat(pht(<< $spec) { if (!is_array($spec)) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is not valid: each entry in '. 'the list must be a dictionary describing a service, but '. @@ -38,7 +36,7 @@ final class PhabricatorNotificationServersConfigOptionType 'disabled' => 'optional bool', )); } catch (Exception $ex) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration has an invalid service '. 'specification (at index "%s"): %s.', @@ -64,7 +62,7 @@ final class PhabricatorNotificationServersConfigOptionType } break; default: - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid '. 'host ("%s", at index "%s") with an unrecognized type ("%s"). '. @@ -81,7 +79,7 @@ final class PhabricatorNotificationServersConfigOptionType case 'https': break; default: - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid '. 'host ("%s", at index "%s") with an invalid protocol ("%s"). '. @@ -95,7 +93,7 @@ final class PhabricatorNotificationServersConfigOptionType $path = idx($spec, 'path'); if ($type == 'admin' && strlen($path)) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid host '. '("%s", at index "%s"). This is an "admin" service but it has a '. @@ -108,7 +106,7 @@ final class PhabricatorNotificationServersConfigOptionType // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it describes the '. 'same host and port ("%s") multiple times. Each host and port '. @@ -120,7 +118,7 @@ final class PhabricatorNotificationServersConfigOptionType if ($value) { if (!$has_admin) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it does not '. 'specify any enabled servers with type "admin". Notifications '. @@ -128,7 +126,7 @@ final class PhabricatorNotificationServersConfigOptionType } if (!$has_client) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it does not '. 'specify any enabled servers with type "client". Notifications '. diff --git a/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php b/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php deleted file mode 100644 index 4cd8c09bbc..0000000000 --- a/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php +++ /dev/null @@ -1,10 +0,0 @@ -deformat(pht(<<deformat(pht(<< $spec) { if (!is_array($spec)) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration is not valid: each entry in the '. 'list must be a dictionary describing a database host, but '. @@ -40,7 +38,7 @@ final class PhabricatorClusterDatabasesConfigOptionType 'persistent' => 'optional bool', )); } catch (Exception $ex) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration has an invalid host '. 'specification (at index "%s"): %s.', @@ -57,7 +55,7 @@ final class PhabricatorClusterDatabasesConfigOptionType case 'replica': break; default: - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration describes an invalid '. 'host ("%s", at index "%s") with an unrecognized role ("%s"). '. @@ -78,7 +76,7 @@ final class PhabricatorClusterDatabasesConfigOptionType // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration is invalid: it describes the '. 'same host ("%s") multiple times. Each host should appear only '. diff --git a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php similarity index 86% rename from src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php rename to src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php index 90ead23e6d..ae54ff1a11 100644 --- a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php +++ b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php @@ -1,20 +1,17 @@ $spec) { From 8e5afb56af200571c045f1d9c17b6ad1bb5d3aea Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 27 Jun 2017 12:08:13 -0700 Subject: [PATCH 301/543] Drop interactive login from sshd example Summary: Prevent slightly disturbing output on SSH authentication failure that implies some kind of interactive logins are possible: `Permission denied (publickey,keyboard-interactive).` Test Plan: doitlive Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18167 --- resources/sshd/sshd_config.phabricator.example | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/sshd/sshd_config.phabricator.example b/resources/sshd/sshd_config.phabricator.example index 2f2a581223..506d32bbbf 100644 --- a/resources/sshd/sshd_config.phabricator.example +++ b/resources/sshd/sshd_config.phabricator.example @@ -18,6 +18,7 @@ AllowTcpForwarding no PrintMotd no PrintLastLog no PasswordAuthentication no +ChallengeResponseAuthentication no AuthorizedKeysFile none PidFile /var/run/sshd-phabricator.pid From 83266e805c4615689b62af2325411c9c9c5ec5a9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 28 Jun 2017 10:37:43 +0200 Subject: [PATCH 302/543] Update people image for projects Summary: Better diversity. Test Plan: Photoshop Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18170 --- resources/builtin/projects/v3/people.png | Bin 13654 -> 13619 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/builtin/projects/v3/people.png b/resources/builtin/projects/v3/people.png index b9118c2cfaa4e905e315cd7b5421fcdcf93de0c5..5bb42656dfdd08b038a15cb304093d026588ef54 100644 GIT binary patch literal 13619 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 NMMXhVzFHOx`Ck6Fj(kaQE+?x4wVh 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 Date: Fri, 30 Jun 2017 06:24:53 -0700 Subject: [PATCH 303/543] Move misplaced validation for ambiguous fields in "Test Plan" to the right place Summary: When users use the web UI to enter text like "Reviewers: x" into the "Summary" or "Test Plan", we can end up with an ambiguous commit message. Some time ago we added a warning about this to the "Summary" field, and //attempted// to add it to the "Test Plan" field, but it actually gets called from the wrong place. Remove the code from the wrong place (no callers, not reachable) and put it in the right place. This fixes an issue where users could edit a test plan from the web UI to add the text "Tests: ..." and cause ambiguities on a later "arc diff --edit". Test Plan: {F5026603} Reviewers: chad, amckinley Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18175 --- .../field/DifferentialTestPlanCommitMessageField.php | 7 ------- .../xaction/DifferentialRevisionTestPlanTransaction.php | 5 ++++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php b/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php index 178e51d985..a477a9036e 100644 --- a/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php +++ b/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php @@ -50,11 +50,4 @@ final class DifferentialTestPlanCommitMessageField ); } - public function validateTransactions($object, array $xactions) { - return $this->validateCommitMessageCorpusTransactions( - $object, - $xactions, - pht('Test Plan')); - } - } diff --git a/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php b/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php index bf2beab3d8..c7c77fbcff 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php @@ -55,7 +55,10 @@ final class DifferentialRevisionTestPlanTransaction } public function validateTransactions($object, array $xactions) { - $errors = array(); + $errors = $this->validateCommitMessageCorpusTransactions( + $object, + $xactions, + pht('Test Plan')); $is_required = PhabricatorEnv::getEnvConfig( 'differential.require-test-plan-field'); From 4e047f7b31d2734a0e69c1bb7333941217361f71 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 30 Jun 2017 06:56:10 -0700 Subject: [PATCH 304/543] Correct a datasource issue when viewing repository URIs in "Manage Repository" Summary: Fixes T12884. In cases other than this UI, applications access URIs through the Repository they're part of. This means that applications interact with URIs which have gone through the correction/adjustment logic in `PhabricatorRepository->attachURIs()`, which fixes up "builtin" URIs to have the right values based on configuration. In this case (and, as far as I can tell, only this case) we load the URI directly //and// act on its properties which depend on configuration and repository state. This can mean we're using a different view of the URI than we should be. To fix this: after loading the URI, reload it through the repository so the relevant adjustments are applied. I think this is the most reasonable fix. We could try to make `RepositoryURIQuery` somehow enforce this, but the cost of this error is small (mild confusion about display state), the other things which do direct loads don't depend on this state (editing), and everything else loads via a repository and is likely to continue doing that forever. Test Plan: {F5026633} Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12884 Differential Revision: https://secure.phabricator.com/D18176 --- .../DiffusionRepositoryURIViewController.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php index 61335d20d0..308df8f0d2 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php @@ -23,6 +23,22 @@ final class DiffusionRepositoryURIViewController return new Aphront404Response(); } + // For display, reload the URI by loading it through the repository. This + // may adjust builtin URIs for repository configuration, so we may end up + // with a different view of builtin URIs than we'd see if we loaded them + // directly from the database. See T12884. + $repository_with_uris = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->needURIs(true) + ->execute(); + + $repository_uris = $repository->getURIs(); + $repository_uris = mpull($repository_uris, null, 'getID'); + $uri = idx($repository_uris, $uri->getID()); + if (!$uri) { + return new Aphront404Response(); + } + $title = array( pht('URI'), $repository->getDisplayName(), From eab8d8a22c4fc93d47ed2f185632a2a7d2f41876 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 30 Jun 2017 07:02:36 -0700 Subject: [PATCH 305/543] Use the correct "completed" time in Harbormaster display UI Summary: Fixes T12883. The task seems correct to me and I think this is a copy/paste mistake that probably blames to me. Test Plan: Fiddled these numbers, viewed a build in Harbormaster, saw the adjusted time. Reviewers: chad, amckinley Reviewed By: chad Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T12883 Differential Revision: https://secure.phabricator.com/D18177 --- .../harbormaster/controller/HarbormasterBuildViewController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 7932451d2e..5b7171a200 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -141,7 +141,7 @@ final class HarbormasterBuildViewController if ($ended) { $when[] = pht( 'Completed at %s', - phabricator_datetime($started, $viewer)); + phabricator_datetime($ended, $viewer)); $duration = ($ended - $started); if ($duration) { From b25b379ca089055b63da6a5ba984f6ab0e2af6b9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Jul 2017 17:38:17 +0200 Subject: [PATCH 306/543] Move Diffusion Browse to a single column layout Summary: The main change here is moving (compare, search, history) into buttons in the header bar on all browse views. This allows Directory Browsing to be full width, since there is no other curtain information. File, Image, LFS, Binary all stay in TwoColumn layouts with the same buttons in the header. Test Plan: Test viewing a directory, file, image, binary file, readme, and fake a gitlfs. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17766 --- resources/celerity/map.php | 2 + .../controller/DiffusionBrowseController.php | 197 +++++++++--------- .../controller/DiffusionController.php | 1 + .../diffusion/view/DiffusionReadmeView.php | 6 +- src/view/layout/PhabricatorActionView.php | 5 + .../diffusion/diffusion-repository.css | 13 ++ 6 files changed, 125 insertions(+), 99 deletions(-) create mode 100644 webroot/rsrc/css/application/diffusion/diffusion-repository.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0619fc34f8..6a3b8778e6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -74,6 +74,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-history.css' => '4540f568', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', + 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', @@ -572,6 +573,7 @@ return array( 'diffusion-history-css' => '4540f568', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '419dd5b6', + 'diffusion-repository-css' => 'ee6f20ec', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 4eb6144ede..3fa9e62948 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -106,7 +106,6 @@ final class DiffusionBrowseController extends DiffusionController { $path = $drequest->getPath(); $blame_key = PhabricatorDiffusionBlameSetting::SETTINGKEY; - $show_blame = $request->getBool( 'blame', $viewer->getUserSetting($blame_key)); @@ -160,6 +159,7 @@ final class DiffusionBrowseController extends DiffusionController { $hit_time_limit = $response['tooSlow']; $file_phid = $response['filePHID']; + $show_editor = false; if ($hit_byte_limit) { $corpus = $this->buildErrorCorpus( pht( @@ -215,6 +215,7 @@ final class DiffusionBrowseController extends DiffusionController { } else { $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); + $show_editor = true; // Build the content of the file. $corpus = $this->buildCorpus( @@ -234,12 +235,7 @@ final class DiffusionBrowseController extends DiffusionController { require_celerity_resource('diffusion-source-css'); // Render the page. - $view = $this->buildCurtain($drequest); - $curtain = $this->enrichCurtain( - $view, - $drequest, - $show_blame); - + $curtain = $this->buildCurtain($drequest, $show_blame, $show_editor); $properties = $this->buildPropertyView($drequest); $header = $this->buildHeaderView($drequest); $header->setHeaderIcon('fa-file-code-o'); @@ -327,7 +323,7 @@ final class DiffusionBrowseController extends DiffusionController { $reason = $results->getReasonForEmptyResultSet(); - $curtain = $this->buildCurtain($drequest); + $actions = $this->getActions($drequest); $details = $this->buildPropertyView($drequest); $header = $this->buildHeaderView($drequest); @@ -363,9 +359,9 @@ final class DiffusionBrowseController extends DiffusionController { ->setPaths($results->getPaths()) ->setUser($request->getUser()); - $browse_header = id(new PHUIHeaderView()) - ->setHeader(nonempty(basename($drequest->getPath()), '/')) - ->setHeaderIcon('fa-folder-open'); + $title = nonempty(basename($drequest->getPath()), '/'); + $icon = 'fa-folder-open'; + $browse_header = $this->buildPanelHeaderView($title, $icon, $actions); $browse_panel = id(new PHUIObjectBoxView()) ->setHeader($browse_header) @@ -400,15 +396,11 @@ final class DiffusionBrowseController extends DiffusionController { $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn( + ->setFooter( array( $branch_panel, $empty_result, $browse_panel, - )) - ->setFooter( - array( $open_revisions, $readme, )); @@ -745,14 +737,14 @@ final class DiffusionBrowseController extends DiffusionController { Javelin::initBehavior('load-blame', array('id' => $id)); - - $edit = $this->renderEditButton(); $file = $this->renderFileButton(); - $header = id(new PHUIHeaderView()) - ->setHeader(basename($this->getDiffusionRequest()->getPath())) - ->setHeaderIcon('fa-file-code-o') - ->addActionLink($edit) - ->addActionLink($file); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-file-code-o'; + $drequest = $this->getDiffusionRequest(); + $actions = $this->getActions($drequest); + + $header = $this->buildPanelHeaderView($title, $icon, $actions); + $header->addActionLink($file); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -791,14 +783,23 @@ final class DiffusionBrowseController extends DiffusionController { return $corpus; } - private function enrichCurtain( - PHUICurtainView $curtain, + private function buildCurtain( DiffusionRequest $drequest, - $show_blame) { + $show_blame, + $show_editor) { + $curtain = $this->newCurtainView($drequest); $viewer = $this->getViewer(); $base_uri = $this->getRequest()->getRequestURI(); + $user = $this->getRequest()->getUser(); + $repository = $drequest->getRepository(); + $path = $drequest->getPath(); + $line = nonempty((int)$drequest->getLine(), 1); + + $editor_link = $user->loadEditorLink($path, $line, $repository); + $template = $user->loadEditorLink($path, '%l', $repository); + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Last Change')) @@ -827,6 +828,15 @@ final class DiffusionBrowseController extends DiffusionController { ->setUser($viewer) ->setRenderAsForm($viewer->isLoggedIn())); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Open in Editor')) + ->setHref($editor_link) + ->setIcon('fa-pencil') + ->setID('editor_link') + ->setMetadata(array('link_template' => $template)) + ->setDisabled(!$editor_link)); + $href = null; if ($this->getRequest()->getStr('lint') !== null) { $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); @@ -897,41 +907,16 @@ final class DiffusionBrowseController extends DiffusionController { return $curtain; } - private function renderEditButton() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $drequest = $this->getDiffusionRequest(); - - $repository = $drequest->getRepository(); - $path = $drequest->getPath(); - $line = nonempty((int)$drequest->getLine(), 1); - - $editor_link = $user->loadEditorLink($path, $line, $repository); - $template = $user->loadEditorLink($path, '%l', $repository); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Open in Editor')) - ->setHref($editor_link) - ->setIcon('fa-pencil') - ->setID('editor_link') - ->setMetadata(array('link_template' => $template)) - ->setDisabled(!$editor_link); - - return $button; - } - private function renderFileButton($file_uri = null, $label = null) { $base_uri = $this->getRequest()->getRequestURI(); if ($file_uri) { - $text = pht('Download Raw File'); + $text = pht('Download Raw'); $href = $file_uri; $icon = 'fa-download'; } else { - $text = pht('View Raw File'); + $text = pht('View Raw'); $href = $base_uri->alter('view', 'raw'); $icon = 'fa-file-text'; } @@ -1374,10 +1359,12 @@ final class DiffusionBrowseController extends DiffusionController { ))); $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(basename($this->getDiffusionRequest()->getPath())) - ->addActionLink($file) - ->setHeaderIcon('fa-file-image-o'); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-file-image-o'; + $drequest = $this->getDiffusionRequest(); + $actions = $this->getActions($drequest); + $header = $this->buildPanelHeaderView($title, $icon, $actions); + $header->addActionLink($file); return id(new PHUIObjectBoxView()) ->setHeader($header) @@ -1393,9 +1380,12 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($text); $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) - ->addActionLink($file); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-file'; + $drequest = $this->getDiffusionRequest(); + $actions = $this->getActions($drequest); + $header = $this->buildPanelHeaderView($title, $icon, $actions); + $header->addActionLink($file); $box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -1656,52 +1646,61 @@ final class DiffusionBrowseController extends DiffusionController { return $header; } - protected function buildCurtain(DiffusionRequest $drequest) { + protected function buildPanelHeaderView($title, $icon, array $actions) { + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($icon) + ->addClass('diffusion-panel-header-view'); + + foreach ($actions as $action_link) { + if ($action_link) { + $header->addActionLink($action_link); + } + } + return $header; + + } + + protected function getActions(DiffusionRequest $drequest) { $viewer = $this->getViewer(); $repository = $drequest->getRepository(); - - $curtain = $this->newCurtainView($drequest); - - $history_uri = $drequest->generateURI( - array( - 'action' => 'history', - )); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) - ->setHref($history_uri) - ->setIcon('fa-list')); - + $history_uri = $drequest->generateURI(array('action' => 'history')); $behind_head = $drequest->getSymbolicCommit(); - - if ($repository->supportsBranchComparison()) { - $compare_uri = $drequest->generateURI( - array( - 'action' => 'compare', - )); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Compare Against...')) - ->setIcon('fa-code-fork') - ->setWorkflow(true) - ->setHref($compare_uri)); - } - $head_uri = $drequest->generateURI( array( 'commit' => '', 'action' => 'browse', )); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Jump to HEAD')) + + if ($repository->supportsBranchComparison()) { + $compare_uri = $drequest->generateURI(array('action' => 'compare')); + $compare = id(new PHUIButtonView()) + ->setText(pht('Compare')) + ->setIcon('fa-code-fork') + ->setWorkflow(true) + ->setTag('a') + ->setHref($compare_uri) + ->setColor(PHUIButtonView::GREY); + } + + $head = null; + if ($behind_head) { + $head = id(new PHUIButtonView()) + ->setText(pht('Back to HEAD')) ->setHref($head_uri) ->setIcon('fa-home') - ->setDisabled(!$behind_head)); + ->setColor(PHUIButtonView::GREY); + } - return $curtain; + $history = id(new PHUIButtonView()) + ->setText(pht('History')) + ->setHref($history_uri) + ->setTag('a') + ->setIcon('fa-history') + ->setColor(PHUIButtonView::GREY); + + return array($history, $compare, $head); } protected function buildPropertyView( @@ -1902,9 +1901,11 @@ final class DiffusionBrowseController extends DiffusionController { // show the user an error if we can't, rather than making them click // through to hit an error. - $header = id(new PHUIHeaderView()) - ->setHeader(basename($this->getDiffusionRequest()->getPath())) - ->setHeaderIcon('fa-archive'); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-archive'; + $drequest = $this->getDiffusionRequest(); + $actions = $this->getActions($drequest); + $header = $this->buildPanelHeaderView($title, $icon, $actions); $severity = PHUIInfoView::SEVERITY_NOTICE; diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index abebbace90..a73285d111 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -44,6 +44,7 @@ abstract class DiffusionController extends PhabricatorController { private function loadContext(array $options) { $request = $this->getRequest(); $viewer = $this->getViewer(); + require_celerity_resource('diffusion-repository-css'); $identifier = $this->getRepositoryIdentifierFromRequest($request); diff --git a/src/applications/diffusion/view/DiffusionReadmeView.php b/src/applications/diffusion/view/DiffusionReadmeView.php index b475fa88ac..88e2e84115 100644 --- a/src/applications/diffusion/view/DiffusionReadmeView.php +++ b/src/applications/diffusion/view/DiffusionReadmeView.php @@ -98,8 +98,12 @@ final class DiffusionReadmeView extends DiffusionView { ->setFluid(true) ->appendChild($readme_content); + $header = id(new PHUIHeaderView()) + ->setHeader($readme_name) + ->addClass('diffusion-panel-header-view'); + return id(new PHUIObjectBoxView()) - ->setHeaderText($readme_name) + ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($document) ->addClass('diffusion-readme-view'); diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 85b9d3abfe..f6de8eca5b 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -119,6 +119,11 @@ final class PhabricatorActionView extends AphrontView { return $this->openInNewWindow; } + public function setID($id) { + $this->id = $id; + return $this; + } + public function getID() { if (!$this->id) { $this->id = celerity_generate_unique_node_id(); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-repository.css b/webroot/rsrc/css/application/diffusion/diffusion-repository.css new file mode 100644 index 0000000000..99ddd72940 --- /dev/null +++ b/webroot/rsrc/css/application/diffusion/diffusion-repository.css @@ -0,0 +1,13 @@ +/** + * @provides diffusion-repository-css + */ + +.diffusion-page-header-view a.phui-header-action-link { + display: block; + float: none; +} + +.phui-box.phui-object-box.phui-box-blue-property + .diffusion-panel-header-view.phui-header-shell { + padding: 8px 4px 8px 16px; +} From 3536fe2877294e0bed5489b0b67edcf30f247454 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 2 Jul 2017 14:25:10 +0000 Subject: [PATCH 307/543] Update Diffusion conduit text Summary: Fixes T12888 Test Plan: Verify text is there. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12888 Differential Revision: https://secure.phabricator.com/D18178 --- .../repository/conduit/RepositoryQueryConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php index 61435e2a1b..8b5005d8ec 100644 --- a/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php +++ b/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php @@ -14,7 +14,7 @@ final class RepositoryQueryConduitAPIMethod public function getMethodStatusDescription() { return pht( 'This method is frozen and will eventually be deprecated. New code '. - 'should use "diffusion.repository.query" instead.'); + 'should use "diffusion.repository.search" instead.'); } public function getMethodDescription() { From a6f018210466a1c7af08602fc91273090f63bc93 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Jul 2017 07:21:27 -0700 Subject: [PATCH 308/543] Fix an issue where repositories with hyphens could sort improperly in typeaheads Summary: Fixes T12894. See that task for discussion. Test Plan: - Created repositories `abcdef`, then `abcdef-a` through `abcdef-f`. - Before patch, awkward sort order. - After patch, query for `abcdef` hits `abcdef` first. - See T12894 for details and screenshots. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12894 Differential Revision: https://secure.phabricator.com/D18179 --- .../diffusion/typeahead/DiffusionRepositoryDatasource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php index 0b3f191563..e0662b1e1a 100644 --- a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php @@ -54,7 +54,7 @@ final class DiffusionRepositoryDatasource $parts[] = $monogram; } - $name = implode(' ', $parts); + $name = implode("\n", $parts); $vcs = $repository->getVersionControlSystem(); $vcs_type = PhabricatorRepositoryType::getNameForRepositoryType($vcs); From 7b6b3d722ad7109a768104ddb875df228318cf87 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Jul 2017 10:25:37 -0700 Subject: [PATCH 309/543] Document the need to restart Phabricator after performing a restore Summary: Depending on how you perform a restore, APC (or, e.g., running daemon processes) might be poisoned with out-of-date caches. Add a note to advise installs to restart after restoring data. See also lengthy fishing expedition support thread. Test Plan: Read the text. Reviewers: chad, amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18180 --- src/docs/user/configuration/configuring_backups.diviner | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/docs/user/configuration/configuring_backups.diviner b/src/docs/user/configuration/configuring_backups.diviner index 1f13e0e17f..9776448216 100644 --- a/src/docs/user/configuration/configuring_backups.diviner +++ b/src/docs/user/configuration/configuring_backups.diviner @@ -24,6 +24,12 @@ same steps you would if you were creating a backup and then restoring it, you will just backup the old machine and then restore the data onto the new machine. +WARNING: You need to restart Phabricator after restoring data. + +Restarting Phabricator after performing a restore makes sure that caches are +flushed properly. For complete instructions, see +@{article:Restarting Phabricator}. + Backup: MySQL Databases ======================= From e516358d54d2e7f15da2e2ddf474c8d88ae7ab55 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Jul 2017 22:09:12 +0100 Subject: [PATCH 310/543] Add tabs to Diffusion for consistent navigation Summary: Adds a responsive tab bar navigation to Diffusion. Working through the new design here in pieces, so keep in mind M1477 is the target. Notably: - Removes "branches" and "tags" from RevisionView, now on tabs - Keeps "browse", "history", "readme" on RevisionView - Adds tabs for all main views, including Graph... unless how that feels, so let me know. Test Plan: Browse all pages, desktop and mobile. Test hg, svn, git repositories. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18161 --- .../DiffusionBranchTableController.php | 3 + .../controller/DiffusionController.php | 80 ++++++- .../controller/DiffusionGraphController.php | 27 +-- .../controller/DiffusionHistoryController.php | 29 +-- .../DiffusionRepositoryController.php | 218 +----------------- .../controller/DiffusionTagListController.php | 3 + 6 files changed, 112 insertions(+), 248 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index 7f8ae11f57..04d00f0306 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -71,8 +71,11 @@ final class DiffusionBranchTableController extends DiffusionController { ->setHeader(pht('Branches')) ->setHeaderIcon('fa-code-fork'); + $tabs = $this->buildTabsView('branch'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter(array( $content, )); diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index a73285d111..8de17c03a2 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -123,10 +123,10 @@ abstract class DiffusionController extends PhabricatorController { private function buildCrumbList(array $spec = array()) { $spec = $spec + array( - 'commit' => null, - 'tags' => null, - 'branches' => null, - 'view' => null, + 'commit' => null, + 'tags' => null, + 'branches' => null, + 'view' => null, ); $crumb_list = array(); @@ -315,7 +315,7 @@ abstract class DiffusionController extends PhabricatorController { protected function renderStatusMessage($title, $body) { return id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle($title) ->setFlush(true) ->appendChild($body); @@ -410,4 +410,74 @@ abstract class DiffusionController extends PhabricatorController { ->setContent($readme_corpus); } + protected function buildTabsView($key) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $view = new PHUIListView(); + + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('home') + ->setName(pht('Home')) + ->setIcon('fa-home') + ->setHref($drequest->generateURI( + array( + 'action' => 'branch', + 'path' => '/', + ))) + ->setSelected($key == 'home')); + + if (!$repository->isSVN()) { + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('branch') + ->setName(pht('Branches')) + ->setIcon('fa-code-fork') + ->setHref($drequest->generateURI( + array( + 'action' => 'branches', + ))) + ->setSelected($key == 'branch')); + } + + if (!$repository->isSVN()) { + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('tags') + ->setName(pht('Tags')) + ->setIcon('fa-tags') + ->setHref($drequest->generateURI( + array( + 'action' => 'tags', + ))) + ->setSelected($key == 'tags')); + } + + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('history') + ->setName(pht('History')) + ->setIcon('fa-history') + ->setHref($drequest->generateURI( + array( + 'action' => 'history', + ))) + ->setSelected($key == 'history')); + + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('graph') + ->setName(pht('Graph')) + ->setIcon('fa-code-fork') + ->setHref($drequest->generateURI( + array( + 'action' => 'graph', + ))) + ->setSelected($key == 'graph')); + + return $view; + + } + } diff --git a/src/applications/diffusion/controller/DiffusionGraphController.php b/src/applications/diffusion/controller/DiffusionGraphController.php index 8428a8152d..8ec909139a 100644 --- a/src/applications/diffusion/controller/DiffusionGraphController.php +++ b/src/applications/diffusion/controller/DiffusionGraphController.php @@ -68,8 +68,11 @@ final class DiffusionGraphController extends DiffusionController { ->setTable($graph) ->setPager($pager); + $tabs = $this->buildTabsView('graph'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter($graph_view); return $this->newPage() @@ -81,25 +84,17 @@ final class DiffusionGraphController extends DiffusionController { private function buildHeader(DiffusionRequest $drequest) { $viewer = $this->getViewer(); - $tag = $this->renderCommitHashTag($drequest); - $history_uri = $drequest->generateURI( - array( - 'action' => 'history', - )); - - $history_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('History')) - ->setHref($history_uri) - ->setIcon('fa-history'); + $no_path = !strlen($drequest->getPath()); + if ($no_path) { + $header_text = pht('Graph'); + } else { + $header_text = $this->renderPathLinks($drequest, $mode = 'history'); + } $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()) - ->addTag($tag) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) - ->setHeaderIcon('fa-code-fork') - ->addActionLink($history_button); + ->setHeader($header_text) + ->setHeaderIcon('fa-code-fork'); return $header; diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index c2f718f11e..fde35133cc 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -59,8 +59,11 @@ final class DiffusionHistoryController extends DiffusionController { ->addClass('mlb') ->appendChild($pager); + $tabs = $this->buildTabsView('history'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter(array( $history_list, $pager, @@ -76,30 +79,18 @@ final class DiffusionHistoryController extends DiffusionController { private function buildHeader(DiffusionRequest $drequest) { $viewer = $this->getViewer(); - $tag = $this->renderCommitHashTag($drequest); - $show_graph = !strlen($drequest->getPath()); + $no_path = !strlen($drequest->getPath()); + if ($no_path) { + $header_text = pht('History'); + } else { + $header_text = $this->renderPathLinks($drequest, $mode = 'history'); + } $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()) - ->addTag($tag) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeader($header_text) ->setHeaderIcon('fa-clock-o'); - if ($show_graph) { - $graph_uri = $drequest->generateURI( - array( - 'action' => 'graph', - )); - - $graph_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Graph')) - ->setHref($graph_uri) - ->setIcon('fa-code-fork'); - $header->addActionLink($graph_button); - } - return $header; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 362f78bd3f..d8451db2db 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -84,9 +84,12 @@ final class DiffusionRepositoryController extends DiffusionController { ->setErrors(array($empty_message)); } + $tabs = $this->buildTabsView('home'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->setTabs($tabs) ->setMainColumn(array( $property_table, $description, @@ -134,32 +137,9 @@ final class DiffusionRepositoryController extends DiffusionController { 'limit' => $browse_pager->getPageSize() + 1, )); - if ($this->needTagFuture()) { - $tag_limit = $this->getTagLimit(); - $this->tagFuture = $this->callConduitMethod( - 'diffusion.tagsquery', - array( - // On the home page, we want to find tags on any branch. - 'commit' => null, - 'limit' => $tag_limit + 1, - )); - } - - if ($this->needBranchFuture()) { - $branch_limit = $this->getBranchLimit(); - $this->branchFuture = $this->callConduitMethod( - 'diffusion.branchquery', - array( - 'closed' => false, - 'limit' => $branch_limit + 1, - )); - } - $futures = array( $this->historyFuture, $this->browseFuture, - $this->tagFuture, - $this->branchFuture, ); $futures = array_filter($futures); $futures = new FutureIterator($futures); @@ -241,26 +221,6 @@ final class DiffusionRepositoryController extends DiffusionController { $history, $history_exception); - try { - $content[] = $this->buildTagListTable($drequest); - } catch (Exception $ex) { - if (!$repository->isImporting()) { - $content[] = $this->renderStatusMessage( - pht('Unable to Load Tags'), - $ex->getMessage()); - } - } - - try { - $content[] = $this->buildBranchListTable($drequest); - } catch (Exception $ex) { - if (!$repository->isImporting()) { - $content[] = $this->renderStatusMessage( - pht('Unable to Load Branches'), - $ex->getMessage()); - } - } - if ($readme) { $content[] = $readme; } @@ -366,6 +326,12 @@ final class DiffusionRepositoryController extends DiffusionController { $this->renderCloneURI($repository, $uri)); } + if (!$view->hasAnyProperties()) { + $view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Repository has no URIs set.')); + } + $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) @@ -412,123 +378,6 @@ final class DiffusionRepositoryController extends DiffusionController { return $box; } - private function buildBranchListTable(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - - if (!$this->needBranchFuture()) { - return null; - } - - $branches = $this->branchFuture->resolve(); - if (!$branches) { - return null; - } - - $limit = $this->getBranchLimit(); - $more_branches = (count($branches) > $limit); - $branches = array_slice($branches, 0, $limit); - - $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); - - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withIdentifiers(mpull($branches, 'getCommitIdentifier')) - ->withRepository($drequest->getRepository()) - ->execute(); - - $table = id(new DiffusionBranchTableView()) - ->setUser($viewer) - ->setDiffusionRequest($drequest) - ->setBranches($branches) - ->setCommits($commits); - - $panel = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Branches')); - - if ($more_branches) { - $header->setSubheader(pht('Showing %d branches.', $limit)); - } - - $button = id(new PHUIButtonView()) - ->setText(pht('Show All')) - ->setTag('a') - ->setIcon('fa-code-fork') - ->setHref($drequest->generateURI( - array( - 'action' => 'branches', - ))); - - $header->addActionLink($button); - $panel->setHeader($header); - $panel->setTable($table); - - return $panel; - } - - private function buildTagListTable(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - $repository = $drequest->getRepository(); - - if (!$this->needTagFuture()) { - return null; - } - - $tags = $this->tagFuture->resolve(); - $tags = DiffusionRepositoryTag::newFromConduit($tags); - if (!$tags) { - return null; - } - - $tag_limit = $this->getTagLimit(); - $more_tags = (count($tags) > $tag_limit); - $tags = array_slice($tags, 0, $tag_limit); - - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withIdentifiers(mpull($tags, 'getCommitIdentifier')) - ->withRepository($repository) - ->needCommitData(true) - ->execute(); - - $view = id(new DiffusionTagTableView()) - ->setUser($viewer) - ->setDiffusionRequest($drequest) - ->setTags($tags) - ->setCommits($commits); - - $phids = $view->getRequiredHandlePHIDs(); - $handles = $this->loadViewerHandles($phids); - $view->setHandles($handles); - - $panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Tags')); - - if ($more_tags) { - $header->setSubheader( - pht('Showing the %d most recent tags.', $tag_limit)); - } - - $button = id(new PHUIButtonView()) - ->setText(pht('Show All Tags')) - ->setTag('a') - ->setIcon('fa-tag') - ->setHref($drequest->generateURI( - array( - 'action' => 'tags', - ))); - - $header->addActionLink($button); - - $panel->setHeader($header); - $panel->setTable($view); - $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - - return $panel; - } - private function buildHistoryTable( $history_results, $history, @@ -567,30 +416,10 @@ final class DiffusionRepositoryController extends DiffusionController { $history_table->setIsHead(true); - $history = id(new PHUIButtonView()) - ->setText(pht('History')) - ->setHref($drequest->generateURI( - array( - 'action' => 'history', - ))) - ->setTag('a') - ->setIcon('fa-history'); - - $graph = id(new PHUIButtonView()) - ->setText(pht('Graph')) - ->setHref($drequest->generateURI( - array( - 'action' => 'graph', - ))) - ->setTag('a') - ->setIcon('fa-code-fork'); - $panel = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Commits')) - ->addActionLink($graph) - ->addActionLink($history); + ->setHeader(pht('Recent Commits')); $panel->setHeader($header); $panel->setTable($history_table); @@ -721,35 +550,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setDisplayURI($display); } - private function needTagFuture() { - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - // No tags in SVN. - return false; - } - - return true; - } - private function getTagLimit() { return 15; } - private function needBranchFuture() { - $drequest = $this->getDiffusionRequest(); - - if ($drequest->getBranch() === null) { - return false; - } - - return true; - } - - private function getBranchLimit() { - return 15; - } - } diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 5e765ddcb6..f7cd032e1a 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -88,8 +88,11 @@ final class DiffusionTagListController extends DiffusionController { )); $crumbs->setBorder(true); + $tabs = $this->buildTabsView('tags'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter($content); return $this->newPage() From 8d11e127ff15fbf133c33f97c994bcbc6400803c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Jul 2017 09:35:59 -0700 Subject: [PATCH 311/543] Fix several pieces of UI language describing "draft/archive" rules in Phame Summary: Ref T12900. We implement one rule, but tell users a different (older) rule. See T12900 for discussion and history. Test Plan: - Verified draft/archived posts can't be seen by users who don't have permission to edit the blog. - Drafted, archived, and published posts and read the related text. - Looked through the changes I dug up in T12900#228748 for other strings I might have missed. {F5033860} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12900 Differential Revision: https://secure.phabricator.com/D18182 --- .../controller/post/PhamePostArchiveController.php | 5 +++-- .../controller/post/PhamePostViewController.php | 12 ++++++++---- src/applications/phame/storage/PhamePost.php | 5 +++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/applications/phame/controller/post/PhamePostArchiveController.php b/src/applications/phame/controller/post/PhamePostArchiveController.php index b8647121ef..093e7019bf 100644 --- a/src/applications/phame/controller/post/PhamePostArchiveController.php +++ b/src/applications/phame/controller/post/PhamePostArchiveController.php @@ -42,8 +42,9 @@ final class PhamePostArchiveController extends PhamePostController { $title = pht('Archive Post'); $body = pht( - 'This post will revert to archived status and no longer be visible '. - 'to other users or members of this blog.'); + 'If you archive this post, it will only be visible to users who can '. + 'edit %s.', + $viewer->renderHandle($post->getBlogPHID())); $button = pht('Archive Post'); return $this->newDialog() diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index a73876a197..63adedb7ae 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -46,8 +46,10 @@ final class PhamePostViewController ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Draft Post')) ->appendChild( - pht('Only you can see this draft until you publish it. '. - 'Use "Publish" to publish this post.'))); + pht( + 'This is a draft, and is only visible to you and other users '. + 'who can edit %s. Use "Publish" to publish this post.', + $viewer->renderHandle($post->getBlogPHID())))); } if ($post->isArchived()) { @@ -56,8 +58,10 @@ final class PhamePostViewController ->setSeverity(PHUIInfoView::SEVERITY_ERROR) ->setTitle(pht('Archived Post')) ->appendChild( - pht('Only you can see this archived post until you publish it. '. - 'Use "Publish" to publish this post.'))); + pht( + 'This post has been archived, and is only visible to you and '. + 'other users who can edit %s.', + $viewer->renderHandle($post->getBlogPHID())))); } if (!$post->getBlog()) { diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index f87a37e7a4..a9525e0be7 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -200,8 +200,9 @@ final class PhamePost extends PhameDAO } public function getPolicy($capability) { - // Draft posts are visible only to the author. Published posts are visible - // to whoever the blog is visible to. + // Draft and archived posts are visible only to the author and other + // users who can edit the blog. Published posts are visible to whoever + // the blog is visible to. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: From 301750f6e692ac1549ebd61135f9d32b041cfc49 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 7 Jul 2017 16:33:45 -0700 Subject: [PATCH 312/543] Add an after-purchase hook to subscriptions in Phortune Summary: Ref T12681. We need this to update the "paid until" window on support pacts. (Instance billing doesn't use this because everything just checks if you have unpaid invoices, nothing actually happens when you pay them.) Test Plan: See D18187. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12681 Differential Revision: https://secure.phabricator.com/D18188 --- .../phortune/product/PhortuneSubscriptionProduct.php | 5 +++-- .../phortune/storage/PhortuneSubscription.php | 9 +++++++++ .../subscription/PhortuneSubscriptionImplementation.php | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/applications/phortune/product/PhortuneSubscriptionProduct.php b/src/applications/phortune/product/PhortuneSubscriptionProduct.php index b63cc7c21c..85eb2999c7 100644 --- a/src/applications/phortune/product/PhortuneSubscriptionProduct.php +++ b/src/applications/phortune/product/PhortuneSubscriptionProduct.php @@ -50,8 +50,9 @@ final class PhortuneSubscriptionProduct public function didPurchaseProduct( PhortuneProduct $product, PhortunePurchase $purchase) { - // TODO: Callback the subscription. - return; + return $this->getSubscription()->didPurchaseProduct( + $product, + $purchase); } public function didRefundProduct( diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php index cc839adbea..a996dbf5d2 100644 --- a/src/applications/phortune/storage/PhortuneSubscription.php +++ b/src/applications/phortune/storage/PhortuneSubscription.php @@ -232,6 +232,15 @@ final class PhortuneSubscription extends PhortuneDAO $purchase); } + public function didPurchaseProduct( + PhortuneProduct $product, + PhortunePurchase $purchase) { + return $this->getImplementation()->didPurchaseProduct( + $this, + $product, + $purchase); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php index f8cbc21155..a337551c8d 100644 --- a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php +++ b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php @@ -48,4 +48,12 @@ abstract class PhortuneSubscriptionImplementation extends Phobject { PhortunePurchase $purchase) { return null; } + + public function didPurchaseProduct( + PhortuneSubscription $subscription, + PhortuneProduct $product, + PhortunePurchase $purchase) { + return null; + } + } From 0b3117bb684eec75a4037649764a77215d78ec2e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 9 Jul 2017 06:40:51 -0700 Subject: [PATCH 313/543] Fix comparison check for SVN in browsing Diffusion Summary: Fixes T12905. Missed setting this variable. Test Plan: Browse an SVN repository. Reviewers: epriestley Subscribers: Korvin Maniphest Tasks: T12905 Differential Revision: https://secure.phabricator.com/D18190 --- .../diffusion/controller/DiffusionBrowseController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 3fa9e62948..06c8336f5c 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1667,6 +1667,7 @@ final class DiffusionBrowseController extends DiffusionController { $repository = $drequest->getRepository(); $history_uri = $drequest->generateURI(array('action' => 'history')); $behind_head = $drequest->getSymbolicCommit(); + $compare = null; $head_uri = $drequest->generateURI( array( 'commit' => '', @@ -1771,11 +1772,11 @@ final class DiffusionBrowseController extends DiffusionController { } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recently Open Revisions')) - ->setHeaderIcon('fa-gear'); + ->setHeader(pht('Recently Open Revisions')); $view = id(new DifferentialRevisionListView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setRevisions($revisions) ->setUser($viewer); From 0bd1dfd52473306c7e9230a0f5b60191c3cfa176 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 9 Jul 2017 09:00:02 -0700 Subject: [PATCH 314/543] Add 8 more project images to builtins Summary: More pretty images. Test Plan: Set a robot as image for security project. So pretty. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18191 --- resources/builtin/projects/v3/book.png | Bin 0 -> 3411 bytes resources/builtin/projects/v3/clipboard.png | Bin 0 -> 4737 bytes resources/builtin/projects/v3/lightbulb.png | Bin 0 -> 6435 bytes resources/builtin/projects/v3/marker.png | Bin 0 -> 8125 bytes resources/builtin/projects/v3/piechart.png | Bin 0 -> 11337 bytes resources/builtin/projects/v3/robot.png | Bin 0 -> 9119 bytes resources/builtin/projects/v3/rocket.png | Bin 0 -> 11037 bytes resources/builtin/projects/v3/sitemap.png | Bin 0 -> 4336 bytes .../PhabricatorProjectEditPictureController.php | 8 ++++++++ 9 files changed, 8 insertions(+) create mode 100644 resources/builtin/projects/v3/book.png create mode 100644 resources/builtin/projects/v3/clipboard.png create mode 100644 resources/builtin/projects/v3/lightbulb.png create mode 100644 resources/builtin/projects/v3/marker.png create mode 100644 resources/builtin/projects/v3/piechart.png create mode 100644 resources/builtin/projects/v3/robot.png create mode 100644 resources/builtin/projects/v3/rocket.png create mode 100644 resources/builtin/projects/v3/sitemap.png diff --git a/resources/builtin/projects/v3/book.png b/resources/builtin/projects/v3/book.png new file mode 100644 index 0000000000000000000000000000000000000000..eceb0bfb4d7c52863963ff876b96c190a04a6cb4 GIT binary patch literal 3411 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 F{0GGBJ@fzo literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/clipboard.png b/resources/builtin/projects/v3/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..60e2acd4f43b697e427ee12322cab45ee83a9bf3 GIT binary patch literal 4737 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 BfY1N{ literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/lightbulb.png b/resources/builtin/projects/v3/lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..1aba0d32f31aaa735d2d693b6408b991844ea507 GIT binary patch literal 6435 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/piechart.png b/resources/builtin/projects/v3/piechart.png new file mode 100644 index 0000000000000000000000000000000000000000..4c707ed6ffc177b1f5d7b3c4ac6a2039b9fdaacf GIT binary patch literal 11337 zcmZviby!qS)c9#O=vY9co24Wa>00ToMRG|6B&4O6rI8K=X^>iAX^>t)P^6@#r34h| zj+gKI`~CUO^W5i|J9E#>oO3>N=G^<-SUnw8QqThs4h{~fx|)gs4i2use;0u0z9k?y z(FX@dfI?kG(a0b7Pwyk#`$k-mRYE`hI>Gzu|5bW?{^a^7JT#lAU7iY*_xpbZ=nCMB zAE5onnAQ7nAi0+Yp^Y6|TjkN`_c1yRJZhk#O&CsuiR{`aN}R~@jW5r={$ zW6C{mdDO%X1&r#yCbTHLMABBK{>3_ngfQ-d1CbPAwW4e(c-|o#}~3Ki94)X;Xs}0HJsnAtitSiWg6(KF%Vb zCeOT%SxGh>!`wNiP&g2Qs*`ap8ApezbtJS&27IbQrq)4(j>{G;{Bh%L*` zOUK+hhxFLa>SNG5xLb5=zk|r5ZUXMKVUhQGIB7?(5u!rikDK;N)>@B&@gnzDu6kz*Y_>X#eV z9p8qBaAS1r2BT55FaMic0hA21RxysK4W=_~JJ!^wM8?7(CsR5{O^N?`C`SIsN2-${ z=l9LF(2Xo_Q(WAz{UD4U5zO;T;WDLtkoU*dCf@6Z+loJv`AaCw!oNSerMUpEyU5&~ z-(!)zBheL83559=j($(CpyD=+uOrXsrHJ#uURv1$aOL+Yo`$2JX$^+5cZUTL20N+4 zQ@**g2X}Q`o*bAMF-%!^?Dac{ak4jJrr9WLxKqT`NK?bEiPRHN4&HK#^~vf4$F>8G zmyyQCL2r+Yb>3+WI_3Y46-JXQzteaWe$0L`X#E4q1sqblE@y zZ#$KhuK;nUfj&3M!?3nT6fae7=`WJ=GCU|CTu>6$g6Kng#sv{$m%Q^;m~CefJ0io4 z;Am=2Nt~F@sF`6GQCCxnIVfl!JGEzbCE3O_4PPHpOmLOKLh#Y*9Vt=fJy?kIV zkJn+uJ-?D@A?9VAp(nByAsbmfWpvshnk4V0t0iPk#fcun_))B1-t1JA$oKoOnCZw0 zQDrilov`<4?&6h6nU}U#R>nacYuB^QA(#B|sD727Q^c?Pg_5&Ot~4#4q>Ns_A&f!6 z>amJr463vuuV>frgZ+I^hbrUZqZ@uOq_QF+sV8`nE?%+n@ZM6)@U8o)$t)??D~YZN zdbq+i5_rP$L@Ss_H6oho9D{Q_l4C_Ose$na-{5nkhf3YwgQhr^mGFKf=`;QzJ{AaT zl~ML$YN(LO!?8+AE%0`$*b{cvbjAHokNwSk5jAaMw$EpVUjo&TT{9}!m}0OwPapnU#}}; z>sfo0J(0A}O0!$6aZROUHjNhZGe+bHKj0Ep@jqmp5(ZIaVt##tPp!nal5xl*f)0`~ zkSorrNC)OCKwbzd^bfV=rV9YkF`&gY`iC%~(46>%d&EDk{z@786wEVdGw$VThzN!iQXY<>#ZPO=0HcFN{@+;Va zCtTGDoV=)mrGm^&t!XQF(O-n2XsxapOoN8DAZHGaXTOzPxz8fD88Fv8Y;C^_?=;)g!fSt>W zqcwH-9TF$LUIi-i&3lNyKln*9D*}jg-CNJ1%3&&G5aB`uGrf-Ql-~DQ1{o@;i*gDS zCRcsBaOl$w+_xrIhtmQuvki^Ez-HB$?t~aNHMkOMwe%Ok!VWHwJtwNFQ4QWbB;lT6 z8@(k=tXw>yCc-J;IU7W|uF^%_y8r`8z5?+&F8**f-VX5K|xtTQ0!Go2@?d$a1k z5CAnl48D&Hn)o|c0l`3d1*DPic-ld&A6(U>z6Dc56BuMNv3$7r4>Z|<)rdL`9Y=~Hze1WazeRb|7yi9CX z>S26iksADVV8jddOq(oM4O+%64bN_>m$~uZ z{fD0omJm{9o~udMhJV;uPgNfY0!yMVBTH@h8Wo<&v@|}xe7vWC{TUes$@uCkA+)(s zZ`sEw^1XFwJ7TuXY>Vd#TQ zp5T*0S{$I}0o(!YERu+JKJ^%?g#V(W6ei2TYm0BKLI)2Ha0aC!+2mAdhXlj&|dF zbS2+3`u61eOfOHb+0cEiuuS__`KQML7Tjh__wICAKNa8rZY^kFVpDh98e##qOFU5>Ibvs@I&e7^G) zD#!zLaePfNR`hhL*R|0)Ze;!Ws062i&^>h)>quWKHU%@>U6rxB^3UaS6$=^D`DvIX z>zFr;xPnB6vpjD`T5UI36s|ttQIP|oHfkHvZ4Sn%{mI5S`f+VnX841qsTivx7QhSr zTvz;L zxv+RvJttLAoLDR4{-YEDGI&?o;60QHPEL(>8-3RW?VDq^_lM+NO1j`RUA7lJ50dR!E@9e* z4(SnxF}xJ;!jt=1U}Z2KpDm|odBUh@imMtt1Xi;=%HsK#(ne@?UF+L$;jAIoG+Sg6Oi-=IF8s-Fy6C2^e@;grm$Kbj4y4EZO6{)FKm1<#R$v41`^TCs<7bfdEVqf*E4*Id(&7rhc=mjP6ndc0)bXxEsl znyZC6nQ*eZL>y+sCF zis?ILZi;;sKdqY02vMvj(-tN$DjtkP`S=s8e{Wi#9MI5T{{~Vt^8)UsQ=(29?zchg zBk4%efX(PgKN5BRg=EZnz4kR@wim~$O0xEntii0X36ZDOKrq*-$qI zU?d||rbSDVxe1J$YD!b%s8y~(U_3Hot;!X?T_oWdbacSCH95C|=KxL~2QmvFS^O-f2cF z_A)Nn7*0%_|2)q1^HcB=+3K1#k8b%)nkdz~<%kA$5HvEwy7a6tKpZ+u&*;fJ(aD0V z`|gzN(i*NB)oW!IaJmuUWJ=LF?DGp|_|^+!Q|FUjboBto-zM$960|i&tk8?UC6m|;El+K66KUczZ;3>! z-9_;V(2ixz-{mhymT~^nsP&j8a&w$b&7Qh zu%2c6zjE>@^7XaBilXxhfd+CvzsCvwz zI?=CYJ&cW_2oK*G)8y<;RX!|K1Ty2AnjCodh*ng{3mwK<0UP2DbD@DPq9uJF zZNhU;jwE4J)0PA>;VxTKmJujcqXAkV8~TStt}2Yyc#oZV?p!CjVJ=WVcRT!GEyw21 zm{j?&Eoiq~4f`nxhf*J91Se!X%Z2Eh_(qm_?n!UL$Y$qei>wXGe0$Ev$>^oWNQwh3 zMLv}|XOo%`?`{Pp2C>IsSm6|$=ns}dhKr;`>nEeASSb~QM|Rqdju6uKqS(&W=@DE3>vd-N37$=Lif?vtdw zDUeOcdJjcjrt}fsZq7pe=)8ye+)?LN>xb5qA z>vnY-#0iIj3JJ$x7Lp&$!C~XorW5`8#`tA3Vcd_Y3*O*%@DS6t&nKiA_|Yvjoh_xl z)itA1!c#=UFj9{KV<%dlpO&zb`byrA4^^&c>wnMnQB5?s?6x9==5Pn?-9#d3&k2JU zxL(o$TiIo-Ajmrz;&!2dEx^EqMqCKqQ?APs|z)fFvTg^>W*%(eemu&Z< zZoRW2o8)%}o9GLEFisDdUZ0=40ufS9tEzF}p;pbJgC1xh-b?%D0loqAHsbmDL-_`# z>7T@17}K7-2oB!pDHL?sTax_rN<&jfdBC?Jt#F0CK+@Q-5ml z!1HG{Ii3Mb(2zB)HqN%A%XkMuOBHobdBZE(YleTE0_>?Mt}`+@Pf6D&*ZMHNP2tV_ z*R55&Av=e={(T$@wEd1Aj59Aj%co*u?z47rCQl2`kurY%=(2mNm6>uw%8Pt^&+CMq z(b6*8^N6Z4Zl?V2g9bC+TD2wV?0&ROtp3ClyBW`FxgH|kANKbkGndm#T z?eX*T`6CUsigylO$|C+<2Da?h7LipN8<83~NKWUx|6IJtDWI&3O(j2;?Ew!L*H-eX zhcB?2>N^Y#6)ujG3_Nj1jTR&DGaP1d1M4qnXxad#OhFi=CtwvR>3tV9W1soQ^m3 zR02~~0kZ^D7RT*`Si=5F=+OF9mg0S)A#S9ZNBA{SNBM5wJvyBfg`xvfxY2iZ|Io$r z@Km9Y(?`MI|9>G_l@T}3bS+dAwfp|v_uc#VFW(2WbbtExJLpZU?4saMVo6)*HH4XW z>$}l%MNLgtlh8!15Ss+!?vxE$lj*$;q!gNIS{q-fNqv9@LH1K^ZW^^cs0^-aDW{Bn z;@!PYD6StWN_#2TNS zCg2Q5hqq78`#ui%xJNVpYbiIxxkqM5Y8KY3u@zIm93>L(Eb!9gi0NkMM-9V}fb);9 zMbBk9^qQx?w9oJG!5Lm_>7i`2X!mxQm>a$q>AVq$|4|$~-Ts^p{=R;`JwDycsHoL! zxaq7aUb;+du;^0sdl4_ZT=V_yv>Rk`xv$6%!qbq+l=yur@}_d0vZoKs867NTX7t6baGy3nB@r7drzY(1S176Z zjg@A~w5NAY7Qt@qqWki^jD^ymf1{2WH}0`_%gPh)?OpA);|z))<{Mp+Rn)lk+1U+> z(h_B}n#dF7Iq7j$lz7*skO)($FVgt(-_JJG-8NV6r9rL@)83<3uQ^agwO(}SRU0NA z`P#5DR8qSv1q~%b4C;4Vcc(t>jV9c|MK+f?E(b8}7dY&BoIQ^p-VcpPN&X}Lmie#5 za@`*>v42+&Yk|{68+kbtMRfF6AN8+<&d6;^1GO>P7G?Wxaia9V@rcD3M;q6^Z_I7F zUcY!h92D7f7P)x0*9H%wC@j;QIvq5XZ=gS!(fY!mY4_b9v+--6#4{-jPL}rs0fX;c zLu$2_f9P$(dM__&FU}~G^m2a0f^`557lA9f9lUT|5Q}f1NZO+oVr{_Od`*j}oLKtF z%cskGuwFb>OJ>mNCW!_epx4`J26@#zH-#*7Zo)hFTs5l&nZ(b3uDBL%0q3BS_n)1S zot;Z2 zr>#G^8rE`?xrAhPRVSWwIkxyVUpQ;mtjlcp^j1z*T)utp?Jl4+IR5JL6n_EODEimD zR^sn`xWukc@1@{k#PUSu?Fx|XCgjy)0y^Tg<#yL;k>@);y?FV8K|eOw*o+;LseXmU zf0rA6$z0EsvuyDKEk=pXFSC(dNq!$L$F?35A|2C)oXbl+LaYXWrty~w2Bg{Mo-z@X z5e>IQoIx1JDQmBWx9_ZLY0xZFonZ&O^NfJDhU77_n6U0ZB8Hs)F(7Dq0vkBDq%io6 z=p*t#&9WJMzLLWScX!k~8xsn+&L(3UAJ)&%W_fz-Wqez9Dm7BAIs+x#)fKqZ=pbS% z*L>Mu>E_}p(Hx-&nl29dc{tp z?laD92XF4KSslJfGC}VFboDTz3lOp?|JvB}LtKT{$CPS|0X!(Jr&bXl>(Q&YQtQG z%@Lz-U9exF;_%ePw0L`wjhVff!yU6hv=o1TNECM{r+gTf)^;~6U9Op9XD8kA>za{u z2B-%7e6ofY?vm%wL`yh%QMl6qFu@Xy&q9i;S?fjG17oNW5;P(sH6b66O;YKAfcVHi2!kEYZwITuz|rGSHciFk2&Ad7@9&)^PF~FE)byKD%7KT~tkls9HM5F$Q^ER{WG)vQV zNK*BYeQs25^ga1+mjGAa?Kz}t;)Ynew0)Av%t<6ev3l|Q9<)C|*SS!1K4LPAmi}h+ z)~xWR6T4-AnU2b*NKZt0pD}L>7(Q{5d>Oqy1Ka9z(N9>dr=Ocj7Km`I5_hArZ1KJK z58)+2shz-j8l71<-Ti5y2@i4?iu+32P+qx6rThwP>tNPWX&}uA{g9wNM7U}On*Mnh zi4F7?Cg$O~dj9P|>A)5&Gz)wxZ;`tIih4ri)%rFZNqB3eC$-bU-4f+rM@UV3;TB4SVK%V$t5G^*o2;1h6 z{*~e1E2;f;SAmOQxRAG=y)NnlcFrvaviTX7q3?XJd@P8ZZz^^jRHfZ={J zw#&X|VwUAErd8Vg&LV!(_Xbo)w2Z$!n_-!3NIWxIfo7P7YXF2!^Zx|vEzut{&Q$M# z@|AnP33gfd*TQ7M))gIXUCIERHqSu(I%y}X7QF7{!2e8jt0HpP_pFdgnAy7OO8T{R zBjD*1dbKZqtX@u3TQ>7<@wnu+XSR~>oBu&T3Gy-1PiqPN{ZvcgkivQ9!hIAL*ssPC6P9x9Ej(%b+6d@njTxz*tC|`$&*B}{-Y97(fQw>+b-%g&p_Ek#V-5Urz3HXLu(g^+Z zqK-m0^8~|x^Q7V3r}pH*b8!*l20XDxRn)z?*a;f+_4@YY6jP3G^yD_;+iH07t{8CY z`G*C(2VQ=UQc~c#3?6cMBTbBe5?J@t7RMP^Xynzk_~_ zQ@_VOm9rY?o#vm`cS|Uy<{t|9Hdm(`~R^|^T8uOkAhjmXsf@9k4ybD-mK%3`BRmx zJBff4G{M=ZrKg>EOwL)c_M)`z?GK=?50rb~^hpv{?Vp1uSi{=!<8mul@v@#pSd0;p ziE=oZ6iATP_My2K!dwjXgD57%pyqWTj^_JWhMqBwYm7GHfQnoLd1b~bmaMYh>xHN2 zveUVStZdXhu5|mI@h;vhPe&O1UTR3WYLQz(S`&XeJo7MibV53&#Mi~qyyx&P zrd;;b$?5Ix+G_L~@m#yMA@8hk&;#3dNZ7dOr@-G9?{wTnhr2@rr_rKR&;=G1>WXUR z-*eH;VFgtrYwLU_G-zXpV5>||_GY0VJNG+1l0(#5@O=u|AXJ&?+E8n58=~-LNa-6p z_kpuSe;l%jeLU_b0YzdS-Gd+V%m$Aoj00>tzkk##PP45Le5<<4sab!YmHk^F)*@#4 zG!|7*4gYJYf#!t^^j#4+Oq{NKVBJJC!+vTkxWHv;n|5}LuyR$|vFeDM@g#=JOa4DU zAw4C*;kn3upiFizgnXvy;Y{?ynpY_~4M5`-mDSFJlX9siw!%hVpJ&+FU5iLMGz-*3 zxJ^tGf*{hr^U6y;Oh)*?7W&H8w5TH z4*1vC9@=@0ihAW9W&ssaw33r~t^~s+7&1pwJ5jOw^;YaFUR6CTbZ{{z`#tydb3(il z;bw^m?Kw-CiG%Kev$T*N20rtNlh?c`2kGoogf(s**+6-uwEx-))#os};`DM0kWH@8 z>kO>kF}~MnTdB6dHTtuZqhL41hu)gjX~hG$-O9(61e7&|X6mZ)TF6)AIT_fx zbJ|n0mp!!RRj|h#H<{4|<#kQ`nu;!$+lAMkS$@10(#8*x_^cN1Ydf^S7;<6rqcy_xXRK3}zn zH3vb2b={}E2VA5#e6Sfys5m7N7t7O?OOm|+DQf3NfyYLbKZmn*d}IZ&Wt+Bt3^0;9 zuC)czO077MrP8|Ocz;TgzW(OR#)b_sL}8b~l)Gh}o+Wh%Tb5lW5|&4Ct1d>YuX%Du zv`P?u%e)37stx$yx|m&k(gEIi#g}Kz3@+U<_);)OSJnolsZ0XPt@a1~x2OyDLTi?V z%U?l$ZK1JMa+le3L`*hU5r2lj=AABS`jZz1Q2}38ibZJlUEWlnb#S`H^6j#a{kSz{ zC*d{u+zaQ<5<-) za@3I}o=?`kKwHR51i^U``kfSn@j=;oY(boi@ug}#KnP?-F6%0;*<2mK@B!elo@9G+ zvQ01g0Px&IOzav$uzea7{G;?}M}=*O;bXR7A>JZ3ORNTL zi5biSJz6bBe#`VPgvqj@LDwgrOb5*w;$e3p3t`bUEt(wUTO_{OjK<_ z&1*y>U@L4v3FQHRlM$1zD!$l8HH59n=P$2lP=AD+m|mor8AWzBl)c4&v0SVKf@agKIEUT6 z#rp01BR{|S`s$^sCys6U!pEp51fIAuS4H*%swlVBt$MMFC_T)e7nTLopkoP3q4lyj zH(hO@x2^&c#=|Z{X?>r^(>>+t5DEgXz4%*blOjxW3!4=R`haY7QxLphiJt_#8~M+6wZnrT-h=m#J75D_ zw4W$LQ+5U>M#bz8JltFn>}6j+ip;0HwNwMNqs*~9UaT%syxxEulr?Rafj@Y8?z0{S z5b%XECZFaOOG%nP#+d*LM0sI-Ru{=wtd#uaGEdLk!C9yqENV<>5?~(LO&4NyPP9rW zcX#JfmPq*D&$~ny=KnsZsDglC1vm~C4@Jl-fP=ur;!!}tF!vRJ%m38~X8pEd)?pH6 Rr}^Iy>M$LZ8YLU#{{U8yX8HgC literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/robot.png b/resources/builtin/projects/v3/robot.png new file mode 100644 index 0000000000000000000000000000000000000000..317544f2dd8447e401b6b3df1f78dccf171541ec GIT binary patch literal 9119 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/rocket.png b/resources/builtin/projects/v3/rocket.png new file mode 100644 index 0000000000000000000000000000000000000000..f5a758ff815d8cec16bdca7dee500de4bbde7adc GIT binary patch literal 11037 zcmb_?XEdDM7q1ed#2{K2y+wbC9$gSof)HbjHqm?UQNjp9lqe$zf)LDL^yo(Kf*6tL zJ$mouPX6nDz2EM$)~q$pDbLyM{LbF{?DzV*8bC4-86F-UQ1iK(0UjQ{(7zuE0M`;2 zlHiMnC$OTart&HPe;e}{AJ>R4x=3W)(IAZb`~P_M$7=njUVE8N&+V}s!WMMJ>_25> zWE_Yd=?6S|?9JSSBnR%g)z1#$9&)=LZVKKyJqDMGD^roe!!E8egAy%+lgs6!CC&U@ zL~CMQP3>8BiTm67_N)Jv3HMFT>4TYC@p9i$VsP)WkOkUX%g+U=jS&U-N|AR_l1ca8 z=~Lc_5?w9Gd=8IZWR@oy?Tp(OGkdGu&KL0?{u|b)1=qRz2!qXued6a4L7yTG_qm(# z9){=tsNR=4Bhu#g1zr=>b;`(-j1KO3Xt8j*ZyLLXPxJme9rrYc@ zVSHKl7jFYLn|O4;-eB+Du3K;s_2yy7*a{U1{w(sd^STgF=q=s7f1yZV7>=%CCxgdy z&J3IW?b?$bvu-;Y^KP3~KF&HRKUQk{qs3G2jP6h=aaUITx~A@mKXb$W>`%!9f6nFl zuJN0;qY3S{hN*1)Dk`K#Xs*s=6|Ch2oTuRD*=syLvvW6on$E`*6FFJ+KdRfNH!j#o zt>|>nb2#n>^7Oko^#i|qgF5ziuJ@YRc;hN@0jDV^!5*oX z9Lxk{>eq)ZPC_mPE#lrjhZ}ju%ugJsgyYk28p^0w#@FZbLy3IZy|j?^+farTM~;m{ zDvSR3JrA9l&#Ax|Z0;>QU`Ki_4>;ENA6JDZYiL_{LnRbanacmG8ht1akxpTq5PZbsOI-#h_>BP93c zsRd44WvqF?JaSHscQbAYT9BR0PkG;!3e8U$?A+#LMXb7=)&tS0je`SEgWe;(9Bb@T zt*IY8sa%cfg*&KwqI_5p2KPBA`5-O0+>4JBVZfHy@K)ZzL zH3J`mrn8r0v-<}wYR#DC(1XW~lF)VGZY_A>PmM)BebLzZDGwPL_Jm)pQln36nwfDD zFtB{r8~(L!CVM1z!b5_2a%zU{73_g9W$(X9ql?q9wwvX8Z;~j%pX>Fq48rTy-2*n5 zpz3s7uTrPB3J~AWX=6hrh2sn1=Ao(3v%#xMtL{e9cd=e=_x;v_yd9(2S?Atkh4NQ# z|Ea%Gp^tGZLeIJRVcD?9Z9UU08;*o=NdGsXp?JOfR*S<9FJ_pxE+CaNa(EHf?9TnkT5h^mMWTI#!+M8UW{c+2wNz{4rzmjdE@lvC!YLR9DR`i$=pc(gax zHj8)*r=`H|c}zF^BRCltvL9NLy!Q(ucMzZm7MhiZh&ZEDA70~G+)FNg?*4^FgCdCi znU^b_`StLkv)}3ydP^$0N~;o(5FJq~-W^R>?;va3$C;cGlh!}^zh~?=%l8%&YgPr* z8|A@5s}vJ$dq*H4X~R8uv?O?qN?5NqmIp@xBM@O)!nE+EXW(g#JcA&@ffePA8)jgv zl75|$)}zHL?B5g1^ND!tH4wp6!p;7^U{XOXkPZy_yYj^4IVqllJi}~wfz~GCY(qAVY`pua-RHatRrW>dU*FS3B zJfjcm)xCxN_N}za8ot|Yl7P77&GF1iDLVR=!=#yva=>@?VbLM=k36}}Y*Q}gLtnvH z0h>+?_+m6o+$nx~nZnKAPKUR}Zp{7W8as_J)Yms}Bq45J>IgCxGv*4+xj@|Em^u|r zJTsay@haG)Gx~E;C_eWXc&_jALO|T zdIva*48#k5+eAIvEZ!}e9^?PBp0fBx)f4?W0sCv1KiS%>2;&tCrL=hzYJ%9BC()(Q z)wi|d;$hkr;d@XokrJC+dU28BdNk;Cl%?>XY|d02b14l((ELnauw@qypkE$%e$er@ zc}4pWbpIoDD{ys>>r8jlM7EU%?d*nl^oVNW^Rp=p#)@rya# zOHXkRk2^8#PXUjm>v2Aggz>CJ+N2DQhdXRC3i6KoqO*)yp7oPPQ(TE@S-3&Xdrxqy zetlIjT)$NBD^J_$*a7TIpq7v!XpHpNCuRkdR#*2R!E01cYm%L@t*w{M+fT zZ({km^1{i)n@S|Y-VVe@71wwq#a6xHmutu{kU~tXHHkfq1$$}%hjm9uIO&3FGTr|y zu4n;yB_RUTb9^jh31{&QoZyxhAz0QAA_HGgU5`qUxdn5TR}LO)9EOC3_QfT>Y#Q-P z#!#l9HSW}j^YicN1w0q-iO%R(+}wm2CH-z*!_onmIg{?PzV7S^s+0Kn(Fav-mnx;m ztim0p!H5L#VuBMz5^sU;Q+m{)2eEd^-t#%KC2_i(`IP{asAdYU3*clwE871ih0?>r z#mG>c<(S3T+$3(*!DwCj34IyqwEdH;P8Oj;RgAT=-6KE`OCntu@>5;q^I**3QOW5X z7jrs#QLYa4wTM0H>))Rf)9z28wd%qNYRDb%n9$iB9+2KpmUd-Wv)j3!R|?vOtooAg z^5MGUwbNmvJcy0y>{X4HP4$ctPNj>7zWw^5(>$swI$g9%^#giaD=}(j<~OMGcq>~s zuHIhpF)i_2i6DNn^-_R#3oFH&zh$tsB7?Da8Cl~42Oc`Ev*h!R@1vsNA*wm=!4ax* zisCrcrt1dzsG_2pqSPPS{Ki5-pF)#57mE+6r?v8l{vc)KcM}mVNAC@BP*$F|pguv2 zb@mDjq)(qQPy1BSU(1;o^i0oN)C@o6rMkN)@l~`rTNziFhusk=5D~<}d^oP};RQ4E zc_1aRv!?$Ypr;jYj<61z;c={GW--=4nx01e_=c&<`hCx)h_eL~a&^ksK1;6A;ae*3 z=y}y9kzIR}Ec9KKbfo1JY+5sf@xOq-rYa~wo=ooFWBxzWFiR!}@12L&<7=`0o*rgQ z!l-eo1L;VR5DVEZv6%R8@y*O8B<}Q`SRPKDH*YxoPf)2L)_VE31F2$=A`)XjK~A(~ zP9wNLiX68$F7Hk;t{0MO)t;#nh*n)5aM($4-H|NBRK;C3+lZ68IIj-r|4`nHdb&)R znZ8a|^jJp3z9~>1a2aLBSoZsm-?MF|r$9s_z>hEkW5!$W%KbWM2vQLXUkIv|oG4P# zwlW-$wg*uU+pP*0+xaLoi|M;6)=S^Iv%^7PWmbX#oTm|C|)%{e7?2P?Xk3 z`HgM;-^xK_Ymd2+7e+m~bQiHQLglgGd>0vpZy2&P=zSYNR+tyFSxIH_@cys*|N57x z0-pILAB@ZJpV51~GJ7Z-e&&pRnx_a9Az;*me0&joy6fwTb~?(8PYg3^{T7i{KOxhu z>FoW4&mskY8`HL;DxA5nP&EjU#CBE6v`>V&Vk<+M=Ss8{f*Ep*!{?AYJYz8 zwTISIt(ms$Au0z667r?f!OOt-;8R3wok(Qv$^DuR8xqHeU6IHN)_ovcDs%ukpi_+o z!-r^jjJ2&6GknqLs779^cYhxSYdFa3=vAx+1n@({$}k3FO0={cm6;-sKhLeche_6L zOWv4lYC~n1E#70j!6QaERZ{o79XhTj_aKqy@k~32&FG{85)9Cb+ z+Z9f_hg5;vwn=w}sR(haGP{tV$>@o`(}mJ3;oa^d& z>E1kr+g4EjfT-NoX{j*G2us(^1arE$x0w%Y{Fjihp5!}waVcH5r27bvoj^a5%Jkrd#4F337Nvc zzhENfdK7W47K@S(p0i(urA~>dlNlYd%4*teGF!OCB4cEq+^hmqr6=WpVi(DK%1T7( zfZU+InJK&=bYmm~>Cj3-2nX@4t(TjluUi0$JBxkJQ7Er$uhPa3gc}AioppW(~M)qI4&JfB$xGGIywA2EwLgY4<}h8BUgo z@&ML|`t1B%Hp)ttKbOci8{Dp@rhKv^%n#+8Kuj#1BdR9J%ft11L6fjTyo>|elb1s? z%WFk!g`eI>TRgtRyNJ?l%Rjr{LXMHhlezo*IKNM!l`#D+d70oEm1mNCuXHfm1)=nk#3$hvcR%A-#Ljy|%-zDn zwDYd?c0Ob6KQs2-myM)5k1$=mMwYwR&B*XMj#j2LX7_M~sZzk!iN`B3+7Ewaw)-KE z@76Le#A#_6oY-ii!(Hd~pOu*tch-bV{cV3?pnjMl@Pr{@Fo9;atd(RB2hCg-60|yd zr!J`ARhs-b_BYNMXVx|$E&gMj=bux-9phkU*m2A?E29MuU_j(oX^!u zgOP@VP!;X!?5fUW{C1lU+LPPo4gIFW-#+%8o@41Le0-Cl*#Hqs8Qdo_9-Kk5$ z#Ln}qw6E|7^ma&OU$xVSn;*$h zvbVR<`qzM>92N%Q`ouE&hKSz&%)ob&w$_K4J=9}Fe2tNkD=@r$yhCMR1{+iF_yloh zN-9`g(q{jIP8Cz7GY0o#vafYUOA%!Jo?e{IgLS96@O|&TQdL3vL%gfSuQf^cK5cJ0 zKKd6zeR1!jtK9s%L3#aYrtbly7!%ufx`h_YwR*&su$i}jQ2$%wALKUIvpHF@&Loj2h7%6ha{5FEB+5E{c&#r#0kFAiNPrzLh)j`a4+~RwC?LAs22@Y9FbWM7pNS091BuRbhMiF4 zJ`!ghGMrby^>G~w!`Ntx@>sfx+O+S`>y8E8dMS2+vzepGy@OqSQC&sapOF5VpMK*b z@$X&(=_-8mv*%Zat_8wB2OO}hQ<>%A#PIBX(l3;%O8lhlL4rhgNo}3N@{%4@<{q=! zkh7OCUq(k?nIAUsM}HF~X`{2Le-~Qjgf4#@I;M~IOZShCAdSyYDXncbw5BVX zx(S&j{5knsdnbPOr`ZJNx zjJH;;F_K`dng|CcosSaW#P&Z=+i+O6_wL(Ua^e0pjx=ewq+e8YRsK3p6R&;#e_Fx4 z->pON56DX7_Jq3zBXwm%&)@b4s1fxWjYG^M*#3C8IH6_LRWY&rEM!3*hNLb^eiuX} zGy)Q`9L1!NT?l(mjQLOco-rjP(6e&4I+d-Y$7?(6* zqE5lrPLV4?vR8qK5YeMDMfcHv0)NPEcq_0+L9}J7imCa;DeHN@&>TnC*+T;2`lZ~FxVSgpN zh_E@7h5Vz1pzqaKF^-n^RnjuUJS<0I;PYa+V5T@Hl-K{xg)l-Jfuaa2JGO~D7;O*P z*7yC}eqFz@qBX1kycZZ10TRr%r|bGjy zruy$px>1foqnvd{J+G|CJtMCJ;W%*SwVY_(muJcfh%r`{q!bJ7!NrX6X^kjE;|T20 zyDIU0l{Xc}KrkqY@m}{_%yAC;9b(X>y`e6qO}pkqcO+NHRK5)7WgV$vBDw_PjTnBW z*X#$GV|q3_Pc4uj6kAMG#8&KsBn{U$`#ah@+-=irVZI!r`IHxJTh71a+@m6vL##o9 zR|of?x7m&;W(q9RqD&G;Bu~+i!$w{QOG;ZnYI+{&Zen5x@TPE?R{SQ0g6M?=ZJiU! zC6b8n;^-^P+Lw9G{qCd`qlMeg;%}ZN&?#jdRtQqK-niu?w5J$-V;##|$bD(~qqMTB zUwJPnZ8nr5Y8}4`u;uLWpzp_T&erNm*3#RQm3gTB-Y#GBTF@=k-XqsReX9FYq|p(( z(le5%$|{vNhLqjHgXE_(pULNc5&;noA*Y|}R1xz77voI_b}w(Ab!&a!=Ug~^!=|g) zrK&kr-af&V^0JoC5{od1PxEc&2PkopoCa@`&wteiVxNA;vvIHv+|SDl`M{PMhy7GF zoGf%`d(z_cjWtXwYa`c{&mq2?POY+ip=+bd+0Se(4=CmNgPoCq6;z1&)&47PHTuk( z35aD{piU~uW;tB6+*0}Nqxat1G(}{9J9KUhnUg|EM9C6U3K`bXYjZ*d%=tD{ywQi- z5d4{D+I6vph1mEtjBzYVB(aPw@z`A-0P_IrcwgJal}~XKTiB2!KR6b=_eDPbvp=P% zI1csulr149E!U5I_znW7cO#Ln@n`OtF39VDhw&9xX08LSqWD5m3Z%zzcM1e_-~zIrKO%daazG&FJe17fx_i;WVk2PCcdTlc&IxOgZL=>@(I#5yIe{5fXGcnd;s^i`Etmd^U@2oOHm2LMy$Ar=W>rC=$7 z-YiyERh3YVAFpO~`<;(s*vOBxBN9r>*{uqrE~ELqol7RY&-l+Nfhqr{U^UO>h!H&x z$cb87&MiU>(t4hMX>Crc=c@)GLoc-MgM>shwj3eAint#KEsVPO85zvXXKJW~?RgE8 ztoS{$-yokY;etRT#w_75#{&YP2tmo`hp@oM&~$dnIo3sMZG17ql-ZRGNa58G?_>HM z)R>IKV=$jqZld@^=7D5o zhgp7LTh8S9`MvqqnQqm#Fr8c@M>OY-tB0nniBEBezBL_>obcWZ#6j}a=wBvfAR)?n&%@`TkNJn}ZPZm~B&e95bPccDJfmsjFap9E+-VUr7gS!pl!VhtC0YUnU}RRi@lp{TQ@N`(}S`(4!JjRXWF$eUkSNxi^QoPveSb**@wOONJGfrR2G5#1#1UK;;8 z8CV#X>3Gl2+$6!1`E)L6fI<8z%r49-t&%IgYJ8kI2GKL1gTox2@{vkVsm8+IY;da+ z`jscTa$v;f<9kGpR;aPjCj*v&{T5m`pQDCSjDck>Ju>DW2S2bs)c=5?t;is%(4BWj zM)1OMp)LVx+%*8{zL?BKe;Qb|n`{A4@woyQZgzN~Tw=iPhPgch`H0`TT9A*flCRD> z^-iv*V1fN~`&IJbQ;oP2iDO4pgs7N=R^WR&+;p);S%o`Eh{aJ5JJ%m<|5IC4oPfq% z7t-xbfRp^^;W=kG)0U&O;r0ws!zr)v!y@LvfF`;bD4#_o)U*=>5}L%PMK~0GdpR>u z1H+A=b|G}f02HQL)Zo67!DP1-^eWV^Iyu;-)dd4z3_5WjR(zvfa{9)`Q-uHOG7U*2C`0cm19j~{lev0A*BKcEEC&N^`^T1yl zi}(E{faobLM-llO(btE!3C^Bi3_vJKReUo%YLC}Aj&O;j)XIh~ ztK#v#^a;J&TJ>^VE*>^}?=Mz$b=aZ1qA#HlGD%mzZ&!oGGB(wJuxd>F7bw*@w)_

YPya(}dajO4BEY+w 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 Date: Sat, 8 Jul 2017 19:54:23 -0700 Subject: [PATCH 315/543] Simplify Diffusion Browse Table Summary: Cleans up colors, removes commit hash and links the text instead. Also unsure how valuable "lint" column is here, but left it. I'd maybe like to understand that workflow since it just seems like clutter overall. Also Fixes T12905 Test Plan: Review Phabricator, hg, and a few other test repositories locally. Holler if anything here seems bad, but this feels easier to read and use to me. {F5038425} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12905 Differential Revision: https://secure.phabricator.com/D18189 --- resources/celerity/map.php | 16 ++++++------ .../constants/DifferentialChangeType.php | 14 +++++----- .../DiffusionLastModifiedController.php | 6 ++++- .../view/DiffusionBrowseTableView.php | 26 +++++-------------- .../diffusion/view/DiffusionView.php | 13 ++++++++++ .../diffusion/diffusion-history.css | 10 +++++++ .../application/diffusion/diffusion-icons.css | 1 + webroot/rsrc/css/phui/phui-icon.css | 4 +++ 8 files changed, 55 insertions(+), 35 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6a3b8778e6..939b48a643 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,12 +9,12 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '37dd219b', + 'core.pkg.css' => '7ae9e755', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4ec4a37a', 'differential.pkg.js' => 'd4ab0e81', - 'diffusion.pkg.css' => 'b93d9b8c', + 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', @@ -71,8 +71,8 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '4540f568', - 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', + 'rsrc/css/application/diffusion/diffusion-history.css' => '898ed727', + 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -161,7 +161,7 @@ return array( 'rsrc/css/phui/phui-header-view.css' => 'e7de7ee2', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', - 'rsrc/css/phui/phui-icon.css' => '4c46b6ba', + 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6e217679', @@ -570,8 +570,8 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '4540f568', - 'diffusion-icons-css' => 'a6a1e2ba', + 'diffusion-history-css' => '898ed727', + 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', 'diffusion-source-css' => '750add59', @@ -849,7 +849,7 @@ return array( 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', - 'phui-icon-view-css' => '4c46b6ba', + 'phui-icon-view-css' => '5c4a5de6', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php index 7ee3c89b65..fd0bcf81bb 100644 --- a/src/applications/differential/constants/DifferentialChangeType.php +++ b/src/applications/differential/constants/DifferentialChangeType.php @@ -71,7 +71,7 @@ final class DifferentialChangeType extends Phobject { self::FILE_TEXT => 'fa-file-text-o', self::FILE_IMAGE => 'fa-file-image-o', self::FILE_BINARY => 'fa-file', - self::FILE_DIRECTORY => 'fa-folder-open', + self::FILE_DIRECTORY => 'fa-folder', self::FILE_SYMLINK => 'fa-link', self::FILE_DELETED => 'fa-file', self::FILE_NORMAL => 'fa-file-text-o', @@ -83,14 +83,14 @@ final class DifferentialChangeType extends Phobject { public static function getIconColorForFileType($type) { static $icons = array( - self::FILE_TEXT => 'black', - self::FILE_IMAGE => 'black', + self::FILE_TEXT => 'bluetext', + self::FILE_IMAGE => 'bluetext', self::FILE_BINARY => 'green', - self::FILE_DIRECTORY => 'blue', - self::FILE_SYMLINK => 'blue', + self::FILE_DIRECTORY => 'bluetext', + self::FILE_SYMLINK => 'bluetext', self::FILE_DELETED => 'red', - self::FILE_NORMAL => 'black', - self::FILE_SUBMODULE => 'blue', + self::FILE_NORMAL => 'bluetext', + self::FILE_SUBMODULE => 'bluetext', ); return idx($icons, $type, 'black'); diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index faf9457c15..945f8a58b5 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -132,7 +132,11 @@ final class DiffusionLastModifiedController extends DiffusionController { } } - $details = AphrontTableView::renderSingleDisplayLine($data->getSummary()); + $details = DiffusionView::linkDetail( + $drequest->getRepository(), + $commit->getCommitIdentifier(), + $data->getSummary()); + $details = AphrontTableView::renderSingleDisplayLine($details); } else { $author = ''; $details = ''; diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index ffc5ce61ed..bd53cf97a1 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -20,6 +20,7 @@ final class DiffusionBrowseTableView extends DiffusionView { public function render() { $request = $this->getDiffusionRequest(); $repository = $request->getRepository(); + require_celerity_resource('diffusion-history-css'); $base_path = trim($request->getPath(), '/'); if ($base_path) { @@ -74,7 +75,6 @@ final class DiffusionBrowseTableView extends DiffusionView { $dict = array( 'lint' => celerity_generate_unique_node_id(), - 'commit' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(), 'author' => celerity_generate_unique_node_id(), 'details' => celerity_generate_unique_node_id(), @@ -86,13 +86,13 @@ final class DiffusionBrowseTableView extends DiffusionView { } $rows[] = array( - $history_link, $browse_link, idx($dict, 'lint'), - $dict['commit'], $dict['details'], $dict['date'], + $history_link, ); + } if ($need_pull) { @@ -113,27 +113,16 @@ final class DiffusionBrowseTableView extends DiffusionView { $lint = $request->getLint(); $view = new AphrontTableView($rows); - $view->setHeaders( - array( - null, - pht('Path'), - ($lint ? $lint : pht('Lint')), - pht('Modified'), - pht('Details'), - pht('Committed'), - )); $view->setColumnClasses( array( - 'nudgeright', '', '', - '', - 'wide', + 'wide commit-detail', 'right', + 'right narrow', )); $view->setColumnVisibility( array( - true, true, $show_lint, true, @@ -144,15 +133,14 @@ final class DiffusionBrowseTableView extends DiffusionView { $view->setDeviceVisibility( array( true, - true, - false, false, true, false, + false, )); - return $view->render(); + return phutil_tag_div('diffusion-browse-table', $view->render()); } } diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index d058dc5cec..eb43e49f3c 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -156,6 +156,19 @@ abstract class DiffusionView extends AphrontView { $commit_name); } + final public static function linkDetail( + PhabricatorRepository $repository, + $commit, + $detail) { + + return phutil_tag( + 'a', + array( + 'href' => $repository->getCommitURI($commit), + ), + $detail); + } + final public static function linkRevision($id) { if (!$id) { return null; diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion-history.css index f4a51f7b56..7339d05ac1 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-history.css @@ -48,6 +48,16 @@ margin-right: 4px; } +/* - Browse Styles ----------------------------------------------------------*/ + +.diffusion-browse-table .commit-detail { + padding-left: 32px; +} + +.diffusion-browse-table .commit-detail a { + color: {$darkbluetext}; +} + /* - Phone Style ------------------------------------------------------------*/ .device-phone.diffusion-history-view .phui-two-column-view diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index 8edc034975..072db01660 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -20,6 +20,7 @@ input.diffusion-clone-uri { .diffusion-browse-name { margin-left: 8px; + letter-spacing: 0.02em; } .diffusion-link-icon + .diffusion-link-icon { diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css index 5fd7fd97bc..acc7818765 100644 --- a/webroot/rsrc/css/phui/phui-icon.css +++ b/webroot/rsrc/css/phui/phui-icon.css @@ -45,6 +45,10 @@ img.phui-image-disabled { filter: grayscale(100%); } +.phui-icon-view.bluetext { + color: {$bluetext}; +} + /* - Icon in a Circle ------------------------------------------------------- */ .phui-icon-circle { From 250aaabd6484e2742512dc285434a07f36d70fe2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 9 Jul 2017 11:15:46 -0700 Subject: [PATCH 316/543] Choose default project image by icon Summary: Builds out a map for icon->image in Projects, selects the icon's image as the default project image if there is no custom image chosen by the user. Test Plan: Select various icons, see image change. Test choose picture, pick a new image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18174 --- .../PhabricatorFilesOnDiskBuiltinFile.php | 16 +++++++ .../PhabricatorProjectConfigOptions.php | 2 + ...habricatorProjectEditPictureController.php | 5 ++- .../PhabricatorProjectTransactionEditor.php | 32 -------------- .../icon/PhabricatorProjectIconSet.php | 42 +++++++++++++++++++ .../project/query/PhabricatorProjectQuery.php | 12 ++---- 6 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php index 4eb5d9d825..ac51e30386 100644 --- a/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php @@ -56,4 +56,20 @@ final class PhabricatorFilesOnDiskBuiltinFile return $map; } + public function getProjectBuiltinFiles() { + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/projects/'; + + $map = array(); + $list = id(new FileFinder($root)) + ->withType('f') + ->withFollowSymlinks(true) + ->find(); + + foreach ($list as $file) { + $map[$file] = $root.$file; + } + return $map; + } + } diff --git a/src/applications/project/config/PhabricatorProjectConfigOptions.php b/src/applications/project/config/PhabricatorProjectConfigOptions.php index 36b6f09d86..c61faa64fb 100644 --- a/src/applications/project/config/PhabricatorProjectConfigOptions.php +++ b/src/applications/project/config/PhabricatorProjectConfigOptions.php @@ -34,6 +34,8 @@ a dictionary, which may contain these keys: - `key` //Required string.// Internal key identifying the icon. - `name` //Required string.// Human-readable icon name. - `icon` //Required string.// Specifies which actual icon image to use. + - `image` //Optional string.// Selects a default image. Select an image from + `resources/builtins/projects/`. - `default` //Optional bool.// Selects a default icon. Exactly one icon must be selected as the default. - `disabled` //Optional bool.// If true, this icon will no longer be diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index f8f1d532a9..97b7fe1792 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -98,7 +98,10 @@ final class PhabricatorProjectEditPictureController $form = id(new PHUIFormLayoutView()) ->setUser($viewer); - $default_image = PhabricatorFile::loadBuiltin($viewer, 'project.png'); + $builtin = PhabricatorProjectIconSet::getIconImage( + $project->getIcon()); + $default_image = PhabricatorFile::loadBuiltin($this->getViewer(), + 'projects/'.$builtin); $images = array(); diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 393c4569f6..7702b74bd6 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -296,10 +296,6 @@ final class PhabricatorProjectTransactionEditor } } - if ($this->getIsNewObject()) { - $this->setDefaultProfilePicture($object); - } - // TODO: We should dump an informational transaction onto the parent // project to show that we created the sub-thing. @@ -457,34 +453,6 @@ final class PhabricatorProjectTransactionEditor return $results; } - private function setDefaultProfilePicture(PhabricatorProject $project) { - if ($project->isMilestone()) { - return; - } - - $compose_color = $project->getDisplayIconComposeColor(); - $compose_icon = $project->getDisplayIconComposeIcon(); - - $builtin = id(new PhabricatorFilesComposeIconBuiltinFile()) - ->setColor($compose_color) - ->setIcon($compose_icon); - - $data = $builtin->loadBuiltinFileData(); - - $file = PhabricatorFile::newFromFileData( - $data, - array( - 'name' => $builtin->getBuiltinDisplayName(), - 'profile' => true, - 'canCDN' => true, - )); - - $project - ->setProfileImagePHID($file->getPHID()) - ->save(); - } - - protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php index 2283026598..f487b216f3 100644 --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -18,87 +18,104 @@ final class PhabricatorProjectIconSet 'icon' => 'fa-briefcase', 'name' => pht('Project'), 'default' => true, + 'image' => 'v3/briefcase.png', ), array( 'key' => 'tag', 'icon' => 'fa-tags', 'name' => pht('Tag'), + 'image' => 'v3/tag.png', ), array( 'key' => 'policy', 'icon' => 'fa-lock', 'name' => pht('Policy'), + 'image' => 'v3/lock.png', ), array( 'key' => 'group', 'icon' => 'fa-users', 'name' => pht('Group'), + 'image' => 'v3/people.png', ), array( 'key' => 'folder', 'icon' => 'fa-folder', 'name' => pht('Folder'), + 'image' => 'v3/folder.png', ), array( 'key' => 'timeline', 'icon' => 'fa-calendar', 'name' => pht('Timeline'), + 'image' => 'v3/calendar.png', ), array( 'key' => 'goal', 'icon' => 'fa-flag-checkered', 'name' => pht('Goal'), + 'image' => 'v3/flag.png', ), array( 'key' => 'release', 'icon' => 'fa-truck', 'name' => pht('Release'), + 'image' => 'v3/truck.png', ), array( 'key' => 'bugs', 'icon' => 'fa-bug', 'name' => pht('Bugs'), + 'image' => 'v3/bug.png', ), array( 'key' => 'cleanup', 'icon' => 'fa-trash-o', 'name' => pht('Cleanup'), + 'image' => 'v3/trash.png', ), array( 'key' => 'umbrella', 'icon' => 'fa-umbrella', 'name' => pht('Umbrella'), + 'image' => 'v3/umbrella.png', ), array( 'key' => 'communication', 'icon' => 'fa-envelope', 'name' => pht('Communication'), + 'image' => 'v3/mail.png', ), array( 'key' => 'organization', 'icon' => 'fa-building', 'name' => pht('Organization'), + 'image' => 'v3/organization.png', ), array( 'key' => 'infrastructure', 'icon' => 'fa-cloud', 'name' => pht('Infrastructure'), + 'image' => 'v3/cloud.png', ), array( 'key' => 'account', 'icon' => 'fa-credit-card', 'name' => pht('Account'), + 'image' => 'v3/creditcard.png', ), array( 'key' => 'experimental', 'icon' => 'fa-flask', 'name' => pht('Experimental'), + 'image' => 'v3/experimental.png', ), array( 'key' => 'milestone', 'icon' => 'fa-map-marker', 'name' => pht('Milestone'), 'special' => self::SPECIAL_MILESTONE, + 'image' => 'v3/marker.png', ), ); } @@ -149,6 +166,11 @@ final class PhabricatorProjectIconSet return idx($spec, 'name', null); } + public static function getIconImage($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'image', 'v3/briefcase.png'); + } + private static function getIconSpec($key) { $icons = self::getIconSpecifications(); foreach ($icons as $icon) { @@ -190,6 +212,7 @@ final class PhabricatorProjectIconSet 'key' => 'string', 'name' => 'string', 'icon' => 'string', + 'image' => 'optional string', 'special' => 'optional string', 'disabled' => 'optional bool', 'default' => 'optional bool', @@ -239,6 +262,25 @@ final class PhabricatorProjectIconSet $is_disabled = idx($value, 'disabled'); + if (idx($value, 'image')) { + $builtin = idx($value, 'image'); + $builtin_map = id(new PhabricatorFilesOnDiskBuiltinFile()) + ->getProjectBuiltinFiles(); + $builtin_map = array_flip($builtin_map); + + $root = dirname(phutil_get_library_root('phabricator')); + $image = $root.'/resources/builtin/projects/'.$builtin; + + if (!array_key_exists($image, $builtin_map)) { + throw new Exception( + pht( + 'The project image ("%s") specified for ("%s") '. + 'was not found in the folder "resources/builtin/projects/".', + $builtin, + $key)); + } + } + if (idx($value, 'default')) { if ($default === null) { if ($is_disabled) { diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 44055b7260..780f072678 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -358,8 +358,6 @@ final class PhabricatorProjectQuery protected function didFilterPage(array $projects) { if ($this->needImages) { - $default = null; - $file_phids = mpull($projects, 'getProfileImagePHID'); $file_phids = array_filter($file_phids); if ($file_phids) { @@ -376,12 +374,10 @@ final class PhabricatorProjectQuery foreach ($projects as $project) { $file = idx($files, $project->getProfileImagePHID()); if (!$file) { - if (!$default) { - $default = PhabricatorFile::loadBuiltin( - $this->getViewer(), - 'project.png'); - } - $file = $default; + $builtin = PhabricatorProjectIconSet::getIconImage( + $project->getIcon()); + $file = PhabricatorFile::loadBuiltin($this->getViewer(), + 'projects/'.$builtin); } $project->attachProfileImageFile($file); } From b4aedab955438fec74b1231d53b9c5a925598fb5 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 9 Jul 2017 12:25:27 -0700 Subject: [PATCH 317/543] Fix sitemap image Summary: Re-export sitemap image. Test Plan: look carefully Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18192 --- resources/builtin/projects/v3/sitemap.png | Bin 4336 -> 4336 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/builtin/projects/v3/sitemap.png b/resources/builtin/projects/v3/sitemap.png index 1812d13416403fc8899ea75efc84562f314d0c6d..8ee6e232b4885d6575767f75380182fb7d69fff7 100644 GIT binary patch literal 4336 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 XhPe?U1_~UO>H7LQ#@c0?h=~6I4BOMy literal 4336 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 Date: Sun, 9 Jul 2017 18:38:38 -0700 Subject: [PATCH 318/543] Move actions into Diffusion header Summary: This moves actions into the Diffusion main header, removes the locate file box, and widens description and cloning details. Projects are not currently in this layout, but will follow up in another diff. Trying to keep these changes small and iterative. Test Plan: Locate some files, test actions dropdown, repository with and without description. Also tablet, mobile layouts. {F5040026} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18193 --- resources/celerity/map.php | 4 +- .../DiffusionRepositoryController.php | 46 ++++++++++--------- .../view/DiffusionBranchListView.php | 2 +- .../view/DiffusionBrowseTableView.php | 2 +- .../view/DiffusionCommitListView.php | 2 +- .../view/DiffusionHistoryListView.php | 2 +- .../diffusion/view/DiffusionTagListView.php | 2 +- .../control/AphrontFormTypeaheadControl.php | 7 +++ .../{diffusion-history.css => diffusion.css} | 39 +++++++++++++++- 9 files changed, 76 insertions(+), 30 deletions(-) rename webroot/rsrc/css/application/diffusion/{diffusion-history.css => diffusion.css} (74%) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 939b48a643..4dc65888b9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -71,11 +71,11 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '898ed727', 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', + 'rsrc/css/application/diffusion/diffusion.css' => '08991f7e', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => 'bba8f811', @@ -570,7 +570,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '898ed727', + 'diffusion-css' => '08991f7e', 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index d8451db2db..1db5716f6c 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -17,6 +17,8 @@ final class DiffusionRepositoryController extends DiffusionController { return $response; } + require_celerity_resource('diffusion-css'); + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -25,11 +27,13 @@ final class DiffusionRepositoryController extends DiffusionController { $crumbs->setBorder(true); $header = $this->buildHeaderView($repository); - $curtain = $this->buildCurtain($repository); $property_table = $this->buildPropertiesTable($repository); + $actions = $this->buildActionList($repository); $description = $this->buildDescriptionView($repository); $locate_file = $this->buildLocateFile(); + $header->setActionList($actions); + // Before we do any work, make sure we're looking at a some content: we're // on a valid branch, and the repository is not empty. $page_has_content = false; @@ -88,14 +92,13 @@ final class DiffusionRepositoryController extends DiffusionController { $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setCurtain($curtain) ->setTabs($tabs) - ->setMainColumn(array( + ->setFooter(array( + $locate_file, $property_table, $description, - $locate_file, - )) - ->setFooter($content); + $content, + )); return $this->newPage() ->setTitle( @@ -236,7 +239,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setPolicyObject($repository) ->setProfileHeader(true) ->setImage($repository->getProfileImageURI()) - ->setImageEditURL('/diffusion/picture/'.$repository->getID().'/'); + ->setImageEditURL('/diffusion/picture/'.$repository->getID().'/') + ->addClass('diffusion-profile-header'); if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); @@ -254,13 +258,15 @@ final class DiffusionRepositoryController extends DiffusionController { return $header; } - private function buildCurtain(PhabricatorRepository $repository) { + private function buildActionList(PhabricatorRepository $repository) { $viewer = $this->getViewer(); $edit_uri = $repository->getPathURI('manage/'); - $curtain = $this->newCurtainView($repository); + $action_view = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($repository); - $curtain->addAction( + $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Manage Repository')) ->setIcon('fa-cogs') @@ -270,14 +276,14 @@ final class DiffusionRepositoryController extends DiffusionController { $push_uri = $this->getApplicationURI( 'pushlog/?repositories='.$repository->getMonogram()); - $curtain->addAction( + $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('View Push Logs')) ->setIcon('fa-list-alt') ->setHref($push_uri)); } - return $curtain; + return $action_view; } private function buildDescriptionView(PhabricatorRepository $repository) { @@ -290,9 +296,8 @@ final class DiffusionRepositoryController extends DiffusionController { $description = new PHUIRemarkupView($viewer, $description); $view->addTextContent($description); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Description')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); + ->appendChild($view) + ->addClass('diffusion-profile-description'); } return null; } @@ -455,15 +460,12 @@ final class DiffusionRepositoryController extends DiffusionController { id(new AphrontFormTypeaheadControl()) ->setHardpointID('locate-control') ->setID('locate-input') - ->setLabel(pht('Locate File'))); + ->setPlaceholder(pht('Locate File'))); $form_box = id(new PHUIBoxView()) - ->appendChild($form->buildLayoutView()); - $locate_panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Locate File')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($form_box); + ->appendChild($form->buildLayoutView()) + ->addClass('diffusion-profile-locate'); } - return $locate_panel; + return $form_box; } private function buildBrowseTable( diff --git a/src/applications/diffusion/view/DiffusionBranchListView.php b/src/applications/diffusion/view/DiffusionBranchListView.php index 8d1df82dc9..1a9df42e05 100644 --- a/src/applications/diffusion/view/DiffusionBranchListView.php +++ b/src/applications/diffusion/view/DiffusionBranchListView.php @@ -23,7 +23,7 @@ final class DiffusionBranchListView extends DiffusionView { $repository = $drequest->getRepository(); $commits = $this->commits; $viewer = $this->getUser(); - require_celerity_resource('diffusion-history-css'); + require_celerity_resource('diffusion-css'); $buildables = $this->loadBuildables($commits); $have_builds = false; diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index bd53cf97a1..48729d0669 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -20,7 +20,7 @@ final class DiffusionBrowseTableView extends DiffusionView { public function render() { $request = $this->getDiffusionRequest(); $repository = $request->getRepository(); - require_celerity_resource('diffusion-history-css'); + require_celerity_resource('diffusion-css'); $base_path = trim($request->getPath(), '/'); if ($base_path) { diff --git a/src/applications/diffusion/view/DiffusionCommitListView.php b/src/applications/diffusion/view/DiffusionCommitListView.php index 4d4bcc1020..d4f20d0014 100644 --- a/src/applications/diffusion/view/DiffusionCommitListView.php +++ b/src/applications/diffusion/view/DiffusionCommitListView.php @@ -73,7 +73,7 @@ final class DiffusionCommitListView extends AphrontView { } public function render() { - require_celerity_resource('diffusion-history-css'); + require_celerity_resource('diffusion-css'); return $this->buildList(); } diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 9dfa46cbef..8f9301c53a 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -7,7 +7,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { $viewer = $this->getUser(); $repository = $drequest->getRepository(); - require_celerity_resource('diffusion-history-css'); + require_celerity_resource('diffusion-css'); Javelin::initBehavior('phabricator-tooltips'); $buildables = $this->loadBuildables( diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index f9030581e3..abab9003c3 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -29,7 +29,7 @@ final class DiffusionTagListView extends DiffusionView { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $viewer = $this->getViewer(); - require_celerity_resource('diffusion-history-css'); + require_celerity_resource('diffusion-css'); $buildables = $this->loadBuildables($this->commits); diff --git a/src/view/form/control/AphrontFormTypeaheadControl.php b/src/view/form/control/AphrontFormTypeaheadControl.php index e4bee7c1ca..1e859ee132 100644 --- a/src/view/form/control/AphrontFormTypeaheadControl.php +++ b/src/view/form/control/AphrontFormTypeaheadControl.php @@ -3,6 +3,7 @@ final class AphrontFormTypeaheadControl extends AphrontFormControl { private $hardpointID; + private $placeholder; public function setHardpointID($hardpoint_id) { $this->hardpointID = $hardpoint_id; @@ -13,6 +14,11 @@ final class AphrontFormTypeaheadControl extends AphrontFormControl { return $this->hardpointID; } + public function setPlaceholder($placeholder) { + $this->placeholder = $placeholder; + return $this; + } + protected function getCustomControlClass() { return 'aphront-form-control-typeahead'; } @@ -30,6 +36,7 @@ final class AphrontFormTypeaheadControl extends AphrontFormControl { 'type' => 'text', 'name' => $this->getName(), 'value' => $this->getValue(), + 'placeholder' => $this->placeholder, 'disabled' => $this->getDisabled() ? 'disabled' : null, 'autocomplete' => 'off', 'id' => $this->getID(), diff --git a/webroot/rsrc/css/application/diffusion/diffusion-history.css b/webroot/rsrc/css/application/diffusion/diffusion.css similarity index 74% rename from webroot/rsrc/css/application/diffusion/diffusion-history.css rename to webroot/rsrc/css/application/diffusion/diffusion.css index 7339d05ac1..599f229152 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-history.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -1,7 +1,44 @@ /** - * @provides diffusion-history-css + * @provides diffusion-css */ +/* - Home Styles ------------------------------------------------------------*/ + +.diffusion-profile-header.phui-profile-header .phui-header-col3 { + vertical-align: middle; +} + +.diffusion-profile-header .phui-header-action-links a.button { + display: block; +} + +.diffusion-profile-locate .phui-form-view { + margin: 0 0 16px 0; + padding: 0; +} + +.diffusion-profile-locate .phui-form-view .aphront-form-control { + padding: 0; +} + +.diffusion-profile-locate .phui-form-view .aphront-form-input { + margin: 0; + width: 480px; +} + +.device .diffusion-profile-locate .phui-form-view .aphront-form-input { + margin: 0; + width: 100%; +} + +.diffusion-profile-description.phui-object-box { + padding: 0; +} + +.device-phone .diffusion-profile-description.phui-object-box { + padding: 0; +} + /* - List Styles ------------------------------------------------------------*/ .diffusion-history-list .phui-oi-link { From 5f1a359a92de5bcd2372cb60257b5ad1a5bbcca6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 10 Jul 2017 11:20:03 -0700 Subject: [PATCH 319/543] Add a UIExamples page for new project image builtins Summary: Adds a page to view all and their path in UIExamples. Test Plan: Review page in UIExamples, hover over image for path. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18196 --- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectBuiltinsExample.php | 71 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 095759a351..d99eb9e2f1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3612,6 +3612,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardManageController' => 'applications/project/controller/PhabricatorProjectBoardManageController.php', 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', + 'PhabricatorProjectBuiltinsExample' => 'applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php', 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', 'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php', @@ -9063,6 +9064,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardManageController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', + 'PhabricatorProjectBuiltinsExample' => 'PhabricatorUIExample', 'PhabricatorProjectCardView' => 'AphrontTagView', 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType', diff --git a/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php b/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php new file mode 100644 index 0000000000..2cc89b56d5 --- /dev/null +++ b/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php @@ -0,0 +1,71 @@ +getRequest()->getUser(); + + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/projects/v3/'; + + Javelin::initBehavior('phabricator-tooltips', array()); + + $map = array(); + $builtin_map = id(new FileFinder($root)) + ->withType('f') + ->withFollowSymlinks(true) + ->find(); + + $images = array(); + foreach ($builtin_map as $image) { + $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/'.$image); + $images[$file->getPHID()] = array( + 'uri' => $file->getBestURI(), + 'tip' => 'v3/'.$image, + ); + } + + $buttons = array(); + foreach ($images as $phid => $spec) { + $button = javelin_tag( + 'img', + array( + 'height' => 100, + 'width' => 100, + 'src' => $spec['uri'], + 'style' => 'float: left; padding: 4px;', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $spec['tip'], + 'size' => 300, + ), + )); + + $buttons[] = $button; + } + + $wrap1 = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Images')) + ->appendChild($buttons) + ->addClass('grouped'); + + return phutil_tag( + 'div', + array(), + array( + $wrap1, + )); + } +} From af71c990eee1754fcaf9f9fc0683a55f79c85194 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 10 Jul 2017 12:01:13 -0700 Subject: [PATCH 320/543] Test 0 and "" cases in Project Icon Config Summary: Better validation for setting a default image in project.icon Test Plan: Test adding `"0"` and `""` as image options in project.icon, see error. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18197 --- src/applications/project/icon/PhabricatorProjectIconSet.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php index f487b216f3..b128a35cad 100644 --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -262,7 +262,8 @@ final class PhabricatorProjectIconSet $is_disabled = idx($value, 'disabled'); - if (idx($value, 'image')) { + $image = idx($value, 'image'); + if ($image !== null) { $builtin = idx($value, 'image'); $builtin_map = id(new PhabricatorFilesOnDiskBuiltinFile()) ->getProjectBuiltinFiles(); From b987b4dd64f54c5bbd8deccc62564589f463ed5b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 10 Jul 2017 16:01:43 -0700 Subject: [PATCH 321/543] Rudamentary PHUILeftRightView Summary: First pass at providing a skeleton framework for laying out basic items in a left/right view. Will likely add some mobile-responsive options. Test Plan: UIExamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18200 --- resources/celerity/map.php | 2 + src/__phutil_library_map__.php | 4 ++ .../examples/PHUILeftRightExample.php | 72 +++++++++++++++++++ src/view/phui/PHUILeftRightView.php | 51 +++++++++++++ webroot/rsrc/css/phui/phui-left-right.css | 32 +++++++++ 5 files changed, 161 insertions(+) create mode 100644 src/applications/uiexample/examples/PHUILeftRightExample.php create mode 100644 src/view/phui/PHUILeftRightView.php create mode 100644 webroot/rsrc/css/phui/phui-left-right.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4dc65888b9..50200049a5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -166,6 +166,7 @@ return array( 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6e217679', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', + 'rsrc/css/phui/phui-left-right.css' => 'f60c67e7', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', 'rsrc/css/phui/phui-list.css' => 'dcafb463', 'rsrc/css/phui/phui-object-box.css' => '9cff003c', @@ -855,6 +856,7 @@ return array( 'phui-info-view-css' => '6e217679', 'phui-inline-comment-view-css' => 'ffd1a542', 'phui-invisible-character-view-css' => '6993d9f0', + 'phui-left-right-css' => 'f60c67e7', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => 'dcafb463', 'phui-object-box-css' => '9cff003c', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d99eb9e2f1..6d24871583 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1773,6 +1773,8 @@ phutil_register_library_map(array( 'PHUIInfoView' => 'view/form/PHUIInfoView.php', 'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php', 'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php', + 'PHUILeftRightExample' => 'applications/uiexample/examples/PHUILeftRightExample.php', + 'PHUILeftRightView' => 'view/phui/PHUILeftRightView.php', 'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php', 'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListView' => 'view/phui/PHUIListView.php', @@ -6927,6 +6929,8 @@ phutil_register_library_map(array( 'PHUIInfoView' => 'AphrontTagView', 'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase', 'PHUIInvisibleCharacterView' => 'AphrontView', + 'PHUILeftRightExample' => 'PhabricatorUIExample', + 'PHUILeftRightView' => 'AphrontTagView', 'PHUIListExample' => 'PhabricatorUIExample', 'PHUIListItemView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView', diff --git a/src/applications/uiexample/examples/PHUILeftRightExample.php b/src/applications/uiexample/examples/PHUILeftRightExample.php new file mode 100644 index 0000000000..46d96ddc38 --- /dev/null +++ b/src/applications/uiexample/examples/PHUILeftRightExample.php @@ -0,0 +1,72 @@ +getRequest(); + $user = $request->getUser(); + + $text = pht('This is a sample of some text.'); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setIcon('fa-bars'); + + $content1 = id(new PHUILeftRightView()) + ->setLeft($text) + ->setRight($button) + ->setVerticalAlign(PHUILeftRightView::ALIGN_TOP); + + $content2 = id(new PHUILeftRightView()) + ->setLeft($text) + ->setRight($button) + ->setVerticalAlign(PHUILeftRightView::ALIGN_MIDDLE); + + $content3 = id(new PHUILeftRightView()) + ->setLeft($text) + ->setRight($button) + ->setVerticalAlign(PHUILeftRightView::ALIGN_BOTTOM); + + + $head2 = id(new PHUIHeaderView()) + ->setHeader('Align Top') + ->addClass('ml'); + + $head3 = id(new PHUIHeaderView()) + ->setHeader(pht('Align Middle')) + ->addClass('ml'); + + $head4 = id(new PHUIHeaderView()) + ->setHeader(pht('Align Bottom')) + ->addClass('ml'); + + $wrap2 = id(new PHUIBoxView()) + ->appendChild($content1) + ->addMargin(PHUI::MARGIN_LARGE); + + $wrap3 = id(new PHUIBoxView()) + ->appendChild($content2) + ->addMargin(PHUI::MARGIN_LARGE); + + $wrap4 = id(new PHUIBoxView()) + ->appendChild($content3) + ->addMargin(PHUI::MARGIN_LARGE); + + return array( + $head2, + $wrap2, + $head3, + $wrap3, + $head4, + $wrap4, + ); + } +} diff --git a/src/view/phui/PHUILeftRightView.php b/src/view/phui/PHUILeftRightView.php new file mode 100644 index 0000000000..c3873c4899 --- /dev/null +++ b/src/view/phui/PHUILeftRightView.php @@ -0,0 +1,51 @@ +left = $left; + return $this; + } + + public function setRight($right) { + $this->right = $right; + return $this; + } + + public function setVerticalAlign($align) { + $this->verticalAlign = $align; + return $this; + } + + protected function getTagAttributes() { + require_celerity_resource('phui-left-right-css'); + + $classes = array(); + $classes[] = 'phui-left-right-view'; + + if ($this->verticalAlign) { + $classes[] = 'phui-lr-view-'.$this->verticalAlign; + } + + return array('class' => implode(' ', $classes)); + } + + protected function getTagName() { + return 'div'; + } + + protected function getTagContent() { + $left = phutil_tag_div('phui-left-view', $this->left); + $right = phutil_tag_div('phui-right-view', $this->right); + + return phutil_tag_div('phui-lr-container', array($left, $right)); + } +} diff --git a/webroot/rsrc/css/phui/phui-left-right.css b/webroot/rsrc/css/phui/phui-left-right.css new file mode 100644 index 0000000000..2d260758b4 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-left-right.css @@ -0,0 +1,32 @@ +/** + * @provides phui-left-right-css + */ + +.phui-left-right-view { + display: table; + width: 100%; +} + +.phui-lr-container { + display: table-row; +} + +.phui-left-view { + display: table-cell; + text-align: left; +} + +.phui-right-view { + display: table-cell; + text-align: right; +} + +.phui-lr-view-top .phui-left-view, +.phui-lr-view-top .phui-right-view { + vertical-align: top; +} + +.phui-lr-view-bottom .phui-left-view, +.phui-lr-view-bottom .phui-right-view { + vertical-align: bottom; +} From a6b550ba0394284441ee55d11e276a05eb568ad9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 10 Jul 2017 20:20:09 -0700 Subject: [PATCH 322/543] Move Clone Repository to Dialog Summary: This moves the clone details on the Repository Home to a button / dialog. Functionally this is to pull content on the page way up, while giving full space to all the clone options. I think we can build this into some FancyJS if needed, but this seems to clean ui the UI dramatically with little overhead. I don't want to attempt the JS dropdown unless we're sure that's the best path (it exposes the most common URI by default, saving a click). Test Plan: Tested hg, svn, git repositories and the raw URL page. Test close button. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18203 --- src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 1 + .../controller/DiffusionCloneController.php | 122 ++++++++++++++ .../DiffusionRepositoryController.php | 151 +++--------------- .../storage/PhabricatorRepository.php | 4 + 5 files changed, 152 insertions(+), 128 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionCloneController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6d24871583..1e3ed214fa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -619,6 +619,7 @@ phutil_register_library_map(array( 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php', + 'DiffusionCloneController' => 'applications/diffusion/controller/DiffusionCloneController.php', 'DiffusionCloneURIView' => 'applications/diffusion/view/DiffusionCloneURIView.php', 'DiffusionCommandEngine' => 'applications/diffusion/protocol/DiffusionCommandEngine.php', 'DiffusionCommandEngineTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php', @@ -5610,6 +5611,7 @@ phutil_register_library_map(array( 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup', + 'DiffusionCloneController' => 'DiffusionController', 'DiffusionCloneURIView' => 'AphrontView', 'DiffusionCommandEngine' => 'Phobject', 'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index dc1b120138..b00fcce77f 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -55,6 +55,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', + 'clone/' => 'DiffusionCloneController', 'history/(?P.*)' => 'DiffusionHistoryController', 'graph/(?P.*)' => 'DiffusionGraphController', 'browse/(?P.*)' => 'DiffusionBrowseController', diff --git a/src/applications/diffusion/controller/DiffusionCloneController.php b/src/applications/diffusion/controller/DiffusionCloneController.php new file mode 100644 index 0000000000..8bc4faa1a2 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionCloneController.php @@ -0,0 +1,122 @@ +getViewer(); + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $display_never = PhabricatorRepositoryURI::DISPLAY_NEVER; + $warning = null; + + $uris = $repository->getURIs(); + foreach ($uris as $uri) { + if ($uri->getIsDisabled()) { + continue; + } + + if ($uri->getEffectiveDisplayType() == $display_never) { + continue; + } + + if ($repository->isSVN()) { + $label = phutil_tag_div('diffusion-clone-label', pht('Checkout')); + } else { + $label = phutil_tag_div('diffusion-clone-label', pht('Clone')); + } + + $view->addProperty( + $label, + $this->renderCloneURI($repository, $uri)); + } + + if (!$view->hasAnyProperties()) { + $view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Repository has no URIs set.')); + } + + $info = null; + + // Try to load alternatives. This may fail for repositories which have not + // cloned yet. If it does, just ignore it and continue. + try { + $alternatives = $drequest->getRefAlternatives(); + } catch (ConduitClientException $ex) { + $alternatives = array(); + } + + if ($alternatives) { + $message = array( + pht( + 'The ref "%s" is ambiguous in this repository.', + $drequest->getBranch()), + ' ', + phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'refs', + )), + ), + pht('View Alternatives')), + ); + + $messages = array($message); + + $warning = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors(array($message)); + } + + $cancel_uri = $drequest->generateURI( + array( + 'action' => 'branch', + 'path' => '/', + )); + + return $this->newDialog() + ->setTitle(pht('Clone Repository')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($cancel_uri, pht('Close')) + ->appendChild(array($view, $warning)); + } + + private function renderCloneURI( + PhabricatorRepository $repository, + PhabricatorRepositoryURI $uri) { + + if ($repository->isSVN()) { + $display = csprintf( + 'svn checkout %R %R', + (string)$uri->getDisplayURI(), + $repository->getCloneName()); + } else { + $display = csprintf('%R', (string)$uri->getDisplayURI()); + } + + $display = (string)$display; + $viewer = $this->getViewer(); + + return id(new DiffusionCloneURIView()) + ->setViewer($viewer) + ->setRepository($repository) + ->setRepositoryURI($uri) + ->setDisplayURI($display); + } + +} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 1db5716f6c..08f77f8c3e 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -27,7 +27,6 @@ final class DiffusionRepositoryController extends DiffusionController { $crumbs->setBorder(true); $header = $this->buildHeaderView($repository); - $property_table = $this->buildPropertiesTable($repository); $actions = $this->buildActionList($repository); $description = $this->buildDescriptionView($repository); $locate_file = $this->buildLocateFile(); @@ -90,12 +89,28 @@ final class DiffusionRepositoryController extends DiffusionController { $tabs = $this->buildTabsView('home'); + $clone_uri = $drequest->generateURI( + array( + 'action' => 'clone', + )); + + $clone_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText('Clone') + ->setColor(PHUIButtonView::GREEN) + ->setIcon('fa-download') + ->setWorkflow(true) + ->setHref($clone_uri); + + $bar = id(new PHUILeftRightView()) + ->setLeft($locate_file) + ->setRight($clone_button); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setTabs($tabs) ->setFooter(array( - $locate_file, - $property_table, + $bar, $description, $content, )); @@ -302,87 +317,6 @@ final class DiffusionRepositoryController extends DiffusionController { return null; } - private function buildPropertiesTable(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $display_never = PhabricatorRepositoryURI::DISPLAY_NEVER; - - $uris = $repository->getURIs(); - foreach ($uris as $uri) { - if ($uri->getIsDisabled()) { - continue; - } - - if ($uri->getEffectiveDisplayType() == $display_never) { - continue; - } - - if ($repository->isSVN()) { - $label = phutil_tag_div('diffusion-clone-label', pht('Checkout')); - } else { - $label = phutil_tag_div('diffusion-clone-label', pht('Clone')); - } - - $view->addProperty( - $label, - $this->renderCloneURI($repository, $uri)); - } - - if (!$view->hasAnyProperties()) { - $view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('Repository has no URIs set.')); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); - - $info = null; - $drequest = $this->getDiffusionRequest(); - - // Try to load alternatives. This may fail for repositories which have not - // cloned yet. If it does, just ignore it and continue. - try { - $alternatives = $drequest->getRefAlternatives(); - } catch (ConduitClientException $ex) { - $alternatives = array(); - } - - if ($alternatives) { - $message = array( - pht( - 'The ref "%s" is ambiguous in this repository.', - $drequest->getBranch()), - ' ', - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'refs', - )), - ), - pht('View Alternatives')), - ); - - $messages = array($message); - - $info = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors(array($message)); - - $box->setInfoView($info); - } - - - return $box; - } - private function buildHistoryTable( $history_results, $history, @@ -504,52 +438,13 @@ final class DiffusionRepositoryController extends DiffusionController { } $browse_uri = $drequest->generateURI(array('action' => 'browse')); - - $browse_panel = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $header = id(new PHUIHeaderView()) - ->setHeader($repository->getName()); - - $button = id(new PHUIButtonView()) - ->setText(pht('Browse')) - ->setTag('a') - ->setIcon('fa-code') - ->setHref($browse_uri); - - $header->addActionLink($button); - $browse_panel->setHeader($header); - $browse_panel->setTable($browse_table); - $pager->setURI($browse_uri, 'offset'); - if ($pager->willShowPagingControls()) { - $browse_panel->setPager($pager); - } - - return $browse_panel; - } - - private function renderCloneURI( - PhabricatorRepository $repository, - PhabricatorRepositoryURI $uri) { - - if ($repository->isSVN()) { - $display = csprintf( - 'svn checkout %R %R', - (string)$uri->getDisplayURI(), - $repository->getCloneName()); - } else { - $display = csprintf('%R', (string)$uri->getDisplayURI()); - } - - $display = (string)$display; - $viewer = $this->getViewer(); - - return id(new DiffusionCloneURIView()) - ->setViewer($viewer) - ->setRepository($repository) - ->setRepositoryURI($uri) - ->setDisplayURI($display); + return id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($browse_table) + ->setPager($pager); } private function getTagLimit() { diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index f8a434e42b..eb6608eb57 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -700,6 +700,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO switch ($action) { case 'history': case 'graph': + case 'clone': case 'browse': case 'change': case 'lastmodified': @@ -818,6 +819,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO // it came from a URI. $uri = rawurldecode("{$path}{$commit}"); break; + case 'clone': + $uri = $this->getPathURI("/{$action}/"); + break; } if ($action == 'rendering-ref') { From 7408483c2f02d955b07b36c278f21721dfe72e05 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Jul 2017 14:33:17 -0700 Subject: [PATCH 323/543] Hide Pager border if no pager exists Summary: I guess we have this magical method that tells me if a pager is coming down the render pipe. Huzzah. Test Plan: Test Branches page in Diffusion, see no pager border. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18202 --- src/view/phui/PHUIObjectBoxView.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 26044ef3a0..0089e8e26e 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -302,7 +302,9 @@ final class PHUIObjectBoxView extends AphrontTagView { $pager = null; if ($this->pager) { - $pager = phutil_tag_div('phui-object-box-pager', $this->pager); + if ($this->pager->willShowPagingControls()) { + $pager = phutil_tag_div('phui-object-box-pager', $this->pager); + } } $content = array( From db57da0f7472b57e17f53ce604e4c8b0350f0a56 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Jul 2017 19:46:58 -0700 Subject: [PATCH 324/543] Fix SVN form_box error Summary: Fixes T12915. Test Plan: Test a SVN repository locally, ensure page loads. Reviewers: epriestley Subscribers: Korvin Maniphest Tasks: T12915 Differential Revision: https://secure.phabricator.com/D18207 --- .../diffusion/controller/DiffusionRepositoryController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 08f77f8c3e..a3ea11f7fc 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -371,7 +371,7 @@ final class DiffusionRepositoryController extends DiffusionController { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $locate_panel = null; + $form_box = null; if ($repository->canUsePathTree()) { Javelin::initBehavior( 'diffusion-locate-file', From 0c4cff28df73632ba94410cf6ac33418186f9ea6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Jul 2017 20:37:06 -0700 Subject: [PATCH 325/543] Clean up NUX a bit on Diffusion Summary: Just some cleanup. Make sure action-bar has consistent space if locate is there or not, hide tabs if repository has no content. Use clone or checkout language depending on SCM. Fixes T12915. Test Plan: Test git, hg, svn blank states. {F5042707} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12915 Differential Revision: https://secure.phabricator.com/D18208 --- resources/celerity/map.php | 4 ++-- .../controller/DiffusionRepositoryController.php | 16 +++++++++++++--- .../rsrc/css/application/diffusion/diffusion.css | 6 +++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 50200049a5..96744377e2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -75,7 +75,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', - 'rsrc/css/application/diffusion/diffusion.css' => '08991f7e', + 'rsrc/css/application/diffusion/diffusion.css' => '8d01932f', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => 'bba8f811', @@ -571,7 +571,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-css' => '08991f7e', + 'diffusion-css' => '8d01932f', 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index a3ea11f7fc..6def808cff 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -94,9 +94,15 @@ final class DiffusionRepositoryController extends DiffusionController { 'action' => 'clone', )); + if ($repository->isSVN()) { + $clone_text = pht('Checkout'); + } else { + $clone_text = pht('Clone'); + } + $clone_button = id(new PHUIButtonView()) ->setTag('a') - ->setText('Clone') + ->setText($clone_text) ->setColor(PHUIButtonView::GREEN) ->setIcon('fa-download') ->setWorkflow(true) @@ -104,17 +110,21 @@ final class DiffusionRepositoryController extends DiffusionController { $bar = id(new PHUILeftRightView()) ->setLeft($locate_file) - ->setRight($clone_button); + ->setRight($clone_button) + ->addClass('diffusion-action-bar'); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setTabs($tabs) ->setFooter(array( $bar, $description, $content, )); + if ($page_has_content) { + $view->setTabs($tabs); + } + return $this->newPage() ->setTitle( array( diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index 599f229152..3d81cd5c62 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -12,8 +12,12 @@ display: block; } +.diffusion-action-bar { + margin-bottom: 16px; +} + .diffusion-profile-locate .phui-form-view { - margin: 0 0 16px 0; + margin: 0; padding: 0; } From 565c49ad0ee8e4601301ef675da94ee744a6954e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Jul 2017 15:21:54 -0700 Subject: [PATCH 326/543] Minor touchup on diff banner Summary: Remove extra icon spacing, swap icons. Test Plan: Review a diff with comments in sandbox. Try dropdown. Follow links Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18216 --- resources/celerity/map.php | 26 +++++++++---------- .../differential/changeset-view.css | 4 --- .../js/application/diff/DiffChangesetList.js | 4 +-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 96744377e2..b418ffcdb4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,8 +12,8 @@ return array( 'core.pkg.css' => '7ae9e755', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '4ec4a37a', - 'differential.pkg.js' => 'd4ab0e81', + 'differential.pkg.css' => 'b00f573e', + 'differential.pkg.js' => '1d80ecc6', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'b5e6be7f', + 'rsrc/css/application/differential/changeset-view.css' => '3cef17bd', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -398,7 +398,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', - 'rsrc/js/application/diff/DiffChangesetList.js' => '79de07c6', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'cb1570cb', 'rsrc/js/application/diff/DiffInline.js' => '1bfa31c7', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -564,7 +564,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'b5e6be7f', + 'differential-changeset-view-css' => '3cef17bd', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '99abf4cd', - 'phabricator-diff-changeset-list' => '79de07c6', + 'phabricator-diff-changeset-list' => 'cb1570cb', 'phabricator-diff-inline' => '1bfa31c7', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1146,6 +1146,9 @@ return array( 'javelin-util', 'javelin-uri', ), + '3cef17bd' => array( + 'phui-inline-comment-view-css', + ), '3dbf94d5' => array( 'javelin-behavior', 'javelin-dom', @@ -1480,10 +1483,6 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), - '79de07c6' => array( - 'javelin-install', - 'phuix-button-view', - ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1815,9 +1814,6 @@ return array( 'javelin-dom', 'javelin-util', ), - 'b5e6be7f' => array( - 'phui-inline-comment-view-css', - ), 'b6993408' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1961,6 +1957,10 @@ return array( 'cae95e89' => array( 'syntax-default-css', ), + 'cb1570cb' => array( + 'javelin-install', + 'phuix-button-view', + ), 'ccf1cbf8' => array( 'javelin-install', 'javelin-dom', diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index bf532d0eb3..98c8f099ba 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -402,10 +402,6 @@ tr.differential-inline-loading { line-height: 28px; } -.diff-banner .phui-icon-view { - margin-right: 4px; -} - .diff-banner-path { color: {$greytext}; } diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 808527876c..89a18a9a8b 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1610,7 +1610,7 @@ JX.install('DiffChangesetList', { _getMenuButton: function() { if (!this._menuButton) { var button = new JX.PHUIXButtonView() - .setIcon('fa-gear') + .setIcon('fa-bars') .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); var dropdown = new JX.PHUIXDropdownMenu(button.getNode()); @@ -1659,7 +1659,7 @@ JX.install('DiffChangesetList', { list.addItem( new JX.PHUIXActionView() - .setIcon('fa-link') + .setIcon('fa-external-link') .setName(pht('List Inline Comments')) .setHref(this.getInlineListURI())); } From b8cd5b0eb89a29d1e1f365818894c02f5c8c5d3d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Jul 2017 14:47:19 -0700 Subject: [PATCH 327/543] Use a less-esoteric spelling of "capabilities" in several places Summary: This spelling can definitely feel a little overplayed at times, but I still think it's a gold standard in spellings of "capabilities". Test Plan: Felt old and uncool. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18215 --- .../diffusion/protocol/DiffusionMercurialWireProtocol.php | 4 ++-- .../policy/controller/PhabricatorPolicyExplainController.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php index 11e864e74e..32a73867e5 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php @@ -105,14 +105,14 @@ final class DiffusionMercurialWireProtocol extends Phobject { * disallow the client from knowing we speak bundle2 by removing it * from the capabilities listing. * - * The format of the capabilties string is: "a space separated list + * The format of the capabilities string is: "a space separated list * of strings representing what commands the server supports" * @link https://www.mercurial-scm.org/wiki/CommandServer#Protocol * * @param string $capabilities - The string of capabilities to * strip the bundle2 capability from. This is expected to be * the space-separated list of strings resulting from the - * querying the 'capabilties' command. + * querying the 'capabilities' command. * * @return string The resulting space-separated list of capabilities * which no longer contains the 'bundle2' capability. This is meant diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php index da4103765b..fc508cfa3a 100644 --- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php +++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php @@ -253,7 +253,7 @@ final class PhabricatorPolicyExplainController ->appendParagraph( pht( 'To access this object, users must have first have access '. - 'capabilties on these other objects:')) + 'capabilities on these other objects:')) ->appendList($items); } From bddd1da053e86b1b7a78236f4115fa2903720d1f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 13 Jul 2017 10:36:34 -0700 Subject: [PATCH 328/543] Update Support diviner document Summary: This updates the support document, specifically, scopes down feature requests, updates community links, and other wordsmithing. Unsure where to direct bug reports right now, but we'll have something soon? Test Plan: Read carefully Reviewers: epriestley Reviewed By: epriestley Subscribers: Luke081515.2, Korvin Differential Revision: https://secure.phabricator.com/D18218 --- src/docs/user/support.diviner | 62 ++++++++--------------------------- 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/src/docs/user/support.diviner b/src/docs/user/support.diviner index 18de79a9a1..9dc06b15a3 100644 --- a/src/docs/user/support.diviner +++ b/src/docs/user/support.diviner @@ -30,19 +30,13 @@ Reporting Bugs ============== The upstream will accept **reproducible** bug reports in modern, first-party -production code running in reasonable environments. +production code running in reasonable environments. Before submitting a bug +report you **must update** to the latest version of Phabricator. This reduces +support costs on the upstream, please be mindful. To report bugs, see @{article:Contributing Bug Reports}. -Requesting Features -=================== - -The upstream accepts feature requests which **describe problems** you would -like Phabricator to be able to solve. - -To request features, see @{article:Contributing Feature Requests}. - Contributing ============ @@ -64,8 +58,8 @@ See [[ https://secure.phabricator.com/w/consulting/ | Consulting ]] for details. Helping individual installs navigate unique setup problems takes our time away from developing Phabricator, so we can not offer this service for free. -You may be able to get free help with these issues from the community. See -below for details. +You may be able to get free help with these issues from the +[[ https://phurl.io/u/discourse | community ]]. See below for details. Hosting @@ -82,42 +76,12 @@ endlessly to make installation a perplexing nightmare that none other than ourselves can hope to navigate. -Prioritization -============== +Phabricator Community +===================== -The upstream offers prioritization, a service which allows you to control -our roadmap and get features you're interested in built sooner at reasonable -rates. See -[[ https://secure.phabricator.com/w/prioritization/ | Prioritization ]] for -details. - - -Consulting -========== - -The upstream offers general-purpose consulting services. See -[[ https://secure.phabricator.com/w/consulting/ | Consulting ]] for details. - - -Community -========= - -These resources are not provided by the upstream. They are not official support -channels and you may not receive support here, or you may receive help which is -misleading or wrong. - -You may be able to get answers to questions on sites like -[[ http://stackoverflow.com | Stack Overflow ]], -[[ https://www.quora.com | Quora ]], -[[ https://jelly.co | Jelly ]], or -[[ https://twitter.com | Twitter ]]. The upstream occasionally participates on -these sites but these are not official support channels and you should not -expect to receive a response. - -There is a -[[ https://secure.phabricator.com/conpherence/1336/ | General Chat ]] -Conpherence room on this install, and you can ask questions in -[[ https://secure.phabricator.com/ponder/ | Ponder ]]. These -are not upstream support channels and you may not receive a response to -questions, but someone in the community may be able to point you in the right -direction. +We provide hosting for a +[[ https://phurl.io/u/discourse | Discussion Forum ]] +where admins and users help and answer questions from other community members. +Upstream developers may occasionally participate, but this is mostly +a user to user community. If you run into general problems, but are not +interested in paid support, this is the main place to find help. From 10d9d2519cff9b88429fdf0c694d38d166378249 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Jul 2017 14:45:24 -0700 Subject: [PATCH 329/543] Update Bug Report diviner document Summary: Fixes T12922. For now this shuffles open source -> discouse, phacility -> phacility. Test Plan: Regenerate diviner docs, click on new links. Reviewers: epriestley, avivey Reviewed By: avivey Subscribers: avivey, Korvin Maniphest Tasks: T12922 Differential Revision: https://secure.phabricator.com/D18229 --- src/docs/contributor/bug_reports.diviner | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/docs/contributor/bug_reports.diviner b/src/docs/contributor/bug_reports.diviner index 91cfbce947..7602b9e655 100644 --- a/src/docs/contributor/bug_reports.diviner +++ b/src/docs/contributor/bug_reports.diviner @@ -12,6 +12,7 @@ accept reports which omit reproduction instructions. For help, see @{article:Providing Reproduction Steps}. + Overview ======== @@ -151,8 +152,8 @@ to reproduce problems. For help, see @{article:Providing Reproduction Steps}. -Create a Task in Maniphest -========================== +For Open Source Users +===================== If you're up to date, supported, have collected information about the problem, and have the best reproduction instructions you can come up with, you're ready @@ -161,18 +162,16 @@ to file an issue. It is **particularly critical** that you include reproduction steps. We will not accept reports which describe issues we can not reproduce. -(NOTE) https://secure.phabricator.com/u/newbug/ +(NOTE) https://discourse.phabricator-community.org/c/bug -If you don't want to file there (or, for example, your bug relates to being -unable to log in or unable to file an issue in Maniphest) you can file on any -other channel, but we can address reports much more effectively if they're -filed against the upstream than if they're filed somewhere else. -| Effectiveness | Filing Method | -|---|---| -| Best | Upstream Maniphest | -| Ehhh | Quora, StackOverflow, Facebook, Jelly, email, etc. | -| What | Passive-aggressive tweet | +For Phacility Customers +======================= + +If you've encountered a bug via a Phacility instance or have a paid support +contract with Phacility, you can find more information on how to get help via +[[ https://admin.phacility.com/book/phacility/article/support/ | Phacility +Support ]]. Next Steps From 7aeefc0cca5d888979b1524813de46d3d8b3b9c5 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Jul 2017 11:08:17 -0700 Subject: [PATCH 330/543] Add an Experimental Dark Mode to Phabricator Summary: Mostly this is an exercise to clean up our CSS and Celerity processor by making sure all important color decisions are generatable. It's somewhat resonable to use if you don't review code. Posting it up here mostly so I don't lose the work. Test Plan: Visit lots and lots of pages with dark mode on and off. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18227 --- resources/celerity/map.php | 116 +++++----- src/__phutil_library_map__.php | 2 + .../CelerityDarkModePostprocessor.php | 218 ++++++++++++++++++ .../CelerityDefaultPostprocessor.php | 19 ++ webroot/rsrc/css/aphront/dialog-view.css | 4 +- webroot/rsrc/css/aphront/list-filter-view.css | 2 +- webroot/rsrc/css/aphront/table-view.css | 2 +- webroot/rsrc/css/aphront/tokenizer.css | 4 +- webroot/rsrc/css/aphront/typeahead-browse.css | 2 +- webroot/rsrc/css/aphront/typeahead.css | 6 +- .../css/application/base/main-menu-view.css | 6 +- .../application/base/notification-menu.css | 4 +- .../application/base/standard-page-view.css | 2 +- .../css/application/config/config-options.css | 4 +- .../conpherence/durable-column.css | 2 +- .../application/conpherence/header-pane.css | 2 +- .../rsrc/css/application/conpherence/menu.css | 3 +- .../application/conpherence/message-pane.css | 8 +- .../conpherence/participant-pane.css | 4 +- .../application/conpherence/transaction.css | 4 +- .../css/application/dashboard/dashboard.css | 2 +- .../diff/inline-comment-summary.css | 7 +- .../differential/changeset-view.css | 14 +- .../differential/phui-inline-comment.css | 10 +- .../diffusion/diffusion-source.css | 2 +- .../css/application/diffusion/diffusion.css | 2 +- .../files/global-drag-and-drop.css | 2 +- .../objectselector/object-selector.css | 2 +- webroot/rsrc/css/application/paste/paste.css | 12 +- .../people/people-picture-menu-item.css | 2 +- webroot/rsrc/css/application/phame/phame.css | 15 +- .../rsrc/css/application/pholio/pholio.css | 2 +- .../application/project/project-card-view.css | 63 ++--- .../search/application-search-view.css | 4 +- .../css/application/search/search-results.css | 2 +- webroot/rsrc/css/core/core.css | 1 + webroot/rsrc/css/core/remarkup.css | 27 +-- .../layout/phabricator-source-code-view.css | 12 +- .../css/phui/button/phui-button-simple.css | 4 +- webroot/rsrc/css/phui/button/phui-button.css | 28 +-- .../css/phui/calendar/phui-calendar-list.css | 2 +- .../css/phui/calendar/phui-calendar-month.css | 6 +- .../rsrc/css/phui/calendar/phui-calendar.css | 2 +- .../css/phui/object-item/phui-oi-big-ui.css | 2 +- .../phui/object-item/phui-oi-list-view.css | 4 +- webroot/rsrc/css/phui/phui-action-list.css | 3 +- webroot/rsrc/css/phui/phui-action-panel.css | 2 +- webroot/rsrc/css/phui/phui-big-info-view.css | 2 +- webroot/rsrc/css/phui/phui-box.css | 12 +- webroot/rsrc/css/phui/phui-comment-form.css | 6 +- webroot/rsrc/css/phui/phui-document-pro.css | 8 +- .../rsrc/css/phui/phui-document-summary.css | 2 +- webroot/rsrc/css/phui/phui-document.css | 2 +- webroot/rsrc/css/phui/phui-fontkit.css | 2 +- webroot/rsrc/css/phui/phui-form.css | 6 +- webroot/rsrc/css/phui/phui-header-view.css | 2 +- webroot/rsrc/css/phui/phui-hovercard.css | 2 +- webroot/rsrc/css/phui/phui-info-view.css | 3 +- .../phui/phui-invisible-character-view.css | 2 +- webroot/rsrc/css/phui/phui-lightbox.css | 6 +- webroot/rsrc/css/phui/phui-list.css | 5 +- webroot/rsrc/css/phui/phui-pinboard-view.css | 4 +- webroot/rsrc/css/phui/phui-tag-view.css | 28 +-- webroot/rsrc/css/phui/phui-timeline-view.css | 6 +- .../rsrc/css/phui/phui-two-column-view.css | 6 +- .../phui/workboards/phui-workboard-color.css | 2 +- .../css/phui/workboards/phui-workcard.css | 4 +- 67 files changed, 479 insertions(+), 279 deletions(-) create mode 100644 src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b418ffcdb4..096f89527a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,12 +7,12 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'ff161f2d', + 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '7ae9e755', + 'core.pkg.css' => '2cac901b', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => 'b00f573e', + 'differential.pkg.css' => '0cdef299', 'differential.pkg.js' => '1d80ecc6', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', @@ -35,17 +35,17 @@ return array( 'rsrc/css/aphront/table-view.css' => 'a3aa6910', 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', 'rsrc/css/aphront/tooltip.css' => '173b9431', - 'rsrc/css/aphront/typeahead-browse.css' => '4f82e510', - 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', + 'rsrc/css/aphront/typeahead-browse.css' => 'f2818435', + 'rsrc/css/aphront/typeahead.css' => '4434bc8a', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', 'rsrc/css/application/base/main-menu-view.css' => '16053029', - 'rsrc/css/application/base/notification-menu.css' => '6a697e43', + 'rsrc/css/application/base/notification-menu.css' => '73fefdfa', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', - 'rsrc/css/application/config/config-options.css' => '0ede4c9b', + 'rsrc/css/application/config/config-options.css' => 'c5aac7b0', 'rsrc/css/application/config/config-page.css' => 'c1d5121b', 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', @@ -53,7 +53,7 @@ return array( 'rsrc/css/application/conpherence/color.css' => 'abb4c358', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', 'rsrc/css/application/conpherence/header-pane.css' => 'cb6f4e19', - 'rsrc/css/application/conpherence/menu.css' => '6953e7ec', + 'rsrc/css/application/conpherence/menu.css' => '69368e97', 'rsrc/css/application/conpherence/message-pane.css' => 'b0f55ecc', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '26a3ce56', @@ -62,11 +62,11 @@ return array( 'rsrc/css/application/countdown/timer.css' => '16c52f5c', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', - 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', + 'rsrc/css/application/diff/inline-comment-summary.css' => 'f23d4e8f', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '3cef17bd', + 'rsrc/css/application/differential/changeset-view.css' => 'bf1a41d8', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', + 'rsrc/css/application/differential/phui-inline-comment.css' => 'c4036846', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -77,7 +77,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/diffusion/diffusion.css' => '8d01932f', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', - 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', + 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 'rsrc/css/application/flag/flag.css' => 'bba8f811', 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', @@ -88,10 +88,10 @@ return array( 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', 'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', - 'rsrc/css/application/paste/paste.css' => '1898e534', + 'rsrc/css/application/paste/paste.css' => '9fcc9773', 'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34', 'rsrc/css/application/people/people-profile.css' => '4df76faf', - 'rsrc/css/application/phame/phame.css' => 'b3a0b3a3', + 'rsrc/css/application/phame/phame.css' => '8cb3afcd', 'rsrc/css/application/pholio/pholio-edit.css' => '07676f51', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => 'ca89d380', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96', - 'rsrc/css/application/project/project-card-view.css' => '3d3c1f91', + 'rsrc/css/application/project/project-card-view.css' => '0010bb52', 'rsrc/css/application/project/project-view.css' => '792c9057', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', @@ -115,8 +115,8 @@ return array( 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '23beb330', - 'rsrc/css/core/remarkup.css' => 'eb37bd0d', + 'rsrc/css/core/core.css' => '1760853c', + 'rsrc/css/core/remarkup.css' => '279e409c', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -124,29 +124,29 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', - 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', + 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', 'rsrc/css/phui/button/phui-button-bar.css' => '39fe680c', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', 'rsrc/css/phui/button/phui-button.css' => '022581b4', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', - 'rsrc/css/phui/calendar/phui-calendar.css' => '477acfaa', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', + 'rsrc/css/phui/calendar/phui-calendar.css' => 'f1ddf11c', 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '19f9369b', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', - 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', + 'rsrc/css/phui/phui-action-list.css' => '6ee16164', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', - 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '269cbc99', + 'rsrc/css/phui/phui-big-info-view.css' => 'd13afcde', + 'rsrc/css/phui/phui-box.css' => '745e881d', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', - 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', + 'rsrc/css/phui/phui-comment-form.css' => '62836121', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 'rsrc/css/phui/phui-form-view.css' => '6175808d', - 'rsrc/css/phui/phui-form.css' => 'a5570f70', + 'rsrc/css/phui/phui-form.css' => 'efa86a27', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => 'e7de7ee2', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', @@ -164,11 +164,11 @@ return array( 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => '6e217679', + 'rsrc/css/phui/phui-info-view.css' => 'e1b4ec37', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-left-right.css' => 'f60c67e7', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', - 'rsrc/css/phui/phui-list.css' => 'dcafb463', + 'rsrc/css/phui/phui-list.css' => '38f8c9bd', 'rsrc/css/phui/phui-object-box.css' => '9cff003c', 'rsrc/css/phui/phui-pager.css' => 'edcbc226', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -177,8 +177,8 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => '93b084cf', - 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', + 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', + 'rsrc/css/phui/phui-timeline-view.css' => 'c4700486', 'rsrc/css/phui/phui-two-column-view.css' => '5b8cd553', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -547,24 +547,24 @@ return array( 'aphront-table-view-css' => 'a3aa6910', 'aphront-tokenizer-control-css' => '15d5ff71', 'aphront-tooltip-css' => '173b9431', - 'aphront-typeahead-control-css' => '8a84cc7d', + 'aphront-typeahead-control-css' => '4434bc8a', 'application-search-view-css' => '66ee5d46', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'conduit-api-css' => '7bc725c4', - 'config-options-css' => '0ede4c9b', + 'config-options-css' => 'c5aac7b0', 'config-page-css' => 'c1d5121b', 'conpherence-color-css' => 'abb4c358', 'conpherence-durable-column-view' => '89ea6bef', 'conpherence-header-pane-css' => 'cb6f4e19', - 'conpherence-menu-css' => '6953e7ec', + 'conpherence-menu-css' => '69368e97', 'conpherence-message-pane-css' => 'b0f55ecc', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '3cef17bd', + 'differential-changeset-view-css' => 'bf1a41d8', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -579,12 +579,12 @@ return array( 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', - 'global-drag-and-drop-css' => '5c1b47c2', + 'global-drag-and-drop-css' => 'b556a948', 'harbormaster-css' => 'f491c9f4', 'herald-css' => 'dc31f6e9', 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', - 'inline-comment-summary-css' => '51efda3a', + 'inline-comment-summary-css' => 'f23d4e8f', 'javelin-aphlict' => 'e1d4b11a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', @@ -764,15 +764,15 @@ return array( 'multirow-row-manager' => 'b5d57730', 'owners-path-editor' => 'aa1733d0', 'owners-path-editor-css' => '2f00933b', - 'paste-css' => '1898e534', + 'paste-css' => '9fcc9773', 'path-typeahead' => 'f7fc67ec', 'people-picture-menu-item-css' => 'a06f7f34', 'people-profile-css' => '4df76faf', - 'phabricator-action-list-view-css' => 'c01858f4', + 'phabricator-action-list-view-css' => '6ee16164', 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '23beb330', + 'phabricator-core-css' => '1760853c', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', @@ -794,22 +794,22 @@ return array( 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', - 'phabricator-notification-menu-css' => '6a697e43', + 'phabricator-notification-menu-css' => '73fefdfa', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => 'eb37bd0d', + 'phabricator-remarkup-css' => '279e409c', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => '4383192f', + 'phabricator-source-code-view-css' => 'aea41829', 'phabricator-standard-page-view' => 'eb5b80c5', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '358b8c04', 'phabricator-ui-example-css' => '528b19de', 'phabricator-zindex-css' => '9d8f7c4b', - 'phame-css' => 'b3a0b3a3', + 'phame-css' => '8cb3afcd', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', 'pholio-inline-comments-css' => '8e545e49', @@ -822,18 +822,18 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => '22c0cf4f', 'phui-basic-nav-view-css' => 'a0705f53', - 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '269cbc99', + 'phui-big-info-view-css' => 'd13afcde', + 'phui-box-css' => '745e881d', 'phui-button-bar-css' => '39fe680c', 'phui-button-css' => '022581b4', 'phui-button-simple-css' => '8e1baf68', - 'phui-calendar-css' => '477acfaa', + 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', - 'phui-calendar-month-css' => '8e10e92c', + 'phui-calendar-month-css' => '21154caf', 'phui-chart-css' => '6bf6f78e', 'phui-cms-css' => '504b4b23', - 'phui-comment-form-css' => '57af2e14', + 'phui-comment-form-css' => '62836121', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', 'phui-curtain-view-css' => '55dd0e59', @@ -843,7 +843,7 @@ return array( 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', - 'phui-form-css' => 'a5570f70', + 'phui-form-css' => 'efa86a27', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => 'e7de7ee2', @@ -853,12 +853,12 @@ return array( 'phui-icon-view-css' => '5c4a5de6', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => '6e217679', - 'phui-inline-comment-view-css' => 'ffd1a542', + 'phui-info-view-css' => 'e1b4ec37', + 'phui-inline-comment-view-css' => 'c4036846', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-left-right-css' => 'f60c67e7', 'phui-lightbox-css' => '0a035e40', - 'phui-list-view-css' => 'dcafb463', + 'phui-list-view-css' => '38f8c9bd', 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', @@ -873,9 +873,9 @@ return array( 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => '93b084cf', + 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => '313c7f22', + 'phui-timeline-view-css' => 'c4700486', 'phui-two-column-view-css' => '5b8cd553', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', @@ -892,7 +892,7 @@ return array( 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => 'fbd45f96', - 'project-card-view-css' => '3d3c1f91', + 'project-card-view-css' => '0010bb52', 'project-view-css' => '792c9057', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', @@ -904,7 +904,7 @@ return array( 'syntax-default-css' => '9923583c', 'syntax-highlighting-css' => 'cae95e89', 'tokens-css' => '3d0f239e', - 'typeahead-browse-css' => '4f82e510', + 'typeahead-browse-css' => 'f2818435', 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( @@ -1146,9 +1146,6 @@ return array( 'javelin-util', 'javelin-uri', ), - '3cef17bd' => array( - 'phui-inline-comment-view-css', - ), '3dbf94d5' => array( 'javelin-behavior', 'javelin-dom', @@ -1870,6 +1867,9 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), + 'bf1a41d8' => array( + 'phui-inline-comment-view-css', + ), 'bf5374ef' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1e3ed214fa..1527091575 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -223,6 +223,7 @@ phutil_register_library_map(array( 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', + 'CelerityDarkModePostprocessor' => 'applications/celerity/postprocessor/CelerityDarkModePostprocessor.php', 'CelerityDefaultPostprocessor' => 'applications/celerity/postprocessor/CelerityDefaultPostprocessor.php', 'CelerityHighContrastPostprocessor' => 'applications/celerity/postprocessor/CelerityHighContrastPostprocessor.php', 'CelerityLargeFontPostprocessor' => 'applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php', @@ -5170,6 +5171,7 @@ phutil_register_library_map(array( 'CalendarTimeUtil' => 'Phobject', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityAPI' => 'Phobject', + 'CelerityDarkModePostprocessor' => 'CelerityPostprocessor', 'CelerityDefaultPostprocessor' => 'CelerityPostprocessor', 'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor', 'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor', diff --git a/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php new file mode 100644 index 0000000000..fbaf4051bb --- /dev/null +++ b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php @@ -0,0 +1,218 @@ + "13px 'Segoe UI', 'Segoe UI Emoji', ". + "'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ". + "Helvetica, Arial, sans-serif", + + 'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ". + "'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ". + "Helvetica, Arial, sans-serif", + + // Drop Shadow + 'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)', + 'whitetextshadow' => '0 1px 0 rgba(255, 255, 255, 1)', + + // Anchors + 'anchor' => '#3498db', + + // Base Colors + 'red' => '#c0392b', + 'lightred' => '#f4dddb', + 'orange' => '#e67e22', + 'lightorange' => '#f7e2d4', + 'yellow' => '#f1c40f', + 'lightyellow' => '#fdf5d4', + 'green' => '#139543', + 'lightgreen' => '#d7eddf', + 'blue' => '#2980b9', + 'lightblue' => '#daeaf3', + 'sky' => '#3498db', + 'lightsky' => '#ddeef9', + 'fire' => '#e62f17', + 'indigo' => '#6e5cb6', + 'lightindigo' => '#eae6f7', + 'pink' => '#da49be', + 'lightpink' => '#fbeaf8', + 'violet' => '#8e44ad', + 'lightviolet' => '#ecdff1', + 'charcoal' => '#4b4d51', + 'backdrop' => '#c4cde0', + 'hoverwhite' => 'rgba(255,255,255,.6)', + 'hovergrey' => '#c5cbcf', + 'hoverblue' => '#2a425f', + 'hoverborder' => '#dfe1e9', + 'hoverselectedgrey' => '#bbc4ca', + 'hoverselectedblue' => '#e6e9ee', + 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', + 'timeline' => '#4e6078', + 'bluepropertybackground' => '#2d435f', + + // Alphas + 'alphawhite' => '255,255,255', + 'alphagrey' => '255,255,255', + 'alphablue' => '255,255,255', + 'alphablack' => '0,0,0', + + // Base Greys + 'lightgreyborder' => 'rgba(255,255,255,.3)', + 'greyborder' => 'rgba(255,255,255,.6)', + 'darkgreyborder' => 'rgba(255,255,255,.9)', + 'lightgreytext' => 'rgba(255,255,255,.3)', + 'greytext' => 'rgba(255,255,255,.6)', + 'darkgreytext' => 'rgba(255,255,255,.9)', + 'lightgreybackground' => '#2a425f', + 'greybackground' => '#304a6d', + 'darkgreybackground' => '#8C98B8', + + // Base Blues + 'thinblueborder' => '#2c405a', + 'lightblueborder' => '#39506d', + 'blueborder' => '#8C98B8', + 'darkblueborder' => '#626E82', + 'lightbluebackground' => 'rgba(255,255,255,.05)', + 'bluebackground' => 'rgba(255,255,255,.1)', + 'lightbluetext' => 'rgba(255,255,255,.3)', + 'bluetext' => 'rgba(255,255,255,.6)', + 'darkbluetext' => 'rgba(255,255,255,.8)', + 'blacktext' => 'rgba(255,255,255,.9)', + + // Base Greens + 'lightgreenborder' => '#bfdac1', + 'greenborder' => '#8cb89c', + 'greentext' => '#3e6d35', + 'lightgreenbackground' => '#e6f2e4', + + // Base Red + 'lightredborder' => '#f4c6c6', + 'redborder' => '#eb9797', + 'redtext' => '#802b2b', + 'lightredbackground' => '#f5e1e1', + + // Base Violet + 'lightvioletborder' => '#cfbddb', + 'violetborder' => '#b589ba', + 'violettext' => '#603c73', + 'lightvioletbackground' => '#e9dfee', + + // Shades are a more muted set of our base colors + // better suited to blending into other UIs. + + // Shade Red + 'sh-lightredborder' => '#efcfcf', + 'sh-redborder' => '#d1abab', + 'sh-redicon' => '#c85a5a', + 'sh-redtext' => '#a53737', + 'sh-redbackground' => '#f7e6e6', + + // Shade Orange + 'sh-lightorangeborder' => '#f8dcc3', + 'sh-orangeborder' => '#dbb99e', + 'sh-orangeicon' => '#e78331', + 'sh-orangetext' => '#ba6016', + 'sh-orangebackground' => '#fbede1', + + // Shade Yellow + 'sh-lightyellowborder' => '#e9dbcd', + 'sh-yellowborder' => '#c9b8a8', + 'sh-yellowicon' => '#9b946e', + 'sh-yellowtext' => '#726f56', + 'sh-yellowbackground' => '#fdf3da', + + // Shade Green + 'sh-lightgreenborder' => '#c6e6c7', + 'sh-greenborder' => '#a0c4a1', + 'sh-greenicon' => '#4ca74e', + 'sh-greentext' => '#326d34', + 'sh-greenbackground' => '#ddefdd', + + // Shade Blue + 'sh-lightblueborder' => '#cfdbe3', + 'sh-blueborder' => '#a7b5bf', + 'sh-blueicon' => '#6b748c', + 'sh-bluetext' => '#464c5c', + 'sh-bluebackground' => '#dee7f8', + + // Shade Indigo + 'sh-lightindigoborder' => '#d1c9ee', + 'sh-indigoborder' => '#bcb4da', + 'sh-indigoicon' => '#8672d4', + 'sh-indigotext' => '#6e5cb6', + 'sh-indigobackground' => '#eae6f7', + + // Shade Violet + 'sh-lightvioletborder' => '#e0d1e7', + 'sh-violetborder' => '#bcabc5', + 'sh-violeticon' => '#9260ad', + 'sh-violettext' => '#69427f', + 'sh-violetbackground' => '#efe8f3', + + // Shade Pink + 'sh-lightpinkborder' => '#f6d5ef', + 'sh-pinkborder' => '#d5aecd', + 'sh-pinkicon' => '#e26fcb', + 'sh-pinktext' => '#da49be', + 'sh-pinkbackground' => '#fbeaf8', + + // Shade Grey + 'sh-lightgreyborder' => '#e3e4e8', + 'sh-greyborder' => '#b2b2b2', + 'sh-greyicon' => '#757575', + 'sh-greytext' => '#555555', + 'sh-greybackground' => '#edeef2', + + // Shade Disabled + 'sh-lightdisabledborder' => '#e5e5e5', + 'sh-disabledborder' => '#cbcbcb', + 'sh-disabledicon' => '#bababa', + 'sh-disabledtext' => '#a6a6a6', + 'sh-disabledbackground' => '#f3f3f3', + + // Diffs + 'new-background' => 'rgba(151, 234, 151, .3)', + 'new-bright' => 'rgba(151, 234, 151, .6)', + 'old-background' => 'rgba(251, 175, 175, .3)', + 'old-bright' => 'rgba(251, 175, 175, .7)', + 'move-background' => '#fdf5d4', + 'copy-background' => '#f1c40f', + + 'paste.content' => '#222222', + 'paste.border' => '#000000', + 'paste.highlight' => '#121212', + + // Background color for "most" themes. + 'page.background' => '#223246', + 'page.sidenav' => '#1c293b', + 'page.content' => '#26374c', + + 'menu.profile.text' => 'rgba(255,255,255,.8)', + 'menu.profile.text.selected' => 'rgba(255,255,255,1)', + 'menu.profile.icon.disabled' => 'rgba(255,255,255,.4)', + + // Buttons + 'blue.button.color' => '#2980b9', + 'blue.button.gradient' => 'linear-gradient(to bottom, #3498db, #2980b9)', + 'green.button.color' => '#139543', + 'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)', + 'grey.button.color' => '#223246', + 'grey.button.gradient' => 'linear-gradient(to bottom, #223246, #223246)', + 'grey.button.hover' => 'linear-gradient(to bottom, #1c293b, #1c293b)', + + ); + } + +} diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 62a4b4fd24..447edbd99f 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -72,6 +72,7 @@ final class CelerityDefaultPostprocessor 'hoverselectedblue' => '#e6e9ee', 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', 'timeline' => '#d5d8e1', + 'bluepropertybackground' => '#eff3fc', // Alphas 'alphawhite' => '255,255,255', @@ -100,6 +101,7 @@ final class CelerityDefaultPostprocessor 'lightbluetext' => '#8C98B8', 'bluetext' => '#6B748C', 'darkbluetext' => '#464C5C', + 'blacktext' => '#000', // Base Greens 'lightgreenborder' => '#bfdac1', @@ -200,9 +202,14 @@ final class CelerityDefaultPostprocessor 'move-background' => '#fdf5d4', 'copy-background' => '#f1c40f', + 'paste.content' => '#fffef5', + 'paste.border' => '#e9dbcd', + 'paste.highlight' => '#fdf3da', + // Background color for "most" themes. 'page.background' => '#f3f5f7', 'page.sidenav' => '#eaedf1', + 'page.content' => '#fff', 'menu.profile.text' => 'rgba(255,255,255,.8)', 'menu.profile.text.selected' => 'rgba(255,255,255,1)', @@ -211,6 +218,18 @@ final class CelerityDefaultPostprocessor 'menu.main.height' => '44px', 'menu.profile.width' => '240px', + // Buttons + 'blue.button.color' => '#2980b9', + 'blue.button.gradient' => 'linear-gradient(to bottom, #3498db, #2980b9)', + 'blue.button.hover' => 'linear-gradient(to bottom, #3498db, #1b6ba0)', + 'green.button.color' => '#139543', + 'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)', + 'green.button.hover' => 'linear-gradient(to bottom, #23BB5B, #178841)', + 'grey.button.color' => '#F7F7F9', + 'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)', + 'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)', + + ); } diff --git a/webroot/rsrc/css/aphront/dialog-view.css b/webroot/rsrc/css/aphront/dialog-view.css index 0e6f6d4cfc..5809ab117e 100644 --- a/webroot/rsrc/css/aphront/dialog-view.css +++ b/webroot/rsrc/css/aphront/dialog-view.css @@ -7,7 +7,7 @@ margin: 32px auto 16px; border: 1px solid {$lightblueborder}; border-radius: 3px; - background-color: #fff; + background-color: {$page.content}; } .jx-client-dialog .aphront-dialog-view { @@ -41,7 +41,7 @@ } .aphront-dialog-body { - background: #fff; + background: {$page.content}; padding: 16px; border: none; } diff --git a/webroot/rsrc/css/aphront/list-filter-view.css b/webroot/rsrc/css/aphront/list-filter-view.css index 50d43f2383..d16ad72eb4 100644 --- a/webroot/rsrc/css/aphront/list-filter-view.css +++ b/webroot/rsrc/css/aphront/list-filter-view.css @@ -5,7 +5,7 @@ .aphront-list-filter-wrap { border: 1px solid {$lightblueborder}; margin: 0 16px; - background: #fff; + background: {$page.content}; border-radius: 3px; } diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 2238f5a041..a02d4dcfbe 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -9,7 +9,7 @@ .aphront-table-view { width: 100%; border-collapse: collapse; - background: #fff; + background: {$page.content}; border: 1px solid {$lightblueborder}; border-bottom: 1px solid {$blueborder}; } diff --git a/webroot/rsrc/css/aphront/tokenizer.css b/webroot/rsrc/css/aphront/tokenizer.css index 3f3f904e22..7cfc9e391b 100644 --- a/webroot/rsrc/css/aphront/tokenizer.css +++ b/webroot/rsrc/css/aphront/tokenizer.css @@ -88,11 +88,11 @@ a.jx-tokenizer-token:hover { a.jx-tokenizer-token-function { border-color: {$sh-lightgreyborder}; - background: #fff; + background: {$page.content}; } a.jx-tokenizer-token-function:hover { - background: #fff; + background: {$page.content}; } a.jx-tokenizer-token-disabled { diff --git a/webroot/rsrc/css/aphront/typeahead-browse.css b/webroot/rsrc/css/aphront/typeahead-browse.css index 7439d299e7..8746d9db4e 100644 --- a/webroot/rsrc/css/aphront/typeahead-browse.css +++ b/webroot/rsrc/css/aphront/typeahead-browse.css @@ -11,7 +11,7 @@ } .typeahead-browse-more { - background: {$lightblue}; + background: {$lightbluebackground}; border: 1px solid {$lightblueborder}; color: {$blue}; } diff --git a/webroot/rsrc/css/aphront/typeahead.css b/webroot/rsrc/css/aphront/typeahead.css index bdda2703e0..92970386bf 100644 --- a/webroot/rsrc/css/aphront/typeahead.css +++ b/webroot/rsrc/css/aphront/typeahead.css @@ -9,10 +9,10 @@ div.jx-typeahead-hardpoint { div.jx-typeahead-results { position: absolute; - border: 1px solid {$hoverborder}; + border: 1px solid {$thinblueborder}; border-top: 0px; padding: 0; - background: #fefefe; + background: {$page.content}; width: 98%; box-shadow: 0px 1px 2px rgba({$alphablack}, 0.2); margin: -1px 1% 0; @@ -31,7 +31,7 @@ div.jx-typeahead-results a.jx-result { } div.jx-typeahead-results a.jx-result + a.jx-result { - border-top: 1px solid {$hoverborder}; + border-top: 1px solid {$thinblueborder}; } div.jx-typeahead-results a.jx-result:hover, diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 4de30fb8ce..39ec89c9fc 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -164,7 +164,7 @@ position: absolute; font-size: {$normalfontsize}; border: none; - background-color: #fff; + background-color: {$page.content}; height: 28px; padding: 3px 28px 3px 52px; float: left; @@ -180,7 +180,7 @@ } .phabricator-main-menu .phabricator-main-menu-search input:focus { - background: #fff; + background: {$page.content}; opacity: 1; color: {$darkbluetext}; box-shadow: none; @@ -270,7 +270,7 @@ a.phabricator-core-user-menu .caret:before { } .phabricator-main-menu-search-target div.jx-typeahead-results { - background: #fff; + background: {$page.content}; word-wrap: break-word; overflow-y: auto; box-shadow: {$dropshadow}; diff --git a/webroot/rsrc/css/application/base/notification-menu.css b/webroot/rsrc/css/application/base/notification-menu.css index 02a3d5a170..1a834b11fb 100644 --- a/webroot/rsrc/css/application/base/notification-menu.css +++ b/webroot/rsrc/css/application/base/notification-menu.css @@ -3,7 +3,7 @@ */ .phabricator-notification-menu { - background: #fff; + background: {$page.content}; font-size: {$smallestfontsize}; word-wrap: break-word; overflow-y: auto; @@ -65,7 +65,7 @@ } .phabricator-notification + .phabricator-notification { - border-top: 1px solid {$hoverborder}; + border-top: 1px solid {$thinblueborder}; } .no-notifications { diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 1205dc93e7..89cbfdbd2e 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -18,7 +18,7 @@ } body.white-background { - background: #fff; + background: {$page.content}; } .phabricator-standard-page-footer { diff --git a/webroot/rsrc/css/application/config/config-options.css b/webroot/rsrc/css/application/config/config-options.css index 67c005094c..efac6f0759 100644 --- a/webroot/rsrc/css/application/config/config-options.css +++ b/webroot/rsrc/css/application/config/config-options.css @@ -5,8 +5,8 @@ .config-option-table { width: 100%; border-collapse: collapse; - border: 1px solid {$lightgreyborder}; - background: #fff; + border: 1px solid {$thinblueborder}; + background: {$page.content}; } .config-option-table th, diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index fdbdbe848f..41645717f3 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -12,7 +12,7 @@ right: 16px; width: 400px; height: 360px; - background: #fff; + background: {$page.content}; border-top-right-radius: 3px; border-top-left-radius: 3px; box-shadow: 0px 1px 8px rgba(55,55,55, .3); diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 54f58682da..3f5965def5 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -9,7 +9,7 @@ .conpherence-header-pane .phui-header-header { font-size: 16px; - color: #000; + color: {$blacktext}; display: block; } diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index b2702d94fd..feb17156d7 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -8,7 +8,7 @@ left: 0; right: 0; top: 44px; - background: #fff; + background: {$page.content}; } .conpherence-menu-pane { @@ -137,7 +137,6 @@ font-weight: bold; font-size: {$normalfontsize}; color: {$darkbluetext}; - text-shadow: 0px 1px 1px #fff; overflow: hidden; width: 165px; text-overflow: ellipsis; diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index ab3c55ba22..0dc33789f3 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -25,7 +25,7 @@ .conpherence-layout .conpherence-content-pane .conpherence-no-threads { top: 44px; right: 0; - background: #fff; + background: {$page.content}; z-index: 26; } @@ -411,7 +411,7 @@ body .conpherence-message-pane .aphront-form-control { bottom: 0; transition: all 0.3s; position: fixed; - background-color: #fff; + background-color: {$page.content}; } .conpherence-layout.loading .conpherence-loading-mask { @@ -445,7 +445,7 @@ body .conpherence-message-pane .aphront-form-control { .show-searchbar .conpherence-search-form-view { display: block; height: 54px; - background: #fff; + background: {$page.content}; position: absolute; top: 0; left: 0; @@ -461,7 +461,7 @@ body .conpherence-message-pane .aphront-form-control { .conpherence-search-results { position: absolute; - background: #fff; + background: {$page.content}; top: 54px; left: 0; right: 0; diff --git a/webroot/rsrc/css/application/conpherence/participant-pane.css b/webroot/rsrc/css/application/conpherence/participant-pane.css index f219707e7b..479bf95637 100644 --- a/webroot/rsrc/css/application/conpherence/participant-pane.css +++ b/webroot/rsrc/css/application/conpherence/participant-pane.css @@ -65,7 +65,7 @@ } .conpherence-participant-pane .person-entry:hover a { - color: #000; + color: {$blacktext}; } .conpherence-participant-pane .person-entry a img { @@ -99,7 +99,7 @@ } .conpherence-participant-pane .person-entry .remove:hover .close-icon { - color: #000; + color: {$blacktext}; } /****** Hide Widgets **********************************************************/ diff --git a/webroot/rsrc/css/application/conpherence/transaction.css b/webroot/rsrc/css/application/conpherence/transaction.css index 6e52a8c30c..42f39bac74 100644 --- a/webroot/rsrc/css/application/conpherence/transaction.css +++ b/webroot/rsrc/css/application/conpherence/transaction.css @@ -13,7 +13,7 @@ .conpherence-transaction-header .phui-link-person { font-weight: bold; font-size: {$biggerfontsize}; - color: #000; + color: {$blacktext}; } .conpherence-transaction-view.date-marker { @@ -23,7 +23,7 @@ .conpherence-transaction-view.date-marker .date { position: relative; top: -11px; - background-color: #fff; + background-color: {$page.content}; color: {$sh-violettext}; font-weight: bold; } diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css index 3def453670..aed87b7673 100644 --- a/webroot/rsrc/css/application/dashboard/dashboard.css +++ b/webroot/rsrc/css/application/dashboard/dashboard.css @@ -20,7 +20,7 @@ } .dashboard-box .phui-header-header { - color: #000; + color: {$blacktext}; } .dashboard-view .phui-oi-empty .phui-info-view { diff --git a/webroot/rsrc/css/application/diff/inline-comment-summary.css b/webroot/rsrc/css/application/diff/inline-comment-summary.css index 572441191d..554fcdd518 100644 --- a/webroot/rsrc/css/application/diff/inline-comment-summary.css +++ b/webroot/rsrc/css/application/diff/inline-comment-summary.css @@ -16,8 +16,7 @@ .phabricator-inline-summary-table .inline-comment-summary-table-header { font-weight: bold; padding: 16px 1px 8px; - background: #fff; - color: #000; + color: {$blacktext}; border-bottom: 1px solid {$thinblueborder}; } @@ -25,14 +24,14 @@ padding: 4px 8px; white-space: nowrap; color: {$darkbluetext}; - background: white; + background: {$page.content}; } .phabricator-inline-summary-table td.inline-line-number { padding: 0; width: 100px; white-space: nowrap; - background: #F8F9FC; + background: {$lightgreybackground}; font-family: "Menlo", "Consolas", monospace; font-size: {$smallestfontsize}; color: {$bluetext}; diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 98c8f099ba..481149fe34 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -20,7 +20,7 @@ } .differential-diff { - background: #fff; + background: {$page.content}; width: 100%; border-top: 1px solid {$lightblueborder}; border-bottom: 1px solid {$lightblueborder}; @@ -113,14 +113,6 @@ cursor: auto; } -.differential-diff th.old { - border-right-color: {$old-bright}; -} - -.differential-diff th.new { - border-right-color: {$new-bright}; -} - .differential-diff td.old { background: {$old-background}; } @@ -288,7 +280,7 @@ td.cov-I { font-size: {$biggestfontsize}; padding: 2px 0 20px 12px; line-height: 20px; - color: #000; + color: {$blacktext}; } .device-phone .differential-changeset h1 { @@ -392,7 +384,7 @@ tr.differential-inline-loading { top: 0; left: 0; right: 0; - background: #fff; + background: {$page.content}; box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); border-bottom: 1px solid {$lightgreyborder}; padding: 8px 18px; diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 146117b1cb..0a1ffecb39 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -25,7 +25,7 @@ .differential-inline-comment, .differential-inline-comment-edit { - background: #fff; + background: {$page.content}; border: 1px solid {$sh-yellowborder}; font: {$basefont}; -moz-box-sizing: border-box; @@ -244,7 +244,7 @@ .differential-inline-comment.inline-is-done .differential-inline-done-label { - background-color: #fff; + background-color: {$page.content}; border-color: {$lightblueborder}; color: {$sky}; opacity: 1; @@ -252,7 +252,7 @@ .device-desktop .differential-inline-comment.inline-is-done .differential-inline-done-label:hover { - background-color: #fff; + background-color: {$page.content}; color: {$sky}; } @@ -273,7 +273,7 @@ } .differential-inline-comment.inline-is-done - .differential-inline-comment-head { + .differential-inline-comment-head { background-color: {$lightgreybackground}; border-bottom-color: {$lightgreyborder}; } @@ -287,7 +287,7 @@ .differential-inline-comment.inline-is-done .differential-inline-comment-head .differential-inline-done-label { color: {$sky}; - background-color: #fff; + background-color: {$page.content}; border-color: {$sky}; } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 1af068fe1a..744f84359a 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -4,7 +4,7 @@ .diffusion-source { width: 100%; - background: #fff; + background: {$page.content}; } .diffusion-source tr.phabricator-source-highlight { diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index 3d81cd5c62..405755140b 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -46,7 +46,7 @@ /* - List Styles ------------------------------------------------------------*/ .diffusion-history-list .phui-oi-link { - color: #000; + color: {$blacktext}; font-size: {$biggerfontsize}; } diff --git a/webroot/rsrc/css/application/files/global-drag-and-drop.css b/webroot/rsrc/css/application/files/global-drag-and-drop.css index e338b987b0..f9981d0492 100644 --- a/webroot/rsrc/css/application/files/global-drag-and-drop.css +++ b/webroot/rsrc/css/application/files/global-drag-and-drop.css @@ -14,7 +14,7 @@ top: 30%; padding: 18px 0; - color: #ffffff; + color: {$page.content}; background: rgba({$alphablack}, 0.8); border-radius: 18px; diff --git a/webroot/rsrc/css/application/objectselector/object-selector.css b/webroot/rsrc/css/application/objectselector/object-selector.css index 927b805155..fc53cc3282 100644 --- a/webroot/rsrc/css/application/objectselector/object-selector.css +++ b/webroot/rsrc/css/application/objectselector/object-selector.css @@ -32,7 +32,7 @@ td.phabricator-object-selector-search-text { .phabricator-object-selector-row:hover a { text-decoration: none; - color: #000; + color: {$blacktext}; } .phabricator-object-selector-search-text input { diff --git a/webroot/rsrc/css/application/paste/paste.css b/webroot/rsrc/css/application/paste/paste.css index b9b2fe2dd2..22138df452 100644 --- a/webroot/rsrc/css/application/paste/paste.css +++ b/webroot/rsrc/css/application/paste/paste.css @@ -3,8 +3,8 @@ */ .paste-embed { - background: {$sh-yellowbackground}; - border: 1px solid {$sh-lightyellowborder}; + background: {$paste.content}; + border: 1px solid {$paste.border}; border-radius: 3px; } @@ -13,7 +13,8 @@ } .paste-embed-head { - border-bottom: 1px solid {$sh-lightyellowborder}; + border-bottom: 1px solid {$paste.border}; + background: {$paste.highlight}; padding: 8px 12px; } @@ -25,3 +26,8 @@ .paste-embed-body { overflow-y: auto; } + +.paste-embed-body .phabricator-source-code-container { + border-top-right-radius: 0; + border-top-left-radius: 0; +} diff --git a/webroot/rsrc/css/application/people/people-picture-menu-item.css b/webroot/rsrc/css/application/people/people-picture-menu-item.css index 8f5da76166..745f9df3cd 100644 --- a/webroot/rsrc/css/application/people/people-picture-menu-item.css +++ b/webroot/rsrc/css/application/people/people-picture-menu-item.css @@ -8,7 +8,7 @@ } .people-menu-image-container { - background: #fff; + background: {$page.content}; padding: 5px; border-radius: 5px; border: 1px solid rgba({$alphablue},.2); diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 080f5ca344..4478e607e9 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -29,6 +29,7 @@ position: absolute; } + .phame-blog-description + .phui-document-view-pro-box { border-top: 1px solid rgba({$alphablue}, 0.20); } @@ -38,11 +39,11 @@ } .phame-home-view .phui-side-column { - background-color: #fff; + background-color: {$page.content}; } .phame-home-view { - background-color: #fff; + background-color: {$page.content}; border-bottom: 1px solid rgba({$alphagrey},.1); } @@ -145,7 +146,7 @@ width: 360px; float: right; text-align: right; - color: #000; + color: {$blacktext}; position: relative; } @@ -179,7 +180,7 @@ width: 360px; float: left; text-align: left; - color: #000; + color: {$blacktext}; position: relative; } @@ -258,7 +259,7 @@ /* Hero Image */ .phame-header-hero { - background-color: #fff; + background-color: {$page.content}; margin-top: 16px; } @@ -281,7 +282,7 @@ .phame-mega-header { margin: 0 auto; text-align: center; - background: #fff; + background: {$page.content}; padding: 16px 0 24px; } @@ -290,7 +291,7 @@ } .phame-mega-header .phame-header-title { - color: #000; + color: {$blacktext}; font-size: 28px; font-weight: bold; padding-top: 24px; diff --git a/webroot/rsrc/css/application/pholio/pholio.css b/webroot/rsrc/css/application/pholio/pholio.css index 52ff4ca5bd..598a5e0abc 100644 --- a/webroot/rsrc/css/application/pholio/pholio.css +++ b/webroot/rsrc/css/application/pholio/pholio.css @@ -181,7 +181,7 @@ } .mock-image-description { - background: #fff; + background: {$page.content}; border-top: 1px solid {$thinblueborder}; text-align: left; } diff --git a/webroot/rsrc/css/application/project/project-card-view.css b/webroot/rsrc/css/application/project/project-card-view.css index 0d45de9485..b960d55cef 100644 --- a/webroot/rsrc/css/application/project/project-card-view.css +++ b/webroot/rsrc/css/application/project/project-card-view.css @@ -5,7 +5,7 @@ .project-card-view { margin: 0 12px 16px 0; text-align: left; - background: #fff; + background: {$page.content}; border: 1px solid {$lightblueborder}; border-radius: 3px; box-shadow: {$dropshadow}; @@ -21,9 +21,9 @@ } .project-card-view .phui-header-shell .phui-header-image { - border: 3px solid #fff; + border: 3px solid {$page.content}; border-radius: 3px; - background-color: #fff; + background-color: {$page.content}; } .project-card-view .phui-header-shell .phui-header-header { @@ -71,7 +71,7 @@ .project-card-header .project-card-name { font-size: 20px; font-weight: bold; - color: #000; + color: {$blacktext}; margin-bottom: 2px; text-overflow: ellipsis; white-space: nowrap; @@ -129,85 +129,48 @@ /* Colors */ -.project-card-view.project-card-red { - border-color: {$sh-redborder}; -} - .project-card-view.project-card-red .phui-header-shell { background: linear-gradient(to bottom, - {$sh-redbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-orange { - border-color: {$sh-orangeborder}; + {$sh-redbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-orange .phui-header-shell { background: linear-gradient(to bottom, - {$sh-orangebackground} 42px, #fff 42px); -} - -.project-card-view.project-card-yellow { - border-color: {$sh-yellowborder}; + {$sh-orangebackground} 42px, {$page.content} 42px); } .project-card-view.project-card-yellow .phui-header-shell { background: linear-gradient(to bottom, - {$sh-yellowbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-green { - border-color: {$sh-greenborder}; + {$sh-yellowbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-green .phui-header-shell { background: linear-gradient(to bottom, - {$sh-greenbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-blue { - border-color: {$sh-blueborder}; + {$sh-greenbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-blue .phui-header-shell { background: linear-gradient(to bottom, - {$sh-bluebackground} 42px, #fff 42px); -} - -.project-card-view.project-card-indigo { - border-color: {$sh-indigoborder}; + {$sh-bluebackground} 42px, {$page.content} 42px); } .project-card-view.project-card-indigo .phui-header-shell { background: linear-gradient(to bottom, - {$sh-indigobackground} 42px, #fff 42px); -} - -.project-card-view.project-card-violet { - border-color: {$sh-violetborder}; + {$sh-indigobackground} 42px, {$page.content} 42px); } .project-card-view.project-card-violet .phui-header-shell { background: linear-gradient(to bottom, - {$sh-violetbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-pink { - border-color: {$sh-pinkborder}; + {$sh-violetbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-pink .phui-header-shell { background: linear-gradient(to bottom, - {$sh-pinkbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-grey, -.project-card-view.project-card-checkered { - border-color: {$sh-greyborder}; + {$sh-pinkbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-grey .phui-header-shell, .project-card-view.project-card-checkered .phui-header-shell { background: linear-gradient(to bottom, - {$sh-greybackground} 42px, #fff 42px); + {$sh-greybackground} 42px, {$page.content} 42px); } diff --git a/webroot/rsrc/css/application/search/application-search-view.css b/webroot/rsrc/css/application/search/application-search-view.css index 1964d010f0..4036124547 100644 --- a/webroot/rsrc/css/application/search/application-search-view.css +++ b/webroot/rsrc/css/application/search/application-search-view.css @@ -3,11 +3,11 @@ */ .application-search-view { - background-color: #fff; + background-color: {$page.content}; } .application-search-view .phui-crumbs-view { - background-color: #fff; + background-color: {$page.content}; } .application-search-view .application-search-results.phui-object-box { diff --git a/webroot/rsrc/css/application/search/search-results.css b/webroot/rsrc/css/application/search/search-results.css index 0f0a31a1b9..02d40fa179 100644 --- a/webroot/rsrc/css/application/search/search-results.css +++ b/webroot/rsrc/css/application/search/search-results.css @@ -14,7 +14,7 @@ .phui-source-fragment strong { background-color: {$lightyellow}; font-weight: normal; - color: #000; + color: {$blacktext}; } .phui-fulltext-tokens { diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index d7145bb485..9d471477ed 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -48,6 +48,7 @@ body { text-align: left; unicode-bidi: embed; background: {$page.background}; + color: {$blacktext}; /* By default, the iPhone zooms all text on the page by some percentage when you rotate from portrait mode to landscape mode. Disable this, since it diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 1de7ccf85a..4007e910ea 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -5,6 +5,7 @@ .phabricator-remarkup { line-height: 1.51em; word-break: break-word; + color: {$darkbluetext}; } .phabricator-remarkup p { @@ -47,7 +48,7 @@ .phabricator-remarkup .remarkup-code-block pre { background: rgba({$alphablue},0.08); display: block; - color: #000; + color: {$blacktext}; overflow: auto; padding: 12px; border-radius: 3px; @@ -80,7 +81,7 @@ } .phabricator-remarkup tt.remarkup-monospaced { - color: #000; + color: {$blacktext}; background: rgba({$alphablue},0.1); padding: 1px 4px; border-radius: 3px; @@ -342,7 +343,7 @@ video.phabricator-media { .phabricator-remarkup-toc { float: right; border-left: 1px solid {$lightblueborder}; - background: #fff; + background: {$page.content}; width: 160px; padding-left: 8px; margin: 0 0 4px 8px; @@ -402,7 +403,7 @@ video.phabricator-media { -webkit-font-smoothing: antialiased; border: 1px solid {$lightblueborder}; border-radius: 3px; - color: #000; + color: {$blacktext}; min-width: 256px; position: relative; /*height: 22px;*/ @@ -509,7 +510,7 @@ video.phabricator-media { } .phabricator-remarkup table.remarkup-table td { - background: #ffffff; + background: {$page.content}; padding: 3px 6px; } @@ -679,7 +680,7 @@ var.remarkup-assist-textarea { position: absolute; width: 300px; box-shadow: {$dropshadow}; - background: #ffffff; + background: {$page.content}; border: 1px solid {$lightgreyborder}; border-radius: 3px; } @@ -721,13 +722,13 @@ var.remarkup-assist-textarea { .phuix-autocomplete-list a.jx-result:hover { text-decoration: none; background: {$sh-bluebackground}; - color: #000; + color: {$blacktext}; } .phuix-autocomplete-list a.jx-result.focused, .phuix-autocomplete-list a.jx-result.focused:hover { background: {$sh-bluebackground}; - color: #000; + color: {$blacktext}; } @@ -735,7 +736,7 @@ var.remarkup-assist-textarea { .phui-box.phui-object-box.phui-comment-form-view.remarkup-assist-pinned { position: fixed; - background-color: #ffffff; + background-color: {$page.content}; border-top: 1px solid {$lightblueborder}; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); @@ -759,7 +760,7 @@ var.remarkup-assist-textarea { .remarkup-inline-preview { display: block; position: relative; - background: #fff; + background: {$page.content}; overflow-y: auto; box-sizing: border-box; width: 100%; @@ -783,7 +784,7 @@ var.remarkup-assist-textarea { } .remarkup-assist-button.preview-active .phui-icon-view { - color: #fff; + color: {$page.content}; } .remarkup-assist-button.preview-active:hover { @@ -791,7 +792,7 @@ var.remarkup-assist-textarea { } .remarkup-assist-button.preview-active:hover .phui-icon-view { - color: #fff; + color: {$page.content}; } .remarkup-preview-active .remarkup-assist, @@ -867,7 +868,7 @@ var.remarkup-assist-textarea { border-width: 1px 0 0 0; outline: none; resize: none; - background: #fff !important; + background: {$page.content} !important; } .remarkup-control-fullscreen-mode textarea.remarkup-assist-textarea:focus { diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 7e956b580a..a855e01447 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -5,9 +5,9 @@ .phabricator-source-code-container { overflow-x: auto; overflow-y: hidden; - border: 1px solid {$sh-lightyellowborder}; + border: 1px solid {$paste.border}; border-radius: 3px; - background-color: #FFFEF5; + background-color: {$paste.content}; } .phui-oi .phabricator-source-code-container { @@ -29,10 +29,10 @@ } .phabricator-source-line { - background-color: {$sh-yellowbackground}; + background-color: {$paste.highlight}; text-align: right; padding: 2px 6px 1px 12px; - border-right: 1px solid {$sh-lightyellowborder}; + border-right: 1px solid {$paste.border}; color: {$sh-yellowtext}; /* When the user selects rows of source, don't visibly select the line @@ -51,12 +51,12 @@ th.phabricator-source-line a { } th.phabricator-source-line:hover { - background: {$sh-lightyellowborder}; + background: {$paste.border}; cursor: pointer; } .phabricator-source-highlight { - background: {$sh-yellowbackground}; + background: {$paste.highlight}; } .phabricator-source-code-summary { diff --git a/webroot/rsrc/css/phui/button/phui-button-simple.css b/webroot/rsrc/css/phui/button/phui-button-simple.css index 4bc6daee23..1ebfea47d5 100644 --- a/webroot/rsrc/css/phui/button/phui-button-simple.css +++ b/webroot/rsrc/css/phui/button/phui-button-simple.css @@ -10,7 +10,7 @@ button.phui-button-simple, input[type="submit"].phui-button-simple, a.phui-button-simple, a.phui-button-simple:visited { - background: #fff; + background: {$page.content}; color: {$bluetext}; border: 1px solid {$lightblueborder}; } @@ -26,7 +26,7 @@ a.button.phui-button-simple:hover, button.phui-button-simple:hover { border-color: {$blueborder}; background-image: none; - background-color: #fff; + background-color: {$page.content}; transition: 0s; } diff --git a/webroot/rsrc/css/phui/button/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css index 69718808c3..3a356f7deb 100644 --- a/webroot/rsrc/css/phui/button/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -33,9 +33,9 @@ button, a.button, a.button:visited, input[type="submit"] { - background-color: #2980b9; - border: 1px solid #2980b9; - background-image: linear-gradient(to bottom, #3498db, #2980b9); + background-color: {$blue.button.color}; + border: 1px solid {$blue.button.color}; + background-image: {$blue.button.gradient}; color: white; cursor: pointer; font-weight: bold; @@ -71,17 +71,17 @@ a.icon:visited { button.button-green, a.button-green.button, a.button-green.button:visited { - background-color: {$green}; - border-color: {$green}; - background-image: linear-gradient(to bottom, #23BB5B, #139543); + background-color: {$green.button.color}; + border-color: {$green.button.color}; + background-image: {$green.button.gradient}; } button.button-grey, input[type="submit"].button-grey, a.button-grey, a.button-grey:visited { - background-color: #F7F7F9; - background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); + background-color: {$grey.button.color}; + background-image: {$grey.button.gradient}; border: 1px solid rgba({$alphablue}, 0.3); color: {$darkgreytext}; } @@ -103,14 +103,14 @@ a.button:hover, button:hover { text-decoration: none; background-color: #2980b9; - background-image: linear-gradient(to bottom, #3498db, #1b6ba0); + background-image: {$blue.button.hover}; border-color: #115988; transition: 0.1s; } a.button.button-grey:hover, button.button-grey:hover { - background-image: linear-gradient(to bottom, #ffffff, #eeebec); + background-image: {$grey.button.hover}; border-color: rgba({$alphablue}, 0.4); transition: 0.1s; } @@ -119,7 +119,7 @@ a.button.button-green:hover, button.button-green:hover { border-color: #127336; background-color: #0DAD48; - background-image: linear-gradient(to bottom, #23BB5B, #178841); + background-image: {$green.button.hover}; transition: 0.1s; } @@ -163,7 +163,7 @@ button.link:hover { .phuix-dropdown-menu { position: absolute; width: 200px; - background: #fff; + background: {$page.content}; margin-top: -1px; padding: 12px; box-shadow: {$dropshadow}; @@ -205,7 +205,7 @@ a.policy-control .phui-button-text { width: 0; height: 0; vertical-align: top; - border-top: 5px solid #fff; + border-top: 5px solid {$page.content}; border-right: 5px solid transparent; border-left: 5px solid transparent; content: ""; @@ -245,7 +245,7 @@ a.policy-control .phui-button-text { } .button-grey.dropdown .caret { - border-top-color: #000; + border-top-color: {$blacktext}; } /* Icons */ diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index c7b844a7eb..5fea904911 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -15,7 +15,7 @@ .project-view-home .phui-object-box .phui-calendar-list-container .phui-header-shell { padding: 8px 0; - background: #fff; + background: {$page.content}; } .phui-calendar-list { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index c649021f0d..49b792023d 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -5,7 +5,7 @@ .phui-calendar-view { width: 100%; border-collapse: collapse; - background: #fff; + background: {$page.content}; } tr.phui-calendar-day-of-week-header th { @@ -33,7 +33,7 @@ tr.phui-calendar-day-of-week-header th .short-weekday-name { } table.phui-calendar-view td { - border: solid #dfdfdf; + border: solid {$lightblueborder}; border-width: 1px 0 0; width: 14.2857%; /* This is one seventh, approximately. */ } @@ -220,7 +220,7 @@ td.phui-calendar-month-number { .device-desktop td.phui-calendar-month-day.calendar-hover, .device-desktop td.phui-calendar-month-number.calendar-hover { - background: {$lightblue}; + background: {$hoverblue}; } .phui-calendar-month-adjacent { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar.css b/webroot/rsrc/css/phui/calendar/phui-calendar.css index ab59886fd8..0d8dcfc56e 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar.css @@ -5,7 +5,7 @@ .phui-calendar-list { /* When hovering over a day, this allows the hover color to peek through the event name, but for event names to mostly remain readable. */ - background: rgba(255, 255, 255, 0.75); + } .application-search-view div.phui-calendar-box { diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css index 7fcc6d4ef7..e4d636cf46 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css @@ -17,7 +17,7 @@ } .phui-oi-list-big a.phui-oi-link { - color: #000; + color: {$blacktext}; font-size: {$biggestfontsize}; } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 5ddedfa24a..e356396451 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -37,7 +37,7 @@ ul.phui-oi-list-view { border-color: {$lightgreyborder}; margin: 5px 0; overflow: hidden; - background: #fff; + background: {$page.content}; margin-bottom: 4px; } @@ -105,7 +105,7 @@ ul.phui-oi-list-view { } .phui-oi-objname { - color: #000; + color: {$blacktext}; cursor: text; font-weight: bold; } diff --git a/webroot/rsrc/css/phui/phui-action-list.css b/webroot/rsrc/css/phui/phui-action-list.css index 4569438d5a..44b93e61be 100644 --- a/webroot/rsrc/css/phui/phui-action-list.css +++ b/webroot/rsrc/css/phui/phui-action-list.css @@ -114,7 +114,7 @@ .device-desktop li.phabricator-action-view-label:hover .phabricator-action-view-item { - background-color: #fff; + background-color: {$page.content}; color: {$bluetext}; } @@ -157,7 +157,6 @@ .phabricator-action-view-icon, .device-desktop .phabricator-action-view-disabled:hover button.phabricator-action-view-icon { - background-color: {$greybackground}; color: {$lightgreytext}; } diff --git a/webroot/rsrc/css/phui/phui-action-panel.css b/webroot/rsrc/css/phui/phui-action-panel.css index 8cfd69a94f..d52a1cbaae 100644 --- a/webroot/rsrc/css/phui/phui-action-panel.css +++ b/webroot/rsrc/css/phui/phui-action-panel.css @@ -4,7 +4,7 @@ .phui-action-panel { position: relative; - background-color: #fff; + background-color: {$page.content}; border: 1px solid {$lightblueborder}; border-radius: 3px; margin: 0 8px; diff --git a/webroot/rsrc/css/phui/phui-big-info-view.css b/webroot/rsrc/css/phui/phui-big-info-view.css index 3c92025d79..38951aa059 100644 --- a/webroot/rsrc/css/phui/phui-big-info-view.css +++ b/webroot/rsrc/css/phui/phui-big-info-view.css @@ -5,7 +5,7 @@ .phui-big-info-view { padding: 64px 32px; margin: 16px 4px; - background-color: {$sh-greybackground}; + background-color: {$page.sidenav}; text-align: center; } diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 9dc99c6f8c..4c44fa04d6 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -4,7 +4,7 @@ .phui-box-border { border: 1px solid {$lightblueborder}; - background-color: #fff; + background-color: {$page.content}; border-radius: 3px; } @@ -13,7 +13,7 @@ } .phui-box-grey { - background-color: #F7F7F9; + background-color: {$lightgreybackground}; border-radius: 3px; border-color: rgba({$alphagrey},.2); } @@ -36,7 +36,7 @@ .phui-box-blue .phui-oi-list-view, .phui-box-grey .phui-oi-list-view { - background-color: #fff; + background-color: {$page.content}; } .phui-box-blue .phui-header-shell { @@ -49,7 +49,7 @@ .phui-object-box.phui-box-blue div.phui-info-severity-nodata, .phui-object-box.phui-box-grey div.phui-info-severity-nodata { - background: #fff; + background: {$page.content}; padding: 32px 0; text-align: center; border: none; @@ -81,7 +81,7 @@ } .phui-box.phui-object-box.phui-box-blue-property .phui-header-shell { - background-color: #eff3fc; + background-color: {$bluepropertybackground}; border-top-right-radius: 3px; border-top-left-radius: 3px; padding: 6px 16px; @@ -113,5 +113,5 @@ body .phui-box-blue-property .phui-header-shell + .phui-object-box { .phui-box-blue-property .phui-header-shell + .phui-object-box .phui-header-shell { - background: #fff; + background: {$page.content}; } diff --git a/webroot/rsrc/css/phui/phui-comment-form.css b/webroot/rsrc/css/phui/phui-comment-form.css index 860dc9dcaf..8fbd3f0c6e 100644 --- a/webroot/rsrc/css/phui/phui-comment-form.css +++ b/webroot/rsrc/css/phui/phui-comment-form.css @@ -3,7 +3,7 @@ */ body .phui-box.phui-object-box.phui-comment-form-view { - background-color: #fff; + background-color: {$page.content}; margin-left: 62px; position: relative; } @@ -67,7 +67,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { } .phui-comment-form-view .aphront-form-input .remarkup-assist-textarea:focus { - background-color: #fff; + background-color: {$page.content}; } .device-phone .phui-comment-form-view .aphront-form-input @@ -106,7 +106,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { .phui-comment-form-view .phui-comment-action-bar { border-bottom: 1px solid {$thinblueborder}; - background-color: rgba(239, 243, 252, .75); + background-color: {$bluepropertybackground}; padding: 4px 12px 4px 12px; margin-bottom: 16px; } diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 60b5106154..35c843f81f 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -9,7 +9,7 @@ } .phui-document-container { - background-color: #fff; + background-color: {$page.content}; position: relative; border-bottom: 1px solid #dedee1; } @@ -79,7 +79,7 @@ body.printable { width: 260px; position: absolute; z-index: 30; - background-color: #fff; + background-color: {$page.content}; top: 52px; left: -40px; } @@ -163,7 +163,7 @@ body.printable { .phui-header-header { font-size: 24px; line-height: 30px; - color: #000; + color: {$blacktext}; } .device-phone .phui-document-view.phui-document-view-pro .phui-header-tall @@ -228,7 +228,7 @@ body.printable { .phui-document-view-pro-box .phui-timeline-title { border-top-right-radius: 3px; border-top-left-radius: 3px; - background-color: #fff; + background-color: {$page.content}; border-bottom: 1px solid #F1F1F4; } diff --git a/webroot/rsrc/css/phui/phui-document-summary.css b/webroot/rsrc/css/phui/phui-document-summary.css index 736efe9d20..322096f481 100644 --- a/webroot/rsrc/css/phui/phui-document-summary.css +++ b/webroot/rsrc/css/phui/phui-document-summary.css @@ -17,7 +17,7 @@ body .phui-document-view .phui-document-summary-view h2.remarkup-header { } .phui-document-summary-view h2.remarkup-header a { - color: #000; + color: {$blacktext}; } .phui-document-summary-view h2.remarkup-header a:hover { diff --git a/webroot/rsrc/css/phui/phui-document.css b/webroot/rsrc/css/phui/phui-document.css index 87cdc60ddd..e5985f8889 100644 --- a/webroot/rsrc/css/phui/phui-document.css +++ b/webroot/rsrc/css/phui/phui-document.css @@ -55,7 +55,7 @@ } .phui-document-content { - background: #fff; + background: {$page.content}; } .phui-document-content .phabricator-remarkup { diff --git a/webroot/rsrc/css/phui/phui-fontkit.css b/webroot/rsrc/css/phui/phui-fontkit.css index 875ac41981..43162ea618 100644 --- a/webroot/rsrc/css/phui/phui-fontkit.css +++ b/webroot/rsrc/css/phui/phui-fontkit.css @@ -3,7 +3,7 @@ */ .diviner-document-section .phui-header-header { - color: #000; + color: {$blacktext}; } .phui-document-view .phabricator-remarkup .remarkup-header { diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css index 8a6f705ec9..98e6e5b28f 100644 --- a/webroot/rsrc/css/phui/phui-form.css +++ b/webroot/rsrc/css/phui/phui-form.css @@ -22,7 +22,7 @@ div.jx-tokenizer-container { display: inline-block; height: 30px; line-height: 18px; - color: #333; + color: {$darkbluetext}; vertical-align: middle; font: {$basefont}; -webkit-font-smoothing: antialiased; @@ -45,7 +45,7 @@ input[type="tel"], input[type="color"], div.jx-tokenizer-container { padding: 4px 6px; - background-color: #ffffff; + background-color: {$page.content}; border: 1px solid {$greyborder}; border-radius: 3px; @@ -105,7 +105,7 @@ select { -moz-appearance: none; appearance: none; - background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC") no-repeat right 8px center; + background: {$page.content} url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC") no-repeat right 8px center; background-size: 8px 10px; border-radius: 3px; color: {$darkbluetext}; diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 87df1d0fc5..7926e431c1 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -338,7 +338,7 @@ body .phui-header-shell.phui-bleed-header .phui-profile-header.phui-header-shell .phui-header-header { font-size: 24px; - color: #000; + color: {$blacktext}; } .phui-profile-header .phui-header-col3 { diff --git a/webroot/rsrc/css/phui/phui-hovercard.css b/webroot/rsrc/css/phui/phui-hovercard.css index 0daa904ade..1f01362b6f 100644 --- a/webroot/rsrc/css/phui/phui-hovercard.css +++ b/webroot/rsrc/css/phui/phui-hovercard.css @@ -19,7 +19,7 @@ box-shadow: {$dropshadow}; border: 1px solid {$lightblueborder}; border-radius: 3px; - background-color: #fff; + background-color: {$page.content}; } .phui-hovercard-head .phui-header-shell { diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 105adf9ed4..268385edec 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -5,7 +5,7 @@ .phui-info-view { border-style: solid; border-width: 1px; - background: #fff; + background: {$page.content}; margin: 16px; padding: 12px; border-radius: 3px; @@ -38,6 +38,7 @@ div.phui-info-view.phui-info-severity-plain { .phui-info-view-body { line-height: 1.6em; + color: {$blacktext}; } .phui-info-view.phui-info-has-icon .phui-info-view-body { diff --git a/webroot/rsrc/css/phui/phui-invisible-character-view.css b/webroot/rsrc/css/phui/phui-invisible-character-view.css index b8a848fa9a..a6a90da536 100644 --- a/webroot/rsrc/css/phui/phui-invisible-character-view.css +++ b/webroot/rsrc/css/phui/phui-invisible-character-view.css @@ -4,7 +4,7 @@ .invisible-special { font-family: monospace; - color: #000; + color: {$blacktext}; background: rgba({$alphablue},0.1); padding: 1px 4px; border-radius: 3px; diff --git a/webroot/rsrc/css/phui/phui-lightbox.css b/webroot/rsrc/css/phui/phui-lightbox.css index 910d902a3b..611b9c97a4 100644 --- a/webroot/rsrc/css/phui/phui-lightbox.css +++ b/webroot/rsrc/css/phui/phui-lightbox.css @@ -80,7 +80,7 @@ right: 0; width: 360px; overflow-y: auto; - background: #fff; + background: {$page.content}; opacity: 1; } @@ -100,7 +100,7 @@ } .lightbox-attachment .lightbox-status { - background: #fff; + background: {$page.content}; position: fixed; top: 0; left: 0; @@ -121,7 +121,7 @@ } .lightbox-attachment .lightbox-status-txt a { - color: #000; + color: {$blacktext}; margin-right: 12px; font-size: {$biggerfontsize}; } diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index 5df6f4a210..5fab89bcd8 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -88,7 +88,7 @@ } .device-desktop .phui-list-sidenav .phui-list-item-href:hover .phui-icon-view { - color: #fff; + color: {$page.content}; } /* - Top, Full Width Navigations ----------------------------------------------- @@ -171,7 +171,7 @@ line-height: 24px; font-weight: bold; font-size: {$biggerfontsize}; - border-top: 4px solid #fff; + border-top: 4px solid transparent; } .phui-list-tabbar .phui-list-item-selected .phui-list-item-href { @@ -186,7 +186,6 @@ .device-desktop .phui-list-tabbar .phui-list-item-href:hover { color: {$sky}; - border-bottom: 4px solid $fff; text-decoration: none; } diff --git a/webroot/rsrc/css/phui/phui-pinboard-view.css b/webroot/rsrc/css/phui/phui-pinboard-view.css index d632f13795..1fb4e95792 100644 --- a/webroot/rsrc/css/phui/phui-pinboard-view.css +++ b/webroot/rsrc/css/phui/phui-pinboard-view.css @@ -16,7 +16,7 @@ margin: 0 12px 16px 0; text-align: left; width: 280px; - background: #fff; + background: {$page.content}; border: 1px solid {$lightblueborder}; border-radius: 3px; } @@ -95,7 +95,7 @@ .dashboard-panel .phui-pinboard-view { margin: 0; padding: 16px 12px 0 12px; - background: #fff; + background: {$page.content}; border-left: 1px solid {$lightblueborder}; border-right: 1px solid {$lightblueborder}; border-bottom: 1px solid {$blueborder}; diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index 885b20c12b..73675a44d6 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -63,12 +63,12 @@ a.phui-tag-view:hover { a.phui-tag-type-object, a.phui-tag-type-object:link, .phui-tag-core-closed .phui-tag-color-object { - color: #000; + color: {$blacktext}; } .phui-tag-type-person { white-space: nowrap; - color: #19558d; + color: {$anchor}; } .phui-tag-color-red { @@ -107,8 +107,8 @@ a.phui-tag-type-object:link, } .phui-tag-color-black { - background-color: #333333; - border-color: #333333; + background-color: {$darkgreybackground}; + border-color: {$darkgreybackground}; } .phui-tag-color-grey { @@ -117,28 +117,28 @@ a.phui-tag-type-object:link, } .phui-tag-color-white { - background-color: #f7f7f7; - border-color: #f7f7f7; + background-color: {$lightgreybackground}; + border-color: {$lightgreybackground}; } .phui-tag-color-object { - background-color: #e7e7e7; - border-color: #e7e7e7; + background-color: {$greybackground}; + border-color: {$lightgreyborder}; } .phui-tag-color-person { - background-color: #f1f7ff; - border-color: #f1f7ff; + background-color: {$bluebackground}; + border-color: {$thinblueborder}; } a.phui-tag-view:hover .phui-tag-core.phui-tag-color-person { - border-color: #d9ebfd; + border-color: {$lightblueborder}; } a.phui-tag-view:hover .phui-tag-core.phui-tag-color-object { - border-color: #d7d7d7; + border-color: {$greyborder}; } .phabricator-handle-tag-list-item + .phabricator-handle-tag-list-item { @@ -514,6 +514,6 @@ a.phui-tag-view:hover.phui-tag-disabled .phui-tag-core { } .phui-tag-type-outline.phui-tag-black .phui-tag-core { - color: #000; - border-color: #000; + color: {$blacktext}; + border-color: {$blacktext}; } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index da6b5950c4..27be9c0e72 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -179,7 +179,7 @@ .phui-timeline-core-content { padding: 16px; line-height: 18px; - background: #fff; + background: {$page.content}; border-top: 1px solid rgba({$alphablue},.1); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; @@ -260,7 +260,7 @@ .conpherence-transaction-content .phui-timeline-value, .phui-feed-story-head .phui-timeline-value { font-style: italic; - color: black; + color: {$blacktext}; } .device-desktop .phui-timeline-extra { @@ -280,7 +280,7 @@ } .phui-timeline-icon-fill.fill-has-color .phui-icon-view { - color: #fff; + color: {$page.content}; } .phui-timeline-icon-fill-red { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 7728980268..75162619c4 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -3,7 +3,7 @@ */ .phui-two-column-view .phui-two-column-header { - background-color: #fff; + background-color: {$page.content}; border-bottom: 1px solid rgba({$alphagrey}, .12); margin-bottom: 24px; } @@ -19,7 +19,7 @@ .phui-two-column-header .phui-header-header { font-size: 20px; - color: #000; + color: {$blacktext}; } .device-phone .phui-two-column-header .phui-header-header { @@ -172,7 +172,7 @@ .phui-two-column-tabs { padding: 0 32px; margin-bottom: 32px; - background: #fff; + background: {$page.content}; box-shadow: 0 1px 3px 0 rgba(0,0,0,0.2); } diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css index e07bc6597f..9973d64979 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css @@ -7,7 +7,7 @@ } .phui-workboard-no-color { - background-color: #fff; + background-color: {$page.content}; } .phui-workboard-color .phui-crumbs-view { diff --git a/webroot/rsrc/css/phui/workboards/phui-workcard.css b/webroot/rsrc/css/phui/workboards/phui-workcard.css index 5af162b4bc..e137e962bc 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workcard.css +++ b/webroot/rsrc/css/phui/workboards/phui-workcard.css @@ -3,7 +3,7 @@ */ .phui-workcard.phui-oi { - background-color: #fff; + background-color: {$page.content}; border-radius: 3px; margin-bottom: 8px; border-left-width: 4px; @@ -37,7 +37,7 @@ .phui-workcard .phui-oi-link { white-space: normal; font-weight: normal; - color: #000; + color: {$blacktext}; margin-left: 2px; } From 887ac740c648a4b8061049ec96dc78daa71474b4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Jul 2017 08:57:28 -0700 Subject: [PATCH 331/543] Add a note about the `/status/` path for load balancers to setup docs Summary: Fixes T12926. This exists but isn't documented. Document it after the section about webserver setup, since that's probably when you'd want to set it up. Test Plan: Read carefully, visited `/status/`. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12926 Differential Revision: https://secure.phabricator.com/D18234 --- src/docs/user/configuration/configuration_guide.diviner | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner index b4c494449b..98c27b9736 100644 --- a/src/docs/user/configuration/configuration_guide.diviner +++ b/src/docs/user/configuration/configuration_guide.diviner @@ -143,6 +143,14 @@ Finally, you should run the following commands to enable php support: Restart lighttpd after making your edits, then continue below. + +Load Balancer Health Checks +=========================== + +If you're using a load balancer in front of your webserver, you can configure +it to perform health checks using the path `/status/`. + + = Setup = Now, navigate to whichever subdomain you set up. You should see instructions to From d1f144b214dfe48470af4a07e39f07591cc0d7f1 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Tue, 18 Jul 2017 17:44:56 +0000 Subject: [PATCH 332/543] Fix Search Application Config Summary: Fix T12924. Looks like this melted in D17384, and nobody noticed yet. Test Plan: Visit page, see fancy table. Reviewers: epriestley, 20after4, #blessed_reviewers Reviewed By: epriestley, 20after4, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T12924 Differential Revision: https://secure.phabricator.com/D18230 --- ...torSearchApplicationStorageEnginePanel.php | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php index 15ecc3f391..6dd0786c0c 100644 --- a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php +++ b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php @@ -16,54 +16,61 @@ final class PhabricatorSearchApplicationStorageEnginePanel $viewer = $this->getViewer(); $application = $this->getApplication(); - $active_engine = PhabricatorFulltextStorageEngine::loadEngine(); - $engines = PhabricatorFulltextStorageEngine::loadAllEngines(); + $services = PhabricatorSearchService::getAllServices(); $rows = array(); $rowc = array(); - foreach ($engines as $key => $engine) { + foreach ($services as $key => $service) { try { - $index_exists = $engine->indexExists() ? pht('Yes') : pht('No'); + $name = $service->getDisplayName(); } catch (Exception $ex) { - $index_exists = pht('N/A'); + $name = phutil_tag('em', array(), pht('Error')); } try { - $index_is_sane = $engine->indexIsSane() ? pht('Yes') : pht('No'); + $can_read = $service->isReadable() ? pht('Yes') : pht('No'); } catch (Exception $ex) { - $index_is_sane = pht('N/A'); + $can_read = pht('N/A'); } - if ($engine == $active_engine) { - $rowc[] = 'highlighted'; - } else { - $rowc[] = null; + try { + $can_write = $service->isWritable() ? pht('Yes') : pht('No'); + } catch (Exception $ex) { + $can_write = pht('N/A'); } $rows[] = array( - $key, - get_class($engine), - $index_exists, - $index_is_sane, + $name, + $can_read, + $can_write, ); } + $instructions = pht( + 'To configure the search engines, edit [[ %s | `%s` ]] configuration. '. + 'See **[[ %s | %s ]]** for documentation.', + '/config/edit/cluster.search/', + 'cluster.search', + PhabricatorEnv::getDoclink('Cluster: Search'), + pht('Cluster: Search')); + + $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No search engines available.')) + ->setNotice(new PHUIRemarkupView($viewer, $instructions)) ->setHeaders( array( - pht('Key'), - pht('Class'), - pht('Index Exists'), - pht('Index Is Sane'), + pht('Engine Name'), + pht('Writable'), + pht('Readable'), )) ->setRowClasses($rowc) ->setColumnClasses( array( - '', 'wide', '', + '', )); $box = id(new PHUIObjectBoxView()) From 4d1ed12a9e0a09574b283f3ac0da57a6380a3872 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Jul 2017 08:49:35 -0700 Subject: [PATCH 333/543] Skip Conduit call log writes in read-only mode, allowing "conduit.ping" to run Summary: Ref T10769. See PHI8. We have an unconditional logging write which we can skip in read-only mode. Test Plan: - Put Phabricator in read-only mode with `cluster.read-only`. - Called `conduit.ping` via web UI. - Before: write-on-read-only exception. - After: good result. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T10769 Differential Revision: https://secure.phabricator.com/D18233 --- .../controller/PhabricatorConduitAPIController.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index 991865b564..12127a1e27 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -119,9 +119,11 @@ final class PhabricatorConduitAPIController ->setError((string)$error_code) ->setDuration(1000000 * ($time_end - $time_start)); - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $log->save(); - unset($unguarded); + if (!PhabricatorEnv::isReadOnly()) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $log->save(); + unset($unguarded); + } $response = id(new ConduitAPIResponse()) ->setResult($result) From 1fdd809d35c5f44faf2cfa153f79a7eafab837e8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Jul 2017 10:39:44 -0700 Subject: [PATCH 334/543] Update some more "contributing" docs Summary: Ref T12922. - Remove most mentions to "Contributing Feature Requests". - Raise the barrier to entry on code contributions. I'm going to tweak "Bug Reports" in a followup to be more similar to "Feature Requests", but that's a slightly more involved change. Test Plan: Read new docs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12922 Differential Revision: https://secure.phabricator.com/D18235 --- src/docs/contributor/bug_reports.diviner | 4 -- src/docs/contributor/contrib_intro.diviner | 21 +++----- .../contributor/contributing_code.diviner | 54 +++++++------------ src/docs/contributor/feature_requests.diviner | 45 ++++++---------- 4 files changed, 41 insertions(+), 83 deletions(-) diff --git a/src/docs/contributor/bug_reports.diviner b/src/docs/contributor/bug_reports.diviner index 7602b9e655..00af6a011a 100644 --- a/src/docs/contributor/bug_reports.diviner +++ b/src/docs/contributor/bug_reports.diviner @@ -32,9 +32,6 @@ The most important things to do are: The rest of this article walks through these points in detail. -If you have a feature request (not a bug report), see -@{article:Contributing Feature Requests} for a more tailored guide. - For general information on contributing to Phabricator, see @{article:Contributor Introduction}. @@ -179,6 +176,5 @@ Next Steps Continue by: - - learning about @{article: Contributing Feature Requests}; or - reading general support information in @{article:Support Resources}; or - returning to the @{article:Contributor Introduction}. diff --git a/src/docs/contributor/contrib_intro.diviner b/src/docs/contributor/contrib_intro.diviner index a93c8530dd..e298edc080 100644 --- a/src/docs/contributor/contrib_intro.diviner +++ b/src/docs/contributor/contrib_intro.diviner @@ -21,9 +21,10 @@ Without writing any code, learning the whole codebase, making a big time commitment, or having to touch PHP, here are some ways you can materially contribute to Phabricator: - - Send us an email or drop by IRC just to say "thanks". A big part of the - reason we build this software is to help people solve problems, and knowing - that our efforts are appreciated is really rewarding. + - Drop by the [[ https://phurl.io/u/discourse | community forum ]] just to + say "thanks". A big part of the reason we build this software is to help + people solve problems, and knowing that our efforts are appreciated is + really rewarding. - Recommend Phabricator to people who you think might find it useful. Our most powerful growth channel is word of mouth, and mentioning or tweeting about Phabricator helps the project grow. If writing a tweet sounds like @@ -36,16 +37,8 @@ contribute to Phabricator: > Phabricator is objectively the best thing. Source: I am a certified, internationally recognized expert. - - Report bugs and request features. We may not always be able to fix or build - things right away, but knowing about issues users are encountering or - features they'd like to see improves our ability to plan and prioritize. - - For details on reporting bugs, see @{article:Contributing Bug Reports}. - - For details on requesting features, see @{article:Contributing Feature - Requests}. - - Give us feedback on planned features. Most of what we'll build in the next - 6-12 months currently exists on the [[ Roadmap ]] or in Maniphest. Telling - us about use cases you have can help us build better products when the time - comes to write the code. + - Submit high-quality bug reports by carefully following the guide in + @{article:Contributing Bug Reports}. If all of this sounds nice but you really just want to write some code, be aware that this project often presents a high barrier to entry for new @@ -58,6 +51,4 @@ Next Steps Continue by: - learning about bug reports in @{article:Contributing Bug Reports}; - - learning about feature requests in - @{article:Contributing Feature Requests}; or - learning about code contributions in @{article:Contributing Code}. diff --git a/src/docs/contributor/contributing_code.diviner b/src/docs/contributor/contributing_code.diviner index 52b96d18fe..faaf5b16da 100644 --- a/src/docs/contributor/contributing_code.diviner +++ b/src/docs/contributor/contributing_code.diviner @@ -3,6 +3,22 @@ Describes how to contribute code to Phabricator. +Level Requirements +================== + +To contribute to the Phabricator upstream, you must first pass a series of +ancient trials and be invited to register an account in the ancestral +homeland of Phabricator, here on `secure.phabricator.com`. The nature and +location of these trials is a closely guarded secret. + +If you have passed these trials, this document can guide you through +contributing code. + +If you have not yet passed these trials, writing code is normally not the best +way to contribute to Phabricator. See @{article:Contributor Introduction} for +more information. + + Overview ======== @@ -32,11 +48,7 @@ For general information on contributing to Phabricator, see Coordinate First ================ -Before sending code, you should file a bug report or feature request describing -what you'd like to write. For details on how to do this, see these articles: - - - @{article:Contributing Bug Reports} - - @{article:Contributing Feature Requests} +Before sending code, you should file a task describing what you'd like to write. When you file a task, mention that you'd like to write the code to fix it. We can help contextualize your request or bug and guide you through writing an @@ -74,8 +86,7 @@ consider valuable enough to put long-term support resources behind, and that you're building it in a way that we're comfortable taking over. **Not a Good Fit**: Many patches aren't good fits for the upstream: they -implement features we simply don't want. You can find more information in -@{article:Contributing Feature Requests}. Coordinating with us first helps +implement features we simply don't want. Coordinating with us first helps make sure we're on the same page and interested in a feature. The most common type of patch along these lines is a patch which adds new @@ -218,35 +229,6 @@ In general, if you're coordinating with us first, we can usually provide guidance on how to implement things. The other articles in this section also provide information on how to work in the Phabricator codebase. -Not Sure Where To Get Started? -============================== - -If you don't have a specific bug or feature in mind and just want to write -some code, you can try to find something simple to get started with. - -Because we're usually quick to fix easy bugs and issues, we often don't have a -very good backlog of starter tasks. - -You can try searching in Maniphest for tasks tagged with #easy, which might -have something, but a lot of time this list is small and the tasks on it aren't -very fun or interesting even if they aren't technically too difficult. - -In general, the best way to contribute is to come to us with a problem you -encountered or something you're interested in building, and then work with us -to find a solution to it or a plan to build it. We can help turn a hacky patch -into something that's upstreamable, and you'll get a fix or feature you want. - -You can also look though the rest of the open tasks for something more -substantive that you're interested in. This will give you a better chance of -finding something that's relevant to you, but many tasks are large or blocked -by other large tasks. - -If you do find something, feel free to leave a comment like "I'm interested in -working on this, is this something I could reasonably help with?". We're happy -to walk through things, break larger tasks down into more detail, provide -pointers to similar changes and the right places in the codebase to get started, -and generally figure out how to attack a problem. - Next Steps ========== diff --git a/src/docs/contributor/feature_requests.diviner b/src/docs/contributor/feature_requests.diviner index a7a2a32af6..19a9f79cd6 100644 --- a/src/docs/contributor/feature_requests.diviner +++ b/src/docs/contributor/feature_requests.diviner @@ -3,27 +3,34 @@ Describes how to file an effective Phabricator feature request. -Describe Your Problem! -====================== -IMPORTANT: When filing a feature request, you **MUST** describe the root -problem you are facing. We will not accept feature requests which do not -include a problem description. See below for details. +Level Requirements +================== + +We accept feature requests through two channels: paid support and community +support. + +If you are a paying customer, use the +[[ https://admin.phacility.com/u/support | Support Channel ]] for your account +to request features. This document may help you frame your requests in a way +that lets us address them more quickly, but you do not need to read it or +follow the guidelines. + +Other users can file requests on the +[[ https://phurl.io/u/discourse | community forum ]]. + Overview ======== -Have a feature you'd like to see in Phabricator? This article describes how -to file an effective feature request. +This article describes how to file an effective feature request. The most important things to do are: - understand the upstream; - make sure your feature makes sense in the project; - align your expectations around timelines and priorities; - - describe your problem, not your solution; and - - file a task in - [[ http://secure.phabricator.com/u/newfeature/ | Maniphest ]]. + - describe your problem, not your solution. The rest of this article walks through these points in detail. @@ -139,10 +146,6 @@ give you any updates or predictions about timelines. One day, out of nowhere, your feature will materialize. That day may be a decade from now. You should have realistic expectations about this when filing a feature request. -If you want a concrete timeline, you can work with us to pay for some control -over our roadmap. For details, see -[[ https://secure.phabricator.com/w/prioritization/ | Prioritization ]]. - Describe Problems ================= @@ -219,20 +222,6 @@ Generally, you should wait until a problem actually occurs before filing a request about it. -Create a Task in Maniphest -========================== - -If you think your feature might be a good fit for the upstream, have reasonable -expectations about it, and have a good description of the problem you're trying -to solve, you're ready to file a feature request. - -It is **particularly critical** that you describe the problem you are facing, -not just the feature you want. We will not accept feature requests which do -not describe the root problem the feature is intended to resolve. - -(NOTE) https://secure.phabricator.com/u/newfeature/ - - Next Steps ========== From 2999e19742beadeda1973fcf526190b56983e04e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Jul 2017 10:45:17 -0700 Subject: [PATCH 335/543] More adjustments to bug reporting document Summary: Ref T12922. - Tell customers where to go at the top. - Fix a couple minor things (e.g., don't advise users to reproduce on `secure` anymore). Test Plan: Read carefully. Reviewers: chad, avivey Reviewed By: chad, avivey Maniphest Tasks: T12922 Differential Revision: https://secure.phabricator.com/D18236 --- src/docs/contributor/bug_reports.diviner | 55 +++++++++++------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/docs/contributor/bug_reports.diviner b/src/docs/contributor/bug_reports.diviner index 00af6a011a..eec9569dc6 100644 --- a/src/docs/contributor/bug_reports.diviner +++ b/src/docs/contributor/bug_reports.diviner @@ -3,32 +3,33 @@ Describes how to file an effective Phabricator bug report. -Include Reproduction Steps! -=========================== +Level Requirements +================== -IMPORTANT: When filing a bug report, you **MUST** include reproduction -instructions. We can not help fix bugs we can not reproduce, and will not -accept reports which omit reproduction instructions. +We accept bug reports through two channels: paid support and community +support. -For help, see @{article:Providing Reproduction Steps}. +If you are a paying customer, use the +[[ https://admin.phacility.com/u/support | Support Channel ]] for your account +to report bugs. This document may help you file reports which we can resolve +more quickly, but you do not need to read it or follow the guidelines. + +Other users can follow the guidelines in this document to file bug reports on +the community forum. Overview ======== -Found a bug with Phabricator? Let us know! This article describes how to file -an effective bug report so we can get your issue fixed or help you work around -it. +This article describes how to file an effective Phabricator bug report. The most important things to do are: - check the list of common fixes below; - make sure Phabricator is up to date; - make sure we support your setup; - - gather debugging information; - - explain how to reproduce the issue; and - - create a task in - [[ http://secure.phabricator.com/u/newbug/ | Maniphest ]]. + - gather debugging information; and + - explain how to reproduce the issue. The rest of this article walks through these points in detail. @@ -139,8 +140,8 @@ Reproducibility The most important part of your report content is instructions on how to reproduce the issue. What did you do? If you do it again, does it still break? -Does it depend on a specific browser? Can you reproduce the issue on -`secure.phabricator.com`? +Does it depend on a specific browser? Can you reproduce the issue on a free +instance on `admin.phabricator.com`? It is nearly impossible for us to resolve many issues if we can not reproduce them. We will not accept reports which do not contain the information required @@ -149,28 +150,20 @@ to reproduce problems. For help, see @{article:Providing Reproduction Steps}. -For Open Source Users -===================== +File a Bug Report +================= -If you're up to date, supported, have collected information about the problem, -and have the best reproduction instructions you can come up with, you're ready -to file an issue. +If you're up to date, have collected information about the problem, and have +the best reproduction instructions you can come up with, you're ready +to file a report. -It is **particularly critical** that you include reproduction steps. We will -not accept reports which describe issues we can not reproduce. +It is **particularly critical** that you include reproduction steps. + +You can file a report on the community forum, here: (NOTE) https://discourse.phabricator-community.org/c/bug -For Phacility Customers -======================= - -If you've encountered a bug via a Phacility instance or have a paid support -contract with Phacility, you can find more information on how to get help via -[[ https://admin.phacility.com/book/phacility/article/support/ | Phacility -Support ]]. - - Next Steps ========== From 652d87ac6b2ffb6885a92d97cea59fcea7603135 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Jul 2017 10:35:35 -0700 Subject: [PATCH 336/543] Fix project creation feed story Summary: Adds feed story copy to project create transactions. Test Plan: Create a new project, visit manage page, feed, see correct text. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18245 --- .../editor/PhabricatorProjectTransactionEditor.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 7702b74bd6..adf4a3138b 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -22,6 +22,14 @@ final class PhabricatorProjectTransactionEditor return pht('Projects'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this project.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); From db546e5558c643aaa995ec926189c5914b157829 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Jul 2017 10:47:22 -0700 Subject: [PATCH 337/543] Fix Pholio new mock feed story Summary: Gives some strength to name (needed to over-ride new images) and new create copy. Test Plan: Create a new mock, see proper story in feed. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18246 --- src/applications/pholio/editor/PholioMockEditor.php | 8 ++++++++ .../pholio/xaction/PholioMockNameTransaction.php | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index ebd5703170..c0fcf31f83 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -22,6 +22,14 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return $this->newImages; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this mock.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); diff --git a/src/applications/pholio/xaction/PholioMockNameTransaction.php b/src/applications/pholio/xaction/PholioMockNameTransaction.php index a96475f223..d1231636af 100644 --- a/src/applications/pholio/xaction/PholioMockNameTransaction.php +++ b/src/applications/pholio/xaction/PholioMockNameTransaction.php @@ -9,6 +9,10 @@ final class PholioMockNameTransaction return $object->getName(); } + public function getActionStrength() { + return 1.4; + } + public function applyInternalEffects($object, $value) { $object->setName($value); if ($object->getOriginalName() === null) { From 2f26dd76de90a1732917fc149fa3f598ab7457a1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Jul 2017 14:38:55 -0700 Subject: [PATCH 338/543] Lots of little fixes for Dark Mode (Experimental) Summary: Cleans up a bunch of Differential odd/special colors. Adds some basic "highlight" colors instead of pure yellow. Test Plan: Test each color change in normal and dark modes. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18239 --- resources/celerity/map.php | 46 +++++++++---------- .../CelerityDarkModePostprocessor.php | 26 +++++++---- .../CelerityDefaultPostprocessor.php | 6 +++ .../css/application/config/config-options.css | 2 +- .../differential/changeset-view.css | 16 +++---- .../differential/phui-inline-comment.css | 10 ++-- webroot/rsrc/css/core/remarkup.css | 2 +- webroot/rsrc/css/diviner/diviner-shared.css | 2 +- .../rsrc/css/phui/button/phui-button-bar.css | 4 +- webroot/rsrc/css/phui/phui-basic-nav-view.css | 2 +- webroot/rsrc/css/phui/phui-comment-form.css | 2 +- webroot/rsrc/css/phui/phui-timeline-view.css | 4 +- 12 files changed, 67 insertions(+), 55 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 096f89527a..8f36da2328 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,10 +9,10 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '2cac901b', + 'core.pkg.css' => 'a1b6f35e', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '0cdef299', + 'differential.pkg.css' => '45951e9e', 'differential.pkg.js' => '1d80ecc6', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', @@ -45,7 +45,7 @@ return array( 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', - 'rsrc/css/application/config/config-options.css' => 'c5aac7b0', + 'rsrc/css/application/config/config-options.css' => 'd55ed093', 'rsrc/css/application/config/config-page.css' => 'c1d5121b', 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', @@ -64,9 +64,9 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => 'f23d4e8f', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'bf1a41d8', + 'rsrc/css/application/differential/changeset-view.css' => 'bf84345b', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => 'c4036846', + 'rsrc/css/application/differential/phui-inline-comment.css' => '65ae3bc2', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -116,7 +116,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '1760853c', - 'rsrc/css/core/remarkup.css' => '279e409c', + 'rsrc/css/core/remarkup.css' => '25dc9ace', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -125,7 +125,7 @@ return array( 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', - 'rsrc/css/phui/button/phui-button-bar.css' => '39fe680c', + 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', 'rsrc/css/phui/button/phui-button.css' => '022581b4', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', @@ -146,7 +146,7 @@ return array( 'rsrc/css/phui/phui-box.css' => '745e881d', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', - 'rsrc/css/phui/phui-comment-form.css' => '62836121', + 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', @@ -178,7 +178,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', - 'rsrc/css/phui/phui-timeline-view.css' => 'c4700486', + 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', 'rsrc/css/phui/phui-two-column-view.css' => '5b8cd553', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -552,7 +552,7 @@ return array( 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'conduit-api-css' => '7bc725c4', - 'config-options-css' => 'c5aac7b0', + 'config-options-css' => 'd55ed093', 'config-page-css' => 'c1d5121b', 'conpherence-color-css' => 'abb4c358', 'conpherence-durable-column-view' => '89ea6bef', @@ -564,7 +564,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'bf1a41d8', + 'differential-changeset-view-css' => 'bf84345b', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -798,7 +798,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => '279e409c', + 'phabricator-remarkup-css' => '25dc9ace', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -824,7 +824,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'd13afcde', 'phui-box-css' => '745e881d', - 'phui-button-bar-css' => '39fe680c', + 'phui-button-bar-css' => 'f1ff5494', 'phui-button-css' => '022581b4', 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => 'f1ddf11c', @@ -833,7 +833,7 @@ return array( 'phui-calendar-month-css' => '21154caf', 'phui-chart-css' => '6bf6f78e', 'phui-cms-css' => '504b4b23', - 'phui-comment-form-css' => '62836121', + 'phui-comment-form-css' => 'ac68149f', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', 'phui-curtain-view-css' => '55dd0e59', @@ -854,7 +854,7 @@ return array( 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => 'e1b4ec37', - 'phui-inline-comment-view-css' => 'c4036846', + 'phui-inline-comment-view-css' => '65ae3bc2', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-left-right-css' => 'f60c67e7', 'phui-lightbox-css' => '0a035e40', @@ -875,7 +875,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => 'c4700486', + 'phui-timeline-view-css' => 'f21db7ca', 'phui-two-column-view-css' => '5b8cd553', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', @@ -1112,10 +1112,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - '39fe680c' => array( - 'phui-button-css', - 'phui-button-simple-css', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1867,14 +1863,14 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), - 'bf1a41d8' => array( - 'phui-inline-comment-view-css', - ), 'bf5374ef' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), + 'bf84345b' => array( + 'phui-inline-comment-view-css', + ), 'bff6884b' => array( 'javelin-install', 'javelin-dom', @@ -2120,6 +2116,10 @@ return array( 'javelin-workflow', 'javelin-json', ), + 'f1ff5494' => array( + 'phui-button-css', + 'phui-button-simple-css', + ), 'f50152ad' => array( 'phui-timeline-view-css', ), diff --git a/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php index fbaf4051bb..39ee4be57a 100644 --- a/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php @@ -32,15 +32,15 @@ final class CelerityDarkModePostprocessor // Base Colors 'red' => '#c0392b', - 'lightred' => '#f4dddb', + 'lightred' => '#7f261c', 'orange' => '#e67e22', 'lightorange' => '#f7e2d4', 'yellow' => '#f1c40f', - 'lightyellow' => '#fdf5d4', + 'lightyellow' => '#a4850a', 'green' => '#139543', - 'lightgreen' => '#d7eddf', + 'lightgreen' => '#0e7032', 'blue' => '#2980b9', - 'lightblue' => '#daeaf3', + 'lightblue' => '#1d5981', 'sky' => '#3498db', 'lightsky' => '#ddeef9', 'fire' => '#e62f17', @@ -49,7 +49,7 @@ final class CelerityDarkModePostprocessor 'pink' => '#da49be', 'lightpink' => '#fbeaf8', 'violet' => '#8e44ad', - 'lightviolet' => '#ecdff1', + 'lightviolet' => '#622f78', 'charcoal' => '#4b4d51', 'backdrop' => '#c4cde0', 'hoverwhite' => 'rgba(255,255,255,.6)', @@ -60,6 +60,7 @@ final class CelerityDarkModePostprocessor 'hoverselectedblue' => '#e6e9ee', 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', 'timeline' => '#4e6078', + 'timeline.icon.background' => '#416086', 'bluepropertybackground' => '#2d435f', // Alphas @@ -183,13 +184,18 @@ final class CelerityDarkModePostprocessor 'sh-disabledbackground' => '#f3f3f3', // Diffs - 'new-background' => 'rgba(151, 234, 151, .3)', - 'new-bright' => 'rgba(151, 234, 151, .6)', - 'old-background' => 'rgba(251, 175, 175, .3)', - 'old-bright' => 'rgba(251, 175, 175, .7)', - 'move-background' => '#fdf5d4', + 'diff.background' => '#121b27', + 'new-background' => 'rgba(151, 234, 151, .55)', + 'new-bright' => 'rgba(151, 234, 151, .75)', + 'old-background' => 'rgba(251, 175, 175, .55)', + 'old-bright' => 'rgba(251, 175, 175, .8)', + 'move-background' => '#faca00', 'copy-background' => '#f1c40f', + // Usually light yellow + 'gentle.highlight' => '#26c1c9', + 'gentle.highlight.border' => '#21a9b0', + 'paste.content' => '#222222', 'paste.border' => '#000000', 'paste.highlight' => '#121212', diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 447edbd99f..39a6c849e1 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -72,6 +72,7 @@ final class CelerityDefaultPostprocessor 'hoverselectedblue' => '#e6e9ee', 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', 'timeline' => '#d5d8e1', + 'timeline.icon.background' => '#E6E9F1', 'bluepropertybackground' => '#eff3fc', // Alphas @@ -195,6 +196,7 @@ final class CelerityDefaultPostprocessor 'sh-disabledbackground' => '#f3f3f3', // Diffs + 'diff.background' => '#fff', 'new-background' => 'rgba(151, 234, 151, .3)', 'new-bright' => 'rgba(151, 234, 151, .6)', 'old-background' => 'rgba(251, 175, 175, .3)', @@ -202,6 +204,10 @@ final class CelerityDefaultPostprocessor 'move-background' => '#fdf5d4', 'copy-background' => '#f1c40f', + // Usually light yellow + 'gentle.highlight' => '#fdf3da', + 'gentle.highlight.border' => '#c9b8a8', + 'paste.content' => '#fffef5', 'paste.border' => '#e9dbcd', 'paste.highlight' => '#fdf3da', diff --git a/webroot/rsrc/css/application/config/config-options.css b/webroot/rsrc/css/application/config/config-options.css index efac6f0759..d8a6af6012 100644 --- a/webroot/rsrc/css/application/config/config-options.css +++ b/webroot/rsrc/css/application/config/config-options.css @@ -52,5 +52,5 @@ .config-options-effective-value, .config-option-table .config-options-effective-value th { - background: {$lightyellow}; + background: {$gentle.highlight}; } diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 481149fe34..194561aa7a 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -20,7 +20,7 @@ } .differential-diff { - background: {$page.content}; + background: {$diff.background}; width: 100%; border-top: 1px solid {$lightblueborder}; border-bottom: 1px solid {$lightblueborder}; @@ -266,9 +266,9 @@ td.cov-I { } .differential-meta-notice { - border-top: 1px solid {$sh-lightyellowborder}; - border-bottom: 1px solid {$sh-lightyellowborder}; - background-color: {$sh-yellowbackground}; + border-top: 1px solid {$gentle.highlight.border}; + border-bottom: 1px solid {$gentle.highlight.border}; + background-color: {$gentle.highlight}; padding: 12px; } @@ -304,9 +304,9 @@ td.cov-I { } .differential-loading { - border-top: 1px solid {$yellow}; - border-bottom: 1px solid {$yellow}; - background-color: {$lightyellow}; + border-top: 1px solid {$gentle.highlight.border}; + border-bottom: 1px solid {$gentle.highlight.border}; + background-color: {$gentle.highlight}; padding: 12px; text-align: center; } @@ -405,7 +405,7 @@ tr.differential-inline-loading { .diff-banner-has-unsaved, .diff-banner-has-unsubmitted, .diff-banner-has-draft-done { - background: {$sh-yellowbackground}; + background: {$gentle.highlight}; } .diff-banner-buttons { diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 0a1ffecb39..2738a64749 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -26,7 +26,7 @@ .differential-inline-comment, .differential-inline-comment-edit { background: {$page.content}; - border: 1px solid {$sh-yellowborder}; + border: 1px solid {$gentle.highlight.border}; font: {$basefont}; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -48,9 +48,9 @@ .differential-inline-comment-head { font-weight: bold; color: {$darkbluetext}; - border-bottom: 1px solid {$sh-lightyellowborder}; + border-bottom: 1px solid {$gentle.highlight.border}; padding: 4px 5px 4px 8px; - background-color: {$sh-yellowbackground}; + background-color: {$gentle.highlight}; } .differential-inline-comment-content { @@ -209,8 +209,8 @@ */ .differential-inline-comment .differential-inline-done-label { - border-color: {$sh-yellowborder}; - color: {$sh-yellowicon}; + border-color: {$gentle.highlight.border}; + color: {$bluetext}; } .differential-inline-comment.inline-state-is-draft diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 4007e910ea..599118f191 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -3,7 +3,7 @@ */ .phabricator-remarkup { - line-height: 1.51em; + line-height: 1.61em; word-break: break-word; color: {$darkbluetext}; } diff --git a/webroot/rsrc/css/diviner/diviner-shared.css b/webroot/rsrc/css/diviner/diviner-shared.css index 56dc6ddf30..a2eb6c9c72 100644 --- a/webroot/rsrc/css/diviner/diviner-shared.css +++ b/webroot/rsrc/css/diviner/diviner-shared.css @@ -66,7 +66,7 @@ } .phui-header-shell.diviner-section-header .phui-header-header { - color: #000; + color: {$blacktext}; font-size: 20px; } diff --git a/webroot/rsrc/css/phui/button/phui-button-bar.css b/webroot/rsrc/css/phui/button/phui-button-bar.css index 17de25e808..87369e4ab6 100644 --- a/webroot/rsrc/css/phui/button/phui-button-bar.css +++ b/webroot/rsrc/css/phui/button/phui-button-bar.css @@ -14,7 +14,7 @@ .phui-button-bar-borderless .button .phui-icon-view { font-size: 15px; - color: rgba({$alphagrey},.4); + color: {$bluetext}; } .phui-button-bar-borderless .button:hover { @@ -24,7 +24,7 @@ } .phui-button-bar-borderless .button:hover .phui-icon-view { - color: rgba({$alphagrey},.9); + color: {$sky}; } .phui-button-bar-borderless .button { diff --git a/webroot/rsrc/css/phui/phui-basic-nav-view.css b/webroot/rsrc/css/phui/phui-basic-nav-view.css index 03b1deb917..70882b0afd 100644 --- a/webroot/rsrc/css/phui/phui-basic-nav-view.css +++ b/webroot/rsrc/css/phui/phui-basic-nav-view.css @@ -48,7 +48,7 @@ } .phui-two-column-view .phui-basic-nav .phabricator-side-menu { - background-color: #fff; + background-color: {$page.content}; } .phui-basic-nav .phabricator-side-menu { diff --git a/webroot/rsrc/css/phui/phui-comment-form.css b/webroot/rsrc/css/phui/phui-comment-form.css index 8fbd3f0c6e..dcf45edb76 100644 --- a/webroot/rsrc/css/phui/phui-comment-form.css +++ b/webroot/rsrc/css/phui/phui-comment-form.css @@ -90,7 +90,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { } .phui-comment-action { - background-color: rgba(239, 243, 252, .75); + background-color: rgba({$alphablue}, .1); border-radius: 3px; margin: 0px 16px 8px; padding: 6px; diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 27be9c0e72..b80a4e8d75 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -221,7 +221,7 @@ } .phui-timeline-icon { - color: {$sh-blueicon}; + color: {$bluetext}; } .phui-icon-view.phui-timeline-icon { @@ -232,7 +232,7 @@ height: 26px; width: 26px; border-radius: 3px; - background-color: #E6E9F1; + background-color: {$timeline.icon.background}; } .phui-timeline-major-event .phui-timeline-icon-fill { From 9d5d59f7594978f354049223f0ce04ba72a9d13d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Jul 2017 14:46:18 -0700 Subject: [PATCH 339/543] Move back to black for remarkup, up line height Summary: Moves default color back to black, increases line-height to clean new object borders Test Plan: Review lots of remarkup in sb, this is same height as "document" CSS. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18249 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8f36da2328..9c685fb1f2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'a1b6f35e', + 'core.pkg.css' => 'f1c7630f', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -116,7 +116,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '1760853c', - 'rsrc/css/core/remarkup.css' => '25dc9ace', + 'rsrc/css/core/remarkup.css' => 'cad18339', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -798,7 +798,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => '25dc9ace', + 'phabricator-remarkup-css' => 'cad18339', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 599118f191..8750598780 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -3,9 +3,8 @@ */ .phabricator-remarkup { - line-height: 1.61em; + line-height: 1.7em; word-break: break-word; - color: {$darkbluetext}; } .phabricator-remarkup p { From e9208ed3da4662d18d230f51c8a78e85d04dad95 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Jul 2017 15:31:55 -0700 Subject: [PATCH 340/543] Fix a spelling error in worker triggers Summary: This word is not spelled properly. Test Plan: Read the word. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18250 --- .../PhabricatorWorkerTriggerManagementFireWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php index a05caa06a1..f4588b5f70 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php @@ -84,7 +84,7 @@ final class PhabricatorWorkerTriggerManagementFireWorkflow $console->writeOut( "%s\n", pht( - 'Trigger is not scheduled to execute. Use --next to simluate '. + 'Trigger is not scheduled to execute. Use --next to simulate '. 'a scheduled event.')); continue; } else { From 018d1b77bf336a6b63c4f2a280a67a85db9bed8d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 20 Jul 2017 09:08:22 -0700 Subject: [PATCH 341/543] Identify compound short search tokens in the form "xx.yy" as unqueryable in the search UI Summary: Ref T12928. The index doesn't work for these, so show the user that there's a problem and drop the terms. This doesn't fix the problem, but makes the behavior more clear. Test Plan: {F5053703} {F5053704} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12928 Differential Revision: https://secure.phabricator.com/D18254 --- .../PhabricatorMySQLFulltextStorageEngine.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index b22076cf92..4ccc85f085 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -235,7 +235,7 @@ final class PhabricatorMySQLFulltextStorageEngine $value = $stemmer->stemToken($value); } - if (phutil_utf8_strlen($value) < $min_length) { + if ($this->isShortToken($value, $min_length)) { $fulltext_token->setIsShort(true); continue; } @@ -549,4 +549,22 @@ final class PhabricatorMySQLFulltextStorageEngine return array($min_len, $stopwords); } + private function isShortToken($value, $min_length) { + // NOTE: The engine tokenizes internally on periods, so terms in the form + // "ab.cd", where short substrings are separated by periods, do not produce + // any queryable tokens. These terms are meaningful if at least one + // substring is longer than the minimum length, like "example.py". See + // T12928. + + $parts = preg_split('/[.]+/', $value); + + foreach ($parts as $part) { + if (phutil_utf8_strlen($part) >= $min_length) { + return false; + } + } + + return true; + } + } From 27a243cd882f39fc33bb3e0131d67c33d8c36070 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 21 Jul 2017 00:51:30 +0000 Subject: [PATCH 342/543] Link to docs from metamta.mail-adapter config Test Plan: Look at new config page, click link. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D18259 --- .../config/option/PhabricatorMetaMTAConfigOptions.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index cc9732dc70..6c846daa57 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -138,15 +138,19 @@ EODOC , 'metamta.public-replies')); + $adapter_doc_href = PhabricatorEnv::getDoclink( + 'Configuring Outbound Email'); + $adapter_doc_name = pht('Configuring Outbound Email'); $adapter_description = $this->deformat(pht(<<deformat(pht(<< Date: Fri, 21 Jul 2017 08:08:24 -0700 Subject: [PATCH 343/543] Fix an issue when deleting the entire content of an unsubmitted inline comment Summary: Ref T12733. In this case, the server returns no new `row`, but we would incorrectly try to bind to one. Test Plan: - Created a new comment. - Edited it. - Deleted all the text. - Saved changes. - Before: Header continues to show phantom "1 Unsubmitted Comment", browser error log reflects one error. - After: Header reflects comment being deleted, error log is quiet. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18262 --- resources/celerity/map.php | 12 ++++++------ webroot/rsrc/js/application/diff/DiffInline.js | 9 ++++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9c685fb1f2..a9c633d7ad 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', - 'differential.pkg.js' => '1d80ecc6', + 'differential.pkg.js' => '414ada25', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -399,7 +399,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', 'rsrc/js/application/diff/DiffChangesetList.js' => 'cb1570cb', - 'rsrc/js/application/diff/DiffInline.js' => '1bfa31c7', + 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -779,7 +779,7 @@ return array( 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '99abf4cd', 'phabricator-diff-changeset-list' => 'cb1570cb', - 'phabricator-diff-inline' => '1bfa31c7', + 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1020,9 +1020,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '1bfa31c7' => array( - 'javelin-dom', - ), '1e911d0f' => array( 'javelin-stratcom', 'javelin-request', @@ -2094,6 +2091,9 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e83d28f3' => array( + 'javelin-dom', + ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 1c93959b32..fef6b2087a 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -702,7 +702,14 @@ JX.install('DiffInline', { JX.DOM.remove(this._row); } - this.bindToRow(new_row); + // If you delete the content on a comment and save it, it acts like a + // delete: the server does not return a new row. + if (new_row) { + this.bindToRow(new_row); + } else { + this.setDeleted(true); + this._row = null; + } this._didUpdate(); }, From 69a7d57c3fdaeecd3b7ec8f98ba4a02b5438b8c3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 24 Jul 2017 13:27:19 -0700 Subject: [PATCH 344/543] Add a branch selector to Diffusion Summary: Fixes T12931. Adds a branch selector that's always visible if the repo has commits. Test Plan: Test a plain hg, svn, git repository. Test setting a bad default branch. Test a good default branch. Test on desktop, mobile layouts. {F5058061} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12931 Differential Revision: https://secure.phabricator.com/D18267 --- resources/celerity/map.php | 10 +- .../DiffusionRepositoryController.php | 128 ++++++++++++++++-- .../css/application/diffusion/diffusion.css | 17 +++ webroot/rsrc/css/phui/button/phui-button.css | 8 ++ 4 files changed, 147 insertions(+), 16 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a9c633d7ad..c724382016 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'f1c7630f', + 'core.pkg.css' => 'c0a7ecfd', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -75,7 +75,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', - 'rsrc/css/application/diffusion/diffusion.css' => '8d01932f', + 'rsrc/css/application/diffusion/diffusion.css' => '67bd971b', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 'rsrc/css/application/flag/flag.css' => 'bba8f811', @@ -127,7 +127,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', - 'rsrc/css/phui/button/phui-button.css' => '022581b4', + 'rsrc/css/phui/button/phui-button.css' => '3a744520', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', @@ -571,7 +571,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-css' => '8d01932f', + 'diffusion-css' => '67bd971b', 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', @@ -825,7 +825,7 @@ return array( 'phui-big-info-view-css' => 'd13afcde', 'phui-box-css' => '745e881d', 'phui-button-bar-css' => 'f1ff5494', - 'phui-button-css' => '022581b4', + 'phui-button-css' => '3a744520', 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 6def808cff..39877317df 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -4,7 +4,7 @@ final class DiffusionRepositoryController extends DiffusionController { private $historyFuture; private $browseFuture; - private $tagFuture; + private $branchButton = null; private $branchFuture; public function shouldAllowPublic() { @@ -53,15 +53,25 @@ final class DiffusionRepositoryController extends DiffusionController { // This is a valid branch, so we necessarily have some content. $page_has_content = true; } else { - $empty_title = pht('No Such Branch'); - $empty_message = pht( - 'There is no branch named "%s" in this repository.', - $drequest->getBranch()); + $default = $repository->getDefaultBranch(); + if ($default != $drequest->getBranch()) { + $empty_title = pht('No Such Branch'); + $empty_message = pht( + 'There is no branch named "%s" in this repository.', + $drequest->getBranch()); + } else { + $empty_title = pht('No Default Branch'); + $empty_message = pht( + 'This repository is configured with default branch "%s" but '. + 'there is no branch with that name in this repository.', + $default); + } } } // If we didn't find any branches, check if there are any commits at all. // This can tailor the message for empty repositories. + $any_commit = null; if (!$page_has_content) { $any_commit = id(new DiffusionCommitQuery()) ->setViewer($viewer) @@ -81,6 +91,9 @@ final class DiffusionRepositoryController extends DiffusionController { if ($page_has_content) { $content = $this->buildNormalContent($drequest); } else { + // If we have a commit somewhere, find branches. + // TODO: Evan will replace + // $this->buildNormalContent($drequest); $content = id(new PHUIInfoView()) ->setTitle($empty_title) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) @@ -110,7 +123,7 @@ final class DiffusionRepositoryController extends DiffusionController { $bar = id(new PHUILeftRightView()) ->setLeft($locate_file) - ->setRight($clone_button) + ->setRight(array($this->branchButton, $clone_button)) ->addClass('diffusion-action-bar'); $view = id(new PHUITwoColumnView()) @@ -145,6 +158,7 @@ final class DiffusionRepositoryController extends DiffusionController { $commit = $drequest->getCommit(); $path = $drequest->getPath(); + $futures = array(); $this->historyFuture = $this->callConduitMethod( 'diffusion.historyquery', array( @@ -153,6 +167,7 @@ final class DiffusionRepositoryController extends DiffusionController { 'offset' => 0, 'limit' => 15, )); + $futures[] = $this->historyFuture; $browse_pager = id(new PHUIPagerView()) ->readFromRequest($request); @@ -164,11 +179,19 @@ final class DiffusionRepositoryController extends DiffusionController { 'path' => $path, 'limit' => $browse_pager->getPageSize() + 1, )); + $futures[] = $this->browseFuture; + + if ($this->needBranchFuture()) { + $branch_limit = $this->getBranchLimit(); + $this->branchFuture = $this->callConduitMethod( + 'diffusion.branchquery', + array( + 'closed' => false, + 'limit' => $branch_limit + 1, + )); + $futures[] = $this->branchFuture; + } - $futures = array( - $this->historyFuture, - $this->browseFuture, - ); $futures = array_filter($futures); $futures = new FutureIterator($futures); foreach ($futures as $future) { @@ -253,6 +276,18 @@ final class DiffusionRepositoryController extends DiffusionController { $content[] = $readme; } + + try { + $branch_button = $this->buildBranchList($drequest); + $this->branchButton = $branch_button; + } catch (Exception $ex) { + if (!$repository->isImporting()) { + $content[] = $this->renderStatusMessage( + pht('Unable to Load Branches'), + $ex->getMessage()); + } + } + return $content; } @@ -375,6 +410,67 @@ final class DiffusionRepositoryController extends DiffusionController { return $panel; } + private function buildBranchList(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + + if (!$this->needBranchFuture()) { + return null; + } + + $branches = $this->branchFuture->resolve(); + if (!$branches) { + return null; + } + + $limit = $this->getBranchLimit(); + $more_branches = (count($branches) > $limit); + $branches = array_slice($branches, 0, $limit); + + $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); + + $actions = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + foreach ($branches as $branch) { + $branch_uri = $drequest->generateURI( + array( + 'action' => 'browse', + 'branch' => $branch->getShortname(), + )); + $actions->addAction( + id(new PhabricatorActionView()) + ->setName($branch->getShortname()) + ->setIcon('fa-code-fork') + ->setHref($branch_uri)); + } + + if ($more_branches) { + $more_uri = $drequest->generateURI( + array( + 'action' => 'branches', + )); + $actions->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('See More Branches')) + ->setIcon('fa-external-link') + ->setHref($more_uri)); + } + + $button = id(new PHUIButtonView()) + ->setText(pht('Branch: %s', $drequest->getBranch())) + ->setTag('a') + ->addClass('mmr') + ->setIcon('fa-code-fork') + ->setColor(PHUIButtonView::GREY) + ->setDropdown(true) + ->setDropdownMenu($actions); + + return $button; + } + private function buildLocateFile() { $request = $this->getRequest(); $viewer = $request->getUser(); @@ -457,7 +553,17 @@ final class DiffusionRepositoryController extends DiffusionController { ->setPager($pager); } - private function getTagLimit() { + private function needBranchFuture() { + $drequest = $this->getDiffusionRequest(); + + if ($drequest->getBranch() === null) { + return false; + } + + return true; + } + + private function getBranchLimit() { return 15; } diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css index 405755140b..4d18e3dcb7 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion.css +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -16,6 +16,23 @@ margin-bottom: 16px; } +.device-phone .diffusion-action-bar { + display: block; +} + +.device-phone .diffusion-action-bar .phui-lr-container { + display: block; +} + +.device-phone .diffusion-action-bar .phui-lr-container .phui-left-view { + display: block; +} + +.device-phone .diffusion-action-bar .phui-lr-container .phui-right-view { + padding-top: 12px; + display: block; +} + .diffusion-profile-locate .phui-form-view { margin: 0; padding: 0; diff --git a/webroot/rsrc/css/phui/button/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css index 3a356f7deb..ac3ae2176d 100644 --- a/webroot/rsrc/css/phui/button/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -265,6 +265,14 @@ a.policy-control .phui-button-text { right: 10px; } +.phui-button-text { + display: inline-block; +} + +.dropdown .phui-button-text { + margin-right: 16px; +} + .button.has-icon .phui-button-text { margin-left: 16px; } From 8034b9d819eb941c7df08bdade6baa0551ccc177 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 24 Jul 2017 11:37:04 -0700 Subject: [PATCH 345/543] Don't require a device be registered in Almanac to do cluster init/resync steps Summary: Fixes T12893. See also PHI15. This is complicated but: - In the documentation, we say "register your web devices with Almanac". We do this ourselves on `secure` and in the production Phacility cluster. - We don't actually require you to do this, don't detect that you didn't, and there's no actual reason you need to. - If you don't register your "web" devices, the only bad thing that really happens is that creating repositories skips version initialization, creating the bug in T12893. This process does not actually require the devices be registered, but the code currently just kind of fails silently if they aren't. Instead, just move forward on these init/resync phases even if the device isn't registered. These steps are safe to run from unregistered hosts since they just wipe the whole table and don't affect specific devices. If this sticks, I'll probably update the docs to not tell you to register `web` devices, or at least add "Optionally, ...". I don't think there's any future reason we'd need them to be registered. Test Plan: This is a bit tough to test without multiple hosts, but I added this piece of code to `AlmanacKeys` so we'd pretend to be a nameless "web" device when creating a repository: ``` if ($_REQUEST['__path__'] == '/diffusion/edit/form/default/') { return null; } ``` Then I created some Git repositories. Before the patch, they came up with `-` versions (no version information). After the patch, they came up with `0` versions (correctly initialized). Reviewers: chad Reviewed By: chad Maniphest Tasks: T12893 Differential Revision: https://secure.phabricator.com/D18273 --- .../DiffusionRepositoryClusterEngine.php | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index a1a22670f9..e822bb76b0 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -58,7 +58,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyAfterCreation() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(false)) { return; } @@ -86,7 +86,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyAfterHostingChange() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(false)) { return; } @@ -133,7 +133,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyBeforeRead() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -288,7 +288,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyBeforeWrite() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -382,7 +382,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { public function synchronizeWorkingCopyAfterDiscovery($new_version) { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -426,7 +426,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyAfterWrite() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -551,7 +551,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { /** * @task internal */ - private function shouldEnableSynchronization() { + private function shouldEnableSynchronization($require_device) { $repository = $this->getRepository(); $service_phid = $repository->getAlmanacServicePHID(); @@ -563,9 +563,11 @@ final class DiffusionRepositoryClusterEngine extends Phobject { return false; } - $device = AlmanacKeys::getLiveDevice(); - if (!$device) { - return false; + if ($require_device) { + $device = AlmanacKeys::getLiveDevice(); + if (!$device) { + return false; + } } return true; From c217d7619c5f3fe7e7c32c50c81972851ec236d1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 24 Jul 2017 16:30:44 -0700 Subject: [PATCH 346/543] Make "A" hide or show all inline comments Summary: Ref T12733. See PHI17. Test Plan: Pressed "A", then pressed "A". Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D18274 --- resources/celerity/map.php | 14 +++++++------- .../view/DifferentialChangesetListView.php | 3 +++ .../js/application/diff/DiffChangesetList.js | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c724382016..fb20ebfa55 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', - 'differential.pkg.js' => '414ada25', + 'differential.pkg.js' => 'b71b8c5d', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -398,7 +398,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'cb1570cb', + 'rsrc/js/application/diff/DiffChangesetList.js' => '8f1cd52c', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -778,7 +778,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '99abf4cd', - 'phabricator-diff-changeset-list' => 'cb1570cb', + 'phabricator-diff-changeset-list' => '8f1cd52c', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1567,6 +1567,10 @@ return array( '8e1baf68' => array( 'phui-button-css', ), + '8f1cd52c' => array( + 'javelin-install', + 'phuix-button-view', + ), '8ff5e24c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1950,10 +1954,6 @@ return array( 'cae95e89' => array( 'syntax-default-css', ), - 'cb1570cb' => array( - 'javelin-install', - 'phuix-button-view', - ), 'ccf1cbf8' => array( 'javelin-install', 'javelin-dom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 2787356bd2..307d424b0e 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -298,6 +298,9 @@ final class DifferentialChangesetListView extends AphrontView { 'Show All Inlines' => pht('Show All Inlines'), 'List Inline Comments' => pht('List Inline Comments'), + + 'Hide or show all inline comments.' => + pht('Hide or show all inline comments.'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 89a18a9a8b..3f47f1ed35 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -190,6 +190,10 @@ JX.install('DiffChangesetList', { label = pht('Collapse or expand inline comment.'); this._installKey('q', label, this._onkeycollapse); + + label = pht('Hide or show all inline comments.'); + this._installKey('A', label, this._onkeyhideall); + }, isAsleep: function() { @@ -448,6 +452,15 @@ JX.install('DiffChangesetList', { this._warnUser(pht('You must select a comment to hide.')); }, + _onkeyhideall: function() { + var inlines = this._getInlinesByType(); + if (inlines.visible.length) { + this._toggleInlines('all'); + } else { + this._toggleInlines('show'); + } + }, + _warnUser: function(message) { new JX.Notification() .setContent(message) @@ -1701,6 +1714,10 @@ JX.install('DiffChangesetList', { this._dropdownMenu.close(); e.prevent(); + this._toggleInlines(type); + }, + + _toggleInlines: function(type) { var inlines = this._getInlinesByType(); // Clear the selection state since we end up in a weird place if the From 1588d3e224fe73df60272842e1d8578e72c4970a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 25 Jul 2017 06:50:40 -0700 Subject: [PATCH 347/543] In Differential, render status for any active Drydock repository operation, not just "Land" operations Summary: See PHI18. Third parties can currently define other types of Drydock operations (like "Merge Check" or "Cherry-Pick") but we won't show them in the UI. This is a simple change which improves third-party support for now. These kinds of operations generally make sense in the upstream, but the pathways to support are longer. Test Plan: - Verified that there are no other types of repository operation which we'd want to exclude in the upstream today by reviewing the "Repository Operation" subclasses. - Will click some buttons in production to make sure this works. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18276 --- .../controller/DifferentialRevisionViewController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index ab03ee6d81..edf65b01ea 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1051,14 +1051,14 @@ final class DifferentialRevisionViewController extends DifferentialController { return null; } + // NOTE: The upstream currently supports only "Land" operations, but + // third parties have other operations (see PHI18). For now, we show any + // operation that exists. We'll refine this in the future. + $operations = id(new DrydockRepositoryOperationQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($revision->getPHID())) ->withIsDismissed(false) - ->withOperationTypes( - array( - DrydockLandRepositoryOperation::OPCONST, - )) ->execute(); if (!$operations) { return null; From ca17e2283d92e9f49ed0ef2870895766e82827a3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 25 Jul 2017 13:25:57 -0700 Subject: [PATCH 348/543] Have Maniphest use create transactions when using email Summary: Fixes T12929. Sets a create transaction if new. Test Plan: test a new task over email via command line Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12929 Differential Revision: https://secure.phabricator.com/D18279 --- src/applications/maniphest/mail/ManiphestReplyHandler.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/maniphest/mail/ManiphestReplyHandler.php b/src/applications/maniphest/mail/ManiphestReplyHandler.php index bbcfe86551..cc2bdff9d0 100644 --- a/src/applications/maniphest/mail/ManiphestReplyHandler.php +++ b/src/applications/maniphest/mail/ManiphestReplyHandler.php @@ -24,6 +24,10 @@ final class ManiphestReplyHandler $xactions = array(); if ($is_new) { + $xactions[] = $this->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE) + ->setNewValue(true); + $xactions[] = $this->newTransaction() ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(nonempty($mail->getSubject(), pht('Untitled Task'))); From e7f94d7528eb885d8de8fc4f7ee6c942e96eadc0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 26 Jul 2017 09:29:47 -0700 Subject: [PATCH 349/543] Properly version Legalpad documents Summary: Fixes T12933. This now creates a new DocumentBody when creating or editing a legalpad document. Test Plan: Create a new document, edit document. Check database that version is saved as new row, and timestamps are correct. ```mysql> select * from legalpad_documentbody; +----+--------------------------------+--------------------------------+--------------------------------+---------+---------------+--------+-------------+--------------+ | id | phid | creatorPHID | documentPHID | version | title | text | dateCreated | dateModified | +----+--------------------------------+--------------------------------+--------------------------------+---------+---------------+--------+-------------+--------------+ | 1 | PHID-LEGB-nsgzqklzfmjahlcgobm7 | PHID-USER-72xwu7eurrpsu2kxgrvw | PHID-LEGD-v7mc3xyithjvbiqeksbj | 2 | Legal Title 1 | Body 2 | 1501037011 | 1501037081 | | 2 | PHID-LEGB-2kaytwmjusljib6pjycc | PHID-USER-72xwu7eurrpsu2kxgrvw | PHID-LEGD-v7mc3xyithjvbiqeksbj | 3 | Legal Title 1 | Body 3 | 1501037521 | 1501037521 | | 3 | PHID-LEGB-h6q6bi42w4rgxrhk3qdb | PHID-USER-72xwu7eurrpsu2kxgrvw | PHID-LEGD-7gxuhafvkoy2izkv4gdd | 1 | New 2 | asdf | 1501037553 | 1501037553 | +----+--------------------------------+--------------------------------+--------------------------------+---------+---------------+--------+-------------+--------------+ 3 rows in set (0.00 sec)``` Reviewers: epriestley Reviewed By: epriestley Subscribers: tmakarios, Korvin Maniphest Tasks: T12933 Differential Revision: https://secure.phabricator.com/D18280 --- .../sql/autopatches/20170725.legalpad.date.01.sql | 2 ++ .../legalpad/editor/LegalpadDocumentEditor.php | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 resources/sql/autopatches/20170725.legalpad.date.01.sql diff --git a/resources/sql/autopatches/20170725.legalpad.date.01.sql b/resources/sql/autopatches/20170725.legalpad.date.01.sql new file mode 100644 index 0000000000..a091220894 --- /dev/null +++ b/resources/sql/autopatches/20170725.legalpad.date.01.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_legalpad.legalpad_documentbody + SET dateCreated = dateModified; diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 7882635285..35f2487a81 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -45,18 +45,23 @@ final class LegalpadDocumentEditor } if ($is_contribution) { + $text = $object->getDocumentBody()->getText(); + $title = $object->getDocumentBody()->getTitle(); $object->setVersions($object->getVersions() + 1); - $body = $object->getDocumentBody(); + + $body = new LegalpadDocumentBody(); + $body->setCreatorPHID($this->getActingAsPHID()); + $body->setText($text); + $body->setTitle($title); $body->setVersion($object->getVersions()); $body->setDocumentPHID($object->getPHID()); $body->save(); $object->setDocumentBodyPHID($body->getPHID()); - $actor = $this->getActor(); $type = PhabricatorContributedToObjectEdgeType::EDGECONST; id(new PhabricatorEdgeEditor()) - ->addEdge($actor->getPHID(), $type, $object->getPHID()) + ->addEdge($this->getActingAsPHID(), $type, $object->getPHID()) ->save(); $type = PhabricatorObjectHasContributorEdgeType::EDGECONST; From e47f85cd98b869fe97b3e6143f5de0670039d6da Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 26 Jul 2017 10:09:36 -0700 Subject: [PATCH 350/543] In Differential, filter repository operations to just "Land" operations again Summary: Reverts D18276. See PHI18 for discussion. The additional rules here (roughly, "only show the first successful operation") didn't actually work out for the other types of operations. This is all just figuring out a stopgap, T12935 and other changes should eventually provide real pathways here. Test Plan: Straight revert. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18281 --- .../controller/DifferentialRevisionViewController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index edf65b01ea..ab03ee6d81 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1051,14 +1051,14 @@ final class DifferentialRevisionViewController extends DifferentialController { return null; } - // NOTE: The upstream currently supports only "Land" operations, but - // third parties have other operations (see PHI18). For now, we show any - // operation that exists. We'll refine this in the future. - $operations = id(new DrydockRepositoryOperationQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($revision->getPHID())) ->withIsDismissed(false) + ->withOperationTypes( + array( + DrydockLandRepositoryOperation::OPCONST, + )) ->execute(); if (!$operations) { return null; From a7121f022f77dc4e30ba937211c3901ee00ac7ff Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 27 Jul 2017 15:12:48 -0700 Subject: [PATCH 351/543] Fix Firefox select dropdowns, maybe Summary: Fixes T12930. I can't verify this fix, but the colors here work in light/dark mode correctly. Test Plan: Wait for @cspeckmim to verify Reviewers: epriestley, cspeckmim Reviewed By: cspeckmim Subscribers: cspeckmim, Korvin Maniphest Tasks: T12930 Differential Revision: https://secure.phabricator.com/D18286 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-form.css | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fb20ebfa55..23e9733c10 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'c0a7ecfd', + 'core.pkg.css' => 'd6046dd9', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 'rsrc/css/phui/phui-form-view.css' => '6175808d', - 'rsrc/css/phui/phui-form.css' => 'efa86a27', + 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => 'e7de7ee2', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', @@ -843,7 +843,7 @@ return array( 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', - 'phui-form-css' => 'efa86a27', + 'phui-form-css' => '7aaa04e3', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => 'e7de7ee2', diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css index 98e6e5b28f..1c587b5d3d 100644 --- a/webroot/rsrc/css/phui/phui-form.css +++ b/webroot/rsrc/css/phui/phui-form.css @@ -22,7 +22,7 @@ div.jx-tokenizer-container { display: inline-block; height: 30px; line-height: 18px; - color: {$darkbluetext}; + color: inherit; vertical-align: middle; font: {$basefont}; -webkit-font-smoothing: antialiased; @@ -108,7 +108,6 @@ select { background: {$page.content} url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC") no-repeat right 8px center; background-size: 8px 10px; border-radius: 3px; - color: {$darkbluetext}; border: 1px solid {$greyborder}; height: 30px; padding: 0 24px 0 8px; From 9cf5bc30cdc057d39fa318583defeb2f285de65e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 27 Jul 2017 14:30:35 -0700 Subject: [PATCH 352/543] Fix some copy and bugs in Ponder Summary: Fixes T12939. Fixes some feed stories, mailtags, and headers. Test Plan: Review changes locally by asking how babby is formed. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12939 Differential Revision: https://secure.phabricator.com/D18285 --- .../controller/PonderQuestionViewController.php | 2 +- .../ponder/editor/PonderQuestionEditor.php | 4 ++-- .../ponder/storage/PonderAnswerTransaction.php | 7 +++++++ .../xaction/PonderAnswerContentTransaction.php | 17 +++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 59da519834..2cd555204c 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -94,7 +94,7 @@ final class PonderQuestionViewController extends PonderController { $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki()); $answer_wiki = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setHeaderText(pht('ANSWER SUMMARY')) + ->setHeaderText(pht('Answer Summary')) ->appendChild($wiki) ->addClass('ponder-answer-wiki'); } diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index a17aebecf8..0720f436b9 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -10,11 +10,11 @@ final class PonderQuestionEditor } public function getCreateObjectTitle($author, $object) { - return pht('%s created this question.', $author); + return pht('%s asked this question.', $author); } public function getCreateObjectTitleForFeed($author, $object) { - return pht('%s created %s.', $author, $object); + return pht('%s asked %s.', $author, $object); } /** diff --git a/src/applications/ponder/storage/PonderAnswerTransaction.php b/src/applications/ponder/storage/PonderAnswerTransaction.php index 784b8a36e7..efbe020106 100644 --- a/src/applications/ponder/storage/PonderAnswerTransaction.php +++ b/src/applications/ponder/storage/PonderAnswerTransaction.php @@ -23,4 +23,11 @@ final class PonderAnswerTransaction return 'PonderAnswerTransactionType'; } + public function getMailTags() { + $tags = parent::getMailTags(); + $tags[] = PonderQuestionTransaction::MAILTAG_OTHER; + + return $tags; + } + } diff --git a/src/applications/ponder/xaction/PonderAnswerContentTransaction.php b/src/applications/ponder/xaction/PonderAnswerContentTransaction.php index 5d5c6ad157..2e55ef5fea 100644 --- a/src/applications/ponder/xaction/PonderAnswerContentTransaction.php +++ b/src/applications/ponder/xaction/PonderAnswerContentTransaction.php @@ -14,12 +14,29 @@ final class PonderAnswerContentTransaction } public function getTitle() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s added an answer.', + $this->renderAuthor()); + } + return pht( '%s updated the answer details.', $this->renderAuthor()); } public function getTitleForFeed() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s added %s.', + $this->renderAuthor(), + $this->renderObject()); + } + return pht( '%s updated the answer details for %s.', $this->renderAuthor(), From d114bc4a19bd31f746dcfd8d6bc0e840621ebd27 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 27 Jul 2017 15:17:20 -0700 Subject: [PATCH 353/543] Fix dark mode highlight in Timeline Summary: Moves these to `gentle.highlight` Test Plan: View a timeline that is collapsed, see correct color in Dark Mode. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18287 --- webroot/rsrc/css/phui/phui-timeline-view.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index b80a4e8d75..33e0f8ed0c 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -344,8 +344,8 @@ } .phui-timeline-older-transactions-are-hidden { - background: {$sh-yellowbackground}; - border: 1px solid {$sh-yellowborder}; + background: {$gentle.highlight}; + border: 1px solid {$gentle.highlight.border}; text-align: center; padding: 12px; color: {$darkgreytext}; From ee884db1f9358d263fb3f6bbaae5fda285ea38b3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 28 Jul 2017 10:37:53 -0700 Subject: [PATCH 354/543] Don't fatal when viewing tags pointing at commits we haven't imported/parsed yet Summary: In Diffusion, the "Tags" view may read commits which haven't imported or parsed yet, and thus don't have loadable objects. Most of this logic tests for `if ($commit)`, but the author part did not. Instead, don't render author information if `$commit` is not present. Test Plan: - Loaded tags view with commits present. - Faked `$commit = null;`, loaded tag view, got this instead of a fatal: {F5068432} Reviewers: chad, amckinley Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18290 --- .../diffusion/view/DiffusionTagListView.php | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index abab9003c3..0f8cbeae71 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -52,24 +52,12 @@ final class DiffusionTagListView extends DiffusionView { 'commit' => $tag->getCommitIdentifier(), )); - $author = null; - if ($commit && $commit->getAuthorPHID()) { - $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); - } else if ($commit && $commit->getCommitData()) { - $author = self::renderName($commit->getCommitData()->getAuthorName()); + if ($commit) { + $author = $this->renderAuthor($tag, $commit); } else { - $author = self::renderName($tag->getAuthor()); + $author = null; } - $committed = phabricator_datetime($commit->getEpoch(), $viewer); - $author_name = phutil_tag( - 'strong', - array( - 'class' => 'diffusion-history-author-name', - ), - $author); - $authored = pht('%s on %s.', $author_name, $committed); - $description = null; if ($tag->getType() == 'git/tag') { // In Git, a tag may be a "real" tag, or just a reference to a commit. @@ -139,16 +127,43 @@ final class DiffusionTagListView extends DiffusionView { ->setHref($tag_href) ->addAttribute(array($commit_tag)) ->addAttribute($description) - ->addAttribute($authored) ->setSideColumn(array( $build_view, $button_bar, )); + if ($author) { + $item->addAttribute($author); + } + $list->addItem($item); } return $list; } + private function renderAuthor( + DiffusionRepositoryTag $tag, + PhabricatorRepositoryCommit $commit) { + $viewer = $this->getViewer(); + + if ($commit->getAuthorPHID()) { + $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); + } else if ($commit->getCommitData()) { + $author = self::renderName($commit->getCommitData()->getAuthorName()); + } else { + $author = self::renderName($tag->getAuthor()); + } + + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $author_name = phutil_tag( + 'strong', + array( + 'class' => 'diffusion-history-author-name', + ), + $author); + + return pht('%s on %s.', $author_name, $committed); + } + } From ea0db5aa9d574d65ecec7701e18938597f44ecea Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 28 Jul 2017 15:11:08 -0700 Subject: [PATCH 355/543] Clean up dropdown carets Summary: Adds dropdown carets to buttons more universally that are actually dropdowns. Test Plan: Differential, Application Search, Diffusion. Mobile and Desktop. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18292 --- resources/celerity/map.php | 6 +-- ...PhabricatorApplicationSearchController.php | 2 +- src/view/phui/PHUIButtonView.php | 48 +++++++++---------- webroot/rsrc/css/phui/button/phui-button.css | 2 +- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 23e9733c10..86c6567f0e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd6046dd9', + 'core.pkg.css' => 'd9c9cfd0', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -127,7 +127,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', - 'rsrc/css/phui/button/phui-button.css' => '3a744520', + 'rsrc/css/phui/button/phui-button.css' => '3ca51caa', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', @@ -825,7 +825,7 @@ return array( 'phui-big-info-view-css' => 'd13afcde', 'phui-box-css' => '745e881d', 'phui-button-bar-css' => 'f1ff5494', - 'phui-button-css' => '3a744520', + 'phui-button-css' => '3ca51caa', 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index b0cab13e07..dbf964f9e4 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -554,7 +554,7 @@ final class PhabricatorApplicationSearchController return id(new PHUIButtonView()) ->setTag('a') ->setHref('#') - ->setText(pht('Use Results...')) + ->setText(pht('Use Results')) ->setIcon('fa-bars') ->setDropdownMenu($action_list) ->addClass('dropdown'); diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index 10ce5b5876..1aaa1cbaad 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -134,6 +134,7 @@ final class PHUIButtonView extends AphrontTagView { Javelin::initBehavior('phui-dropdown-menu'); $this->addSigil('phui-dropdown-menu'); + $this->setDropdown(true); $this->setMetadata($actions->getDropdownMenuMetadata()); return $this; @@ -223,32 +224,29 @@ final class PHUIButtonView extends AphrontTagView { protected function getTagContent() { - $icon = null; - $text = $this->text; - if ($this->icon) { - $icon = $this->icon; + $icon = $this->icon; + $text = null; + $subtext = null; - $subtext = null; - if ($this->subtext) { - $subtext = phutil_tag( - 'div', - array( - 'class' => 'phui-button-subtext', - ), - $this->subtext); - } + if ($this->subtext) { + $subtext = phutil_tag( + 'div', + array( + 'class' => 'phui-button-subtext', + ), + $this->subtext); + } - if ($this->text !== null) { - $text = phutil_tag( - 'div', - array( - 'class' => 'phui-button-text', - ), - array( - $text, - $subtext, - )); - } + if ($this->text !== null) { + $text = phutil_tag( + 'div', + array( + 'class' => 'phui-button-text', + ), + array( + $this->text, + $subtext, + )); } $caret = null; @@ -259,7 +257,7 @@ final class PHUIButtonView extends AphrontTagView { if ($this->iconFirst == true) { return array($icon, $text, $caret); } else { - return array($text, $icon); + return array($text, $icon, $caret); } } } diff --git a/webroot/rsrc/css/phui/button/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css index ac3ae2176d..8cdd818f12 100644 --- a/webroot/rsrc/css/phui/button/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -270,7 +270,7 @@ a.policy-control .phui-button-text { } .dropdown .phui-button-text { - margin-right: 16px; + margin-right: 8px; } .button.has-icon .phui-button-text { From 2538f6717836779b866041213e79115ef390f555 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 30 Jul 2017 11:36:24 -0700 Subject: [PATCH 356/543] Add some more builtin project images Summary: Moves over some of the icons we build for SAAS that can be useful for projects to. Also make builtin list dynamic. Test Plan: Edit a project image, select a cool sword. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18297 --- resources/builtin/projects/v3/contact.png | Bin 0 -> 6819 bytes resources/builtin/projects/v3/gears.png | Bin 0 -> 15975 bytes resources/builtin/projects/v3/gold.png | Bin 0 -> 7077 bytes resources/builtin/projects/v3/home.png | Bin 0 -> 8787 bytes resources/builtin/projects/v3/manage.png | Bin 0 -> 6277 bytes resources/builtin/projects/v3/silver.png | Bin 0 -> 6059 bytes resources/builtin/projects/v3/support.png | Bin 0 -> 4726 bytes resources/builtin/projects/v3/sword.png | Bin 0 -> 6791 bytes ...habricatorProjectEditPictureController.php | 38 ++++-------------- 9 files changed, 8 insertions(+), 30 deletions(-) create mode 100644 resources/builtin/projects/v3/contact.png create mode 100644 resources/builtin/projects/v3/gears.png create mode 100644 resources/builtin/projects/v3/gold.png create mode 100644 resources/builtin/projects/v3/home.png create mode 100644 resources/builtin/projects/v3/manage.png create mode 100644 resources/builtin/projects/v3/silver.png create mode 100644 resources/builtin/projects/v3/support.png create mode 100644 resources/builtin/projects/v3/sword.png diff --git a/resources/builtin/projects/v3/contact.png b/resources/builtin/projects/v3/contact.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3095dc3d4245ed9fbddcf1d87a4b1626ab4a09 GIT binary patch literal 6819 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�le4rDn&S3CSN`cOH*Ebs64kVbGM% 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Ν&aA~i5hbuH>_F$Fa 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/home.png b/resources/builtin/projects/v3/home.png new file mode 100644 index 0000000000000000000000000000000000000000..808d639f0dbfc39113f669fe24cf0b65c7307c85 GIT binary patch literal 8787 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 H82LW{G(Z)v literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/manage.png b/resources/builtin/projects/v3/manage.png new file mode 100644 index 0000000000000000000000000000000000000000..7804360a29456b72bb1f0b4bcc7ab4c91ea5c75f GIT binary patch literal 6277 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/support.png b/resources/builtin/projects/v3/support.png new file mode 100644 index 0000000000000000000000000000000000000000..88a97467089a421956f723901c9fa7159698e10d GIT binary patch literal 4726 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/sword.png b/resources/builtin/projects/v3/sword.png new file mode 100644 index 0000000000000000000000000000000000000000..30040633fd4b9674c92041184f0949e79352b118 GIT binary patch literal 6791 zcmZvBdpy&B^uOyAlS}4)3#kmbmn4@)E{QH?#%4Aml9KBrmql|Ym##{ri*2^aT~qFt zN~yM?B%g>(R2qf&zSi&Z`2F$QKP3Q_<)x zOip^D9zycGcJ;d8)!(0S{U!1iS@7BUuygA5Z@~xDzI3hIHf~tBp7v!|rOtm{{qOO$ z7w>*qua90i(-QfjP5A)SE*0h_Bzjh89isdFxL5o4jtxgdwAGWXZPJa7s&$=P;?)ty zRSW|eiX<`?LchGbgDops&rn$xm$!e4XDB)%e4h1ATxF=b;l$R3UXd>K>5-fe)9r~9 zt7j#G9QMqmKCoB+&jFIX^Jm$X$P9W1zd*>tL|P`Tsq4;|mZ8o*ajf{AjB$H~6VLXp ziRYzWQhlnYSJBw6Q6Ta6tk7-oA1RV?V%g+U$1UW`v=GLv{LszCsqr5oRSc(fvqaCe z&fJXBiwR~b^srqbj!>eJ{C*cM`Ipr)udOFU#V|y-1=eMBW!|{>iA421gG$%YftKL% z7#AyOWP!%p$5HX)n$}Uch3vU&Hwy=Q&Wv%BrAbbc{&s1V+DGJ)58V!ODslI9BU!*= zTmnd*`WLpEHJ(CmnHvdqa=8hkJo;gJ${>QBurQ6=1-2c&Bi-w{fj`q<_{)3yG4%j= zOhUvP%-h|SW0kcrQIF@w+76mKuP|#*oV1*|^=;EmNOoVsN74Kgv4aCYb^7ybN@uAj zPtj)z?=A(7SkxYk9emM0@i!4F5~K-t4|J>_Y?e?l)R|5&T9fWN(NXs2N>V`jiF;OV z=UpHC+9T69`Rv~5_xp7(FU@oE-8bQb`%!+?i_KZehB_h5VoRi%+O}xkqs9LCg=;Sk zM-(R6#AWPufiOkjjD2@vwu9)^L)!IQnPwYXi_Gzs2h1u~6OspUa@L)XUW-=1B?5ka z8?S%PX4d|0xzKXw4`D(^QO+p!m@MWL#VzR7(zlxYk}6i4`||k!&2^mIQ7E|v0X1)o zEQ?8?uBm3#TcJGDBqJH*8GTvIxyI@KUz3VHU18G=zHk?*>b{5EKXZv4YKA&1+tlmL zXKtz9igKdHp6VI6qUHB!@lR`~5)eW6i≶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 A&j0`b literal 0 HcmV?d00001 diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 97b7fe1792..e601d744c0 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -124,38 +124,16 @@ final class PhabricatorProjectEditPictureController } } - $builtins = array( - 'projects/v3/book.png', - 'projects/v3/bug.png', - 'projects/v3/calendar.png', - 'projects/v3/clipboard.png', - 'projects/v3/cloud.png', - 'projects/v3/creditcard.png', - 'projects/v3/database.png', - 'projects/v3/desktop.png', - 'projects/v3/experimental.png', - 'projects/v3/flag.png', - 'projects/v3/folder.png', - 'projects/v3/lightbulb.png', - 'projects/v3/lock.png', - 'projects/v3/mail.png', - 'projects/v3/marker.png', - 'projects/v3/mobile.png', - 'projects/v3/organization.png', - 'projects/v3/people.png', - 'projects/v3/piechart.png', - 'projects/v3/robot.png', - 'projects/v3/rocket.png', - 'projects/v3/servers.png', - 'projects/v3/sitemap.png', - 'projects/v3/tag.png', - 'projects/v3/trash.png', - 'projects/v3/truck.png', - 'projects/v3/umbrella.png', - ); + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/projects/v3/'; + + $builtins = id(new FileFinder($root)) + ->withType('f') + ->withFollowSymlinks(true) + ->find(); foreach ($builtins as $builtin) { - $file = PhabricatorFile::loadBuiltin($viewer, $builtin); + $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/'.$builtin); $images[$file->getPHID()] = array( 'uri' => $file->getBestURI(), 'tip' => pht('Builtin Image'), From 1ad369b3065c9aa47cbf43fac42972c8269b9c4e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 30 Jul 2017 11:56:27 -0700 Subject: [PATCH 357/543] Remove PHUIInfoPanelView Summary: We've never used this, and no current plans to. Test Plan: grep for use cases. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18298 --- resources/celerity/map.php | 2 - src/__phutil_library_map__.php | 4 - .../examples/PHUIInfoPanelExample.php | 138 ------------------ src/view/phui/PHUIInfoPanelView.php | 120 --------------- webroot/rsrc/css/phui/phui-info-panel.css | 50 ------- 5 files changed, 314 deletions(-) delete mode 100644 src/applications/uiexample/examples/PHUIInfoPanelExample.php delete mode 100644 src/view/phui/PHUIInfoPanelView.php delete mode 100644 webroot/rsrc/css/phui/phui-info-panel.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 86c6567f0e..88f2db1eec 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -163,7 +163,6 @@ return array( 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', - 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => 'e1b4ec37', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-left-right.css' => 'f60c67e7', @@ -852,7 +851,6 @@ return array( 'phui-icon-set-selector-css' => '87db8fee', 'phui-icon-view-css' => '5c4a5de6', 'phui-image-mask-css' => 'a8498f9c', - 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => 'e1b4ec37', 'phui-inline-comment-view-css' => '65ae3bc2', 'phui-invisible-character-view-css' => '6993d9f0', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1527091575..4488bacd9a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1770,8 +1770,6 @@ phutil_register_library_map(array( 'PHUIImageMaskExample' => 'applications/uiexample/examples/PHUIImageMaskExample.php', 'PHUIImageMaskView' => 'view/phui/PHUIImageMaskView.php', 'PHUIInfoExample' => 'applications/uiexample/examples/PHUIInfoExample.php', - 'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php', - 'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php', 'PHUIInfoView' => 'view/form/PHUIInfoView.php', 'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php', 'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php', @@ -6928,8 +6926,6 @@ phutil_register_library_map(array( 'PHUIImageMaskExample' => 'PhabricatorUIExample', 'PHUIImageMaskView' => 'AphrontTagView', 'PHUIInfoExample' => 'PhabricatorUIExample', - 'PHUIInfoPanelExample' => 'PhabricatorUIExample', - 'PHUIInfoPanelView' => 'AphrontView', 'PHUIInfoView' => 'AphrontTagView', 'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase', 'PHUIInvisibleCharacterView' => 'AphrontView', diff --git a/src/applications/uiexample/examples/PHUIInfoPanelExample.php b/src/applications/uiexample/examples/PHUIInfoPanelExample.php deleted file mode 100644 index 74ed18f37c..0000000000 --- a/src/applications/uiexample/examples/PHUIInfoPanelExample.php +++ /dev/null @@ -1,138 +0,0 @@ -setHeader(pht('Conpherence')); - - $header2 = id(new PHUIHeaderView()) - ->setHeader(pht('Diffusion')); - - $header3 = id(new PHUIHeaderView()) - ->setHeader(pht('Backend Ops Projects')); - - $header4 = id(new PHUIHeaderView()) - ->setHeader(pht('Revamp Liberty')) - ->setSubHeader(pht('For great justice')) - ->setImage( - celerity_get_resource_uri('/rsrc/image/people/washington.png')); - - $header5 = id(new PHUIHeaderView()) - ->setHeader(pht('Phacility Redesign')) - ->setSubHeader(pht('Move them pixels')) - ->setImage( - celerity_get_resource_uri('/rsrc/image/people/harding.png')); - - $header6 = id(new PHUIHeaderView()) - ->setHeader(pht('Python Phlux')) - ->setSubHeader(pht('No. Sleep. Till Brooklyn.')) - ->setImage( - celerity_get_resource_uri('/rsrc/image/people/taft.png')); - - $column1 = id(new PHUIInfoPanelView()) - ->setHeader($header1) - ->setColumns(3) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(12, pht('Low')) - ->addInfoBlock(123, pht('Wishlist')); - - $column2 = id(new PHUIInfoPanelView()) - ->setHeader($header2) - ->setColumns(3) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(12, pht('Low')) - ->addInfoBlock(123, pht('Wishlist')); - - $column3 = id(new PHUIInfoPanelView()) - ->setHeader($header3) - ->setColumns(3) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(12, pht('Low')) - ->addInfoBlock(123, pht('Wishlist')); - - $column4 = id(new PHUIInfoPanelView()) - ->setHeader($header4) - ->setColumns(3) - ->setProgress(90) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(0, pht('Wishlist')); - - $column5 = id(new PHUIInfoPanelView()) - ->setHeader($header5) - ->setColumns(2) - ->setProgress(25) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')); - - $column6 = id(new PHUIInfoPanelView()) - ->setHeader($header6) - ->setColumns(2) - ->setProgress(50) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')); - - $layout1 = id(new AphrontMultiColumnView()) - ->addColumn($column1) - ->addColumn($column2) - ->addColumn($column3) - ->setFluidLayout(true); - - $layout2 = id(new AphrontMultiColumnView()) - ->addColumn($column4) - ->addColumn($column5) - ->addColumn($column6) - ->setFluidLayout(true); - - - $head1 = id(new PHUIHeaderView()) - ->setHeader(pht('Flagged')); - - $head2 = id(new PHUIHeaderView()) - ->setHeader(pht('Sprints')); - - - $wrap1 = id(new PHUIBoxView()) - ->appendChild($layout1) - ->addMargin(PHUI::MARGIN_LARGE_BOTTOM); - - $wrap2 = id(new PHUIBoxView()) - ->appendChild($layout2) - ->addMargin(PHUI::MARGIN_LARGE_BOTTOM); - - - return phutil_tag( - 'div', - array(), - array( - $head1, - $wrap1, - $head2, - $wrap2, - )); - } -} diff --git a/src/view/phui/PHUIInfoPanelView.php b/src/view/phui/PHUIInfoPanelView.php deleted file mode 100644 index 91f9d2b8e4..0000000000 --- a/src/view/phui/PHUIInfoPanelView.php +++ /dev/null @@ -1,120 +0,0 @@ -header = $header; - return $this; - } - - public function setProgress($progress) { - $this->progress = $progress; - return $this; - } - - public function setColumns($columns) { - $this->columns = $columns; - return $this; - } - - public function addInfoblock($num, $text) { - $this->infoblock[] = array($num, $text); - return $this; - } - - public function render() { - require_celerity_resource('phui-info-panel-css'); - - $trs = array(); - $rows = ceil(count($this->infoblock) / $this->columns); - for ($i = 0; $i < $rows; $i++) { - $tds = array(); - $ii = 1; - foreach ($this->infoblock as $key => $cell) { - $tds[] = $this->renderCell($cell); - unset($this->infoblock[$key]); - $ii++; - if ($ii > $this->columns) { - break; - } - } - $trs[] = phutil_tag( - 'tr', - array( - 'class' => 'phui-info-panel-table-row', - ), - $tds); - } - - $table = phutil_tag( - 'table', - array( - 'class' => 'phui-info-panel-table', - ), - $trs); - - $table = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_MEDIUM) - ->appendChild($table); - - $progress = null; - if ($this->progress) { - $progress = phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel-progress', - 'style' => 'width: '.(int)$this->progress.'%;', - ), - null); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeader($this->header) - ->appendChild($table) - ->appendChild($progress); - - return phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel', - ), - $box); - } - - private function renderCell($cell) { - $number = phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel-number', - ), - $cell[0]); - - $text = phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel-text', - ), - $cell[1]); - - return phutil_tag( - 'td', - array( - 'class' => 'phui-info-panel-table-cell', - 'align' => 'center', - 'width' => floor(100 / $this->columns).'%', - ), - array( - $number, - $text, - )); - } -} diff --git a/webroot/rsrc/css/phui/phui-info-panel.css b/webroot/rsrc/css/phui/phui-info-panel.css deleted file mode 100644 index a182292a89..0000000000 --- a/webroot/rsrc/css/phui/phui-info-panel.css +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @provides phui-info-panel-css - */ - -.phui-info-panel .phui-object-box .phui-header-has-image { - padding: 2px 0 0 2px; -} - -.phui-info-panel .phui-object-box .phui-header-image { - margin: 0 8px 0 0; -} - -.phui-info-panel-table { - border-collapse: collapse; - border-style: hidden; - width: 100%; -} - -.phui-info-panel-table td, -.phui-info-panel-table th { - border: 1px solid {$thinblueborder}; -} - -.phui-info-panel-table-cell { - padding: 8px; -} - -.phui-info-panel-number, -.phui-info-panel-number a { - font-size: 30px; - font-weight: bold; - color: {$lightgreytext}; - -webkit-font-smoothing: antialiased; -} - -.phui-info-panel-text, -.phui-info-panel-text a { - color: {$lightgreytext}; -} - -.phui-info-panel-number a:hover, -.phui-info-panel-text a:hover { - color: {$greytext}; - text-decoration: none; -} - -.phui-info-panel-progress { - background: {$green}; - height: 6px; -} From ddd7cbb698cf63602c8a74f1fc1e5450e8a7d0c3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 30 Jul 2017 12:21:36 -0700 Subject: [PATCH 358/543] Add setImage to PHUIActionPanelView Summary: Additonal option to use newly made images in these views. Test Plan: Built an example in UIExamples. {F5071682} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18299 --- resources/celerity/map.php | 4 ++-- .../examples/PHUIActionPanelExample.php | 7 ++++++- src/view/phui/PHUIActionPanelView.php | 21 +++++++++++++++++++ webroot/rsrc/css/phui/phui-action-panel.css | 6 ++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 88f2db1eec..2437a1e4c2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -139,7 +139,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => '6ee16164', - 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', + 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'd13afcde', @@ -818,7 +818,7 @@ return array( 'phortune-invoice-css' => '476055e2', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '4282e4ad', - 'phui-action-panel-css' => '91c7b835', + 'phui-action-panel-css' => 'b4798122', 'phui-badge-view-css' => '22c0cf4f', 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'd13afcde', diff --git a/src/applications/uiexample/examples/PHUIActionPanelExample.php b/src/applications/uiexample/examples/PHUIActionPanelExample.php index 9294dcbea6..e71f2ce846 100644 --- a/src/applications/uiexample/examples/PHUIActionPanelExample.php +++ b/src/applications/uiexample/examples/PHUIActionPanelExample.php @@ -11,10 +11,15 @@ final class PHUIActionPanelExample extends PhabricatorUIExample { } public function renderExample() { + $viewer = $this->getRequest()->getUser(); $view = id(new AphrontMultiColumnView()) ->setFluidLayout(true); + $credit = PhabricatorFile::loadBuiltin( + $viewer, 'projects/v3/creditcard.png'); + $image = $credit->getBestURI(); + /* Action Panels */ $panel1 = id(new PHUIActionPanelView()) ->setIcon('fa-book') @@ -53,7 +58,7 @@ final class PHUIActionPanelExample extends PhabricatorUIExample { /* Action Panels */ $panel1 = id(new PHUIActionPanelView()) - ->setIcon('fa-credit-card') + ->setImage($image) ->setHeader(pht('Account Balance')) ->setHref('#') ->setSubHeader(pht('You were last billed $2,245.12 on Dec 12, 2014.')) diff --git a/src/view/phui/PHUIActionPanelView.php b/src/view/phui/PHUIActionPanelView.php index f5f74b2807..8ced0a642b 100644 --- a/src/view/phui/PHUIActionPanelView.php +++ b/src/view/phui/PHUIActionPanelView.php @@ -4,6 +4,7 @@ final class PHUIActionPanelView extends AphrontTagView { private $href; private $fontIcon; + private $image; private $header; private $subHeader; private $bigText; @@ -29,6 +30,11 @@ final class PHUIActionPanelView extends AphrontTagView { return $this; } + public function setImage($image) { + $this->image = $image; + return $this; + } + public function setBigText($text) { $this->bigText = $text; return $this; @@ -89,6 +95,21 @@ final class PHUIActionPanelView extends AphrontTagView { $fonticon); } + if ($this->image) { + $image = phutil_tag( + 'img', + array( + 'class' => 'phui-action-panel-image', + 'src' => $this->image, + )); + $icon = phutil_tag( + 'span', + array( + 'class' => 'phui-action-panel-icon', + ), + $image); + } + $header = null; if ($this->header) { $header = phutil_tag( diff --git a/webroot/rsrc/css/phui/phui-action-panel.css b/webroot/rsrc/css/phui/phui-action-panel.css index d52a1cbaae..63bd351807 100644 --- a/webroot/rsrc/css/phui/phui-action-panel.css +++ b/webroot/rsrc/css/phui/phui-action-panel.css @@ -47,6 +47,12 @@ display: table-cell; } +.phui-action-panel-image { + width: 48px; + height: 48px; + margin: 0 auto; +} + .phui-action-panel-icon a { display: block; } From 1b63a1bd437c2365c5183e11197fca6c9edb4bca Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 30 Jul 2017 16:49:04 -0700 Subject: [PATCH 359/543] Some more project images Summary: Felt like tinkering with Illustrator. Test Plan: Edit Picture, pick new image. Reviewers: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18301 --- resources/builtin/projects/v3/basic-book.png | Bin 0 -> 3221 bytes resources/builtin/projects/v3/one-server.png | Bin 0 -> 4128 bytes resources/builtin/projects/v3/three-servers.png | Bin 0 -> 6623 bytes resources/builtin/projects/v3/two-servers.png | Bin 0 -> 5460 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/builtin/projects/v3/basic-book.png create mode 100644 resources/builtin/projects/v3/one-server.png create mode 100644 resources/builtin/projects/v3/three-servers.png create mode 100644 resources/builtin/projects/v3/two-servers.png diff --git a/resources/builtin/projects/v3/basic-book.png b/resources/builtin/projects/v3/basic-book.png new file mode 100644 index 0000000000000000000000000000000000000000..b2a6d99415ed219817485e281eca4efcbd837898 GIT binary patch literal 3221 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 Ld#lG5UN`;)BiG

3R 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 M(J)j;s6u1@3yO#=d;kCd literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/two-servers.png b/resources/builtin/projects/v3/two-servers.png new file mode 100644 index 0000000000000000000000000000000000000000..d5d408d9ad6d3cf8741c15bbb5882870c482d5e9 GIT binary patch literal 5460 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 Kmy3`t;r{}kg#=yz literal 0 HcmV?d00001 From a546b029b0bc84678fc9ffc2a276281cc156fa4b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 31 Jul 2017 09:40:29 -0700 Subject: [PATCH 360/543] Censor credentials possibly present in Git remote URIs when pulls fail because of a URI mismatch Summary: Fixes T12945. Test Plan: Mostly faked this, got a censored error: ``` $ ./bin/repository update R38 [2017-07-31 19:40:13] EXCEPTION: (Exception) Working copy at "/Users/epriestley/dev/core/repo/local/38/" has a mismatched origin URI, "https://********@example.com/". The expected origin URI is "https://github.com/phacility/libphutil.git". Fix your configuration, or set the remote URI correctly. To avoid breaking anything, Phabricator will not automatically fix this. at [/src/applications/repository/engine/PhabricatorRepositoryEngine.php:186] ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T12945 Differential Revision: https://secure.phabricator.com/D18304 --- .../repository/engine/PhabricatorRepositoryEngine.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index 445357e783..244ed0cd45 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -135,6 +135,11 @@ abstract class PhabricatorRepositoryEngine extends Phobject { $exists = true; } + // These URIs may have plaintext HTTP credentials. If they do, censor + // them for display. See T12945. + $display_remote = phutil_censor_credentials($remote_uri); + $display_expect = phutil_censor_credentials($expect_remote); + if (!$valid) { if (!$exists) { // If there's no "origin" remote, just create it regardless of how @@ -172,8 +177,8 @@ abstract class PhabricatorRepositoryEngine extends Phobject { 'set the remote URI correctly. To avoid breaking anything, '. 'Phabricator will not automatically fix this.', $repository->getLocalPath(), - $remote_uri, - $expect_remote); + $display_remote, + $display_expect); throw new Exception($message); } } From f48f2dae9f39bccd61ea4868c6d5d95dc0ec68e0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 31 Jul 2017 12:20:58 -0700 Subject: [PATCH 361/543] Move Phabricator to use PhutilBinaryAnalyzer and show binary versions Summary: Fixes T12942. - Adds binary version and path information to {nav Config > Version Information}. - Replaces old code all over the place with new consolidated code. Test Plan: {F5073531} Also faked some cases of missing binaries, bad versions, etc. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12942 Differential Revision: https://secure.phabricator.com/D18306 --- src/__phutil_library_map__.php | 4 -- .../check/PhabricatorBinariesSetupCheck.php | 9 ++--- .../PhabricatorConfigVersionController.php | 23 +++++++++++ .../DiffusionLowLevelMercurialPathsQuery.php | 9 +++-- ...fusionLowLevelMercurialPathsQueryTests.php | 31 -------------- .../PhabricatorRepositoryVersion.php | 40 ------------------- .../PhabricatorRepositoryPullEngine.php | 6 +-- 7 files changed, 34 insertions(+), 88 deletions(-) delete mode 100644 src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php delete mode 100644 src/applications/repository/constants/PhabricatorRepositoryVersion.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4488bacd9a..6427e95432 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -747,7 +747,6 @@ phutil_register_library_map(array( 'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php', 'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php', 'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php', - 'DiffusionLowLevelMercurialPathsQueryTests' => 'applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php', 'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php', 'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php', 'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php', @@ -3857,7 +3856,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryURITransaction' => 'applications/repository/storage/PhabricatorRepositoryURITransaction.php', 'PhabricatorRepositoryURITransactionQuery' => 'applications/repository/query/PhabricatorRepositoryURITransactionQuery.php', 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', - 'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php', 'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php', 'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php', 'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php', @@ -5741,7 +5739,6 @@ phutil_register_library_map(array( 'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery', - 'DiffusionLowLevelMercurialPathsQueryTests' => 'PhabricatorTestCase', 'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelQuery' => 'Phobject', 'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery', @@ -9387,7 +9384,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryURITransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryURITransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', - 'PhabricatorRepositoryVersion' => 'Phobject', 'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO', 'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler', 'PhabricatorResourceSite' => 'PhabricatorSite', diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php index 0d577b5297..cf3c87f480 100644 --- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php +++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php @@ -99,12 +99,12 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck { continue; } - $version = null; + $version = PhutilBinaryAnalyzer::getForBinary($binary) + ->getBinaryVersion(); + switch ($vcs['versionControlSystem']) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $bad_versions = array(); - list($err, $stdout, $stderr) = exec_manual('git --version'); - $version = trim(substr($stdout, strlen('git version '))); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $bad_versions = array( @@ -117,8 +117,6 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck { 'for files added in rN (Subversion issue #2873), fixed in 1.7.2.', 'svn diff -c N'), ); - list($err, $stdout, $stderr) = exec_manual('svn --version --quiet'); - $version = trim($stdout); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $bad_versions = array( @@ -134,7 +132,6 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck { 'in 2.2.1. Pushing fails with this version as well; see %s.', 'T3046#54922'), ); - $version = PhabricatorRepositoryVersion::getMercurialVersion(); break; } diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php index 8f43192b3b..ca638051e4 100644 --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -64,6 +64,29 @@ final class PhabricatorConfigVersionController $version_from_file); } + $binaries = PhutilBinaryAnalyzer::getAllBinaries(); + foreach ($binaries as $binary) { + if (!$binary->isBinaryAvailable()) { + $binary_info = pht('Not Available'); + } else { + $version = $binary->getBinaryVersion(); + $path = $binary->getBinaryPath(); + if ($path === null && $version === null) { + $binary_info = pht('-'); + } else if ($path === null) { + $binary_info = $version; + } else if ($version === null) { + $binary_info = pht('- at %s', $path); + } else { + $binary_info = pht('%s at %s', $version, $path); + } + } + + $version_property_list->addProperty( + $binary->getBinaryName(), + $binary_info); + } + return $version_property_list; } diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php index 12b9e661d7..f4e27db670 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php @@ -24,11 +24,12 @@ final class DiffusionLowLevelMercurialPathsQuery $path = $this->path; $commit = $this->commit; - $hg_paths_command = 'locate --print0 --rev %s -I %s'; - $hg_version = PhabricatorRepositoryVersion::getMercurialVersion(); - if (PhabricatorRepositoryVersion::isMercurialFilesCommandAvailable( - $hg_version)) { + $has_files = PhutilBinaryAnalyzer::getForBinary('hg') + ->isMercurialFilesCommandAvailable(); + if ($has_files) { $hg_paths_command = 'files --print0 --rev %s -I %s'; + } else { + $hg_paths_command = 'locate --print0 --rev %s -I %s'; } $match_against = trim($path, '/'); diff --git a/src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php b/src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php deleted file mode 100644 index 075ca2f1a5..0000000000 --- a/src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php +++ /dev/null @@ -1,31 +0,0 @@ - pht('Versions which should not use `files`'), - 'versions' => array('2.6.2', '2.9', '3.1'), - 'match' => false, - ), - - array( - 'name' => pht('Versions which should use `files`'), - 'versions' => array('3.2', '3.3', '3.5.2'), - 'match' => true, - ), - ); - - foreach ($cases as $case) { - foreach ($case['versions'] as $version) { - $actual = PhabricatorRepositoryVersion - ::isMercurialFilesCommandAvailable($version); - $expect = $case['match']; - $this->assertEqual($expect, $actual, $case['name']); - } - } - } - -} diff --git a/src/applications/repository/constants/PhabricatorRepositoryVersion.php b/src/applications/repository/constants/PhabricatorRepositoryVersion.php deleted file mode 100644 index 5f722fa40a..0000000000 --- a/src/applications/repository/constants/PhabricatorRepositoryVersion.php +++ /dev/null @@ -1,40 +0,0 @@ -='); - } - -} diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index b144ecfd96..32e99e619d 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -485,8 +485,8 @@ final class PhabricatorRepositoryPullEngine // On vulnerable versions of Mercurial, we refuse to clone remotes which // contain characters which may be interpreted by the shell. - $hg_version = PhabricatorRepositoryVersion::getMercurialVersion(); - $is_vulnerable = version_compare($hg_version, '3.2.4', '<'); + $hg_binary = PhutilBinaryAnalyzer::getForBinary('hg'); + $is_vulnerable = $hg_binary->isMercurialVulnerableToInjection(); if ($is_vulnerable) { $cleartext = $remote->openEnvelope(); // The use of "%R" here is an attempt to limit collateral damage @@ -501,7 +501,7 @@ final class PhabricatorRepositoryPullEngine 'command injection security vulnerability. The remote URI for '. 'this repository (%s) is potentially unsafe. Upgrade Mercurial '. 'to at least 3.2.4 to clone it.', - $hg_version, + $hg_binary->getBinaryVersion(), $repository->getMonogram())); } } From 3f158aa71b14d6d33515e75188b257437ac93eac Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 1 Aug 2017 07:54:18 -0700 Subject: [PATCH 362/543] More project v3 images Summary: These seem reasonably re-usable in the upstream. Test Plan: Edit Project Image Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18312 --- resources/builtin/projects/v3/archive.png | Bin 0 -> 4491 bytes resources/builtin/projects/v3/discussion.png | Bin 0 -> 10059 bytes resources/builtin/projects/v3/download.png | Bin 0 -> 9392 bytes resources/builtin/projects/v3/library.png | Bin 0 -> 5783 bytes resources/builtin/projects/v3/police-badge.png | Bin 0 -> 12753 bytes .../builtin/projects/v3/purchase-order.png | Bin 0 -> 5189 bytes .../projects/v3/server-documentation.png | Bin 0 -> 5590 bytes resources/builtin/projects/v3/shield.png | Bin 0 -> 9232 bytes resources/builtin/projects/v3/upload.png | Bin 0 -> 9463 bytes resources/builtin/projects/v3/wand.png | Bin 0 -> 8717 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/builtin/projects/v3/archive.png create mode 100644 resources/builtin/projects/v3/discussion.png create mode 100644 resources/builtin/projects/v3/download.png create mode 100644 resources/builtin/projects/v3/library.png create mode 100644 resources/builtin/projects/v3/police-badge.png create mode 100644 resources/builtin/projects/v3/purchase-order.png create mode 100644 resources/builtin/projects/v3/server-documentation.png create mode 100644 resources/builtin/projects/v3/shield.png create mode 100644 resources/builtin/projects/v3/upload.png create mode 100644 resources/builtin/projects/v3/wand.png diff --git a/resources/builtin/projects/v3/archive.png b/resources/builtin/projects/v3/archive.png new file mode 100644 index 0000000000000000000000000000000000000000..77ff79e555d1a4a842bec214719e568dca944988 GIT binary patch literal 4491 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/download.png b/resources/builtin/projects/v3/download.png new file mode 100644 index 0000000000000000000000000000000000000000..f086222212e4fc0a043df574b32c93d6f4dead23 GIT binary patch literal 9392 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 ofd8k@!v8`CdLrusXU4q+KJpPQ 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 F`d`DLO*sGn literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/police-badge.png b/resources/builtin/projects/v3/police-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..8b729bc35a63be9a369442ba34eae50894e38f50 GIT binary patch literal 12753 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/purchase-order.png b/resources/builtin/projects/v3/purchase-order.png new file mode 100644 index 0000000000000000000000000000000000000000..906d72a5297c4a209d088c745f0d82ce190703d8 GIT binary patch literal 5189 zcmb7IdpOiv_eX<4Vk%QECrz%MnL#2Ue*lu9#CNYEHkDzhN{sqwTgW!j7os<>h z_FzE+6K%)6vpX+t`nJe`^k8>&DpmwR@Lde?rgwxpIQ>h0im-k!dTJDD+k5X?f9DCgT+0 zbEq=6=dhLG0$UL}Yh_XU#P$*~m)PLT39J?rw;>{S`b7jMO3?zw3-odK&^_)wnP zB%aU=NjI4Y525(htIE9U+9$nT;&*h}*$MTH&Ti9#2bWdSrqm#aeQPH@1d^z<*`}*6 z=zVmQ!SU+nWo`3k%UNIr9V*;lLnUhHiy)?4(X? z&PaMK1S*pV8u*>1S{N6(Xnh?!EgV1B?X~)a#1R@9&b!=tY9p=lL)MU5s(B|tY5utf zIJejG%I6k^QgheBRGM1rOOEu~sXmB>=^dgOJ`TM#c|;Rx(olI*eCf=WQ^IB6dzfj% zW9s-m6;uxP$WE;YvVO5#0!0(X2}F*lH^wV|6cJ|?{6trjTh(UOQGzwBRy3(0H4b%4AC4YZ? z|IE9}y4^ccu49vF@^?xmI~x=kOwniv;w&o!5yQ%hh|$I~#jqas{w?Kr{B+>ZpCt1V z*iZ1C{Ym0a{BK|a@csquJ>WmaWoWyJjcQ|`_=!5uIniJy^ZwE;1AFbPD=&o%hc_%I zzv8MNR#R?b>*uRRkEd&PSFQ%g8REg_3|0fDfyVG6dx4X<-_uvJFdU)RBTO9%yoM@C znE9~#Xb7&JGdC3n&Wg``^6M|ZC^A%qm)wJQ3E^D=98i*eHJT=uE@>&KLY|-5#;IK# zs#c5&ZBEDq)~$2ork(+3HW*Av=}vz8V#fE>X2V(p2AoZw1d4m>A>R8oA+M8Cu zO+XBAUXK&F8dtASa)EZTT*?p?xVgU8U{Oq<8PF;1*{coUNg z+s*yBHv_$n04sZJM<&H8mFJ874X*C*JUlgQ2agRzuFhCV7Fp{_#VZvTzm)PAqM&Y6 zgtrC=vTg$uP^K`)P@N9Aa&XwIDnEhsGjyym@rIiJA9Da8Wx4+k^L~czEUH81KX{1T ztNf#>=SSB-EJfy({|jA!RASWah_wLwzN+wbt!k_vMjW*$f)XgNTiK5_s&bUWZ3!MSQvZCgfpV~yH&7>|KJzpgfR%}~!A z&P1VCO`2M)nsr3z{A-qqvY`!W2dty=Jcjr5Uq-q&);~U!kDTV|tdMr|cnocn#U75x zLx;m%lx|hrtd&!gJ&d;gkoI@7t*`A|H~2bt=7hL2Fa~wbzT4(`WfZt+B6eo0TLwTC z#PvWS6m!*e&cJ!EVfHQIE)kdy2F^o&&l>%p074{_Hxd2t&+4T=?OXv zJWzH5pMz8|maR>`u2@@EaoiYx6_1`wPPUaTZ0)g(7Bb{3WoH3(seuQVi+pJakMw*W z=H0c6cADx1LY|&E3c4B-X}E;2XIyhn^D9R*@h!S|w(@-FCK2xySUlb;^O;r}y@tKy zArmp>VlPixJw_r3(asq{-zyAuY&W%V40@x}{2Yq|tQb4yap=%kH0@I&5FykCzI76E zsHuS)G#U@6OAu5cTAF`$vf-x2GmT&Mjl!k%0yt&qgZm zt`yU|G=6zv-WT7lvFf?yNraHUA1efR?u^oFN`tH~&3`f~IVRuUWag;dxOiUzHE|Z@ z7N!Kwj92k0pP^tO7P0ae{DrFPPs#WHfZ&XGzR}4~w&;S${i-~|aVVu#6F-5yuXmTM zF^}r7w_9Y)>eJRRK^uBBCnd(@o#TjNa{pOmz3(N9mm>F`!vT*F(we^&oOsKRTkno} z@yRM&M@aaP`8A7cvE#(cpX=6eh#xwNVDoI%%r(+tL|(5Ie63<|?ns}okK5z>c%J#X zH%7tkHx@u%N;cm_Q9Td@FXDsWxVtopT@c{RIh0-sWU%X%0AQ#^8!uyle>Wi!M_dJ_ zR-vS~-c%{>WTdGs_CP*HiZP;lvl%Enp8KMF;5J>#EtQtH>+B{S05(HO8jfkYkqxq;ejQ}?r}RJv z)6%iSJ@u$JA!jWudXBiX{*doCJKw7t3p@&fV81_@Azp`pjTJY|ICmM|Ez^Dp27)3Y z)i71nX*OXCCzN*vCxHSzKs#it1F2cxtjCM0$4GeYw`mw~N5;32n=Z`5qI&Et{r17G zZl+qmo}z2g4cWb1r2N8HpXa2J|JEt+T$_@ z)P~2rtwi9N^Sgl~l*N`beChkW!I(M9YJ^-i9!=}$UWDDN<5NC-9*yA{@5B z2l$JUvtM9p%R9OX(#!0Ng%oC3xV-^fQE-#hy*z%FIub=A#b-y+Gu9)GzCi&At zH32vqm?qLqEsm=!4jT}bk{;cNMw7mtytC%Io8a4ntY)~B6Bu6GzD-h z3ivpXxp4uu4&wbsw_WdoPSrC&KN%gGR>{LiuN_ddny`lh)6sPGXaX!7y>+OG| z8b~akR*p_P@ZxxPTJsY5k<|w`>TJjRc@#iwR@6({46g?8e!va+TI8pM@-X93{?Cku z%id|Hl8hFTH)4Fk!MH|NsIr)bE3(5qIQ_B_XnMx;Ru2J03_Vci5sU@`v*-8Apg>*? zFdF!@$^_OG*kv&HHQlSWzCQV^$>?hYmLIsXgYD{Z6KmlI2k_Ue%L)*@j~TcM&BJfT z8m0-Yi$$Zw1pX;v{8JCJ-Gkf~f;(-sRzan#AtOP&IOB6DWN6Onf_JMlGYC%pr;;&u zJk&i*1-|_=3MDZ1-mPV_-(Uc z^|3+_De2Qub06PIGyGFGPy8Z(Z92u0I)3_9eC7gYO^AhyKAS=BvnKFht0DK`HBHCR zHLYJ15~YiLy%x}Af4(DSCk@oq;0x##S$P7uUxD(GRzdx`EOvCEtg)rpIHIxU4+twVxh>>b221R=TV3o<)Zp zajH}-Dl}A|wZhfsa z`}aRQM>iRU+__1oj~lp=e49^=c3=OZ?A~2b-_lvp2r@Z`ilrliV)!oPI{V}IDE_Ec zoT1L>D%EaSt4mh|`Put!N&(j9jE%r@Aa3FnWDw&N^eQ*1cO8u>1!{f-C7$Tg0ykb{ z+r6j)nz(#Zg0-u%G=5-gN9DcJM|ZCpO#5b!Ay2NSylCz?P#{thSjg^_Ie4am@!;)u zn=FK2E8qHcy4B#WfqJL4E5GdkgS@MckL>4#=gjLn<1QM@St$_LsN^D9-_s$!S8ln% zQSos2Ou-7i+L69jsa{qWc~W9dfn#uA+5LXb(|xRf4uvM2tB_`KclwPf@)C5?tUD;` zIycXyhS8J(lJP_}Xnx~p&GZeA6^UoXi}68Qsy68CVg?)=0jcQ9kRlG@BGb?q^DJ1< zU|p&O6eDgm7Zg=v#AiF{%^ARFF>)+A)w|{4K*Ku~AhL}w(l2554mSKTWYV=>Rk6?N ztQ`-V!OThq=S4>ORKK?9fJNGXcG{Dg;@9P!bs1(tHDQyR>M;%YC|Ru$+x{e}KgNoZ z=_gcJ{VSSerlLjoh+pztY5BQcTq{eHNnYK*&J{is1Hl`kVec196Uj)P=%M_^#RqUs z25qWAlX7j6z`H9abBsBB15RQSCAcf1n zED+Ai5qf_;HuTUBhWfF4e`Pp5WY*~lV_~XTN5^KlXCOCXWXsB0<$Eh{F1y?kgr}%AhVWSkuyZkip-j98CeZ)h*^$-^tb06|~S75BCVpqYp)HT;}zk!<8nb=_45x1y|dp|NNN|v<-eq422 zTob?Tw&lI0KH{Qc&A0rlxlGd5a4s=r*d)SQvy7v>!SHthsUTcw=kR73afhI6e-kQ= zko6`D=GnJp*?3|)?HocPoR8YDv3oL}EP&6YK&{ekGMHd36M#eqU;-RT9=h9y}l z-n>pm3t4T7DAXU>uQ1Y5nXRY#%Bg^t3Sp0)UI>M&uqqh-&bs9NG1sNc6Jydv%t66U zxk;rwG-VTVmhl$Kt6dOt^26Y!k@Bh)ium_=c792sC>d|zv04ZN2|+f-AyR2Y&$U9A zKfRLTByf)zU#=|PQi33(9H3aURRpIN+4y}~9oZj+n%Q)hSB>5%fBkoJf&OhMbZ;B0 z6*_nCq;8b;?;q1=cAHz|^DL%dGTuk?4C}Mhz4`K57(>^*N1UC1_+W5WRl>x5i4@hN z9Bg6RKQZix=H_PG4DK-_&g^b;N-F6%2uj3(RYU9_GzSOvlX?$vGpl(TpaJbbs**LJIc}}C;?{Hc6(9NZfQYi#(aT8>=llQ2n5(s+I-FNI=RNtI! zD>7kJfGP+i3(hl%tQaI{V7v z*b(!)t1?{#YO|@{102{@7T*|HF)6a#AuQdMOzOGM-V?T{J{W0pCry}0!C%eh2SR6( zPTa!b@E1?O3gd_B8)OGD^8NvqDE%P6BOs{W#qzhs*RXCdHuJkA*$vK9KA2+nJYgck zZ4Ju}6`};Ikl+xcf16RN`S*$P3mgRQyaob82Zef7W0^`T2&%*~L58T1B4DK9sLGhP z5k~M*Vvjn&KzlJ+vJ+NK-fOTt=8)9Acx01QX zdU#PhEAezu5KaFPlk)UGoN>mvtY4J_;2_$m0f(E7!5tJEj+m~-=AuS(PY4Vb!R4Ed zn{SgwxSn%Z^+u(N`gS(<9;Z^*8AHR-qd1SVXaC%;fXW~|6#!pJjd#@bevh2Sv)~}F zE2#$p8W=a9eH?B+qhPr)Z1*dx(;yygkgA%!L=6OhG%kOjHb?8*&Ds)M@@C|kdFaVl z{FVlU(f7gHY!p{8QHtD%s^Eo=VZ?`Q<#|d?QAq?S)a6GG)6>OXfUQu2NgOL=g3dT=~M03@6 zPF!v(!1-{UvY)jjlL?pPC|z`vqthvG%otc1dwuHTJ3-+wj4ui2@SaMEu85&WTnh7b z+==nJ88iuv232^EeY)kOtvz73t+4^IpE-8W+VQy{tCz0eq*X20^TGf-=lNV#OtNG3 z7|)72T?Rx8!V428U7T;VJU-7ayP!Jms)&djHTik83GYFeW9;+^( zDMJ`rZO(dg%~=7ZIXjUW>OaGFs;)X*z`{-r)octD*!4db5I0rbOAx0#?J>$VgD~p) zkh7GU4{!K({5Q^*Xg|GBqPRFURc@7d(Ej+yDR`#qHmioW-h(k1sUr4y^JoOCW9FIl zh~2NV;5sD%-(Ty_A%=}UXSnpVvq1U{Ht%3g{a@Yb3ZNCl*PO<{%CWT{`k42@h-fve zMaY5n_`6F2(!lgdPLu-L1qh?K;8pNVw?=u{^_LYxivtnkA7|sk@^9YwhqmX@CH~mP z3!yV&bYb7#(2Kc!eiZUSNP;ktZ(ird^Ek)sv0Msz7Ulj%qU$h)Q#8%B5mQ21JZn%* z9=02H8s%_NyP#7%e(F3SI9vY3#vYH{LEy=*!XC!>6ZtS?-gHRcGr>8kO6nNp>9|vI zO)quljxg~ZwX(58iFhR(;S!Ki`qQsuX%DN*!wO)&J4S&lpPF??>5WK_;vSFc+iCBA zI>^1J=8~&!j*+m{`|66)2So9tEwyu9NXVDo*t>&q1woDKoa*&0x10CXh2&SqPd8ND z2NEB>YGsZ*uWo%HOC>ouI(aiq5B1+9v&ta!=mmAPQzufWy$-XK zo3YTYN1Voej?uyrnGGv!CY*b0|2-nCUf8T3^_qa8# zx%#N2`eWMYl>J}JX7ZVQJA0Rnb1%`0uqWn;Ce`E;CG6M~nV4v%FGDYiz-y}bo&NU}gK#RVRC~A-XRp>JaoThCmZK2$am&Yant&mYc%C=D=`IWi2di>l2HnnxF z*>dV>wd10`L)NGUG~;C&u;yJ-c3iwTY;g4P2Ww%7AN~d-O9XM0MGH=8l`c&-N@z zn=>7qC?ZXR%Y>YN)whhewx8@8^0v;L|GdKH6`G5VAa-p+LKCi|N3`seK^c(Gdu!bi zksbP_7kZ+w&UN?U8BxD$#PFP=%F058k{B{KW0ubx-g-fnC|2s@jXk$qozURh5xULU zQuS8M=>hZr7`BJ=^+utA5U5Na1lM!j?#e zt%lgJy4RIME?`k;GoV_&?wb1eP4A}2fa_MRXKG!l+`^t~j3k+mdwsd)!er&OEeY*< z&Pozt(wty+C@VXZA9Nnp+y8;_Q_X4Hpv)lDH%1&O2n%4R@tgP7r}|1fYn<=fR3}TJ z7~IIBP`{nJ zd%L?mET%c7csa1;z&YC+huCW37w5VEG8kCF&K<@zTxI8H^^cy5dYZgHM+HjUnPwi9 z{-yVymIGwxQ>UxazuxU4|C)I<%mV@Vb=#2DhSxuNgZbFcSElytMbSVzpo=7YLn-pz zyLZC2TAztIXpSDk)X6G472_`QGH4CEzxvobjt~&Lyff?E($XC>Zeiow4sIyz+p&ooOT8RNTHAz)kdD>zZ+?=j7!VLsyw#$8{1z~{3Ub?@BOL&_ zmfqD}&$&`+$;14OtX~8#!wQ(m$z8Ud%~`77PEkQlc+Nx+_RxKgo0MvoL8e-HkUW z^xwj;_1-+wBAhnTiIzPAO7h7-k3DbEmzqx3aE`sh$GhSH`%g^pw&TG1e=Yqkg%53? zOjkW{t4p5cr`19G&-Tjf z2rE*+J{R~}2K9k6YP==r9+=V~M0&}_mKd+}$;12xH*%Q^bPcvrd$J^H4Lle_xw0q5 z^HK-uI`W({$CU`MFLm?g9Hw`|Ua8GVE7Y$?ys1J1E>Vg9t%~#=F2Se0&}66ClN0)V zZ_lBW?r4?H5lXzY5f%K%5AFVc=YtMv*Owj=(Rn-&Ysdd2fBku_F6+23$#oRciO4{+ zf=i#s931e8NdPQCBa%3l+QOjjIstaYnP5P&?4VbtNKaLO{dTf5O(8Y_zmdYmo{s+J zWSNfEBV9S0Kl4N>vGynfUxHN9ag~i8pTzLobTAvj#WeX*zjmN;Z$tuSFpBKZW3;Vk zw#3a7qJpHE1!UaQ;@P&5_3ey{rkqeg?p;Y!;c!DTT(sIYzT7OPWp~iu+JLOA;W^D; zE4XySnanK98CwB#pw>=RKX&_4wV`6`F)Yp3{hMVIw`jpZi-`NoVICqis!{IeFtF-h zzt!4xv~$of?HI=~Ze*E!ed*np{GO1|4m;{LE*$h!m^5&j%{8cVFxka<>p(YdHO-lF zd~V8&t97X$ci@5&${8=?%ledomO?o+vs!`}LwF7omZXRg_JB(R?IL%I+gzI*gc8uj zapd6rMf&S7e{!wZ={spG?9iY)n%e_T9*djk>!ZppIUdSvf+T>TGkBH5r@taHK?Iy1 zv?V9nBq5xaVeHs4TtG^X!dY2i?&zv5Vq?e)hg!4&cUtn&o7{EJ`HJ4lKCeTE6*?wM zk~F8rAVVEQiSlg4kVtwo>h|8^{ouVtdiy8)^P3)ne1d1eus_wG2Esj7f80QjKl!&8+oU*^e#I{3S~99OiWz%Rhn=EV`Z^@ zAGDjiUeW@~xFH7QrzvPwV7Y~0%ma+sk~joOqL+cFb;p*n7$nYDz(u^|z#^Z0qHq0u z5fSj{2qgQqjBjBHn|0HLtvIFyO)j?@?iPiuC10vv5M+M$K6u-%=~tgpsl^Ub6NAMO zVlAqBC%>QD)dtQ-ZbG~F{nw2)A)To)*Ylp-$@gV5Vdep;av>NAE~syuva$lceIx-_ zW?5zFgg*da(v+BpgToz$9eiTrGl|y??s?OODc29nzQg%{L&k6Np(++NxB7nh%Vi8EpjisUHJ4_@*)D zLOEW<`3&udhaKCoFl17q7UQ)MXE*b`@b~dX{(mV5F_{IZ190gwr&$CJ|IX&viJDvU zAE(w!z*ocTp4^_v2l~xD%+BiU`MZkJ6SEJ1GZA3LFqt-O>{EFpS-+5b6d-{*p)r%KbGaG0`HAnM z2!=^f=tU+9pjNZ|Z)1|hyp1s1pgzh=UVJc4+H@I?DE78$5?v#qY`!yk$(r!4Xx6o?3iV^)LB0KBB#4yPKhn zwKHio0T2@5(chf`3oSesf}0@SF$$Ev+L~L@*0kS*Dkt)K!ZQluI|f&!|C?hIm`_PdF}YUUIVy7@5CPzihZMv5u(T#4rB zgdb@{{(boH^?m3Ac96AV$2{kL|Mdw7%DRRKT?KR>_q0*BRR z%E!bWg(UoX>*XU}sSo{Oxmhm^V>x5pn{&O`KDzztld4guZ11ymr}lxvYo(bS%s)yw z|GEfxS()-21qnhD$Sij`xIqRe7Ao6a4k_3d1sDs4mCE+72gAzj`R5`9`{yDEkz)39 c{~se0SV|&CS-_RaDCQhyCYHukhUldK0rFv0c>n+a literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/shield.png b/resources/builtin/projects/v3/shield.png new file mode 100644 index 0000000000000000000000000000000000000000..fd938307fd65a1c4eaa66f3bd6c894960466a1ad GIT binary patch literal 9232 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 S?|b{UlBSxTYK^jO#Qy^6uDKHc literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/wand.png b/resources/builtin/projects/v3/wand.png new file mode 100644 index 0000000000000000000000000000000000000000..6de1cb55ab2f71a4cd7b3b43a8d5f45c3e8dd1ab GIT binary patch literal 8717 zcmaJ{c|4R~)R%4S#?EAotYa%%_BFdA#@L7X$zBoJ3uE6STe4&;W-w!mtYe#yM8=Zr zM2JRNLV6#)f4}d1KJ$F;bC+|@J?Gqe&-vab>9&O-BOQc}jEs!Y*ht@sjO-Hh;!i^j z)PzQ+2a%BpO&aU#SchEN!AApiWaOpGAQwceq~pcQ|M?5tnGX)Mp6=WvNqfIrcYZOL z9R9>&Z(x0y{Z*^%M&;9B+s~$rCDNbN%7iCbXpGCpQ}q@3{_CM;C~G~ssW>n{$z8}@ z2xjO$a#}rTdWJgm%($C=H}LBb!9)7X-J266+l`&Zv4vomb9~w{xPCcLPN&M6aJE## z<{Yc=kj^D)NW{boYb{_zFNtW#+KRuwbcHc@SivTfbH~slA5+#9J1q!?YTGixL~j@Q z)42KbnZYb?>8hVs;fE==#4=U9T^r(0u2oQF_+YO^C+n`L#MH^z>6pjB9n0`0Vnn_@ zU3-5vGuZ8LAY&FnJUhch|28e%>F<4!+Kt8OT{&Db0-xsuH!kxpHo#2uBx`hu^FKh@<7=a{Cf1(%0A^2t-Weq6oc}G-}ZuL@@Snw~X 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 literal 0 HcmV?d00001 From 03423c632c5acb9a41042eadfe5bdb3ade3306ea Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 31 Jul 2017 13:21:58 -0700 Subject: [PATCH 363/543] Treat commit hook execution in observed repositories as a no-op, not an error Summary: See PHI24. If you create a hosted Mercurial repository and switch it to observed, you can end up with a hook installed that runs on pulls and complains. Instead, just bail out if we're running on a pull. The corresponding Git hook doesn't run on pulls, so there's no issue in Git. Test Plan: Executed the hook in an observed Mercurial repository, got a clean exit. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18307 --- scripts/repository/commit_hook.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php index 44c772225c..51abcb6c89 100755 --- a/scripts/repository/commit_hook.php +++ b/scripts/repository/commit_hook.php @@ -48,8 +48,13 @@ if (!$repository) { } if (!$repository->isHosted()) { - // This should be redundant, but double check just in case. - throw new Exception(pht('Repository "%s" is not hosted!', $argv[1])); + // In Mercurial, the "pretxnchangegroup" hook fires for both pulls and + // pushes. Normally we only install the hook for hosted repositories, but + // if a hosted repository is later converted into an observed repository we + // can end up with an observed repository that has the hook installed. + // If we're running hooks from an observed repository, just exit without + // taking action. For more discussion, see PHI24. + return 0; } $engine->setRepository($repository); From ab018e1b49d52025c69aa359061fd3f34f417eda Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Aug 2017 08:48:39 -0700 Subject: [PATCH 364/543] When destorying a repository, print a notification about removing the working copy Summary: Fixes T12946. `bin/remove destroy` does not remove working copies: it's more dangerous than usual, and we can't do it in the general (clustered) case. Print a notification message after destroying a repository. Test Plan: - Destroyed a repository, got a hint about the working copy. - Destroyed a task, things worked normally. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12946 Differential Revision: https://secure.phabricator.com/D18313 --- src/__phutil_library_map__.php | 6 ++ ...PhabricatorRepositoryDestructibleCodex.php | 23 +++++++ .../storage/PhabricatorRepository.php | 9 +++ .../codex/PhabricatorDestructibleCodex.php | 66 +++++++++++++++++++ .../engine/PhabricatorDestructionEngine.php | 23 +++++++ .../PhabricatorDestructibleCodexInterface.php | 18 +++++ ...PhabricatorSystemRemoveDestroyWorkflow.php | 17 ++++- 7 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php create mode 100644 src/applications/system/codex/PhabricatorDestructibleCodex.php create mode 100644 src/applications/system/interface/PhabricatorDestructibleCodexInterface.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6427e95432..afaccaacf9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2642,6 +2642,8 @@ phutil_register_library_map(array( 'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php', 'PhabricatorDesktopNotificationsSetting' => 'applications/settings/setting/PhabricatorDesktopNotificationsSetting.php', 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', + 'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php', + 'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', 'PhabricatorDestructionEngineExtension' => 'applications/system/engine/PhabricatorDestructionEngineExtension.php', @@ -3780,6 +3782,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryCommitTestCase.php', 'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php', + 'PhabricatorRepositoryDestructibleCodex' => 'applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php', 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', 'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php', 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', @@ -7937,6 +7940,7 @@ phutil_register_library_map(array( 'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle', 'PhabricatorDesktopNotificationsSetting' => 'PhabricatorInternalSetting', 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorDestructibleCodex' => 'Phobject', 'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDestructionEngineExtension' => 'Phobject', 'PhabricatorDestructionEngineExtensionModule' => 'PhabricatorConfigModule', @@ -9241,6 +9245,7 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorMarkupInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorDestructibleCodexInterface', 'PhabricatorProjectInterface', 'PhabricatorSpacesInterface', 'PhabricatorConduitResultInterface', @@ -9283,6 +9288,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', + 'PhabricatorRepositoryDestructibleCodex' => 'PhabricatorDestructibleCodex', 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorRepositoryEngine' => 'Phobject', diff --git a/src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php b/src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php new file mode 100644 index 0000000000..eaa5cf4a77 --- /dev/null +++ b/src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php @@ -0,0 +1,23 @@ +getObject(); + + $notes = array(); + + if ($repository->hasLocalWorkingCopy()) { + $notes[] = pht( + 'Database records for repository "%s" were destroyed, but this '. + 'script does not remove working copies on disk. If you also want to '. + 'destroy the repository working copy, manually remove "%s".', + $repository->getDisplayName(), + $repository->getLocalPath()); + } + + return $notes; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index eb6608eb57..b660ff9aa7 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -12,6 +12,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO PhabricatorFlaggableInterface, PhabricatorMarkupInterface, PhabricatorDestructibleInterface, + PhabricatorDestructibleCodexInterface, PhabricatorProjectInterface, PhabricatorSpacesInterface, PhabricatorConduitResultInterface, @@ -2557,6 +2558,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } +/* -( PhabricatorDestructibleCodexInterface )------------------------------ */ + + + public function newDestructibleCodex() { + return new PhabricatorRepositoryDestructibleCodex(); + } + + /* -( PhabricatorSpacesInterface )----------------------------------------- */ diff --git a/src/applications/system/codex/PhabricatorDestructibleCodex.php b/src/applications/system/codex/PhabricatorDestructibleCodex.php new file mode 100644 index 0000000000..1c37907c34 --- /dev/null +++ b/src/applications/system/codex/PhabricatorDestructibleCodex.php @@ -0,0 +1,66 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setObject( + PhabricatorDestructibleCodexInterface $object) { + $this->object = $object; + return $this; + } + + final public function getObject() { + return $this->object; + } + + final public static function newFromObject( + PhabricatorDestructibleCodexInterface $object, + PhabricatorUser $viewer) { + + if (!($object instanceof PhabricatorDestructibleInterface)) { + throw new Exception( + pht( + 'Object (of class "%s") implements interface "%s", but must also '. + 'implement interface "%s".', + get_class($object), + 'PhabricatorDestructibleCodexInterface', + 'PhabricatorDestructibleInterface')); + } + + $codex = $object->newDestructibleCodex(); + if (!($codex instanceof PhabricatorDestructibleCodex)) { + throw new Exception( + pht( + 'Object (of class "%s") implements interface "%s", but defines '. + 'method "%s" incorrectly: this method must return an object of '. + 'class "%s".', + get_class($object), + 'PhabricatorDestructibleCodexInterface', + 'newDestructibleCodex()', + __CLASS__)); + } + + $codex + ->setObject($object) + ->setViewer($viewer); + + return $codex; + } + +} diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php index f8e5be2ccc..336df57756 100644 --- a/src/applications/system/engine/PhabricatorDestructionEngine.php +++ b/src/applications/system/engine/PhabricatorDestructionEngine.php @@ -3,6 +3,17 @@ final class PhabricatorDestructionEngine extends Phobject { private $rootLogID; + private $collectNotes; + private $notes = array(); + + public function setCollectNotes($collect_notes) { + $this->collectNotes = $collect_notes; + return $this; + } + + public function getNotes() { + return $this->notes; + } public function getViewer() { return PhabricatorUser::getOmnipotentUser(); @@ -36,6 +47,18 @@ final class PhabricatorDestructionEngine extends Phobject { $this->rootLogID = $log->getID(); } + if ($this->collectNotes) { + if ($object instanceof PhabricatorDestructibleCodexInterface) { + $codex = PhabricatorDestructibleCodex::newFromObject( + $object, + $this->getViewer()); + + foreach ($codex->getDestructionNotes() as $note) { + $this->notes[] = $note; + } + } + } + $object->destroyObjectPermanently($this); if ($object_phid) { diff --git a/src/applications/system/interface/PhabricatorDestructibleCodexInterface.php b/src/applications/system/interface/PhabricatorDestructibleCodexInterface.php new file mode 100644 index 0000000000..3725990ee6 --- /dev/null +++ b/src/applications/system/interface/PhabricatorDestructibleCodexInterface.php @@ -0,0 +1,18 @@ +>DestructibleCodex(); + } + +*/ diff --git a/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php b/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php index 4e9d745540..bd6b4e361a 100644 --- a/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php +++ b/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php @@ -145,6 +145,7 @@ EOBANNER; $console->writeOut("%s\n", pht('Destroying objects...')); + $notes = array(); foreach ($named_objects as $object_name => $object) { $console->writeOut( pht( @@ -152,8 +153,14 @@ EOBANNER; get_class($object), $object_name)); - id(new PhabricatorDestructionEngine()) - ->destroyObject($object); + $engine = id(new PhabricatorDestructionEngine()) + ->setCollectNotes(true); + + $engine->destroyObject($object); + + foreach ($engine->getNotes() as $note) { + $notes[] = $note; + } } $console->writeOut( @@ -162,6 +169,12 @@ EOBANNER; 'Permanently destroyed %s object(s).', phutil_count($named_objects))); + if ($notes) { + id(new PhutilConsoleList()) + ->addItems($notes) + ->draw(); + } + return 0; } From ba4b936dffce095f884f56588fb6841ceebd9cd8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Aug 2017 12:26:40 -0700 Subject: [PATCH 365/543] Use Log In vs. Login when it's a verb Summary: Cursory research indicates that "login" is a noun, referring to a form, and "log in" is a verb, referring to the action of logging in. I went though every instances of 'login' I could find and tried to clarify all this language. Also, we have "Phabricator" on the registration for like 4-5 times, which is a bit verbose, so I tried to simplify that language as well. Test Plan: Tested logging in and logging out. Pages feel simpler. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18322 --- .../PhabricatorAuthFinishController.php | 2 +- .../PhabricatorAuthLoginController.php | 15 +++++++-------- .../PhabricatorAuthOneTimeLoginController.php | 6 +++--- .../PhabricatorAuthRegisterController.php | 8 ++++---- .../PhabricatorAuthStartController.php | 6 +++--- .../PhabricatorAuthUnlinkController.php | 2 +- .../PhabricatorEmailLoginController.php | 2 +- .../controller/PhabricatorLogoutController.php | 4 ++-- .../PhabricatorMustVerifyEmailController.php | 2 +- .../auth/provider/PhabricatorAuthProvider.php | 6 +++--- .../provider/PhabricatorLDAPAuthProvider.php | 18 +++++++++--------- .../PhabricatorPasswordAuthProvider.php | 6 +++--- .../controller/ConpherenceViewController.php | 2 +- .../PhabricatorFileLightboxController.php | 2 +- .../PhabricatorOAuthServerApplication.php | 2 +- .../ponder/view/PonderAddAnswerView.php | 2 +- ...icatorApplicationTransactionCommentView.php | 2 +- .../uiexample/examples/PHUIButtonExample.php | 2 +- 18 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthFinishController.php b/src/applications/auth/controller/PhabricatorAuthFinishController.php index 387679b44e..4b775a2e4b 100644 --- a/src/applications/auth/controller/PhabricatorAuthFinishController.php +++ b/src/applications/auth/controller/PhabricatorAuthFinishController.php @@ -54,7 +54,7 @@ final class PhabricatorAuthFinishController ->addHiddenInput(AphrontRequest::TYPE_HISEC, true) ->appendParagraph( pht( - 'Welcome, %s. To complete the login process, provide your '. + 'Welcome, %s. To complete the process of logging in, provide your '. 'multi-factor credentials.', phutil_tag('strong', array(), $viewer->getUsername()))) ->appendChild($form->buildLayoutView()) diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php index 3264e61216..39b6318481 100644 --- a/src/applications/auth/controller/PhabricatorAuthLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php @@ -92,8 +92,8 @@ final class PhabricatorAuthLoginController } else { return $this->renderError( pht( - 'The external account ("%s") you just used to login is already '. - 'associated with another Phabricator user account. Login to the '. + 'The external account ("%s") you just used to log in is already '. + 'associated with another Phabricator user account. Log in to the '. 'other Phabricator account and unlink the external account before '. 'linking it to a new Phabricator account.', $provider->getProviderName())); @@ -214,7 +214,7 @@ final class PhabricatorAuthLoginController if (!$provider) { return $this->renderError( pht( - 'The account you are attempting to login with uses a nonexistent '. + 'The account you are attempting to log in with uses a nonexistent '. 'or disabled authentication provider (with key "%s"). An '. 'administrator may have recently disabled this provider.', $this->providerKey)); @@ -240,14 +240,14 @@ final class PhabricatorAuthLoginController if ($this->getRequest()->getUser()->isLoggedIn()) { $crumbs->addTextCrumb(pht('Link Account'), $provider->getSettingsURI()); } else { - $crumbs->addTextCrumb(pht('Login'), $this->getApplicationURI('start/')); + $crumbs->addTextCrumb(pht('Log In'), $this->getApplicationURI('start/')); } $crumbs->addTextCrumb($provider->getProviderName()); $crumbs->setBorder(true); return $this->newPage() - ->setTitle(pht('Login')) + ->setTitle(pht('Log In')) ->setCrumbs($crumbs) ->appendChild($content); } @@ -257,9 +257,8 @@ final class PhabricatorAuthLoginController $message) { $message = pht( - 'Authentication provider ("%s") encountered an error during login. %s', - $provider->getProviderName(), - $message); + 'Authentication provider ("%s") encountered an error while attempting '. + 'to log in. %s', $provider->getProviderName(), $message); return $this->renderError($message); } diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index ebfd07e7ac..534bda3f35 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -68,7 +68,7 @@ final class PhabricatorAuthOneTimeLoginController if (!$token) { return $this->newDialog() - ->setTitle(pht('Unable to Login')) + ->setTitle(pht('Unable to Log In')) ->setShortTitle(pht('Login Failure')) ->appendParagraph( pht( @@ -170,7 +170,7 @@ final class PhabricatorAuthOneTimeLoginController case PhabricatorAuthSessionEngine::ONETIME_USERNAME: case PhabricatorAuthSessionEngine::ONETIME_RESET: default: - $title = pht('Login to Phabricator'); + $title = pht('Log in to Phabricator'); break; } @@ -193,7 +193,7 @@ final class PhabricatorAuthOneTimeLoginController $dialog = $this->newDialog() ->setTitle($title) - ->addSubmitButton(pht('Login (%s)', $target_user->getUsername())) + ->addSubmitButton(pht('Log In (%s)', $target_user->getUsername())) ->addCancelButton('/'); foreach ($body as $paragraph) { diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index cdb7980a16..a68e12c801 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -483,14 +483,14 @@ final class PhabricatorAuthRegisterController if ($can_edit_username) { $form->appendChild( id(new AphrontFormTextControl()) - ->setLabel(pht('Phabricator Username')) + ->setLabel(pht('Username')) ->setName('username') ->setValue($value_username) ->setError($e_username)); } else { $form->appendChild( id(new AphrontFormMarkupControl()) - ->setLabel(pht('Phabricator Username')) + ->setLabel(pht('Username')) ->setValue($value_username) ->setError($e_username)); } @@ -546,7 +546,7 @@ final class PhabricatorAuthRegisterController } else { $submit ->addCancelButton($this->getApplicationURI('start/')) - ->setValue(pht('Register Phabricator Account')); + ->setValue(pht('Register Account')); } @@ -560,7 +560,7 @@ final class PhabricatorAuthRegisterController } else { $crumbs->addTextCrumb(pht('Register')); $crumbs->addTextCrumb($provider->getProviderName()); - $title = pht('Phabricator Registration'); + $title = pht('Create a New Account'); } $crumbs->setBorder(true); diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php index 5cfcb9b9ef..9af8f25bc7 100644 --- a/src/applications/auth/controller/PhabricatorAuthStartController.php +++ b/src/applications/auth/controller/PhabricatorAuthStartController.php @@ -198,7 +198,7 @@ final class PhabricatorAuthStartController $crumbs->addTextCrumb(pht('Login')); $crumbs->setBorder(true); - $title = pht('Login to Phabricator'); + $title = pht('Login'); $view = array( $header, $invite_message, @@ -239,8 +239,8 @@ final class PhabricatorAuthStartController return $this->newDialog() ->setTitle(pht('Login Required')) - ->appendParagraph(pht('You must login to take this action.')) - ->addSubmitButton(pht('Login')) + ->appendParagraph(pht('You must log in to take this action.')) + ->addSubmitButton(pht('Log In')) ->addCancelButton('/'); } diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php index 2c21f63407..6211e78110 100644 --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -104,7 +104,7 @@ final class PhabricatorAuthUnlinkController pht( 'You can not unlink this account because you have no other '. 'valid login accounts. If you removed it, you would be unable '. - 'to login. Add another authentication method before removing '. + 'to log in. Add another authentication method before removing '. 'this one.')) ->addCancelButton($this->getDoneURI()); diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php index e9cf693514..92accc7494 100644 --- a/src/applications/auth/controller/PhabricatorEmailLoginController.php +++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php @@ -114,7 +114,7 @@ final class PhabricatorEmailLoginController ->setTitle(pht('Check Your Email')) ->setShortTitle(pht('Email Sent')) ->appendParagraph( - pht('An email has been sent with a link you can use to login.')) + pht('An email has been sent with a link you can use to log in.')) ->addCancelButton('/', pht('Done')); } } diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php index f4b61f88a3..47592e0a2d 100644 --- a/src/applications/auth/controller/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/PhabricatorLogoutController.php @@ -52,9 +52,9 @@ final class PhabricatorLogoutController if ($viewer->getPHID()) { return $this->newDialog() - ->setTitle(pht('Log out of Phabricator?')) + ->setTitle(pht('Log Out?')) ->appendChild(pht('Are you sure you want to log out?')) - ->addSubmitButton(pht('Logout')) + ->addSubmitButton(pht('Log Out')) ->addCancelButton('/'); } diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php index 670a95f977..bf3410139d 100644 --- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php +++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php @@ -33,7 +33,7 @@ final class PhabricatorMustVerifyEmailController } $must_verify = pht( - 'You must verify your email address to login. You should have a '. + 'You must verify your email address to log in. You should have a '. 'new email message from Phabricator with verification instructions '. 'in your inbox (%s).', phutil_tag('strong', array(), $email_address)); diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 4dd7f4da1b..d655efd790 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -389,7 +389,7 @@ abstract class PhabricatorAuthProvider extends Phobject { * @param AphrontRequest HTTP request. * @param string Request mode string. * @param map Additional parameters, see above. - * @return wild Login button. + * @return wild Log in button. */ protected function renderStandardLoginButton( AphrontRequest $request, @@ -414,9 +414,9 @@ abstract class PhabricatorAuthProvider extends Phobject { } else if ($mode == 'invite') { $button_text = pht('Register Account'); } else if ($this->shouldAllowRegistration()) { - $button_text = pht('Login or Register'); + $button_text = pht('Log In or Register'); } else { - $button_text = pht('Login'); + $button_text = pht('Log In'); } $icon = id(new PHUIIconView()) diff --git a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php index 013cd21736..7c2bf38618 100644 --- a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php @@ -80,11 +80,11 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { $dialog->addCancelButton($this->getSettingsURI()); } else { if ($this->shouldAllowRegistration()) { - $dialog->setTitle(pht('Login or Register with LDAP')); - $dialog->addSubmitButton(pht('Login or Register')); + $dialog->setTitle(pht('Log In or Register with LDAP')); + $dialog->addSubmitButton(pht('Log In or Register')); } else { - $dialog->setTitle(pht('Login with LDAP')); - $dialog->addSubmitButton(pht('Login')); + $dialog->setTitle(pht('Log In with LDAP')); + $dialog->addSubmitButton(pht('Log In')); } if ($mode == 'login') { $dialog->addCancelButton($this->getStartURI()); @@ -315,7 +315,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { "credentials (which is more complicated, but more powerful).\n\n". "For many installs, direct binding is sufficient. However, you may ". "want to search first if:\n\n". - " - You want users to be able to login with either their username ". + " - You want users to be able to log in with either their username ". " or their email address.\n". " - The login/username is not part of the distinguished name in ". " your LDAP records.\n". @@ -344,16 +344,16 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { "`sn`, which will work the same way direct binding works:\n\n". " lang=text,name=Simple Example\n". " sn\n\n". - "A slightly more complex configuration might let the user login with ". + "A slightly more complex configuration might let the user log in with ". "either their login name or email address:\n\n". " lang=text,name=Match Several Attributes\n". " mail\n". " sn\n\n". "If your LDAP directory is more complex, or you want to perform ". "sophisticated filtering, you can use more complex queries. Depending ". - "on your directory structure, this example might allow users to login ". - "with either their email address or username, but only if they're in ". - "specific departments:\n\n". + "on your directory structure, this example might allow users to log ". + "in with either their email address or username, but only if they're ". + "in specific departments:\n\n". " lang=text,name=Complex Example\n". " (&(mail=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n". " (&(sn=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n\n". diff --git a/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php b/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php index 68dbf1e879..206eba4578 100644 --- a/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php @@ -100,7 +100,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider { public function getDescriptionForCreate() { return pht( - 'Allow users to login or register using a username and password.'); + 'Allow users to log in or register using a username and password.'); } public function getAdapter() { @@ -174,8 +174,8 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider { $dialog = id(new AphrontDialogView()) ->setSubmitURI($this->getLoginURI()) ->setUser($viewer) - ->setTitle(pht('Login to Phabricator')) - ->addSubmitButton(pht('Login')); + ->setTitle(pht('Log In')) + ->addSubmitButton(pht('Log In')); if ($this->shouldAllowRegistration()) { $dialog->addCancelButton( diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 792a7f8182..996417a307 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -195,7 +195,7 @@ final class ConpherenceViewController extends ->appendChild( id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Login to Participate')) + ->setText(pht('Log In to Participate')) ->setHref((string)$login_href)); } } diff --git a/src/applications/files/controller/PhabricatorFileLightboxController.php b/src/applications/files/controller/PhabricatorFileLightboxController.php index 11d4b95bc1..1f679d621b 100644 --- a/src/applications/files/controller/PhabricatorFileLightboxController.php +++ b/src/applications/files/controller/PhabricatorFileLightboxController.php @@ -76,7 +76,7 @@ final class PhabricatorFileLightboxController ->appendChild( id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Login to Comment')) + ->setText(pht('Log In to Comment')) ->setHref((string)$login_href)); } diff --git a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php index 024d9101dd..90e2d5a7dd 100644 --- a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php +++ b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php @@ -23,7 +23,7 @@ final class PhabricatorOAuthServerApplication extends PhabricatorApplication { } public function getFlavorText() { - return pht('Login with Phabricator'); + return pht('Log In with Phabricator'); } public function getApplicationGroup() { diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index d958e9843e..43bfd0d6ba 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -72,7 +72,7 @@ final class PonderAddAnswerView extends AphrontView { ->appendChild( id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Login to Answer')) + ->setText(pht('Log In to Answer')) ->setHref((string)$login_href)); } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index 232420d4c2..a5dcfcb4ae 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -199,7 +199,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { 'class' => 'login-to-comment button', 'href' => $uri, ), - pht('Login to Comment'))); + pht('Log In to Comment'))); } $data = array(); diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index fe58123f03..a5ece198fd 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -158,7 +158,7 @@ final class PHUIButtonExample extends PhabricatorUIExample { ->setSize(PHUIButtonView::BIG) ->setColor(PHUIButtonView::GREY) ->setIcon($image) - ->setText(pht('Login or Register')) + ->setText(pht('Log In or Register')) ->setSubtext($icon) ->addClass(PHUI::MARGIN_MEDIUM_RIGHT); } From cfb86dddd2ef547d41278a1388226e0681a8a8bb Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 2 Aug 2017 16:01:49 -0700 Subject: [PATCH 366/543] Warn users that compound terms separated by apostrophes don't work in the MySQL FULLTEXT index either Summary: Ref T12928. Like `v0.1`, terms in the form `yo's` (sequences of two or fewer characters separated by apostrophes) do not get indexed. Test Plan: {F5078192} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12928 Differential Revision: https://secure.phabricator.com/D18324 --- .../PhabricatorMySQLFulltextStorageEngine.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 4ccc85f085..fe526a8133 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -554,9 +554,10 @@ final class PhabricatorMySQLFulltextStorageEngine // "ab.cd", where short substrings are separated by periods, do not produce // any queryable tokens. These terms are meaningful if at least one // substring is longer than the minimum length, like "example.py". See - // T12928. + // T12928. This also applies to words with intermediate apostrophes, like + // "to's". - $parts = preg_split('/[.]+/', $value); + $parts = preg_split('/[.\']+/', $value); foreach ($parts as $part) { if (phutil_utf8_strlen($part) >= $min_length) { From 020f3c729a0a05c86abe7e444e9f84b17c095f11 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Aug 2017 09:31:01 -0700 Subject: [PATCH 367/543] Fix username typeahead in Remarkup with German keyboard layout Summary: Ref T10252. The previous fix rPa8a9fddb0738 only works for macOS. Under Windows the @ symbol is composed of AltGr+q. For Chrome and Edge the "AltGr" keypressEvent is like pressing the Control key and the Alt key at the same time. This fix changes the condition in such a way, that this case (pressing Control and Alt at the same time) is not blocked. Test Plan: Testing for the issue: - Launch Windows 10, Select German Keyboard, Use latest Chrome (60) - Observe typing `@` does not trigger typeahead - Apply patch, retest, see typeahead. Regression tested: - Windows 10, Chrome, Firefox, Edge - Mac OS, Chrome, Firefox, Safari - Keyboard layouts, English, French, German, Spanish All tests passed Reviewers: benwick, epriestley Reviewed By: epriestley Subscribers: epriestley Maniphest Tasks: T10252 Differential Revision: https://secure.phabricator.com/D18269 --- resources/celerity/map.php | 16 ++++++++-------- webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2437a1e4c2..fb3fd8d64a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '4b7430ab', 'rsrc/js/phuix/PHUIXButtonView.js' => 'a37126bd', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', @@ -881,7 +881,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '442efd08', - 'phuix-autocomplete' => 'f6699267', + 'phuix-autocomplete' => '4b7430ab', 'phuix-button-view' => 'a37126bd', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', @@ -1237,6 +1237,12 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + '4b7430ab' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '4c193c96' => array( 'javelin-behavior', 'javelin-uri', @@ -2127,12 +2133,6 @@ return array( 'javelin-util', 'javelin-reactor', ), - 'f6699267' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index ac073a0e38..df4a031377 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -199,7 +199,7 @@ JX.install('PHUIXAutocomplete', { // to press Alt to type characters like "@" on a German keyboard layout. // The cost of misfiring autocompleters is very small since we do not // eat the keystroke. See T10252. - if (r.metaKey || r.ctrlKey) { + if (r.metaKey || (r.ctrlKey && !r.altKey)) { return; } From 7621376aab9778580c7013029644fae2159996fd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Aug 2017 17:18:30 -0700 Subject: [PATCH 368/543] Allow images to be used with PHUIBigInfoView Summary: Allows setting on an image here if wanted. Test Plan: Set a rocket to launch a new instance on rSAAS Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18334 --- resources/celerity/map.php | 4 +- src/view/phui/PHUIBigInfoView.php | 41 +++++++++++++++----- webroot/rsrc/css/phui/phui-big-info-view.css | 7 ++++ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fb3fd8d64a..be593429cd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -142,7 +142,7 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', - 'rsrc/css/phui/phui-big-info-view.css' => 'd13afcde', + 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', 'rsrc/css/phui/phui-box.css' => '745e881d', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', @@ -821,7 +821,7 @@ return array( 'phui-action-panel-css' => 'b4798122', 'phui-badge-view-css' => '22c0cf4f', 'phui-basic-nav-view-css' => 'a0705f53', - 'phui-big-info-view-css' => 'd13afcde', + 'phui-big-info-view-css' => 'acc3492c', 'phui-box-css' => '745e881d', 'phui-button-bar-css' => 'f1ff5494', 'phui-button-css' => '3ca51caa', diff --git a/src/view/phui/PHUIBigInfoView.php b/src/view/phui/PHUIBigInfoView.php index 0f5bcecfbd..ad10a9fa4a 100644 --- a/src/view/phui/PHUIBigInfoView.php +++ b/src/view/phui/PHUIBigInfoView.php @@ -5,6 +5,7 @@ final class PHUIBigInfoView extends AphrontTagView { private $icon; private $title; private $description; + private $image; private $actions = array(); public function setIcon($icon) { @@ -22,6 +23,11 @@ final class PHUIBigInfoView extends AphrontTagView { return $this; } + public function setImage($image) { + $this->image = $image; + return $this; + } + public function addAction(PHUIButtonView $button) { $this->actions[] = $button; return $this; @@ -43,16 +49,33 @@ final class PHUIBigInfoView extends AphrontTagView { protected function getTagContent() { require_celerity_resource('phui-big-info-view-css'); - $icon = id(new PHUIIconView()) - ->setIcon($this->icon) - ->addClass('phui-big-info-icon'); + if ($this->icon) { + $icon = id(new PHUIIconView()) + ->setIcon($this->icon) + ->addClass('phui-big-info-icon'); - $icon = phutil_tag( - 'div', - array( - 'class' => 'phui-big-info-icon-container', - ), - $icon); + $icon = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-icon-container', + ), + $icon); + } + + if ($this->image) { + $image = phutil_tag( + 'img', + array( + 'class' => 'phui-big-info-image', + 'src' => $this->image, + )); + $icon = phutil_tag( + 'span', + array( + 'class' => 'phui-big-info-icon-container', + ), + $image); + } $title = phutil_tag( 'div', diff --git a/webroot/rsrc/css/phui/phui-big-info-view.css b/webroot/rsrc/css/phui/phui-big-info-view.css index 38951aa059..b8fbab55ce 100644 --- a/webroot/rsrc/css/phui/phui-big-info-view.css +++ b/webroot/rsrc/css/phui/phui-big-info-view.css @@ -35,3 +35,10 @@ .phui-big-info-button + .phui-big-info-button { margin-left: 12px; } + +.phui-big-info-view .phui-big-info-image { + height: 64px; + width: 64px; + margin: 0 auto; + padding-bottom: 12px; +} From fedf08743ffd9882444259481c0f21144c647fdb Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Aug 2017 16:50:01 -0700 Subject: [PATCH 369/543] Allow setting of tabs at StandardPageView Summary: Rather than have tabs live in two column view, sometimes like `admin` we'll want a global set of tabs that work well with all layouts and crumbs. Test Plan: I tested this in an upcoming diff for instances. {F5080228} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18332 --- resources/celerity/map.php | 6 +++--- src/view/page/PhabricatorStandardPageView.php | 21 +++++++++++++++++++ .../application/base/standard-page-view.css | 11 ++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index be593429cd..f795cc096f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd9c9cfd0', + 'core.pkg.css' => '851ae625', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -42,7 +42,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => '16053029', 'rsrc/css/application/base/notification-menu.css' => '73fefdfa', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', - 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', + 'rsrc/css/application/base/standard-page-view.css' => 'a0dae682', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => 'd55ed093', @@ -802,7 +802,7 @@ return array( 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'aea41829', - 'phabricator-standard-page-view' => 'eb5b80c5', + 'phabricator-standard-page-view' => 'a0dae682', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '358b8c04', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index a138d077b5..d26cca945f 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -19,6 +19,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView private $showFooter = true; private $showDurableColumn = true; private $quicksandConfig = array(); + private $tabs; private $crumbs; private $navigation; @@ -159,6 +160,17 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $this->crumbs; } + public function setTabs(PHUIListView $tabs) { + $tabs->setType(PHUIListView::TABBAR_LIST); + $tabs->addClass('phabricator-standard-page-tabs'); + $this->tabs = $tabs; + return $this; + } + + public function getTabs() { + return $this->tabs; + } + public function setNavigation(AphrontSideNavFilterView $navigation) { $this->navigation = $navigation; return $this; @@ -528,6 +540,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $footer = $this->renderFooter(); $nav = $this->getNavigation(); + $tabs = $this->getTabs(); if ($nav) { $crumbs = $this->getCrumbs(); if ($crumbs) { @@ -541,9 +554,17 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $crumbs = $this->getCrumbs(); if ($crumbs) { + if ($this->getTabs()) { + $crumbs->setBorder(true); + } $content[] = $crumbs; } + $tabs = $this->getTabs(); + if ($tabs) { + $content[] = $tabs; + } + $content[] = $body; $content[] = $footer; diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 89cbfdbd2e..f697b1f7a8 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -176,3 +176,14 @@ a.handle-status-closed:hover { position: absolute; left: -50px; } + +.phabricator-standard-page-tabs { + padding: 0 32px; + margin-bottom: 32px; + background: {$page.content}; + box-shadow: 0 0 3px 0 rgba(0,0,0,0.2); +} + +.phabricator-standard-page-tabs.phui-list-tabbar .phui-list-item-href { + padding: 12px 24px; +} From 68ab9b2642b8809388dea36e0fe3e86367086cc2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Aug 2017 19:52:35 -0700 Subject: [PATCH 370/543] Switch fluid to fixed on PHUITwoColumnView Summary: We don't ever set fluid, since it already is fluid, also no CSS. Add an actual fixed version. Test Plan: For use in Instances. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18336 --- resources/celerity/map.php | 6 +++--- src/view/phui/PHUITwoColumnView.php | 10 +++++----- webroot/rsrc/css/phui/phui-two-column-view.css | 5 +++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f795cc096f..d50a470c34 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '851ae625', + 'core.pkg.css' => 'cc0772c6', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -178,7 +178,7 @@ return array( 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', - 'rsrc/css/phui/phui-two-column-view.css' => '5b8cd553', + 'rsrc/css/phui/phui-two-column-view.css' => 'ae38a939', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', @@ -874,7 +874,7 @@ return array( 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => 'f21db7ca', - 'phui-two-column-view-css' => '5b8cd553', + 'phui-two-column-view-css' => 'ae38a939', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index d0443280b6..6e261ffeeb 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -6,7 +6,7 @@ final class PHUITwoColumnView extends AphrontTagView { private $sideColumn = null; private $navigation; private $display; - private $fluid; + private $fixed; private $header; private $subheader; private $footer; @@ -71,8 +71,8 @@ final class PHUITwoColumnView extends AphrontTagView { return $this->curtain; } - public function setFluid($fluid) { - $this->fluid = $fluid; + public function setFixed($fixed) { + $this->fixed = $fixed; return $this; } @@ -94,8 +94,8 @@ final class PHUITwoColumnView extends AphrontTagView { $classes[] = 'phui-two-column-view'; $classes[] = $this->getDisplay(); - if ($this->fluid) { - $classes[] = 'phui-two-column-fluid'; + if ($this->fixed) { + $classes[] = 'phui-two-column-fixed'; } if ($this->tabs) { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 75162619c4..2957528929 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -2,6 +2,11 @@ * @provides phui-two-column-view-css */ +.phui-two-column-fixed { + max-width: 1140px; + margin: 0 auto; +} + .phui-two-column-view .phui-two-column-header { background-color: {$page.content}; border-bottom: 1px solid rgba({$alphagrey}, .12); From 83f66ce55e1a7684cac963053bd62c823fe07baf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 4 Aug 2017 10:16:18 -0700 Subject: [PATCH 371/543] Update Settings to use full side-navigation Summary: Moves Settings to use a normal side navigation vs. a two column side navigation. It also updates Edit Engine to do the same, but I don't think there are other callsites. Added a consistent header for better clarification if you were editng your settings, global settings, or a bot's settings. Test Plan: Test each page on a personal account, create global settings, test each page there, create a bot account, and test each page on the bot account. Anything else? Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18342 --- .../PhabricatorConduitTokensSettingsPanel.php | 1 + ...OAuthServerAuthorizationsSettingsPanel.php | 1 + .../PhabricatorSettingsMainController.php | 16 +++++++- .../editor/PhabricatorSettingsEditEngine.php | 7 +++- .../PhabricatorActivitySettingsPanel.php | 1 + ...catorDesktopNotificationsSettingsPanel.php | 1 + .../PhabricatorEditEngineSettingsPanel.php | 1 - ...PhabricatorEmailAddressesSettingsPanel.php | 1 + ...abricatorEmailPreferencesSettingsPanel.php | 1 + ...abricatorExternalAccountsSettingsPanel.php | 2 + .../PhabricatorMultiFactorSettingsPanel.php | 1 + .../PhabricatorPasswordSettingsPanel.php | 1 + .../panel/PhabricatorSSHKeysSettingsPanel.php | 1 + .../PhabricatorSessionsSettingsPanel.php | 3 +- .../panel/PhabricatorTokensSettingsPanel.php | 1 + .../editengine/PhabricatorEditEngine.php | 41 ++++++------------- 16 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php index fc89d310e1..3da467e4f4 100644 --- a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php +++ b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php @@ -108,6 +108,7 @@ final class PhabricatorConduitTokensSettingsPanel $panel = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); return $panel; diff --git a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php index 596f3decc9..cfc0bc91a8 100644 --- a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php +++ b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php @@ -134,6 +134,7 @@ final class PhabricatorOAuthServerAuthorizationsSettingsPanel $panel = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); return $panel; diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php index 841529a051..4c1bfab6f1 100644 --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -112,15 +112,27 @@ final class PhabricatorSettingsMainController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($panel->getPanelName()); + $crumbs->setBorder(true); + + if ($this->user) { + $header_text = pht('Edit Settings (%s)', $user->getUserName()); + } else { + $header_text = pht('Edit Global Settings'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader($header_text) + ->setHeaderIcon('fa-pencil'); $title = $panel->getPanelName(); $view = id(new PHUITwoColumnView()) - ->setNavigation($nav) - ->setMainColumn($response); + ->setHeader($header) + ->setFooter($response); return $this->newPage() ->setTitle($title) + ->setNavigation($nav) ->setCrumbs($crumbs) ->appendChild($view); diff --git a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php index 4bcfa08d1d..e12654e1a4 100644 --- a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php +++ b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php @@ -63,7 +63,12 @@ final class PhabricatorSettingsEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit Settings'); + $user = $object->getUser(); + if ($user) { + return pht('Edit Settings (%s)', $user->getUserName()); + } else { + return pht('Edit Global Settings'); + } } protected function getObjectEditShortText($object) { diff --git a/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php b/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php index a16984c140..0b3533f287 100644 --- a/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php @@ -48,6 +48,7 @@ final class PhabricatorActivitySettingsPanel extends PhabricatorSettingsPanel { $panel = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Account Activity Logs')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $pager_box = id(new PHUIBoxView()) diff --git a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php b/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php index c08a833f1b..f94de959cd 100644 --- a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php @@ -157,6 +157,7 @@ final class PhabricatorDesktopNotificationsSettingsPanel ->setHeader(pht('Desktop Notifications')) ->addActionLink($test_button)) ->setForm($form) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setInfoView($status_box) ->setFormSaved($request->getBool('saved')); diff --git a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php index 27161218d1..5aad71785e 100644 --- a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php @@ -22,7 +22,6 @@ abstract class PhabricatorEditEngineSettingsPanel $engine = id(new PhabricatorSettingsEditEngine()) ->setController($this->getController()) ->setNavigation($this->getNavigation()) - ->setHideHeader(true) ->setIsSelfEdit($is_self) ->setProfileURI($profile_uri); diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php index d628341dea..66fd0396ad 100644 --- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php @@ -153,6 +153,7 @@ final class PhabricatorEmailAddressesSettingsPanel } $view->setHeader($header); $view->setTable($table); + $view->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $view; } diff --git a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php index 0c775c5a4d..77364e0aa0 100644 --- a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php @@ -136,6 +136,7 @@ final class PhabricatorEmailPreferencesSettingsPanel ->setHeaderText(pht('Email Preferences')) ->setFormSaved($request->getStr('saved')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); return $form_box; diff --git a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php index 662d4e6cf6..068c58d549 100644 --- a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php @@ -131,10 +131,12 @@ final class PhabricatorExternalAccountsSettingsPanel $linked_box = id(new PHUIObjectBoxView()) ->setHeader($linked_head) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($linked); $linkable_box = id(new PHUIObjectBoxView()) ->setHeader($linkable_head) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($linkable); return array( diff --git a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php index 8f1a5c643c..68d1812616 100644 --- a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php @@ -126,6 +126,7 @@ final class PhabricatorMultiFactorSettingsPanel $panel->setHeader($header); $panel->setTable($table); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $panel; } diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index 17b8cdde95..2a1b482c80 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -206,6 +206,7 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { ->setHeaderText(pht('Change Password')) ->setFormSaved($request->getStr('saved')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); return array( diff --git a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php index 54a217300d..3e339e9145 100644 --- a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php @@ -50,6 +50,7 @@ final class PhabricatorSSHKeysSettingsPanel extends PhabricatorSettingsPanel { $panel->setHeader($header); $panel->setTable($table); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $panel; } diff --git a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php index e643f2ee08..fb60e40d81 100644 --- a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php @@ -136,7 +136,8 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel { $panel = id(new PHUIObjectBoxView()) ->setHeader($header) - ->setTable($table); + ->setTable($table) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $panel; } diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php index c2659d5226..d2cc0dedb6 100644 --- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php @@ -84,6 +84,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { $panel = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); return $panel; diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 1b891cddee..c9d8a6887f 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -29,7 +29,6 @@ abstract class PhabricatorEditEngine private $page; private $pages; private $navigation; - private $hideHeader; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -127,15 +126,6 @@ abstract class PhabricatorEditEngine return $this->navigation; } - public function setHideHeader($hide_header) { - $this->hideHeader = $hide_header; - return $this; - } - - public function getHideHeader() { - return $this->hideHeader; - } - /* -( Managing Fields )---------------------------------------------------- */ @@ -1194,15 +1184,10 @@ abstract class PhabricatorEditEngine $crumbs = $this->buildCrumbs($object, $final = true); - if ($this->getHideHeader()) { - $header = null; - $crumbs->setBorder(false); - } else { - $header = id(new PHUIHeaderView()) - ->setHeader($header_text) - ->setHeaderIcon($header_icon); - $crumbs->setBorder(true); - } + $header = id(new PHUIHeaderView()) + ->setHeader($header_text) + ->setHeaderIcon($header_icon); + $crumbs->setBorder(true); if ($action_button) { $header->addActionLink($action_button); @@ -1231,19 +1216,19 @@ abstract class PhabricatorEditEngine $view->setHeader($header); } - $navigation = $this->getNavigation(); - if ($navigation) { - $view - ->setNavigation($navigation) - ->setMainColumn($content); - } else { - $view->setFooter($content); - } + $view->setFooter($content); - return $controller->newPage() + $page = $controller->newPage() ->setTitle($header_text) ->setCrumbs($crumbs) ->appendChild($view); + + $navigation = $this->getNavigation(); + if ($navigation) { + $page->setNavigation($navigation); + } + + return $page; } protected function newEditResponse( From 58db64c81f5c92ed6afd8e7a73916b0bb9ddf60d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Aug 2017 20:05:04 -0700 Subject: [PATCH 372/543] Hide curtainview on mobile if it's empty Summary: If we don't have any panels, just an action list, we want to hide the entire box on mobile since it's just an empty line. Test Plan: Review Owners, Differential curtains on mobile, desktop. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18350 --- resources/celerity/map.php | 6 +++--- src/view/layout/PHUICurtainView.php | 9 ++++++++- webroot/rsrc/css/phui/phui-curtain-view.css | 4 ++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d50a470c34..aa02c547de 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'cc0772c6', + 'core.pkg.css' => '04da74af', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -149,7 +149,7 @@ return array( 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', + 'rsrc/css/phui/phui-curtain-view.css' => 'ca363f15', 'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', @@ -835,7 +835,7 @@ return array( 'phui-comment-form-css' => 'ac68149f', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => '55dd0e59', + 'phui-curtain-view-css' => 'ca363f15', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => '8af7ea27', diff --git a/src/view/layout/PHUICurtainView.php b/src/view/layout/PHUICurtainView.php index af02ceb932..49ff55384a 100644 --- a/src/view/layout/PHUICurtainView.php +++ b/src/view/layout/PHUICurtainView.php @@ -46,10 +46,17 @@ final class PHUICurtainView extends AphrontTagView { $panels = $this->renderPanels(); - return id(new PHUIObjectBoxView()) + $box = id(new PHUIObjectBoxView()) ->appendChild($action_list) ->appendChild($panels) ->addClass('phui-two-column-properties'); + + // We want to hide this UI on mobile if there are no child panels + if (!$panels) { + $box->addClass('curtain-no-panels'); + } + + return $box; } private function renderPanels() { diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index 0d7d4f942f..8bd8a331c4 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -46,3 +46,7 @@ .phui-side-column .phui-curtain-panel-body .phui-tag-view { white-space: pre-wrap; } + +.device .curtain-no-panels { + display: none; +} From 8ca29a607a2bf82bf83f1a6dcc61d85de6d6367d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Aug 2017 19:32:26 -0700 Subject: [PATCH 373/543] Remove incorrect policy language on Diff reviewers Summary: Fixes T12952. This never work AFAIK, so resolves this mis-information. See T4411 for follow up. Test Plan: Click on policy for a diff, no longer see text. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12952 Differential Revision: https://secure.phabricator.com/D18349 --- src/applications/differential/storage/DifferentialRevision.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 0e968dccf4..7ad01c02ca 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -523,7 +523,6 @@ final class DifferentialRevision extends DifferentialDAO switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - $description[] = pht("A revision's reviewers can always view it."); $description[] = pht( 'If a revision belongs to a repository, other users must be able '. 'to view the repository in order to view the revision.'); From ec5f20b399e53cf8378e996e82591cc4f2cf5dfd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Aug 2017 15:55:31 -0700 Subject: [PATCH 374/543] Move PHUIInfoView Summary: Just moves this because I can never easily find it. Test Plan: Check UIExamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18348 --- src/__phutil_library_map__.php | 2 +- src/view/{form => phui}/PHUIInfoView.php | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/view/{form => phui}/PHUIInfoView.php (100%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index afaccaacf9..5741de915b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1769,7 +1769,7 @@ phutil_register_library_map(array( 'PHUIImageMaskExample' => 'applications/uiexample/examples/PHUIImageMaskExample.php', 'PHUIImageMaskView' => 'view/phui/PHUIImageMaskView.php', 'PHUIInfoExample' => 'applications/uiexample/examples/PHUIInfoExample.php', - 'PHUIInfoView' => 'view/form/PHUIInfoView.php', + 'PHUIInfoView' => 'view/phui/PHUIInfoView.php', 'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php', 'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php', 'PHUILeftRightExample' => 'applications/uiexample/examples/PHUILeftRightExample.php', diff --git a/src/view/form/PHUIInfoView.php b/src/view/phui/PHUIInfoView.php similarity index 100% rename from src/view/form/PHUIInfoView.php rename to src/view/phui/PHUIInfoView.php From ca8ae2d4ca5d0f4f8655426e8c99ad35fd3039d0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Aug 2017 14:57:36 -0700 Subject: [PATCH 375/543] Add a red button to PHUIButtonView Summary: Danger Danger Test Plan: UIExamples {F5084035} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18347 --- resources/celerity/map.php | 6 +++--- .../CelerityDefaultPostprocessor.php | 3 +++ .../uiexample/examples/PHUIButtonExample.php | 1 + src/view/phui/PHUIButtonView.php | 1 + webroot/rsrc/css/phui/button/phui-button.css | 20 ++++++++++++++++++- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index aa02c547de..fc4556782c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '04da74af', + 'core.pkg.css' => 'cea08376', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -127,7 +127,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', - 'rsrc/css/phui/button/phui-button.css' => '3ca51caa', + 'rsrc/css/phui/button/phui-button.css' => '340f55c1', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', @@ -824,7 +824,7 @@ return array( 'phui-big-info-view-css' => 'acc3492c', 'phui-box-css' => '745e881d', 'phui-button-bar-css' => 'f1ff5494', - 'phui-button-css' => '3ca51caa', + 'phui-button-css' => '340f55c1', 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 39a6c849e1..61f6176f15 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -231,6 +231,9 @@ final class CelerityDefaultPostprocessor 'green.button.color' => '#139543', 'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)', 'green.button.hover' => 'linear-gradient(to bottom, #23BB5B, #178841)', + 'red.button.color' => '#b33225', + 'red.button.gradient' => 'linear-gradient(to bottom, #d25454, #b33225)', + 'red.button.hover' => 'linear-gradient(to bottom, #d25454, #982115)', 'grey.button.color' => '#F7F7F9', 'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)', 'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)', diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index a5ece198fd..c7107250ff 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -20,6 +20,7 @@ final class PHUIButtonExample extends PhabricatorUIExample { $colors = array( null, PHUIButtonView::GREEN, + PHUIButtonView::RED, PHUIButtonView::GREY, ); $sizes = array(null, PHUIButtonView::SMALL); diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index 1aaa1cbaad..8d6eca9ec7 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -5,6 +5,7 @@ final class PHUIButtonView extends AphrontTagView { const GREEN = 'green'; const GREY = 'grey'; const BLUE = 'blue'; + const RED = 'red'; const DISABLED = 'disabled'; const SMALL = 'small'; diff --git a/webroot/rsrc/css/phui/button/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css index 8cdd818f12..a36d69d4bc 100644 --- a/webroot/rsrc/css/phui/button/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -50,7 +50,9 @@ input[type="submit"] { button .phui-icon-view, a.button .phui-icon-view, button.button-green .phui-icon-view, -a.button.button-green .phui-icon-view { +a.button.button-green .phui-icon-view, +button.button-red .phui-icon-view, +a.button.button-red .phui-icon-view { color: white; } @@ -76,6 +78,14 @@ a.button-green.button:visited { background-image: {$green.button.gradient}; } +button.button-red, +a.button-red.button, +a.button-red.button:visited { + background-color: {$red.button.color}; + border-color: {$red.button.color}; + background-image: {$red.button.gradient}; +} + button.button-grey, input[type="submit"].button-grey, a.button-grey, @@ -123,6 +133,14 @@ button.button-green:hover { transition: 0.1s; } +a.button.button-red:hover, +button.button-red:hover { + border-color: #79150b; + background-color: #0DAD48; + background-image: {$red.button.hover}; + transition: 0.1s; +} + body a.button.disabled:hover, body button.disabled:hover, body a.button.disabled:active, From fd3cb18fe4a4da81cefcb96db34b19d7ad5f45f9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 7 Aug 2017 13:34:00 +0000 Subject: [PATCH 376/543] Don't force buttons to grey with PHUIInfoView Summary: I'd like to use red buttons. Test Plan: Set a button to red in InfoView, see red button. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18352 --- src/view/phui/PHUIInfoView.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/view/phui/PHUIInfoView.php b/src/view/phui/PHUIInfoView.php index 8ba74056b8..18f5af3374 100644 --- a/src/view/phui/PHUIInfoView.php +++ b/src/view/phui/PHUIInfoView.php @@ -95,7 +95,6 @@ final class PHUIInfoView extends AphrontTagView { } public function addButton(PHUIButtonView $button) { - $button->setColor(PHUIButtonView::GREY); $this->buttons[] = $button; return $this; } From 7119c9874475b2a82f2e564790b98b65f8b6dc87 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 7 Aug 2017 13:53:19 -0700 Subject: [PATCH 377/543] Add a UIExamples page for PHUIBigInfoView Summary: Fixes the icon bug and builds a basic examples page for future testing. Test Plan: Visit uiexampls and various types of info views. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18356 --- src/__phutil_library_map__.php | 2 + .../uiexample/examples/PHUIBigInfoExample.php | 48 +++++++++++++++++++ src/view/phui/PHUIBigInfoView.php | 1 + 3 files changed, 51 insertions(+) create mode 100644 src/applications/uiexample/examples/PHUIBigInfoExample.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5741de915b..d44e9370bb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1710,6 +1710,7 @@ phutil_register_library_map(array( 'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php', 'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php', 'PHUIBadgeView' => 'view/phui/PHUIBadgeView.php', + 'PHUIBigInfoExample' => 'applications/uiexample/examples/PHUIBigInfoExample.php', 'PHUIBigInfoView' => 'view/phui/PHUIBigInfoView.php', 'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php', 'PHUIBoxView' => 'view/phui/PHUIBoxView.php', @@ -6867,6 +6868,7 @@ phutil_register_library_map(array( 'PHUIBadgeExample' => 'PhabricatorUIExample', 'PHUIBadgeMiniView' => 'AphrontTagView', 'PHUIBadgeView' => 'AphrontTagView', + 'PHUIBigInfoExample' => 'PhabricatorUIExample', 'PHUIBigInfoView' => 'AphrontTagView', 'PHUIBoxExample' => 'PhabricatorUIExample', 'PHUIBoxView' => 'AphrontTagView', diff --git a/src/applications/uiexample/examples/PHUIBigInfoExample.php b/src/applications/uiexample/examples/PHUIBigInfoExample.php new file mode 100644 index 0000000000..519cdba88e --- /dev/null +++ b/src/applications/uiexample/examples/PHUIBigInfoExample.php @@ -0,0 +1,48 @@ +getRequest(); + $viewer = $request->getUser(); + + $image = PhabricatorFile::loadBuiltin($viewer, + 'projects/v3/rocket.png'); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Launch Away')) + ->setColor(PHUIButtonView::GREEN) + ->setHref('#'); + + $views = array(); + $views[] = id(new PHUIBigInfoView()) + ->setTitle(pht('Simply Slim')) + ->setDescription(pht('A simple description')) + ->addAction($button); + + $views[] = id(new PHUIBigInfoView()) + ->setTitle(pht('Basicly Basic')) + ->setIcon('fa-rocket') + ->setDescription(pht('A more basic description')) + ->addAction($button); + + $views[] = id(new PHUIBigInfoView()) + ->setTitle(pht('A Modern Example')) + ->setImage($image->getBestURI()) + ->setDescription(pht('A modern description with lots of frills.')) + ->addAction($button); + + + return phutil_tag_div('ml', $views); + } +} diff --git a/src/view/phui/PHUIBigInfoView.php b/src/view/phui/PHUIBigInfoView.php index ad10a9fa4a..31a458ed60 100644 --- a/src/view/phui/PHUIBigInfoView.php +++ b/src/view/phui/PHUIBigInfoView.php @@ -49,6 +49,7 @@ final class PHUIBigInfoView extends AphrontTagView { protected function getTagContent() { require_celerity_resource('phui-big-info-view-css'); + $icon = null; if ($this->icon) { $icon = id(new PHUIIconView()) ->setIcon($this->icon) From 80b18278b8c55dbc44121c846518ac285e626dbe Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 8 Aug 2017 16:39:11 -0700 Subject: [PATCH 378/543] Center page tabs on mobile Summary: Centers tabs when used above the page header when on mobile. Test Plan: Test mobile and desktop layouts of Instances. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18368 --- resources/celerity/map.php | 6 +++--- .../css/application/base/standard-page-view.css | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fc4556782c..a8b40f8e88 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'cea08376', + 'core.pkg.css' => '5a682e14', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -42,7 +42,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => '16053029', 'rsrc/css/application/base/notification-menu.css' => '73fefdfa', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', - 'rsrc/css/application/base/standard-page-view.css' => 'a0dae682', + 'rsrc/css/application/base/standard-page-view.css' => 'c581d2ac', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => 'd55ed093', @@ -802,7 +802,7 @@ return array( 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'aea41829', - 'phabricator-standard-page-view' => 'a0dae682', + 'phabricator-standard-page-view' => 'c581d2ac', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '358b8c04', diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index f697b1f7a8..d930a73797 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -184,6 +184,20 @@ a.handle-status-closed:hover { box-shadow: 0 0 3px 0 rgba(0,0,0,0.2); } +.device .phabricator-standard-page-tabs { + margin-bottom: 20px; +} + +.device-phone .phabricator-standard-page-tabs { + text-align: center; +} + +.device-phone + .phabricator-standard-page-tabs.phui-list-view.phui-list-tabbar > li { + display: inline-block; + float: none; +} + .phabricator-standard-page-tabs.phui-list-tabbar .phui-list-item-href { padding: 12px 24px; } From b1e3cf627de11d5140fa3b6f050df24468427a56 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 8 Aug 2017 17:45:56 -0700 Subject: [PATCH 379/543] Add more repo images Summary: Just a few more. Test Plan: Edit Picture, see new image, choose image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18370 --- resources/builtin/repo/repo-git.png | Bin 0 -> 4899 bytes resources/builtin/repo/repo-hg.png | Bin 0 -> 4828 bytes resources/builtin/repo/repo-svn.png | Bin 0 -> 5058 bytes ...ffusionRepositoryProfilePictureController.php | 3 +++ 4 files changed, 3 insertions(+) create mode 100644 resources/builtin/repo/repo-git.png create mode 100644 resources/builtin/repo/repo-hg.png create mode 100644 resources/builtin/repo/repo-svn.png diff --git a/resources/builtin/repo/repo-git.png b/resources/builtin/repo/repo-git.png new file mode 100644 index 0000000000000000000000000000000000000000..b8dfed8ad28218651f6213bfd6917298fc508266 GIT binary patch literal 4899 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/repo/repo-hg.png b/resources/builtin/repo/repo-hg.png new file mode 100644 index 0000000000000000000000000000000000000000..d12c2e533941ee38c08ee61023fe092c0bccc876 GIT binary patch literal 4828 zcmb_gc{tQ<_n#SCNRkIxq7o{DB4jTQ3E69&u}_QTv6hVNjir<|$spN7nUOI_mSMEm zmyi(#m6*nm#8^gnzu(dO`{(yu*Zar&x~{pd?|trbpL0Ivoco+}yKil2BEToX2Z2BY zOwSwHLLg8$_}j$|P(mV-(GbYKB2%NY7lWa5EhnJ>@%6!tQjIr}DB$y-{;YFI4Z;4h zj>0GO8yj&^n{#)wLW17hZa>}AEv5RW&&Wfwmzf=V)B-cLdiM6Zm2CM%Bz#ZK3z~g| zyJl(i);=3EuJy0q?6!DjUX=IB_vVIOt?TTN=(gZ;oS#6g zOg(SvolWw*6$Dqi;aHpCH^3p}%YmPm^Y+sje;Rj~#fxIPY|~a`HR(0?03L}M0SgQl zQsZJ!ny3*c62pZs0+um;#3$UtryqufIJ`_s?&@D7Jn7rhKJ?{W9fEH->jQ>naBHqI zmRk4TNwv_*N&|{(+vuAd_0S`jeb{0kf_{DVi^o&o>zq4LkIgu(`O3mI5y28WX#QKJend#TPWm5hZzamNKoDtjpq7w2O zMKa*Y!!Wov41SnQl>e_kj{*|#x%`CcDo4eEC*7tuPK*td71?VckJ2L@VF%gvvGxxA zY>&?~e-`H)JJy~80EaNFpGVO4xl=sOa1tPj!5H;b_pm;>#t>`+95j&l)#>%BR>oe? zNzvGazG?wnL7edJCJs>)mmooT{zZ)dUa7wkvkP?6omtOvxNM3%;tw3l2+?%oPJ^NV zc+fCtBizo?|L^&%*wOp$_WHV=uS8H#>>&`d`TrVzLPJ@->@&aW;enabfcmrf)COI_ zg9RKQnmfi7HHF5mW}Qic7JH#^GM_Z+!1G5;ZC_KtMu-pym(S^EdEgj%z%PfU3^<3M zfY-_(?D-vUYO6-B`Pu{7TPoaA;R+zTc5~6F24O~43%YZ9sO5^Om79wV-t5njA9j+v zKnc~79ufhKHa3k8qo2*}C-gk`*8-P@9s2(jb})JH(z^-O-W%S;n=cMJ7Oi7n_H=Z0 ztP{hhjt4_TQ8fq2qq=Fs-sPUX-pA_DFL=RxZQ+2AyH%r4WYwB-|3>fS3kH9DiM9_W z3PhB)Zyy*}H_dCKcZWVnn0&vkdBNK)31Qhi~N z>2Dbs|IV>#;+&<3!|0Zijq<|tsJKHQbRyo$l2WXyt}vx?Apw!pP2c9$1@m*rIU*RZ zylG|GA-Qc(`D{y}DQ>9A&MxI!2O7J{6N9(CcL zOvT+Ffuw{Y!~!=A?Joc9%&lZ6Fbtn!bqd0c1pC|+V3`og9<&gORsV;@AK(r`46G6h zKO3J=Z6EbmnhnGxznzRXT~5mU79j0>J|h2#H^adwC-wH_TlPUu)oi7*MLc|++^NGw z+qvQaMy|BV^=PX0f*>klo<}i63)$PXVd4csbdyDD_NhM=x9GgvCZ9Bk>uHouA9PGm zT=B5JP-b_t$u&h;^YPf;jHQN7!A5sA?61z%OY*e9V{DI(z$o|cMDy&PklUEL+mJn7 zd>yONGh4$^dMido#x-2*RYhjlr(u69HxOwTH(^=Dd%sk5C9QONaPYH?aNj(>BlosICm2nGG)MqTcRwF5sw;YiH)O|hz#M@*vHho*(H8-^(BU(l)=-K1JHf<8Z ze6aHP0Uou8ai*F87{XZuVdX1PsjWcBf;*I??djJP`C~hf;J=hF`i zm?X3&cRTAK(`y8?UCr0nhN%*|@6jJc%{`E<`yBgrB2h^)t@T2Hy`8AM%j3le-|?Sy z8hQ!}8e(5(qskI4SOh+P{Z7yAz{TN~6&D`%rM#PWzkRHxky`iBDZmA*PfXI4T#V}! zwu|H;Fl~BPA*_jTMATAM2I@7eK`iUwRuX-Rkl?D;OG zRgde{S;IQ5E>N~?4N`TZ)rwz^_uAQW9uKo$6yI!492O4^J^mq(H|Ha3=5a2Ffu9gK z*S3xB5k6ty-(o6WQPkzNdw4Ok=6mye$0<^oue%c=`yA&rTo$b-yHKe7~{I$!un=WpGEC5Z7~VA z#JUgMSITkD1c6_H_Etv;D;bSE-%DMx{$B4hqQ!x(0XDr|4~cQZogYwx2!R_?V00aV zFzBbLn%&qupZS-qVjNq#zd@5Hf3dvESs~xTmC1Zyg+MtQ_~udwIvKtreMPz_`H?Na zaTvU(I@3WROhQa2d(faX(r_0tid}&9qT1FZG_y0-GTlEgzW8Bh_Jd?RVG8;9d;Q-1 zIES~1h3xi=ar60^E^d(dP4zIBr@g|$Q`Xd699SXhA?AWUOy2k}C9V}!6q#U>q=#g^ z-+KZm50^$Ajt+s_n`+F2qX9ny8H z{&vIViAUQ%i+J@GX-nSOO%qcG_kf;*!QXuG?92KgO%MLKxc>Uf8?|1|#L3dCSuneB z9P1-FpIBL}_^5R800$yHS30w7={ZI3ii?AAn4)}KS)JVHx}f~obrS1nnJYU-ayo84 zuoq0V-W%<*;aT;G_l)dgpY`N|_}>0=E@H+E)6=Yl&?lL>zlzSpY12(gjeuegMU<+} zHN0pLizUS@J$@46cDooCDY{rQF?FRrmIp+jomoiyK-U^= zz9988QtCrsN(hK$)`xYpI3W)0!D+!$6{E)&8eUd;9s7)ZQIo&MfXsIuWOajC)+y&*THOPeCE(cKq|f z{Y3rb*Ta^+N-*q6K;b~!`Kj@~0<&IC7ZzH_R{Tw>OG4{XxU(2&^7$4dCH8%tz>V>& zv$s6o`(9g~EIyg`hT+ zpOK0kQ6Bj0AX`tH`nM(-3!Jw}U|lw@?Ntxd|LfkoTX&CdVpm*SV$3R=zNJu~JK^x? zwWr~*;mfbdWvBGLAHU}VGwl!Qimr|0toCPlu-R~>J`G8=+x;K0B3 zmTfGUzX!PZUZ_5B8Sl3p$cm&0(cW>|w8fJiK;UGSS3gZ^d{!#KF&|$l%Ygdz{`hr%o(}9)wJ{7sIMf1*E?s7mb4VaL#!|Hz} znE}=o=hdIcx5NxFp_Fe$$&JnLqBV~J4+RQTp?}lg8hlZFTS}&m^kq**19BMW<29SO zfPHMT^Z00&B%6q9jM5~@frnZ+W9G5oyGab!NN2-9Mqjl8w4ld{S@Ylq_`{%!KezW+ z%f_XV1{V`$WqdgJ0X4Z$^S0K974o5>6+dPk$kwh#aRX|GP;}M2x90Bn&;sA7d??Vf z)sZx_(D|YMA8(SW*NPzk4+%AYYafmqKA&^vG&o<4@KKu9W?>93;nQG&;3u#Lj_{pY zCPL4~dQE1kCoR7NtAKY+bWNTA`N+}LSapk43t(tL|A}GJmB~ad;Boab=ztOlnn~ruzEzuG2f+ z&cyM=reR8N`39^U$As3A40h}oO5K`R=2t40w8J-j3%^_*{tE5%eJ%x@nt%zBlozsT zXd<25wfNQHotKSRJmYIj&(#2L*J@v2AK*BkzG+)kVRh72a4x{DsImFP@w~arN7-Q> zx-yc`1OW5vPRa z(Bf9B#f2n&iL^~M$(`P-J#o^&Q$X*JjPk27APX>&%xh93XXPW!NJLYbC)lq5Z{fEC zgnJgo5nD;TORi?@C#Z-Q)dD;A!A{LK_@6M{PoJx(?)h1k6zKB|aq-FOtl6c_>AczzIh&uZkG*BVnQh zRwZ8n7aYou+(h1a55r=(@O$tsKRu!5WEeskSLJ1|=hn|A>mm{={*S(=8Y72-DkfhN zW6KwLS0=n8|H}WDZhhfHTyZv(FHf|bqMMm88&3hL^>nlV*E#*Tb7N@Hfc|G|7A^ncm!p-k8lKP u{tzQ)N`A-*H-%w=6+q%ef~$XKh=6sn6uhok%k+T%k}@^6G!T$PzKa*Pak1TL>8sg`p54OC$T5BwLgjlq6diJofB^D_do2 zY*SgXWLK8#?lau){r&N~_jTtr^UOJ)v%SyzoM&D$6M5Z0>j)D!6AT7BqN9Dq7zTr* zsV@dP=)@-=76*fIM(SKqzu^s^{}Mv00Y4T#rl9`(3cOr1s!Uh*QrTrvhTQj4@I(<; zdj3n7EJs?|9D-$_*#Gn(RkCicoK5Y0;X0EztMzdO`TR`$EZg%%NAfmU zrf-r*r<2hVl7drq$f!G6SgebY7I&R_L)nEfK1w6r1`=hQ!~ z<(JCW#)=2aeQthr8ei>cqPQ&i{;K0@+}d1R-^YKs!VJt+Sr@GHsVm5eH&mZ1oV41e zmm+b1!HG70W?|(1h^s%k`xVB2Pk6fLZ!m#KG}i7rA12InUr^B9GHu{aet zGtl6I>MY-wdW;mo;*=p>P<)quyQ=4S2$_=_y)ISX?y$|76ijA@)U90eog)1AKt7-@ z0m?N=FUaZ|Km({ehZLaD_WyoA0*r@;f4;?By+u*rb09dm+ZP8|4x8&`XOI4h zgJzF~W{;w>4h2KPL2K0MKo*D$1RhDWIszQ%kOQRu_mJw7U@*?dOk$R%MfARCS^1_! zxT3t7&SwgOfeIF{btC_!a)aLBshYbB{A$!}7p&S{2~zuBrv+wcpwe=?z7;i(tSt+} zgqoo;HIF1#ZH6s1sGeeVgf5o+7&e2gNxv;Bgl~%Avy*_bmfqiO`=)cht~69u!)|#2HF9{@|k~ghqSV;9>822dz^%IP9GfP~icE z{^o=h{og}yGFbTB*52mCmF|{$jhoIasOL0@hK3;+)HKqoqnbyB3HWdE52 zRcgu{C-TS74 ziuJ1i;SYZ&+mw&(MQ-#T7>H$-!={}qM2Or=NGLN#kGF$xyLmUYC2!gF`>YJavIj|b zYxSOr!Hs-(tXE+H?>YRBsYY6Fh>LxO|GB#P12lN*_8&AGEKHpb2k5Kv|8I-@%0tJw zR!Yo!uQ%L&_8##gitNoru~$c!1*MnAwWDS4e|AZFVrf#P*id>Z&H9>}?5~!rmO(<7 zR#uQ0_w7>m6}JMxqOJim!uX~1<<6DJt-)(w70W87S2~*qv>e_JD{#?Ag>U$V%e|45 zlx!LMoi?P*Y^mgNmPu{7D9A_QGZzC$3{Z|x{+cJbl86DHYb2Yg-kkNt=mwa#=0`> z|LlJT#47|H3tx0h)%eW88Tf5sMYGJi$W4Qh&t9K;Bry$LE&lC`qeIu+O(}x+8@dR8 zF66^^P~R7{S=~yuT;CUZ2{;D=^BjQH6)Zd88FoPc9`NRz7~iLN@x6Rq^i#_Y&(k+% zy|D2pfhL_laM_lMxFT7R3eAZ}+7BK`ywx9As@%^gwXZM}1R2nXPPwPc`LmJ4&26$9 z;*_&3r8oW>|I*rolS!?i!t3Ld0Ym$i^{)H1G^ev4hWMAxk(ok-pO6GVDQzhr`>kj4 zc*>vJK4P9pRvO=0K*PWs|qjuKDzyr*GiPjO>YI5V=oT?!8=H-@+~RTN^=X7EX# z50TB*%63z98CQO8KN8^;wn}P^ms{Fidg(bexRo@BQW8*G^2#`SH5PUxVS<&EQVm|v z!^T8GSOag(=0=Wgr_QGMi0)9LcqIA^QJ}575`F5q@$Uy8E@bXW zSO$kL^(ZFF5Pl0^acg5kYy{WlsD9wsg1~5Ezso~N$A z&=rMg3b(%S^sOW)P`HnQ94AG=*rK@PmKEHUIL?uWNfp*#y?tGf&Il@^qr8k&Iqyu; zo>V`(9^_pt{q*A%+*=>_ZUHg(_USj5Zp)5O&b+aYov=+uBv)1I;L zmF(`|)y_3twc4to6gr9%l0MMUp}$~#0;a9kxUOBxWh6?o1Ftyp^26fFQ~39rRAfb? zUBY^3Njd?R<&p##_{Fqa&H8;JKUQ7iCl3V=txgn)yS-$T-GuW-bX-Dv2=lH$%Cpz$ z1W{mZja(Ot=e%5aP6|6lL0WsMA0Sti=rL;qJ0>5drRhy7zg5W>3Y0vr6|->n^gtCt zk8)|@v^fX$Zpq5$d5)QR;9XaHlA%_rz*vDbf~A1A4*>_9mB)j%k|K+4=V5bw(DeISsNhFV$MkKqN2Hb-Y&Dug2se z>0%q(f~<^;)#}BsV?Q1F8bT|h4tWWH^DCE=TyqW5ObmVJn%22n#5)^TbQ7(PZEtx| z%qE`rzYMfE3ISpVeiUW^C%>ege2~ueDlj^bhJYW{a+MhMv-MSCe=&%k_+AQ@OSWE_dwFcNg-rD4!y~<3{)p z_1JTqm^1fph@aRnsguMqxTR0AoQ?2~eY4x@=Fwf}#T9#Ezu1#4s^qp2>@mf#JLwO| z_h!MdLATI_XEIE~tI!4dT|@aWn<_7kOColtZ8$`fn+0$w1S)WXWk}#kaF!5B0D+c+ zqu8;zjNlWPo_??<5_pP03y_RCvFvbs3M`5N{Bq!W_TR3wbU;K-3_gIfwe7Pz29Y34GOzE6cTy)&TUoU(ue`wVlATt7t8(aCvQ2i@c!9V zspM`E@kn`?Ci@W(=4|SeG*LsAnLq+PN5G0H`nLDqhmSpzTvtOObfU_VE|#`1MS-N5$t`ff=mU*_t`+$+z6K+x4DtS#ONtOf*DU$alGix z`2u;!KSZ zvg3~L(#%q&8P=-?l15UQRGBCG_%56pC$|2VM?hWZfjoS{sL_oBj>2xX78VwoO1+|b zmR=P*X&^vC5My2^ge_1{BBp+xxCWh|RVqiJZ+BQrV=p^p=(h(n07&Cay?=)yBzW`e z4MXL#JW8c3=sY?!Hjg zplq)vVv<9j+Y-x3R38*^?L*WNAW#HVHA>aFh&b0;(uE3`BIFM}AqJU2F&63-mjAuI z^OQ(N_KP0~6c&!T)o3vDvMN>4`{bnZD`ei@CzhO@&lYq*TBw$*jBb|YWTG(7Oqk-q`LqM&*xT8W<( zznb?i%?;K{d$L_ z?>)wkG@fjp{5T$c%hK$g++lD9t;FmLbtS&s{e`x9BM)CAi}S>HiQ>-8c3wR2d`Bu^ z5qU-@A6H_W$@81fv|E^nFTLhqle}lF!?``MF>sek1JrlFAy?>HYkz-P zW7VjFFgE-s9CNW_I5VA$lW1E#V{ZORw=HF>XOCx1?WHuDQyj$GweBrB_muPu@7yIl z)mgdMtI@)QW>OBq#htD}42)TfOJgk`hnCyGj3}<@n2A zLn{;=oy$*ojBI)^>J_S`z1rDx9$9Sk^xMJfnf$yDIofYy`9Lpnc7&-<$V7tUg zUk`wUT;gya2daq!BbvWg_9z^D#MP0fCSh|1Z4+KHYk1(2PTV?lF~hv{w`kG;nmQbC zCa6ilmw50f9{g<{V?6H2$ZW$Xo@T0bV1MdBPCT%^HOFNOqaO(q0nE2Umlvi%2QEx@ z@p0I=UU*adT*K zL#);ABb$x8@nVNxm1O>3XK^>qcY69rR0b^!z-ggB#Yo^@f%T6Qn6aIzPzF>m7S4d; o2VkMJ{J?<>U8pj2@E?qq6qTgNnnM?ZsDG~MXc}B8)WC-R6HvkoeE Date: Fri, 4 Aug 2017 05:29:49 -0700 Subject: [PATCH 380/543] Reduce callsites to "ArcanistDifferentialRevisionStatus" in Phabricator Summary: Ref T2543. These are currently numeric values, like "0" and "3". I want to replace them with strings, like "accepted", and move definitions from Arcanist to Phabricator. To set the stage for this, reduce the number of callsites where Phabricator invokes `ArcanistDifferentialRevisionStatus`. This is just the easy ones. I'll hold this until the release cut. Test Plan: - Called `differential.find`. - Called `differential.getrevision`. - Called `differential.query`. - Removed all reviewers from a revision, saw warning. - Abandoned the no-reviewers revision, no more warning. - Attached a revision to a task to get it to show the state icon with the status on a tooltip. - Viewed revision bucketing on dashboard. - Used `bin/search index` to reindex a revision. - Hit the "Land Revision" endpoint. I didn't explicitly test these cases: - Doorkeeper Asana integration, since setup takes a thousand years. - Disambiguation logic when multiple hashes match, since setup is also very involved. - Releeph because it's Releeph. Reviewers: chad Reviewed By: chad Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18339 --- .../DifferentialFindConduitAPIMethod.php | 4 +- ...ifferentialGetRevisionConduitAPIMethod.php | 4 +- .../DifferentialQueryConduitAPIMethod.php | 4 +- .../customfield/DifferentialBranchField.php | 9 ++-- .../DifferentialReviewersField.php | 3 +- ...alDoorkeeperRevisionFeedStoryPublisher.php | 6 +-- .../phid/DifferentialRevisionPHIDType.php | 3 +- ...tialRevisionRequiredActionResultBucket.php | 15 ++---- .../DifferentialRevisionResultBucket.php | 3 +- .../DifferentialRevisionSearchEngine.php | 4 +- .../DifferentialRevisionFulltextEngine.php | 3 +- .../storage/DifferentialRevision.php | 5 ++ .../DiffusionLowLevelCommitFieldsQuery.php | 3 +- .../DrydockLandRepositoryOperation.php | 49 +++++++++---------- ...entialReleephRequestFieldSpecification.php | 3 +- 15 files changed, 49 insertions(+), 69 deletions(-) diff --git a/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php index 92dce067fe..79d3fc5d7d 100644 --- a/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php @@ -88,9 +88,7 @@ final class DifferentialFindConduitAPIMethod 'uri' => PhabricatorEnv::getProductionURI('/D'.$id), 'dateCreated' => $revision->getDateCreated(), 'authorPHID' => $revision->getAuthorPHID(), - 'statusName' => - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $revision->getStatus()), + 'statusName' => $revision->getStatusDisplayName(), 'sourcePath' => $diff->getSourcePath(), ); } diff --git a/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php index b05efb5c15..9109849292 100644 --- a/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php @@ -83,9 +83,7 @@ final class DifferentialGetRevisionConduitAPIMethod 'uri' => PhabricatorEnv::getURI('/D'.$revision->getID()), 'title' => $revision->getTitle(), 'status' => $revision->getStatus(), - 'statusName' => - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $revision->getStatus()), + 'statusName' => $revision->getStatusDisplayName(), 'summary' => $revision->getSummary(), 'testPlan' => $revision->getTestPlan(), 'lineCount' => $revision->getLineCount(), diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php index 3b88087a67..1051983324 100644 --- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php @@ -221,9 +221,7 @@ final class DifferentialQueryConduitAPIMethod 'dateModified' => $revision->getDateModified(), 'authorPHID' => $revision->getAuthorPHID(), 'status' => $revision->getStatus(), - 'statusName' => - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $revision->getStatus()), + 'statusName' => $revision->getStatusDisplayName(), 'properties' => $revision->getProperties(), 'branch' => $diff->getBranch(), 'summary' => $revision->getSummary(), diff --git a/src/applications/differential/customfield/DifferentialBranchField.php b/src/applications/differential/customfield/DifferentialBranchField.php index 16be0ce0c9..2387e3cd3f 100644 --- a/src/applications/differential/customfield/DifferentialBranchField.php +++ b/src/applications/differential/customfield/DifferentialBranchField.php @@ -76,16 +76,17 @@ final class DifferentialBranchField PhabricatorApplicationTransactionEditor $editor, array $xactions) { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; + $revision = $this->getObject(); // Show the "BRANCH" section only if there's a new diff or the revision // is "Accepted". - if ((!$editor->getDiffUpdateTransaction($xactions)) && - ($this->getObject()->getStatus() != $status_accepted)) { + $is_update = (bool)$editor->getDiffUpdateTransaction($xactions); + $is_accepted = $revision->isAccepted(); + if (!$is_update && !$is_accepted) { return; } - $branch = $this->getBranchDescription($this->getObject()->getActiveDiff()); + $branch = $this->getBranchDescription($revision->getActiveDiff()); if ($branch === null) { return; } diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index 7633bfb492..f835854e2f 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -68,8 +68,7 @@ final class DifferentialReviewersField public function getWarningsForRevisionHeader(array $handles) { $revision = $this->getObject(); - $status_needs_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - if ($revision->getStatus() != $status_needs_review) { + if (!$revision->isNeedsReview()) { return array(); } diff --git a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php index 9583486c98..59223f20da 100644 --- a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php +++ b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php @@ -35,8 +35,7 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher } public function getActiveUserPHIDs($object) { - $status = $object->getStatus(); - if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { + if ($object->isNeedsReview()) { return $object->getReviewerPHIDs(); } else { return array(); @@ -44,8 +43,7 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher } public function getPassiveUserPHIDs($object) { - $status = $object->getStatus(); - if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { + if ($object->isNeedsReview()) { return array(); } else { return $object->getReviewerPHIDs(); diff --git a/src/applications/differential/phid/DifferentialRevisionPHIDType.php b/src/applications/differential/phid/DifferentialRevisionPHIDType.php index 5a6cf701ae..8547ec9e83 100644 --- a/src/applications/differential/phid/DifferentialRevisionPHIDType.php +++ b/src/applications/differential/phid/DifferentialRevisionPHIDType.php @@ -50,8 +50,7 @@ final class DifferentialRevisionPHIDType extends PhabricatorPHIDType { $icon = DifferentialRevisionStatus::getRevisionStatusIcon($status); $color = DifferentialRevisionStatus::getRevisionStatusColor($status); - $name = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $status); + $name = $revision->getStatusDisplayName(); $handle ->setStateIcon($icon) diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index 6d83d97a47..3e6daa6317 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -134,13 +134,11 @@ final class DifferentialRevisionRequiredActionResultBucket } private function filterShouldLand(array $phids) { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - $objects = $this->getRevisionsAuthored($this->objects, $phids); $results = array(); foreach ($objects as $key => $object) { - if ($object->getStatus() != $status_accepted) { + if (!$object->isAccepted()) { continue; } @@ -175,13 +173,11 @@ final class DifferentialRevisionRequiredActionResultBucket } private function filterWaitingForReview(array $phids) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - $objects = $this->getRevisionsAuthored($this->objects, $phids); $results = array(); foreach ($objects as $key => $object) { - if ($object->getStatus() != $status_review) { + if (!$object->isNeedsReview()) { continue; } @@ -217,16 +213,11 @@ final class DifferentialRevisionRequiredActionResultBucket } private function filterWaitingOnOtherReviewers(array $phids) { - $statuses = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, - ); - $statuses = array_fuse($statuses); - $objects = $this->getRevisionsNotAuthored($this->objects, $phids); $results = array(); foreach ($objects as $key => $object) { - if (!isset($statuses[$object->getStatus()])) { + if (!$object->isNeedsReview()) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index 762e2d97f4..54705649eb 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -15,9 +15,8 @@ abstract class DifferentialRevisionResultBucket $objects = $this->getRevisionsNotAuthored($objects, $phids); - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; foreach ($objects as $key => $object) { - if ($object->getStatus() != $status_review) { + if (!$object->isNeedsReview()) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 4248119cb9..ef88c0ea1c 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -235,11 +235,9 @@ final class DifferentialRevisionSearchEngine } private function loadUnlandedDependencies(array $revisions) { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - $phids = array(); foreach ($revisions as $revision) { - if ($revision->getStatus() != $status_accepted) { + if (!$revision->isAccepted()) { continue; } diff --git a/src/applications/differential/search/DifferentialRevisionFulltextEngine.php b/src/applications/differential/search/DifferentialRevisionFulltextEngine.php index 45639edbbe..cdb9f1a86d 100644 --- a/src/applications/differential/search/DifferentialRevisionFulltextEngine.php +++ b/src/applications/differential/search/DifferentialRevisionFulltextEngine.php @@ -34,8 +34,7 @@ final class DifferentialRevisionFulltextEngine // If a revision needs review, the owners are the reviewers. Otherwise, the // owner is the author (e.g., accepted, rejected, closed). - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - if ($revision->getStatus() == $status_review) { + if ($revision->isNeedsReview()) { $reviewers = $revision->getReviewerPHIDs(); $reviewers = array_fuse($reviewers); diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 7ad01c02ca..cd6df4b6e5 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -626,6 +626,11 @@ final class DifferentialRevision extends DifferentialDAO return ($this->getStatus() == $status_accepted); } + public function isNeedsReview() { + $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; + return ($this->getStatus() == $status_review); + } + public function getStatusIcon() { $map = array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php index ab8f5e3e2e..a0548d5178 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php @@ -122,9 +122,8 @@ final class DiffusionLowLevelCommitFieldsQuery $revisions = array_reverse($revisions); // Try to find an accepted revision first. - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; foreach ($revisions as $revision) { - if ($revision->getStatus() == $status_accepted) { + if ($revision->isAccepted()) { return $revision; } } diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 4e306772a3..1ccc82eb7b 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -289,31 +289,30 @@ final class DrydockLandRepositoryOperation ); } - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - if ($revision->getStatus() != $status_accepted) { - switch ($revision->getStatus()) { - case ArcanistDifferentialRevisionStatus::CLOSED: - return array( - 'title' => pht('Revision Closed'), - 'body' => pht( - 'This revision has already been closed. Only open, accepted '. - 'revisions may land.'), - ); - case ArcanistDifferentialRevisionStatus::ABANDONED: - return array( - 'title' => pht('Revision Abandoned'), - 'body' => pht( - 'This revision has been abandoned. Only accepted revisions '. - 'may land.'), - ); - default: - return array( - 'title' => pht('Revision Not Accepted'), - 'body' => pht( - 'This revision is still under review. Only revisions which '. - 'have been accepted may land.'), - ); - } + if ($revision->isAccepted()) { + // We can land accepted revisions, so continue below. Otherwise, raise + // an error with tailored messaging for the most common cases. + } else if ($revision->isAbandoned()) { + return array( + 'title' => pht('Revision Abandoned'), + 'body' => pht( + 'This revision has been abandoned. Only accepted revisions '. + 'may land.'), + ); + } else if ($revision->isClosed()) { + return array( + 'title' => pht('Revision Closed'), + 'body' => pht( + 'This revision has already been closed. Only open, accepted '. + 'revisions may land.'), + ); + } else { + return array( + 'title' => pht('Revision Not Accepted'), + 'body' => pht( + 'This revision is still under review. Only revisions which '. + 'have been accepted may land.'), + ); } // Check for other operations. Eventually this should probably be more diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index bdb63e6c9c..dd67f5bc59 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -79,8 +79,7 @@ final class DifferentialReleephRequestFieldSpecification extends Phobject { return null; } - $status = $this->getRevision()->getStatus(); - if ($status == ArcanistDifferentialRevisionStatus::CLOSED) { + if ($this->getRevision()->isClosed()) { $verb = $tense[$this->releephAction]['past']; } else { $verb = $tense[$this->releephAction]['future']; From 70088f7eec8fe8e33c7f6deae9dc289395ace42c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Aug 2017 08:08:18 -0700 Subject: [PATCH 381/543] Continue reducing callsites to ArcanistDifferentialRevisionStatus Summary: Ref T2543. Further consolidates status management into DifferentialRevisionStatus. One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial). I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially). Test Plan: - Viewed revisions. - Viewed revision list. - Viewed revisions linked to a task in Maniphest. - Viewed revision graph of dependencies in Differential. - Grepped for `COLOR_STATUS_...` constants. - Grepped for removed method `getRevisionStatusIcon()` (no callsites). - Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline). - Grepped for removed method `isClosedStatus()` (no callsites after other changes). Reviewers: chad Reviewed By: chad Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18340 --- .../constants/DifferentialRevisionStatus.php | 178 +++++++++++------- .../DifferentialRevisionViewController.php | 10 +- .../phid/DifferentialRevisionPHIDType.php | 4 +- .../storage/DifferentialRevision.php | 39 ++-- .../view/DifferentialRevisionListView.php | 5 +- .../graph/DifferentialRevisionGraph.php | 4 +- 6 files changed, 144 insertions(+), 96 deletions(-) diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index 38e6a2dd49..0b2abe51fe 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -1,74 +1,130 @@ - self::COLOR_STATUS_DEFAULT, - ArcanistDifferentialRevisionStatus::NEEDS_REVISION => - self::COLOR_STATUS_RED, - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => - self::COLOR_STATUS_RED, - ArcanistDifferentialRevisionStatus::ACCEPTED => - self::COLOR_STATUS_GREEN, - ArcanistDifferentialRevisionStatus::CLOSED => - self::COLOR_STATUS_DARK, - ArcanistDifferentialRevisionStatus::ABANDONED => - self::COLOR_STATUS_DARK, - ArcanistDifferentialRevisionStatus::IN_PREPARATION => - self::COLOR_STATUS_BLUE, - ); - return idx($map, $status, $default); + public function getIcon() { + return idx($this->spec, 'icon'); } - public static function getRevisionStatusIcon($status) { - $default = 'fa-square-o bluegrey'; - - $map = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW => - 'fa-square-o bluegrey', - ArcanistDifferentialRevisionStatus::NEEDS_REVISION => - 'fa-refresh', - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => - 'fa-headphones', - ArcanistDifferentialRevisionStatus::ACCEPTED => - 'fa-check', - ArcanistDifferentialRevisionStatus::CLOSED => - 'fa-check-square-o', - ArcanistDifferentialRevisionStatus::ABANDONED => - 'fa-plane', - ArcanistDifferentialRevisionStatus::IN_PREPARATION => - 'fa-question-circle', - ); - return idx($map, $status, $default); + public function getIconColor() { + return idx($this->spec, 'color.icon', 'bluegrey'); } - public static function renderFullDescription($status) { - $status_name = - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); + public function getTagColor() { + return idx($this->spec, 'color.tag', 'bluegrey'); + } - $tag = id(new PHUITagView()) - ->setName($status_name) - ->setIcon(self::getRevisionStatusIcon($status)) - ->setColor(self::getRevisionStatusColor($status)) - ->setType(PHUITagView::TYPE_SHADE); + public function getDisplayName() { + return idx($this->spec, 'name'); + } - return $tag; + public function isClosedStatus() { + return idx($this->spec, 'closed'); + } + + public function isAbandoned() { + return ($this->key === self::ABANDONED); + } + + public function isAccepted() { + return ($this->key === self::ACCEPTED); + } + + public function isNeedsReview() { + return ($this->key === self::NEEDS_REVIEW); + } + + public static function newForLegacyStatus($legacy_status) { + $result = new self(); + + $map = self::getMap(); + foreach ($map as $key => $spec) { + if (!isset($spec['legacy'])) { + continue; + } + + if ($spec['legacy'] != $legacy_status) { + continue; + } + + $result->key = $key; + $result->spec = $spec; + break; + } + + return $result; + } + + private static function getMap() { + $close_on_accept = PhabricatorEnv::getEnvConfig( + 'differential.close-on-accept'); + + return array( + self::NEEDS_REVIEW => array( + 'name' => pht('Needs Review'), + 'legacy' => 0, + 'icon' => 'fa-code', + 'closed' => false, + 'color.icon' => 'grey', + 'color.tag' => 'bluegrey', + 'color.ansi' => 'magenta', + ), + self::NEEDS_REVISION => array( + 'name' => pht('Needs Revision'), + 'legacy' => 1, + 'icon' => 'fa-refresh', + 'closed' => false, + 'color.icon' => 'red', + 'color.tag' => 'red', + 'color.ansi' => 'red', + ), + self::CHANGES_PLANNED => array( + 'name' => pht('Changes Planned'), + 'legacy' => 5, + 'icon' => 'fa-headphones', + 'closed' => false, + 'color.icon' => 'red', + 'color.tag' => 'red', + 'color.ansi' => 'red', + ), + self::ACCEPTED => array( + 'name' => pht('Accepted'), + 'legacy' => 2, + 'icon' => 'fa-check', + 'closed' => $close_on_accept, + 'color.icon' => 'green', + 'color.tag' => 'green', + 'color.ansi' => 'green', + ), + self::PUBLISHED => array( + 'name' => pht('Closed'), + 'legacy' => 3, + 'icon' => 'fa-check-square-o', + 'closed' => true, + 'color.icon' => 'black', + 'color.tag' => 'indigo', + 'color.ansi' => 'cyan', + ), + self::ABANDONED => array( + 'name' => pht('Abandoned'), + 'legacy' => 4, + 'icon' => 'fa-plane', + 'closed' => true, + 'color.icon' => 'black', + 'color.tag' => 'indigo', + 'color.ansi' => null, + ), + ); } public static function getClosedStatuses() { @@ -100,8 +156,4 @@ final class DifferentialRevisionStatus extends Phobject { ); } - public static function isClosedStatus($status) { - return in_array($status, self::getClosedStatuses()); - } - } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index ab03ee6d81..5c7f5104f5 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -508,11 +508,13 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setPolicyObject($revision) ->setHeaderIcon('fa-cog'); - $status = $revision->getStatus(); - $status_name = - DifferentialRevisionStatus::renderFullDescription($status); + $status_tag = id(new PHUITagView()) + ->setName($revision->getStatusDisplayName()) + ->setIcon($revision->getStatusIcon()) + ->setColor($revision->getStatusIconColor()) + ->setType(PHUITagView::TYPE_SHADE); - $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); + $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_tag); return $view; } diff --git a/src/applications/differential/phid/DifferentialRevisionPHIDType.php b/src/applications/differential/phid/DifferentialRevisionPHIDType.php index 8547ec9e83..d652a8c056 100644 --- a/src/applications/differential/phid/DifferentialRevisionPHIDType.php +++ b/src/applications/differential/phid/DifferentialRevisionPHIDType.php @@ -48,8 +48,8 @@ final class DifferentialRevisionPHIDType extends PhabricatorPHIDType { $status = $revision->getStatus(); - $icon = DifferentialRevisionStatus::getRevisionStatusIcon($status); - $color = DifferentialRevisionStatus::getRevisionStatusColor($status); + $icon = $revision->getStatusIcon($status); + $color = $revision->getStatusIconColor($status); $name = $revision->getStatusDisplayName(); $handle diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index cd6df4b6e5..c2856f5d20 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -613,47 +613,36 @@ final class DifferentialRevision extends DifferentialDAO } public function isClosed() { - return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); + return $this->getStatusObject()->isClosedStatus(); } public function isAbandoned() { - $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; - return ($this->getStatus() == $status_abandoned); + return $this->getStatusObject()->isAbandoned(); } public function isAccepted() { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - return ($this->getStatus() == $status_accepted); + return $this->getStatusObject()->isAccepted(); } public function isNeedsReview() { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - return ($this->getStatus() == $status_review); + return $this->getStatusObject()->isNeedsReview(); } public function getStatusIcon() { - $map = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW - => 'fa-code grey', - ArcanistDifferentialRevisionStatus::NEEDS_REVISION - => 'fa-refresh red', - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED - => 'fa-headphones red', - ArcanistDifferentialRevisionStatus::ACCEPTED - => 'fa-check green', - ArcanistDifferentialRevisionStatus::CLOSED - => 'fa-check-square-o black', - ArcanistDifferentialRevisionStatus::ABANDONED - => 'fa-plane black', - ); - - return idx($map, $this->getStatus()); + return $this->getStatusObject()->getIcon(); } public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function getStatusIconColor() { + return $this->getStatusObject()->getIconColor(); + } + + public function getStatusObject() { $status = $this->getStatus(); - return ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $status); + return DifferentialRevisionStatus::newForLegacyStatus($status); } public function getFlag(PhabricatorUser $viewer) { diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 5373192b31..ed97435746 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -145,8 +145,11 @@ final class DifferentialRevisionListView extends AphrontView { $item->setDisabled(true); } + $icon = $revision->getStatusIcon(); + $color = $revision->getStatusIconColor(); + $item->setStatusIcon( - $revision->getStatusIcon(), + "{$icon} {$color}", $revision->getStatusDisplayName()); $list->addItem($item); diff --git a/src/infrastructure/graph/DifferentialRevisionGraph.php b/src/infrastructure/graph/DifferentialRevisionGraph.php index 892540f610..715820e9e3 100644 --- a/src/infrastructure/graph/DifferentialRevisionGraph.php +++ b/src/infrastructure/graph/DifferentialRevisionGraph.php @@ -27,10 +27,12 @@ final class DifferentialRevisionGraph if ($object) { $status_icon = $object->getStatusIcon(); + $status_color = $object->getStatusIconColor(); $status_name = $object->getStatusDisplayName(); $status = array( - id(new PHUIIconView())->setIcon($status_icon), + id(new PHUIIconView()) + ->setIcon($status_icon, $status_color), ' ', $status_name, ); From 03ab7224bb4ef1b89fa9f813259f946e2e6a1c9d Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Aug 2017 08:20:20 -0700 Subject: [PATCH 382/543] Reduce STATUS_CLOSED (now internally "Published") revision status callsites Summary: Ref T2543. Add `isPublished()` to mean: exactly the status 'closed', which is now interally called 'published', but still shown as 'closed' to users. We have some callsites which are about "exactly that status", vs "any 'closed' status", e.g. including "abandoned". This also introduces `isChangePlanned()`, which felt less awkward than `isChangesPlanned()` but more consistent than `hasChangesPlanned()` or `isStatusChangesPlanned()` or similar. Test Plan: `grep`, loaded revisions, requested review. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18341 --- .../DifferentialUpdateRevisionConduitAPIMethod.php | 2 +- .../differential/constants/DifferentialRevisionStatus.php | 8 ++++++++ .../differential/storage/DifferentialRevision.php | 8 ++++++++ .../DifferentialRevisionPlanChangesTransaction.php | 4 +--- .../xaction/DifferentialRevisionReopenTransaction.php | 5 +---- .../DifferentialRevisionRequestReviewTransaction.php | 3 +-- .../PhabricatorRepositoryCommitMessageParserWorker.php | 5 +---- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php index 3ad5b07564..d9b0779211 100644 --- a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php @@ -69,7 +69,7 @@ final class DifferentialUpdateRevisionConduitAPIMethod throw new ConduitException('ERR_BAD_REVISION'); } - if ($revision->getStatus() == ArcanistDifferentialRevisionStatus::CLOSED) { + if ($revision->isPublished()) { throw new ConduitException('ERR_CLOSED'); } diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index 0b2abe51fe..caca7ed85b 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -44,6 +44,14 @@ final class DifferentialRevisionStatus extends Phobject { return ($this->key === self::NEEDS_REVIEW); } + public function isPublished() { + return ($this->key === self::PUBLISHED); + } + + public function isChangePlanned() { + return ($this->key === self::CHANGES_PLANNED); + } + public static function newForLegacyStatus($legacy_status) { $result = new self(); diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index c2856f5d20..a6ce99080a 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -628,6 +628,14 @@ final class DifferentialRevision extends DifferentialDAO return $this->getStatusObject()->isNeedsReview(); } + public function isChangePlanned() { + return $this->getStatusObject()->isChangePlanned(); + } + + public function isPublished() { + return $this->getStatusObject()->isPublished(); + } + public function getStatusIcon() { return $this->getStatusObject()->getIcon(); } diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index e7cdaa2455..58152bdb67 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -56,9 +56,7 @@ final class DifferentialRevisionPlanChangesTransaction } protected function validateAction($object, PhabricatorUser $viewer) { - $status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - - if ($object->getStatus() == $status_planned) { + if ($object->isChangePlanned()) { throw new Exception( pht( 'You can not request review of this revision because this '. diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index 84015b9286..e2b217603a 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -39,10 +39,7 @@ final class DifferentialRevisionReopenTransaction } protected function validateAction($object, PhabricatorUser $viewer) { - // Note that we're testing for "Closed", exactly, not just any closed - // status. - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - if ($object->getStatus() != $status_closed) { + if ($object->isPublished()) { throw new Exception( pht( 'You can not reopen this revision because it is not closed. '. diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 32fcb3271e..65b96d6d8e 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -37,8 +37,7 @@ final class DifferentialRevisionRequestReviewTransaction } protected function validateAction($object, PhabricatorUser $viewer) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - if ($object->getStatus() == $status_review) { + if ($object->isNeedsReview()) { throw new Exception( pht( 'You can not request review of this revision because this '. diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index f7e3a735a4..ab11a83b38 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -203,10 +203,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $revision->getID(), $commit->getPHID()); - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - $should_close = ($revision->getStatus() != $status_closed) && - $should_autoclose; - + $should_close = !$revision->isPublished() && $should_autoclose; if ($should_close) { $commit_close_xaction = id(new DifferentialTransaction()) ->setTransactionType(DifferentialTransaction::TYPE_ACTION) From 46d1596bf797890f9455147186f5e74369b5cec4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Aug 2017 10:19:28 -0700 Subject: [PATCH 383/543] Pull legacy revision query status filters out of the main Query class Summary: Ref T2543. Currently, Differential uses a set of hard-coded query filters (like "open" and "closed") to query revisions by status (for example, "open" means any of "review, revision, changes planned, accepted [usually]"). In other applications, like Maniphest, we've replaced this with a low level list of the actual statuses, plus higher level convenience UI through tokenizer functions. This basically has all of the benefits of the hard-coded filters with none of the drawbacks, and is generally more flexible. I'd like to do that in Differential, too, although we'll need to keep the legacy maps around for a while because they're used by `differential.find` and `differential.getrevision`. To prepare for this, pull all the legacy stuff out into a separate class. Then I'll modernize where I can, and we can get rid of this junk some day. Test Plan: Grepped for `RevisionQuery::STATUS`. Ran queries via Differential UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18343 --- src/__phutil_library_map__.php | 2 + .../DifferentialFindConduitAPIMethod.php | 4 +- .../DifferentialQueryConduitAPIMethod.php | 7 +- .../constants/DifferentialLegacyQuery.php | 86 +++++++++++++++++++ .../constants/DifferentialRevisionStatus.php | 49 +++++------ .../DifferentialDiffViewController.php | 2 +- .../DifferentialRevisionViewController.php | 2 +- .../query/DifferentialRevisionQuery.php | 73 ++-------------- .../DifferentialRevisionSearchEngine.php | 18 ++-- .../controller/DiffusionBrowseController.php | 2 +- 10 files changed, 130 insertions(+), 115 deletions(-) create mode 100644 src/applications/differential/constants/DifferentialLegacyQuery.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d44e9370bb..4089df2bbb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -471,6 +471,7 @@ phutil_register_library_map(array( 'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', + 'DifferentialLegacyQuery' => 'applications/differential/constants/DifferentialLegacyQuery.php', 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', @@ -5447,6 +5448,7 @@ phutil_register_library_map(array( 'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', 'DifferentialLegacyHunk' => 'DifferentialHunk', + 'DifferentialLegacyQuery' => 'Phobject', 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialHarbormasterField', 'DifferentialLintStatus' => 'Phobject', diff --git a/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php index 79d3fc5d7d..a199f599e8 100644 --- a/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php @@ -52,12 +52,12 @@ final class DifferentialFindConduitAPIMethod switch ($type) { case 'open': $query - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withStatus(DifferentialLegacyQuery::STATUS_OPEN) ->withAuthors($guids); break; case 'committable': $query - ->withStatus(DifferentialRevisionQuery::STATUS_ACCEPTED) + ->withStatus(DifferentialLegacyQuery::STATUS_ACCEPTED) ->withAuthors($guids); break; case 'revision-ids': diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php index 1051983324..329a44c857 100644 --- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php @@ -25,12 +25,7 @@ final class DifferentialQueryConduitAPIMethod $hash_types = ArcanistDifferentialRevisionHash::getTypes(); $hash_const = $this->formatStringConstants($hash_types); - $status_types = array( - DifferentialRevisionQuery::STATUS_ANY, - DifferentialRevisionQuery::STATUS_OPEN, - DifferentialRevisionQuery::STATUS_ACCEPTED, - DifferentialRevisionQuery::STATUS_CLOSED, - ); + $status_types = DifferentialLegacyQuery::getAllConstants(); $status_const = $this->formatStringConstants($status_types); $order_types = array( diff --git a/src/applications/differential/constants/DifferentialLegacyQuery.php b/src/applications/differential/constants/DifferentialLegacyQuery.php new file mode 100644 index 0000000000..4a20267f10 --- /dev/null +++ b/src/applications/differential/constants/DifferentialLegacyQuery.php @@ -0,0 +1,86 @@ +getLegacyKey(); + if ($legacy_key !== null) { + $values[] = $legacy_key; + } + } + + return $values; + } + + private static function getMap() { + $all = array( + DifferentialRevisionStatus::NEEDS_REVIEW, + DifferentialRevisionStatus::NEEDS_REVISION, + DifferentialRevisionStatus::CHANGES_PLANNED, + DifferentialRevisionStatus::ACCEPTED, + DifferentialRevisionStatus::PUBLISHED, + DifferentialRevisionStatus::ABANDONED, + ); + + $open = array(); + $closed = array(); + + foreach ($all as $status) { + $status_object = DifferentialRevisionStatus::newForStatus($status); + if ($status_object->isClosedStatus()) { + $closed[] = $status_object->getKey(); + } else { + $open[] = $status_object->getKey(); + } + } + + return array( + self::STATUS_ANY => $all, + self::STATUS_OPEN => $open, + self::STATUS_ACCEPTED => array( + DifferentialRevisionStatus::ACCEPTED, + ), + self::STATUS_NEEDS_REVIEW => array( + DifferentialRevisionStatus::NEEDS_REVIEW, + ), + self::STATUS_NEEDS_REVISION => array( + DifferentialRevisionStatus::NEEDS_REVISION, + ), + self::STATUS_CLOSED => $closed, + self::STATUS_ABANDONED => array( + DifferentialRevisionStatus::ABANDONED, + ), + ); + } + +} diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index caca7ed85b..37b51f1b3b 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -12,6 +12,14 @@ final class DifferentialRevisionStatus extends Phobject { private $key; private $spec = array(); + public function getKey() { + return $this->key; + } + + public function getLegacyKey() { + return idx($this->spec, 'legacy'); + } + public function getIcon() { return idx($this->spec, 'icon'); } @@ -52,6 +60,18 @@ final class DifferentialRevisionStatus extends Phobject { return ($this->key === self::CHANGES_PLANNED); } + public static function newForStatus($status) { + $result = new self(); + + $map = self::getMap(); + if (isset($map[$status])) { + $result->key = $status; + $result->spec = $map[$status]; + } + + return $result; + } + public static function newForLegacyStatus($legacy_status) { $result = new self(); @@ -135,33 +155,4 @@ final class DifferentialRevisionStatus extends Phobject { ); } - public static function getClosedStatuses() { - $statuses = array( - ArcanistDifferentialRevisionStatus::CLOSED, - ArcanistDifferentialRevisionStatus::ABANDONED, - ); - - if (PhabricatorEnv::getEnvConfig('differential.close-on-accept')) { - $statuses[] = ArcanistDifferentialRevisionStatus::ACCEPTED; - } - - return $statuses; - } - - public static function getOpenStatuses() { - return array_diff(self::getAllStatuses(), self::getClosedStatuses()); - } - - public static function getAllStatuses() { - return array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, - ArcanistDifferentialRevisionStatus::NEEDS_REVISION, - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED, - ArcanistDifferentialRevisionStatus::ACCEPTED, - ArcanistDifferentialRevisionStatus::CLOSED, - ArcanistDifferentialRevisionStatus::ABANDONED, - ArcanistDifferentialRevisionStatus::IN_PREPARATION, - ); - } - } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index dae65bb200..488d2cec82 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -177,7 +177,7 @@ final class DifferentialDiffViewController extends DifferentialController { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withAuthors(array($viewer->getPHID())) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withStatus(DifferentialLegacyQuery::STATUS_OPEN) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 5c7f5104f5..81b2aebda6 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -808,7 +808,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $query = id(new DifferentialRevisionQuery()) ->setViewer($this->getRequest()->getUser()) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withStatus(DifferentialLegacyQuery::STATUS_OPEN) ->withUpdatedEpochBetween($recent, null) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setLimit(10) diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 701463e6a7..f53ec46a9f 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -1,13 +1,6 @@ withStatus(DifferentialRevisionQuery::STATUS_OPEN) - * ->execute(); - * * @task config Query Configuration * @task exec Query Execution * @task internal Internals @@ -18,13 +11,6 @@ final class DifferentialRevisionQuery private $pathIDs = array(); private $status = 'status-any'; - const STATUS_ANY = 'status-any'; - const STATUS_OPEN = 'status-open'; - const STATUS_ACCEPTED = 'status-accepted'; - const STATUS_NEEDS_REVIEW = 'status-needs-review'; - const STATUS_NEEDS_REVISION = 'status-needs-revision'; - const STATUS_CLOSED = 'status-closed'; - const STATUS_ABANDONED = 'status-abandoned'; private $authors = array(); private $draftAuthors = array(); @@ -149,7 +135,7 @@ final class DifferentialRevisionQuery /** * Filter results to revisions with a given status. Provide a class constant, - * such as `DifferentialRevisionQuery::STATUS_OPEN`. + * such as `DifferentialLegacyQuery::STATUS_OPEN`. * * @param const Class STATUS constant, like STATUS_OPEN. * @return this @@ -711,57 +697,12 @@ final class DifferentialRevisionQuery // NOTE: Although the status constants are integers in PHP, the column is a // string column in MySQL, and MySQL won't use keys on string columns if // you put integers in the query. - - switch ($this->status) { - case self::STATUS_ANY: - break; - case self::STATUS_OPEN: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - DifferentialRevisionStatus::getOpenStatuses()); - break; - case self::STATUS_NEEDS_REVIEW: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, - )); - break; - case self::STATUS_NEEDS_REVISION: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::NEEDS_REVISION, - )); - break; - case self::STATUS_ACCEPTED: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::ACCEPTED, - )); - break; - case self::STATUS_CLOSED: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - DifferentialRevisionStatus::getClosedStatuses()); - break; - case self::STATUS_ABANDONED: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::ABANDONED, - )); - break; - default: - throw new Exception( - pht("Unknown revision status filter constant '%s'!", $this->status)); + $statuses = DifferentialLegacyQuery::getQueryValues($this->status); + if ($statuses !== null) { + $where[] = qsprintf( + $conn_r, + 'r.status IN (%Ls)', + $statuses); } $where[] = $this->buildWhereClauseParts($conn_r); diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index ef88c0ea1c..8cb8ffd2fb 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -115,7 +115,7 @@ final class DifferentialRevisionSearchEngine return $query ->setParameter('responsiblePHIDs', array($viewer->getPHID())) - ->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN) + ->setParameter('status', DifferentialLegacyQuery::STATUS_OPEN) ->setParameter('bucket', $bucket_key); case 'authored': return $query @@ -129,13 +129,13 @@ final class DifferentialRevisionSearchEngine private function getStatusOptions() { return array( - DifferentialRevisionQuery::STATUS_ANY => pht('All'), - DifferentialRevisionQuery::STATUS_OPEN => pht('Open'), - DifferentialRevisionQuery::STATUS_ACCEPTED => pht('Accepted'), - DifferentialRevisionQuery::STATUS_NEEDS_REVIEW => pht('Needs Review'), - DifferentialRevisionQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'), - DifferentialRevisionQuery::STATUS_CLOSED => pht('Closed'), - DifferentialRevisionQuery::STATUS_ABANDONED => pht('Abandoned'), + DifferentialLegacyQuery::STATUS_ANY => pht('All'), + DifferentialLegacyQuery::STATUS_OPEN => pht('Open'), + DifferentialLegacyQuery::STATUS_ACCEPTED => pht('Accepted'), + DifferentialLegacyQuery::STATUS_NEEDS_REVIEW => pht('Needs Review'), + DifferentialLegacyQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'), + DifferentialLegacyQuery::STATUS_CLOSED => pht('Closed'), + DifferentialLegacyQuery::STATUS_ABANDONED => pht('Abandoned'), ); } @@ -267,7 +267,7 @@ final class DifferentialRevisionSearchEngine $blocking_revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withPHIDs($revision_phids) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withStatus(DifferentialLegacyQuery::STATUS_OPEN) ->execute(); $blocking_revisions = mpull($blocking_revisions, null, 'getPHID'); diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 06c8336f5c..32381207fa 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1758,7 +1758,7 @@ final class DiffusionBrowseController extends DiffusionController { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withPath($repository->getID(), $path_id) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withStatus(DifferentialLegacyQuery::STATUS_OPEN) ->withUpdatedEpochBetween($recent, null) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setLimit(10) From bd47d001b5482d8f8160a011ac8a0dd76f147e13 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Wed, 9 Aug 2017 22:49:25 +0000 Subject: [PATCH 384/543] Remove chatbot example configuration Summary: This is no longer needed after the chatbot was removed in D17756. Test Plan: N/A Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18378 --- resources/chatbot/example_config.json | 24 ------------------- .../files/storage/PhabricatorFile.php | 8 +++++-- 2 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 resources/chatbot/example_config.json diff --git a/resources/chatbot/example_config.json b/resources/chatbot/example_config.json deleted file mode 100644 index 7d150e65ae..0000000000 --- a/resources/chatbot/example_config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "server" : "irc.freenode.net", - "port" : 6667, - "nick" : "phabot", - "join" : [ - "#phabot-test" - ], - "handlers" : [ - "PhabricatorBotObjectNameHandler", - "PhabricatorBotSymbolHandler", - "PhabricatorBotLogHandler", - "PhabricatorBotFeedNotificationHandler", - "PhabricatorBotWhatsNewHandler", - "PhabricatorBotMacroHandler" - ], - - "conduit.uri" : null, - "conduit.token" : null, - - "macro.size" : 48, - "macro.aspect" : 0.66, - - "notification.channels" : ["#phabot-test"] -} diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 19f0bed90a..97c2ef6acc 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -854,7 +854,7 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getTransformedURI($transform->getTransformKey()); } - private function getTransformedURI($transform) { + private function getTransformedURI($transform, $cdn) { $parts = array(); $parts[] = 'file'; $parts[] = 'xform'; @@ -871,7 +871,11 @@ final class PhabricatorFile extends PhabricatorFileDAO $path = implode('/', $parts); $path = $path.'/'; - return PhabricatorEnv::getCDNURI($path); + if ($cdn) { + return PhabricatorEnv::getCDNURI($path); + } else { + return PhabricatorEnv::getURI($path); + } } public function isViewableInBrowser() { From 3ba196152f9bfb4bbcc909b94b4fafa7847f49c3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 9 Aug 2017 20:01:31 -0700 Subject: [PATCH 385/543] Clean up some dialog spacing Summary: Makes dialogs a little wider, form dialogs a lot wider (space controls). Also cleans up Passphrase dialogs. Fixes T12833. I think forms probably need to move to tables for better layout flexibility like veritical alignment. Test Plan: Passphrase create, edit, etc. Other dialogs. Reviewers: epriestley Subscribers: Korvin Maniphest Tasks: T12833 Differential Revision: https://secure.phabricator.com/D18382 --- resources/celerity/map.php | 10 +++++----- .../passphrase/view/PassphraseCredentialControl.php | 5 +++-- webroot/rsrc/css/aphront/dialog-view.css | 4 ++-- webroot/rsrc/css/phui/phui-form-view.css | 1 + 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a8b40f8e88..c0bc1bf629 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '5a682e14', + 'core.pkg.css' => '0e4a68ad', 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -26,7 +26,7 @@ return array( 'rsrc/audio/basic/ting.mp3' => '17660001', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/dark-console.css' => 'f7b071f1', - 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', + 'rsrc/css/aphront/dialog-view.css' => '6bfc244b', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', 'rsrc/css/aphront/notification.css' => '3f6c89c9', @@ -155,7 +155,7 @@ return array( 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', - 'rsrc/css/phui/phui-form-view.css' => '6175808d', + 'rsrc/css/phui/phui-form-view.css' => 'ae9f8d16', 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => 'e7de7ee2', @@ -539,7 +539,7 @@ return array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', 'aphront-dark-console-css' => 'f7b071f1', - 'aphront-dialog-view-css' => '685c7e2d', + 'aphront-dialog-view-css' => '6bfc244b', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', @@ -843,7 +843,7 @@ return array( 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', 'phui-form-css' => '7aaa04e3', - 'phui-form-view-css' => '6175808d', + 'phui-form-view-css' => 'ae9f8d16', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => 'e7de7ee2', 'phui-hovercard' => '1bd28176', diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 411afd6f85..1c189e30e7 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -113,11 +113,12 @@ final class PassphraseCredentialControl extends AphrontFormControl { 'a', array( 'href' => '#', - 'class' => 'button button-grey', + 'class' => 'button button-grey mll', 'sigil' => 'passphrase-credential-add', 'mustcapture' => true, + 'style' => 'height: 20px;', // move aphront-form to tables ), - pht('Add Credential')); + pht('Add New Credential')); } else { $button = null; } diff --git a/webroot/rsrc/css/aphront/dialog-view.css b/webroot/rsrc/css/aphront/dialog-view.css index 5809ab117e..153685548e 100644 --- a/webroot/rsrc/css/aphront/dialog-view.css +++ b/webroot/rsrc/css/aphront/dialog-view.css @@ -3,7 +3,7 @@ */ .aphront-dialog-view { - width: 560px; + width: 580px; margin: 32px auto 16px; border: 1px solid {$lightblueborder}; border-radius: 3px; @@ -32,7 +32,7 @@ } .aphront-dialog-view-width-form { - width: 640px; + width: 820px; } .aphront-dialog-view-width-full { diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index da1a524abb..1b7400656a 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -196,6 +196,7 @@ .aphront-form-control-markup .aphront-form-input { font-size: {$normalfontsize}; + padding: 3px 0; } .aphront-form-control-static .aphront-form-input { From 71eaf3e8c4d61203ab2887ab1a294ea81bd6ae83 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Thu, 10 Aug 2017 08:59:52 +1000 Subject: [PATCH 386/543] Remove code that was accidentally landed Summary: See some discussion in D18378. Test Plan: N/A Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18380 --- src/applications/files/storage/PhabricatorFile.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 97c2ef6acc..19f0bed90a 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -854,7 +854,7 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getTransformedURI($transform->getTransformKey()); } - private function getTransformedURI($transform, $cdn) { + private function getTransformedURI($transform) { $parts = array(); $parts[] = 'file'; $parts[] = 'xform'; @@ -871,11 +871,7 @@ final class PhabricatorFile extends PhabricatorFileDAO $path = implode('/', $parts); $path = $path.'/'; - if ($cdn) { - return PhabricatorEnv::getCDNURI($path); - } else { - return PhabricatorEnv::getURI($path); - } + return PhabricatorEnv::getCDNURI($path); } public function isViewableInBrowser() { From 92c49c3772d856472f3b457f4f5d4e32f96c0e9b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 9 Aug 2017 19:31:05 -0700 Subject: [PATCH 387/543] Minor UX tweaks to Phortune autopay Summary: Fixes T12958. Adds a success message when card is added, also switches to use radio buttons for clarity. Updated redirect uri for deleting methods as well. Test Plan: Add cards, remove cards. {F5091084} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12958 Differential Revision: https://secure.phabricator.com/D18381 --- .../PhortunePaymentMethodCreateController.php | 3 ++- ...PhortunePaymentMethodDisableController.php | 5 ++-- .../PhortuneSubscriptionEditController.php | 27 ++++++++++++++----- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php index 794f97aa8e..87bddd4d33 100644 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php @@ -123,7 +123,8 @@ final class PhortunePaymentMethodCreateController $next_uri = $this->getApplicationURI( "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); } else if ($subscription_id) { - $next_uri = $cancel_uri; + $next_uri = new PhutilURI($cancel_uri); + $next_uri->setQueryParam('added', true); } else { $account_uri = $this->getApplicationURI($account->getID().'/'); $next_uri = new PhutilURI($account_uri); diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php index d00db2725f..f5feec8a29 100644 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php @@ -25,11 +25,12 @@ final class PhortunePaymentMethodDisableController } $account = $method->getAccount(); - $account_uri = $this->getApplicationURI($account->getID().'/'); + $account_id = $account->getID(); + $account_uri = $this->getApplicationURI("/account/billing/{$account_id}/"); if ($request->isFormPost()) { - // TODO: ApplicationTransactions! + // TODO: ApplicationTransactions!!!! $method ->setStatus(PhortunePaymentMethod::STATUS_DISABLED) ->save(); diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php index 185bbdbcc6..e7287f3d29 100644 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php +++ b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php @@ -4,6 +4,7 @@ final class PhortuneSubscriptionEditController extends PhortuneController { public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + $added = $request->getBool('added'); $subscription = id(new PhortuneSubscriptionQuery()) ->setViewer($viewer) @@ -112,6 +113,7 @@ final class PhortuneSubscriptionEditController extends PhortuneController { pht('Subscription %d', $subscription->getID()), $view_uri); $crumbs->addTextCrumb(pht('Edit')); + $crumbs->setBorder(true); $uri = $this->getApplicationURI($account->getID().'/card/new/'); @@ -127,15 +129,19 @@ final class PhortuneSubscriptionEditController extends PhortuneController { ), pht('Add Payment Method...')); + $radio = id(new AphrontFormRadioButtonControl()) + ->setName('defaultPaymentMethodPHID') + ->setLabel(pht('Autopay With')) + ->setValue($current_phid) + ->setError($e_method); + + foreach ($options as $key => $value) { + $radio->addButton($key, $value, null); + } + $form = id(new AphrontFormView()) ->setUser($viewer) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('defaultPaymentMethodPHID') - ->setLabel(pht('Autopay With')) - ->setValue($current_phid) - ->setError($e_method) - ->setOptions($options)) + ->appendChild($radio) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($add_method_button)) @@ -151,6 +157,13 @@ final class PhortuneSubscriptionEditController extends PhortuneController { ->setFormErrors($errors) ->appendChild($form); + if ($added) { + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_SUCCESS) + ->appendChild(pht('Payment method has been successfully added.')); + $box->setInfoView($info_view); + } + $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit %s', $subscription->getSubscriptionName())) ->setHeaderIcon('fa-pencil'); From e89087fc5136444fb669aa74751ad825214976c0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 03:31:20 -0700 Subject: [PATCH 388/543] Fix a hang in fancy date picker for Ye Olde Time Years Summary: Fixes T12960. When the user enters a date like "1917", we currently loop ~20 million times. Instead: - Be a little more careful about parsing. - Javascript's default behavior of interpreting "2001-02-31" as "2001-03-03" ("February 31" -> "March 3") already seems reasonable, so just let it do that. Test Plan: Verified these behaviors: - `2017-08-08` - Valid, recent. - `17-08-08` - Valid, recent. - `1917-08-08` - Valid, a century ago, no loop. - `2017-02-31` - "February 31", interpreted as "March 3" by Javascsript, seems reasonable. - `Quack` - Default, current time. - `0/0/0`, `0/99/0` - Default, current time. Reviewers: avivey, chad Reviewed By: chad Maniphest Tasks: T12960 Differential Revision: https://secure.phabricator.com/D18383 --- resources/celerity/map.php | 18 ++++---- .../rsrc/js/core/behavior-fancy-datepicker.js | 44 ++++++++++++------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c0bc1bf629..54832d23ae 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -484,7 +484,7 @@ return array( 'rsrc/js/core/behavior-device.js' => 'bb1dd507', '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' => 'a9210d03', + 'rsrc/js/core/behavior-fancy-datepicker.js' => 'ecf4e799', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -633,7 +633,7 @@ return array( 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => 'b41537c9', - 'javelin-behavior-fancy-datepicker' => 'a9210d03', + 'javelin-behavior-fancy-datepicker' => 'ecf4e799', 'javelin-behavior-global-drag-and-drop' => '960f6a39', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', @@ -1716,13 +1716,6 @@ return array( 'javelin-uri', 'phabricator-keyboard-shortcut', ), - 'a9210d03' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), 'a9f88de2' => array( 'javelin-behavior', 'javelin-dom', @@ -2105,6 +2098,13 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'ecf4e799' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js index 15558dbfe1..afd5ff25ad 100644 --- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js +++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js @@ -287,26 +287,38 @@ JX.behavior('fancy-datepicker', function(config, statics) { }; function getValidDate() { - var written_date = new Date(value_y, value_m-1, value_d); + var year_int = parseInt(value_y, 10); + if (isNaN(year_int) || year_int < 0) { + return new Date(); + } + + // If the user enters "11" for the year, interpret it as "2011" (which + // is almost certainly what they mean) not "1911" (which is the default + // behavior of Javascript). + if (year_int < 70) { + year_int += 2000; + } + + var month_int = parseInt(value_m, 10); + if (isNaN(month_int) || month_int < 1 || month_int > 12) { + return new Date(); + } + + // In Javascript, January is "0", not "1", so adjust the value down. + month_int = month_int - 1; + + var day_int = parseInt(value_d, 10); + if (isNaN(day_int) || day_int < 1 || day_int > 31) { + return new Date(); + } + + var written_date = new Date(year_int, month_int, day_int); if (isNaN(written_date.getTime())) { return new Date(); - } else { - //year 01 should be 2001, not 1901 - if (written_date.getYear() < 70) { - value_y += 2000; - written_date = new Date(value_y, value_m-1, value_d); - } - - // adjust for a date like February 31 - var adjust = 1; - while (written_date.getMonth() !== value_m-1) { - written_date = new Date(value_y, value_m-1, value_d-adjust); - adjust++; - } - - return written_date; } + + return written_date; } From 8443366f32d3f8bda19e67744386a68c0cf4387f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 06:04:52 -0700 Subject: [PATCH 389/543] Remove `bin/files purge` workflow Summary: Fixes T12948. See that task for substantial discussion and context. Briefly: - This workflow is very old, and won't work for large (>2GB) files. - This workflow has become more dangerous than it once was, and can fail in several ways that delete data and/or make recovery much more difficult (see T12948 for more discussion). - This was originally added in D6068, which is a bit muddled, but looks like "one install ran into a weird issue so I wrote a script for them"; this would be a Consulting/Support issue and not come upstream today. I can't identify any arguments for retaining this workflow there, at least. Test Plan: - Grepped for `files purge`, got nothing. - Grepped for `purge`, looked for anything that looked like instructions or documentation, got nothing. I don't recall recommending anyone run this script in many years, and didn't even remember that it existed or what it did when T12948 was reported, so I believe it is not in widespread use. Reviewers: joshuaspence, chad Reviewed By: joshuaspence Maniphest Tasks: T12948 Differential Revision: https://secure.phabricator.com/D18384 --- src/__phutil_library_map__.php | 2 - ...habricatorFilesManagementPurgeWorkflow.php | 71 ------------------- 2 files changed, 73 deletions(-) delete mode 100644 src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4089df2bbb..2b45c3856a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2900,7 +2900,6 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php', 'PhabricatorFilesManagementIntegrityWorkflow' => 'applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php', 'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php', - 'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php', 'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php', 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php', @@ -8238,7 +8237,6 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementIntegrityWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow', - 'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile', diff --git a/src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php b/src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php deleted file mode 100644 index bf2c04a584..0000000000 --- a/src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php +++ /dev/null @@ -1,71 +0,0 @@ -setName('purge') - ->setSynopsis(pht('Delete files with missing data.')) - ->setArguments( - array( - array( - 'name' => 'all', - 'help' => pht('Update all files.'), - ), - array( - 'name' => 'dry-run', - 'help' => pht('Show what would be updated.'), - ), - array( - 'name' => 'names', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $iterator = $this->buildIterator($args); - if (!$iterator) { - throw new PhutilArgumentUsageException( - pht( - 'Either specify a list of files to purge, or use `%s` '. - 'to purge all files.', - '--all')); - } - - $is_dry_run = $args->getArg('dry-run'); - - foreach ($iterator as $file) { - $fid = 'F'.$file->getID(); - - try { - $file->loadFileData(); - $okay = true; - } catch (Exception $ex) { - $okay = false; - } - - if ($okay) { - $console->writeOut( - "%s\n", - pht('%s: File data is OK, not purging.', $fid)); - } else { - if ($is_dry_run) { - $console->writeOut( - "%s\n", - pht('%s: Would purge (dry run).', $fid)); - } else { - $console->writeOut( - "%s\n", - pht('%s: Purging.', $fid)); - $file->delete(); - } - } - } - - return 0; - } -} From 7f90ef2d82610f0d30a41d41ac8c7856872af3df Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 08:18:56 -0700 Subject: [PATCH 390/543] Make the "Requested Changes to Prior Diff" reviewer icon red, not bluegrey Summary: See PHI31. The "Accepted Older Revision" icon is (more reasonably) bluegrey, but that rule spilled over here where it doesn't make much sense. "Requested Changes to Prior Diff" remains in effect across updates, but the coloration implies otherwise. Test Plan: "Requested Changes to This Diff" (unchanged): {F5092019} "Requested Changes to Prior Diff" (now red, previously bluegrey): {F5092020} Note that the icons are different so this is technically colorblind-safe, and it's normally not important to distinguish between these two reds anyway. Reviewers: chad, lvital Reviewed By: lvital Subscribers: lvital Differential Revision: https://secure.phabricator.com/D18385 --- .../differential/view/DifferentialReviewersView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 034bf99edb..8ddea7c8c0 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -116,7 +116,7 @@ final class DifferentialReviewersView extends AphrontView { } } else { $icon = 'fa-times-circle-o'; - $color = 'bluegrey'; + $color = 'red'; if ($authority_name !== null) { $label = pht( 'Requested Changes to Prior Diff (by %s)', From a7124f8f7a116d844e979506f2cb91cccb183a68 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 10 Aug 2017 13:22:46 -0700 Subject: [PATCH 391/543] Add status table to Diffusion Branch manage page Summary: Fixes T12832. Adds a basic table (not paginated?) to view tracking and autoclose status. Test Plan: Review a large repository (Krita) with setting various states of tracking and autoclose. {F5092117} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12832 Differential Revision: https://secure.phabricator.com/D18386 --- ...usionRepositoryBranchesManagementPanel.php | 61 ++++++++++++++++++- .../DiffusionRepositoryManagementPanel.php | 4 ++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index aa2bb49ccb..a735b8d580 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -67,6 +67,7 @@ final class DiffusionRepositoryBranchesManagementPanel public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $content = array(); $view = id(new PHUIPropertyListView()) ->setViewer($viewer); @@ -90,8 +91,66 @@ final class DiffusionRepositoryBranchesManagementPanel } $view->addProperty(pht('Autoclose Only'), $autoclose_only); + $content[] = $this->newBox(pht('Branches'), $view); - return $this->newBox(pht('Branches'), $view); + // Branch Autoclose Table + if (!$repository->isImporting()) { + $request = $this->getRequest(); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + $params = array( + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, + 'repository' => $repository->getID(), + ); + + $branches = id(new ConduitCall('diffusion.branchquery', $params)) + ->setUser($viewer) + ->execute(); + $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); + $branches = $pager->sliceResults($branches); + + $rows = array(); + foreach ($branches as $branch) { + $branch_name = $branch->getShortName(); + $tracking = $repository->shouldTrackBranch($branch_name); + $autoclosing = $repository->shouldAutocloseBranch($branch_name); + + $rows[] = array( + $branch_name, + $tracking ? pht('Tracking') : pht('Off'), + $autoclosing ? pht('Autoclose On') : pht('Off'), + ); + } + $branch_table = new AphrontTableView($rows); + $branch_table->setHeaders( + array( + pht('Branch'), + pht('Track'), + pht('Autoclose'), + )); + $branch_table->setColumnClasses( + array( + 'pri', + 'narrow', + 'wide', + )); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Branch Status')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($branch_table) + ->setPager($pager); + $content[] = $box; + } else { + $content[] = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Branch status in unavailable while the repository '. + 'is still importing.')); + } + + return $content; } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index 0098fe1df9..f9e76eb523 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -25,6 +25,10 @@ abstract class DiffusionRepositoryManagementPanel return $this->repository; } + final public function getRequest() { + return $this->controller->getRequest(); + } + final public function setController(PhabricatorController $controller) { $this->controller = $controller; return $this; From 0860b7f27c749dba15aa9936959516440c143c39 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 10 Aug 2017 13:51:09 -0700 Subject: [PATCH 392/543] Update Autoclose document language Summary: Rewords the document to note new location and status table. Test Plan: Read, reread. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18387 --- .../user/userguide/diffusion_autoclose.diviner | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/docs/user/userguide/diffusion_autoclose.diviner b/src/docs/user/userguide/diffusion_autoclose.diviner index f2533bbdc6..796a3b0404 100644 --- a/src/docs/user/userguide/diffusion_autoclose.diviner +++ b/src/docs/user/userguide/diffusion_autoclose.diviner @@ -17,21 +17,19 @@ troubleshoot it. Troubleshooting Autoclose ========================= -You can check if a branch is currently configured to autoclose on the main -repository view, or in the branches list view. Hover over the {icon check} or -{icon times} icon and you should see one of these messages: +You can check if a branch is currently configured to autoclose on the +management page for the given repository under the branches menu item. +You should see one of these statuses next to the name of the branch: - - {icon check} **Autoclose Enabled** Autoclose is active for this branch. - - {icon times} **Repository Importing** This repository is still importing. + - **Autoclose On** Autoclose is active for this branch. + - **Repository Importing** This repository is still importing. Autoclose does not activate until a repository finishes importing for the first time. This prevents situations where you import a repository and accidentally close hundreds of related objects during import. Autoclose will activate for new commits after the initial import completes. - - {icon times} **Repository Autoclose Disabled** Autoclose is disabled for - this entire repository. You can enable it in **Edit Repository**. - - {icon times} **Branch Untracked** This branch is not tracked. Because it + - **Tracking Off** This branch is not tracked. Because it is not tracked, commits on it won't be seen and won't be discovered. - - {icon times} **Branch Autoclose Disabled** Autoclose is not enabled for + - **Autoclose Off** Autoclose is not enabled for this branch. You can adjust which branches autoclose in **Edit Repository**. This option is only available in Git. From 794e185bf90eefa61cac3a48f41859e1020f90df Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 17:04:11 -0700 Subject: [PATCH 393/543] Pass SSH wrappers to VCS commands unconditonally, not just if there's an SSH remote Summary: Ref T12961. In Mercurial, it's possible to have "subrepos" which may use a different protocol than the main repository. By putting an SSH repository inside an HTTP repository, an attacker can theoretically get us to execute `hg` without overriding `ui.ssh`, then execute code via the SSH hostname attack. As an immediate mitigation to this attack, specify `ui.ssh` unconditionally. Normally, this will have no effect (it will just be ignored). In the specific case of an SSH repo inside an HTTP repo, it will defuse the `ssh` protocol. For good measure and consistency, do the same for Subversion and Git. However, we don't normally maintain working copies for either Subversion or Git so it's unlikely that similar attacks exist there. Test Plan: - Put an SSH subrepo with an attack URI inside an HTTP outer repo in Mercurial. - Ran `hg up` with and without `ui.ssh` specified. - Got dangerous badness without `ui.ssh` and safe `ssh` subprocesses with `ui.ssh`. I'm not yet able to confirm that `hg pull -u -- ` can actually trigger this, but this can't hurt and our SSH wrapper is safer than the native behavior for all Subversion, Git and Mercurial versions released prior to today. Reviewers: chad Reviewed By: chad Subscribers: cspeckmim Maniphest Tasks: T12961 Differential Revision: https://secure.phabricator.com/D18389 --- .../protocol/DiffusionGitCommandEngine.php | 4 +--- .../protocol/DiffusionMercurialCommandEngine.php | 14 ++++++++------ .../protocol/DiffusionSubversionCommandEngine.php | 4 +--- .../__tests__/DiffusionCommandEngineTestCase.php | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php index a16416ae1d..168b18caa5 100644 --- a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php @@ -26,9 +26,7 @@ final class DiffusionGitCommandEngine $env['HOME'] = PhabricatorEnv::getEmptyCWD(); - if ($this->isAnySSHProtocol()) { - $env['GIT_SSH'] = $this->getSSHWrapper(); - } + $env['GIT_SSH'] = $this->getSSHWrapper(); if ($this->isAnyHTTPProtocol()) { $uri = $this->getURI(); diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php index 49c618b085..9499b8f5f1 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php @@ -11,12 +11,14 @@ final class DiffusionMercurialCommandEngine protected function newFormattedCommand($pattern, array $argv) { $args = array(); - if ($this->isAnySSHProtocol()) { - $pattern = "hg --config ui.ssh=%s {$pattern}"; - $args[] = $this->getSSHWrapper(); - } else { - $pattern = "hg {$pattern}"; - } + // NOTE: Here, and in Git and Subversion, we override the SSH command even + // if the repository does not use an SSH remote, since our SSH wrapper + // defuses an attack against older versions of Mercurial, Git and + // Subversion (see T12961) and it's possible to execute this attack + // in indirect ways, like by using an SSH subrepo inside an HTTP repo. + + $pattern = "hg --config ui.ssh=%s {$pattern}"; + $args[] = $this->getSSHWrapper(); return array($pattern, array_merge($args, $argv)); } diff --git a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php index d8e9e6ff17..75b8785a6e 100644 --- a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php @@ -44,9 +44,7 @@ final class DiffusionSubversionCommandEngine protected function newCustomEnvironment() { $env = array(); - if ($this->isAnySSHProtocol()) { - $env['SVN_SSH'] = $this->getSSHWrapper(); - } + $env['SVN_SSH'] = $this->getSSHWrapper(); return $env; } diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php index 76d9ad9170..1df3ea3522 100644 --- a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php +++ b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php @@ -26,7 +26,7 @@ final class DiffusionCommandEngineTestCase extends PhabricatorTestCase { )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', @@ -102,7 +102,7 @@ final class DiffusionCommandEngineTestCase extends PhabricatorTestCase { )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', From 2c150076b00f73918d6b3c86dc28e424317fc826 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 17:22:32 -0700 Subject: [PATCH 394/543] Stop populating or updating working copies in observed Mercurial repositories Summary: Ref T12961. Fixes T4416. Currently, for observed Mercurial repositories, we build a working copy with `pull -u` (for "update"). This should be unnecessary, and we don't do it for hosted Mercurial repositories. We also stopped doing it years ago for Git repositories. We also don't clone Mercurial repositories with a working copy. It's possible something has slipped through the cracks here so I'll hold this until after the release cut, but I believe there are no actual technical blockers here. Test Plan: - Observed a public Mercurial repository on Bitbucket. - Let it import. - Browsed commits, branches, file content, etc., without any apparent issues. Reviewers: chad Reviewed By: chad Subscribers: cspeckmim Maniphest Tasks: T12961, T4416 Differential Revision: https://secure.phabricator.com/D18390 --- .../repository/engine/PhabricatorRepositoryPullEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 32e99e619d..10eddd38d4 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -529,7 +529,7 @@ final class PhabricatorRepositoryPullEngine // This is a local command, but needs credentials. $remote = $repository->getRemoteURIEnvelope(); - $future = $repository->getRemoteCommandFuture('pull -u -- %P', $remote); + $future = $repository->getRemoteCommandFuture('pull -- %P', $remote); $future->setCWD($path); try { From 45b0fd8f9b53c5a3712bc0ffc8f78eb416c1e70d Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Fri, 11 Aug 2017 05:50:43 -0700 Subject: [PATCH 395/543] Remove a debugging "echo" that crept in in dccd799b Summary: This echo was accidentally added in dccd799b Test Plan: Inspection. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D18391 --- src/applications/differential/view/DifferentialReviewersView.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 8ddea7c8c0..3fcb1c0ccf 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -158,7 +158,6 @@ final class DifferentialReviewersView extends AphrontView { private function isCurrent($action_phid) { if (!$this->diff) { - echo "A\n"; return true; } From 8160baec2a6bba1a9717d25f6805e9b5670fdbf6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 11 Aug 2017 07:55:54 -0700 Subject: [PATCH 396/543] Add a Differential revision status tokenizer datasource Summary: Ref T2543. This adds a tokenizer, similar to the Maniphest tokenizer, so the hard-coded `" with tokenizer Summary: Ref T2543. This updates the UI control in the web UI. Also: - This implicitly makes this queryable with the API (`differential.revision.search`); it previously was not. - This does NOT migrate existing saved queries. I'll do those in the next change, and hold this until it happens. - This will break some existing `/differential/?status=XYZ` links. For example, `status=open` now needs to be `status=open()`. I couldn't find any of these in the upstream, and I suspect these are rare in the wild (users would normally link directly to saved queries, not use URI query construction). Test Plan: {F5093611} Reviewers: chad Reviewed By: chad Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18393 --- .../constants/DifferentialLegacyQuery.php | 6 +++++- .../query/DifferentialRevisionQuery.php | 13 +++++++++++++ .../query/DifferentialRevisionSearchEngine.php | 15 ++++++++------- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/constants/DifferentialLegacyQuery.php b/src/applications/differential/constants/DifferentialLegacyQuery.php index 4a20267f10..6a87688e99 100644 --- a/src/applications/differential/constants/DifferentialLegacyQuery.php +++ b/src/applications/differential/constants/DifferentialLegacyQuery.php @@ -28,8 +28,12 @@ final class DifferentialLegacyQuery $status)); } + return self::getLegacyValues($map[$status]); + } + + public static function getLegacyValues(array $modern_values) { $values = array(); - foreach ($map[$status] as $status_constant) { + foreach ($modern_values as $status_constant) { $status_object = DifferentialRevisionStatus::newForStatus( $status_constant); diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index f53ec46a9f..79227df8ac 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -25,6 +25,7 @@ final class DifferentialRevisionQuery private $repositoryPHIDs; private $updatedEpochMin; private $updatedEpochMax; + private $statuses; const ORDER_MODIFIED = 'order-modified'; const ORDER_CREATED = 'order-created'; @@ -146,6 +147,11 @@ final class DifferentialRevisionQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + /** * Filter results to revisions on given branches. @@ -705,6 +711,13 @@ final class DifferentialRevisionQuery $statuses); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn_r, + 'r.status in (%Ls)', + DifferentialLegacyQuery::getLegacyValues($this->statuses)); + } + $where[] = $this->buildWhereClauseParts($conn_r); return $this->formatWhereClause($where); } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 8cb8ffd2fb..dcbbaeedee 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -41,8 +41,8 @@ final class DifferentialRevisionSearchEngine $query->withRepositoryPHIDs($map['repositoryPHIDs']); } - if ($map['status']) { - $query->withStatus($map['status']); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; @@ -77,10 +77,11 @@ final class DifferentialRevisionSearchEngine ->setDatasource(new DiffusionRepositoryFunctionDatasource()) ->setDescription( pht('Find revisions from specific repositories.')), - id(new PhabricatorSearchSelectField()) - ->setLabel(pht('Status')) - ->setKey('status') - ->setOptions($this->getStatusOptions()) + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Statuses')) + ->setKey('statuses') + ->setAliases(array('status')) + ->setDatasource(new DifferentialRevisionStatusFunctionDatasource()) ->setDescription( pht('Find revisions with particular statuses.')), ); @@ -115,7 +116,7 @@ final class DifferentialRevisionSearchEngine return $query ->setParameter('responsiblePHIDs', array($viewer->getPHID())) - ->setParameter('status', DifferentialLegacyQuery::STATUS_OPEN) + ->setParameter('statuses', array('open()')) ->setParameter('bucket', $bucket_key); case 'authored': return $query From 212d4d0dc7530ed32e1e13d0b4870ba37922ddd4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 11 Aug 2017 09:03:47 -0700 Subject: [PATCH 398/543] Migrate Differential Revision SavedQueries to the new "statuses" tokenizer Summary: Ref T2543. This migrates existing saved queries so they use the right modern values for the new tokenizer control, introduced in D18393. Test Plan: - Saved a query with "Abandoned" selected as the status in the old "