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

Configure Feed

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

Modernize Hovercard implementation

Summary:
Ref T8980. Move away from events to EngineExtensions.

This also simplifies hovercards a bit:

- Removes tasks from revision cards.
- Removes blockers/blocked from task cards.
- Removes "Send Message" from user cards.

These mostly felt cluttery to me. Open to arguments to retain them. I think we can make better use of the space, though (e.g., flags, projects + board columns).

Test Plan:
- Viewed people, task, revision, commit and project hovercards.

{F1043256}

{F1043257}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T8980

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

+407 -384
+10 -10
resources/celerity/map.php
··· 8 8 return array( 9 9 'names' => array( 10 10 'core.pkg.css' => 'a419cf4b', 11 - 'core.pkg.js' => 'cf262309', 11 + 'core.pkg.js' => 'b826f522', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '2de124c9', 14 14 'differential.pkg.js' => '64e69521', ··· 452 452 'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac', 453 453 'rsrc/js/core/DraggableList.js' => 'a16ec1c6', 454 454 'rsrc/js/core/FileUpload.js' => '477359c8', 455 - 'rsrc/js/core/Hovercard.js' => '14ac66f5', 455 + 'rsrc/js/core/Hovercard.js' => '6914d0dd', 456 456 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 457 457 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 458 458 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', ··· 747 747 'phabricator-file-upload' => '477359c8', 748 748 'phabricator-filetree-view-css' => 'fccf9f82', 749 749 'phabricator-flag-css' => '5337623f', 750 - 'phabricator-hovercard' => '14ac66f5', 750 + 'phabricator-hovercard' => '6914d0dd', 751 751 'phabricator-hovercard-view-css' => '1239cd52', 752 752 'phabricator-keyboard-shortcut' => '1ae869f2', 753 753 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', ··· 935 935 'javelin-dom', 936 936 'javelin-history', 937 937 ), 938 - '14ac66f5' => array( 939 - 'javelin-install', 940 - 'javelin-dom', 941 - 'javelin-vector', 942 - 'javelin-request', 943 - 'javelin-uri', 944 - ), 945 938 '1ad0a787' => array( 946 939 'javelin-install', 947 940 'javelin-reactor', ··· 1310 1303 ), 1311 1304 '6882e80a' => array( 1312 1305 'javelin-dom', 1306 + ), 1307 + '6914d0dd' => array( 1308 + 'javelin-install', 1309 + 'javelin-dom', 1310 + 'javelin-vector', 1311 + 'javelin-request', 1312 + 'javelin-uri', 1313 1313 ), 1314 1314 '69adf288' => array( 1315 1315 'javelin-install',
+12 -10
src/__phutil_library_map__.php
··· 262 262 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 263 263 'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php', 264 264 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 265 - 'ConpherenceHovercardEventListener' => 'applications/conpherence/events/ConpherenceHovercardEventListener.php', 266 265 'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php', 267 266 'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php', 268 267 'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php', ··· 420 419 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 421 420 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', 422 421 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', 423 - 'DifferentialHovercardEventListener' => 'applications/differential/event/DifferentialHovercardEventListener.php', 422 + 'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php', 424 423 'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php', 425 424 'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php', 426 425 'DifferentialHunkParserTestCase' => 'applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php', ··· 620 619 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 621 620 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 622 621 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 623 - 'DiffusionHovercardEventListener' => 'applications/diffusion/events/DiffusionHovercardEventListener.php', 622 + 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', 624 623 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 625 624 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', 626 625 'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php', ··· 1292 1291 'ManiphestExcelFormatTestCase' => 'applications/maniphest/export/__tests__/ManiphestExcelFormatTestCase.php', 1293 1292 'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php', 1294 1293 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php', 1295 - 'ManiphestHovercardEventListener' => 'applications/maniphest/event/ManiphestHovercardEventListener.php', 1294 + 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', 1296 1295 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', 1297 1296 'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php', 1298 1297 'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php', ··· 2370 2369 'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php', 2371 2370 'PhabricatorHomePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorHomePreferencesSettingsPanel.php', 2372 2371 'PhabricatorHomeQuickCreateController' => 'applications/home/controller/PhabricatorHomeQuickCreateController.php', 2372 + 'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php', 2373 + 'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php', 2373 2374 'PhabricatorHovercardUIExample' => 'applications/uiexample/examples/PhabricatorHovercardUIExample.php', 2374 2375 'PhabricatorHovercardView' => 'view/widget/hovercard/PhabricatorHovercardView.php', 2375 2376 'PhabricatorHunksManagementMigrateWorkflow' => 'applications/differential/management/PhabricatorHunksManagementMigrateWorkflow.php', ··· 2711 2712 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', 2712 2713 'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php', 2713 2714 'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php', 2714 - 'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php', 2715 + 'PhabricatorPeopleHovercardEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php', 2715 2716 'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php', 2716 2717 'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php', 2717 2718 'PhabricatorPeopleInviteSendController' => 'applications/people/controller/PhabricatorPeopleInviteSendController.php', ··· 4172 4173 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 4173 4174 'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl', 4174 4175 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 4175 - 'ConpherenceHovercardEventListener' => 'PhabricatorEventListener', 4176 4176 'ConpherenceImageData' => 'ConpherenceConstants', 4177 4177 'ConpherenceIndex' => 'ConpherenceDAO', 4178 4178 'ConpherenceLayoutView' => 'AphrontView', ··· 4347 4347 'DifferentialHostField' => 'DifferentialCustomField', 4348 4348 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', 4349 4349 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', 4350 - 'DifferentialHovercardEventListener' => 'PhabricatorEventListener', 4350 + 'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 4351 4351 'DifferentialHunk' => array( 4352 4352 'DifferentialDAO', 4353 4353 'PhabricatorPolicyInterface', ··· 4568 4568 'DiffusionHistoryController' => 'DiffusionController', 4569 4569 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 4570 4570 'DiffusionHistoryTableView' => 'DiffusionView', 4571 - 'DiffusionHovercardEventListener' => 'PhabricatorEventListener', 4571 + 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 4572 4572 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 4573 4573 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', 4574 4574 'DiffusionLastModifiedController' => 'DiffusionController', ··· 5361 5361 'ManiphestExcelFormatTestCase' => 'PhabricatorTestCase', 5362 5362 'ManiphestExportController' => 'ManiphestController', 5363 5363 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod', 5364 - 'ManiphestHovercardEventListener' => 'PhabricatorEventListener', 5364 + 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 5365 5365 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', 5366 5366 'ManiphestNameIndex' => 'ManiphestDAO', 5367 5367 'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType', ··· 6624 6624 'PhabricatorHomeMainController' => 'PhabricatorHomeController', 6625 6625 'PhabricatorHomePreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 6626 6626 'PhabricatorHomeQuickCreateController' => 'PhabricatorHomeController', 6627 + 'PhabricatorHovercardEngineExtension' => 'Phobject', 6628 + 'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule', 6627 6629 'PhabricatorHovercardUIExample' => 'PhabricatorUIExample', 6628 6630 'PhabricatorHovercardView' => 'AphrontView', 6629 6631 'PhabricatorHunksManagementMigrateWorkflow' => 'PhabricatorHunksManagementWorkflow', ··· 7010 7012 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', 7011 7013 'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController', 7012 7014 'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType', 7013 - 'PhabricatorPeopleHovercardEventListener' => 'PhabricatorEventListener', 7015 + 'PhabricatorPeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 7014 7016 'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController', 7015 7017 'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController', 7016 7018 'PhabricatorPeopleInviteSendController' => 'PhabricatorPeopleInviteController',
-6
src/applications/conpherence/application/PhabricatorConpherenceApplication.php
··· 28 28 ); 29 29 } 30 30 31 - public function getEventListeners() { 32 - return array( 33 - new ConpherenceHovercardEventListener(), 34 - ); 35 - } 36 - 37 31 public function getRoutes() { 38 32 return array( 39 33 '/Z(?P<id>[1-9]\d*)' => 'ConpherenceViewController',
-41
src/applications/conpherence/events/ConpherenceHovercardEventListener.php
··· 1 - <?php 2 - 3 - /** 4 - * This event listener is tasked with probably one of the most important 5 - * missions in this world: Adding a Conpherence button to a hovercard. 6 - * 7 - * Handle with care when modifying! 8 - * 9 - * @task event 10 - */ 11 - final class ConpherenceHovercardEventListener extends PhabricatorEventListener { 12 - 13 - public function register() { 14 - $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD); 15 - } 16 - 17 - public function handleEvent(PhutilEvent $event) { 18 - switch ($event->getType()) { 19 - case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD: 20 - $this->handleHovercardEvent($event); 21 - break; 22 - } 23 - } 24 - 25 - private function handleHovercardEvent($event) { 26 - $hovercard = $event->getValue('hovercard'); 27 - $user = $event->getValue('object'); 28 - 29 - if (!($user instanceof PhabricatorUser)) { 30 - return; 31 - } 32 - 33 - $conpherence_uri = id(new PhutilURI('/conpherence/new/')) 34 - ->setQueryParam('participant', $user->getPHID()); 35 - $name = pht('Send a Message'); 36 - $hovercard->addAction($name, $conpherence_uri, true); 37 - 38 - $event->setValue('hovercard', $hovercard); 39 - } 40 - 41 - }
-1
src/applications/differential/application/PhabricatorDifferentialApplication.php
··· 44 44 public function getEventListeners() { 45 45 return array( 46 46 new DifferentialActionMenuEventListener(), 47 - new DifferentialHovercardEventListener(), 48 47 new DifferentialLandingActionMenuEventListener(), 49 48 ); 50 49 }
+77
src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php
··· 1 + <?php 2 + 3 + final class DifferentialHovercardEngineExtension 4 + extends PhabricatorHovercardEngineExtension { 5 + 6 + const EXTENSIONKEY = 'differential'; 7 + 8 + public function isExtensionEnabled() { 9 + return PhabricatorApplication::isClassInstalled( 10 + 'PhabricatorDifferentialApplication'); 11 + } 12 + 13 + public function getExtensionName() { 14 + return pht('Differential Revisions'); 15 + } 16 + 17 + public function canRenderObjectHovercard($object) { 18 + return ($object instanceof DifferentialRevision); 19 + } 20 + 21 + public function willRenderHovercards(array $objects) { 22 + $viewer = $this->getViewer(); 23 + $phids = mpull($objects, 'getPHID'); 24 + 25 + $revisions = id(new DifferentialRevisionQuery()) 26 + ->setViewer($viewer) 27 + ->withPHIDs($phids) 28 + ->needReviewerStatus(true) 29 + ->execute(); 30 + $revisions = mpull($revisions, null, 'getPHID'); 31 + 32 + return array( 33 + 'revisions' => $revisions, 34 + ); 35 + } 36 + 37 + public function renderHovercard( 38 + PhabricatorHovercardView $hovercard, 39 + PhabricatorObjectHandle $handle, 40 + $object, 41 + $data) { 42 + 43 + $viewer = $this->getViewer(); 44 + 45 + $revision = idx($data['revisions'], $object->getPHID()); 46 + if (!$revision) { 47 + return; 48 + } 49 + 50 + $hovercard->setTitle('D'.$revision->getID()); 51 + $hovercard->setDetail($revision->getTitle()); 52 + 53 + $hovercard->addField( 54 + pht('Author'), 55 + $viewer->renderHandle($revision->getAuthorPHID())); 56 + 57 + $reviewer_phids = $revision->getReviewerStatus(); 58 + $reviewer_phids = mpull($reviewer_phids, 'getReviewerPHID'); 59 + 60 + $hovercard->addField( 61 + pht('Reviewers'), 62 + $viewer->renderHandleList($reviewer_phids)->setAsInline(true)); 63 + 64 + $summary = $revision->getSummary(); 65 + if (strlen($summary)) { 66 + $summary = id(new PhutilUTF8StringTruncator()) 67 + ->setMaximumGlyphs(120) 68 + ->truncateString($summary); 69 + 70 + $hovercard->addField(pht('Summary'), $summary); 71 + } 72 + 73 + $tag = DifferentialRevisionDetailView::renderTagForRevision($revision); 74 + $hovercard->addTag($tag); 75 + } 76 + 77 + }
-71
src/applications/differential/event/DifferentialHovercardEventListener.php
··· 1 - <?php 2 - 3 - final class DifferentialHovercardEventListener 4 - extends PhabricatorEventListener { 5 - 6 - public function register() { 7 - $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD); 8 - } 9 - 10 - public function handleEvent(PhutilEvent $event) { 11 - switch ($event->getType()) { 12 - case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD: 13 - $this->handleHovercardEvent($event); 14 - break; 15 - } 16 - } 17 - 18 - private function handleHovercardEvent($event) { 19 - $viewer = $event->getUser(); 20 - $hovercard = $event->getValue('hovercard'); 21 - $object_handle = $event->getValue('handle'); 22 - $phid = $object_handle->getPHID(); 23 - $rev = $event->getValue('object'); 24 - 25 - if (!($rev instanceof DifferentialRevision)) { 26 - return; 27 - } 28 - 29 - $rev->loadRelationships(); 30 - $reviewer_phids = $rev->getReviewers(); 31 - $e_task = DifferentialRevisionHasTaskEdgeType::EDGECONST; 32 - $edge_query = id(new PhabricatorEdgeQuery()) 33 - ->withSourcePHIDs(array($phid)) 34 - ->withEdgeTypes( 35 - array( 36 - $e_task, 37 - )); 38 - $edge_query->execute(); 39 - $tasks = $edge_query->getDestinationPHIDs(); 40 - 41 - $hovercard->setTitle('D'.$rev->getID()); 42 - $hovercard->setDetail($rev->getTitle()); 43 - 44 - $hovercard->addField( 45 - pht('Author'), 46 - $viewer->renderHandle($rev->getAuthorPHID())); 47 - 48 - $hovercard->addField( 49 - pht('Reviewers'), 50 - $viewer->renderHandleList($reviewer_phids)->setAsInline(true)); 51 - 52 - if ($tasks) { 53 - $hovercard->addField( 54 - pht('Tasks'), 55 - $viewer->renderHandleList($tasks)->setAsInline(true)); 56 - } 57 - 58 - if ($rev->getSummary()) { 59 - $hovercard->addField(pht('Summary'), 60 - id(new PhutilUTF8StringTruncator()) 61 - ->setMaximumGlyphs(120) 62 - ->truncateString($rev->getSummary())); 63 - } 64 - 65 - $hovercard->addTag( 66 - DifferentialRevisionDetailView::renderTagForRevision($rev)); 67 - 68 - $event->setValue('hovercard', $hovercard); 69 - } 70 - 71 - }
-6
src/applications/diffusion/application/PhabricatorDiffusionApplication.php
··· 37 37 ); 38 38 } 39 39 40 - public function getEventListeners() { 41 - return array( 42 - new DiffusionHovercardEventListener(), 43 - ); 44 - } 45 - 46 40 public function getRemarkupRules() { 47 41 return array( 48 42 new DiffusionCommitRemarkupRule(),
+53
src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php
··· 1 + <?php 2 + 3 + final class DiffusionHovercardEngineExtension 4 + extends PhabricatorHovercardEngineExtension { 5 + 6 + const EXTENSIONKEY = 'diffusion'; 7 + 8 + public function isExtensionEnabled() { 9 + return PhabricatorApplication::isClassInstalled( 10 + 'PhabricatorDiffusionApplication'); 11 + } 12 + 13 + public function getExtensionName() { 14 + return pht('Diffusion Commits'); 15 + } 16 + 17 + public function canRenderObjectHovercard($object) { 18 + return ($object instanceof PhabricatorRepositoryCommit); 19 + } 20 + 21 + public function renderHovercard( 22 + PhabricatorHovercardView $hovercard, 23 + PhabricatorObjectHandle $handle, 24 + $commit, 25 + $data) { 26 + 27 + $viewer = $this->getViewer(); 28 + 29 + $author_phid = $commit->getAuthorPHID(); 30 + if ($author_phid) { 31 + $author = $viewer->loadHandle($author)->renderLink(); 32 + } else { 33 + $commit_data = $commit->loadCommitData(); 34 + $author = phutil_tag('em', array(), $commit_data->getAuthorName()); 35 + } 36 + 37 + $hovercard->setTitle($handle->getName()); 38 + $hovercard->setDetail($commit->getSummary()); 39 + 40 + $hovercard->addField(pht('Author'), $author); 41 + $hovercard->addField(pht('Date'), 42 + phabricator_date($commit->getEpoch(), $viewer)); 43 + 44 + if ($commit->getAuditStatus() != 45 + PhabricatorAuditCommitStatusConstants::NONE) { 46 + 47 + $hovercard->addField(pht('Audit Status'), 48 + PhabricatorAuditCommitStatusConstants::getStatusName( 49 + $commit->getAuditStatus())); 50 + } 51 + } 52 + 53 + }
-75
src/applications/diffusion/events/DiffusionHovercardEventListener.php
··· 1 - <?php 2 - 3 - final class DiffusionHovercardEventListener extends PhabricatorEventListener { 4 - 5 - public function register() { 6 - $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD); 7 - } 8 - 9 - public function handleEvent(PhutilEvent $event) { 10 - switch ($event->getType()) { 11 - case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD: 12 - $this->handleHovercardEvent($event); 13 - break; 14 - } 15 - } 16 - 17 - private function handleHovercardEvent($event) { 18 - $viewer = $event->getUser(); 19 - $hovercard = $event->getValue('hovercard'); 20 - $object_handle = $event->getValue('handle'); 21 - $commit = $event->getValue('object'); 22 - 23 - if (!($commit instanceof PhabricatorRepositoryCommit)) { 24 - return; 25 - } 26 - 27 - $commit_data = $commit->loadCommitData(); 28 - 29 - $revision = PhabricatorEdgeQuery::loadDestinationPHIDs( 30 - $commit->getPHID(), 31 - DiffusionCommitHasRevisionEdgeType::EDGECONST); 32 - $revision = reset($revision); 33 - 34 - $author = $commit->getAuthorPHID(); 35 - 36 - $phids = array_filter(array( 37 - $revision, 38 - $author, 39 - )); 40 - 41 - $handles = id(new PhabricatorHandleQuery()) 42 - ->setViewer($viewer) 43 - ->withPHIDs($phids) 44 - ->execute(); 45 - 46 - if ($author) { 47 - $author = $handles[$author]->renderLink(); 48 - } else { 49 - $author = phutil_tag('em', array(), $commit_data->getAuthorName()); 50 - } 51 - 52 - $hovercard->setTitle($object_handle->getName()); 53 - $hovercard->setDetail($commit->getSummary()); 54 - 55 - $hovercard->addField(pht('Author'), $author); 56 - $hovercard->addField(pht('Date'), 57 - phabricator_date($commit->getEpoch(), $viewer)); 58 - 59 - if ($commit->getAuditStatus() != 60 - PhabricatorAuditCommitStatusConstants::NONE) { 61 - 62 - $hovercard->addField(pht('Audit Status'), 63 - PhabricatorAuditCommitStatusConstants::getStatusName( 64 - $commit->getAuditStatus())); 65 - } 66 - 67 - if ($revision) { 68 - $rev_handle = $handles[$revision]; 69 - $hovercard->addField(pht('Revision'), $rev_handle->renderLink()); 70 - } 71 - 72 - $event->setValue('hovercard', $hovercard); 73 - } 74 - 75 - }
-6
src/applications/maniphest/application/PhabricatorManiphestApplication.php
··· 36 36 ); 37 37 } 38 38 39 - public function getEventListeners() { 40 - return array( 41 - new ManiphestHovercardEventListener(), 42 - ); 43 - } 44 - 45 39 public function getRemarkupRules() { 46 40 return array( 47 41 new ManiphestRemarkupRule(),
+47
src/applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php
··· 1 + <?php 2 + 3 + final class ManiphestHovercardEngineExtension 4 + extends PhabricatorHovercardEngineExtension { 5 + 6 + const EXTENSIONKEY = 'maniphest'; 7 + 8 + public function isExtensionEnabled() { 9 + return PhabricatorApplication::isClassInstalled( 10 + 'PhabricatorManiphestApplication'); 11 + } 12 + 13 + public function getExtensionName() { 14 + return pht('Maniphest Tasks'); 15 + } 16 + 17 + public function canRenderObjectHovercard($object) { 18 + return ($object instanceof ManiphestTask); 19 + } 20 + 21 + public function renderHovercard( 22 + PhabricatorHovercardView $hovercard, 23 + PhabricatorObjectHandle $handle, 24 + $task, 25 + $data) { 26 + $viewer = $this->getViewer(); 27 + 28 + $hovercard 29 + ->setTitle($task->getMonogram()) 30 + ->setDetail($task->getTitle()); 31 + 32 + $owner_phid = $task->getOwnerPHID(); 33 + if ($owner_phid) { 34 + $owner = $viewer->renderHandle($owner_phid); 35 + } else { 36 + $owner = phutil_tag('em', array(), pht('None')); 37 + } 38 + $hovercard->addField(pht('Assigned To'), $owner); 39 + 40 + $hovercard->addField( 41 + pht('Priority'), 42 + ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); 43 + 44 + $hovercard->addTag(ManiphestView::renderTagForTask($task)); 45 + } 46 + 47 + }
-93
src/applications/maniphest/event/ManiphestHovercardEventListener.php
··· 1 - <?php 2 - 3 - final class ManiphestHovercardEventListener extends PhabricatorEventListener { 4 - 5 - public function register() { 6 - $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD); 7 - } 8 - 9 - public function handleEvent(PhutilEvent $event) { 10 - switch ($event->getType()) { 11 - case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD: 12 - $this->handleHovercardEvent($event); 13 - break; 14 - } 15 - } 16 - 17 - private function handleHovercardEvent(PhutilEvent $event) { 18 - $viewer = $event->getUser(); 19 - $hovercard = $event->getValue('hovercard'); 20 - $handle = $event->getValue('handle'); 21 - $phid = $handle->getPHID(); 22 - $task = $event->getValue('object'); 23 - 24 - if (!($task instanceof ManiphestTask)) { 25 - return; 26 - } 27 - 28 - $e_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 29 - // Fun with "Unbeta Pholio", hua hua 30 - $e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; 31 - $e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; 32 - 33 - $edge_query = id(new PhabricatorEdgeQuery()) 34 - ->withSourcePHIDs(array($phid)) 35 - ->withEdgeTypes( 36 - array( 37 - $e_project, 38 - $e_dep_on, 39 - $e_dep_by, 40 - )); 41 - $edges = idx($edge_query->execute(), $phid); 42 - $edge_phids = $edge_query->getDestinationPHIDs(); 43 - 44 - $owner_phid = $task->getOwnerPHID(); 45 - 46 - $hovercard 47 - ->setTitle(pht('T%d', $task->getID())) 48 - ->setDetail($task->getTitle()); 49 - 50 - if ($owner_phid) { 51 - $owner = $viewer->renderHandle($owner_phid); 52 - } else { 53 - $owner = phutil_tag('em', array(), pht('None')); 54 - } 55 - $hovercard->addField(pht('Assigned To'), $owner); 56 - $hovercard->addField( 57 - pht('Priority'), 58 - ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); 59 - 60 - if ($edge_phids) { 61 - $edge_types = array( 62 - $e_project => pht('Projects'), 63 - $e_dep_by => pht('Blocks'), 64 - $e_dep_on => pht('Blocked By'), 65 - ); 66 - 67 - $max_count = 6; 68 - foreach ($edge_types as $edge_type => $edge_name) { 69 - if ($edges[$edge_type]) { 70 - // TODO: This can be made more sophisticated. We still load all 71 - // edges into memory. Only load the ones we need. 72 - $edge_overflow = array(); 73 - if (count($edges[$edge_type]) > $max_count) { 74 - $edges[$edge_type] = array_slice($edges[$edge_type], 0, 6, true); 75 - $edge_overflow = ', ...'; 76 - } 77 - 78 - $hovercard->addField( 79 - $edge_name, 80 - array( 81 - $viewer->renderHandleList(array_keys($edges[$edge_type])), 82 - $edge_overflow, 83 - )); 84 - } 85 - } 86 - } 87 - 88 - $hovercard->addTag(ManiphestView::renderTagForTask($task)); 89 - 90 - $event->setValue('hovercard', $hovercard); 91 - } 92 - 93 - }
-6
src/applications/people/application/PhabricatorPeopleApplication.php
··· 34 34 return false; 35 35 } 36 36 37 - public function getEventListeners() { 38 - return array( 39 - new PhabricatorPeopleHovercardEventListener(), 40 - ); 41 - } 42 - 43 37 public function getRoutes() { 44 38 return array( 45 39 '/people/' => array(
+37 -27
src/applications/people/event/PhabricatorPeopleHovercardEventListener.php src/applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php
··· 1 1 <?php 2 2 3 - final class PhabricatorPeopleHovercardEventListener 4 - extends PhabricatorEventListener { 3 + final class PhabricatorPeopleHovercardEngineExtension 4 + extends PhabricatorHovercardEngineExtension { 5 5 6 - public function register() { 7 - $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD); 6 + const EXTENSIONKEY = 'people'; 7 + 8 + public function isExtensionEnabled() { 9 + return true; 8 10 } 9 11 10 - public function handleEvent(PhutilEvent $event) { 11 - switch ($event->getType()) { 12 - case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD: 13 - $this->handleHovercardEvent($event); 14 - break; 15 - } 12 + public function getExtensionName() { 13 + return pht('User Accounts'); 16 14 } 17 15 18 - private function handleHovercardEvent($event) { 19 - $viewer = $event->getUser(); 20 - $hovercard = $event->getValue('hovercard'); 21 - $object_handle = $event->getValue('handle'); 22 - $phid = $object_handle->getPHID(); 23 - $user = $event->getValue('object'); 16 + public function canRenderObjectHovercard($object) { 17 + return ($object instanceof PhabricatorUser); 18 + } 24 19 25 - if (!($user instanceof PhabricatorUser)) { 26 - return; 27 - } 20 + public function willRenderHovercards(array $objects) { 21 + $viewer = $this->getViewer(); 22 + $phids = mpull($objects, 'getPHID'); 28 23 29 - // Reload to get availability. 30 - $user = id(new PhabricatorPeopleQuery()) 24 + $users = id(new PhabricatorPeopleQuery()) 31 25 ->setViewer($viewer) 32 - ->withIDs(array($user->getID())) 26 + ->withPHIDs($phids) 33 27 ->needAvailability(true) 34 28 ->needProfile(true) 35 29 ->needBadges(true) 36 - ->executeOne(); 30 + ->execute(); 31 + $users = mpull($users, null, 'getPHID'); 32 + 33 + return array( 34 + 'users' => $users, 35 + ); 36 + } 37 + 38 + public function renderHovercard( 39 + PhabricatorHovercardView $hovercard, 40 + PhabricatorObjectHandle $handle, 41 + $object, 42 + $data) { 43 + $viewer = $this->getViewer(); 44 + 45 + $user = idx($data['users'], $object->getPHID()); 46 + if (!$user) { 47 + return; 48 + } 37 49 38 50 $hovercard->setTitle($user->getUsername()); 51 + 39 52 $profile = $user->getUserProfile(); 40 53 $detail = $user->getRealName(); 41 54 if ($profile->getTitle()) { 42 - $detail .= ' - '.$profile->getTitle().'.'; 55 + $detail .= ' - '.$profile->getTitle(); 43 56 } 44 57 $hovercard->setDetail($detail); 45 58 ··· 70 83 foreach ($badges as $badge) { 71 84 $hovercard->addBadge($badge); 72 85 } 73 - 74 - $event->setValue('hovercard', $hovercard); 75 86 } 76 87 77 88 private function buildBadges( ··· 100 111 } 101 112 return $items; 102 113 } 103 - 104 114 105 115 }
+1 -1
src/applications/search/application/PhabricatorSearchApplication.php
··· 35 35 'select/(?P<type>\w+)/(?:(?P<action>\w+)/)?' 36 36 => 'PhabricatorSearchSelectController', 37 37 'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController', 38 - 'hovercard/(?P<mode>retrieve|test)/' 38 + 'hovercard/' 39 39 => 'PhabricatorSearchHovercardController', 40 40 'edit/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController', 41 41 'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
+54 -28
src/applications/search/controller/PhabricatorSearchHovercardController.php
··· 15 15 ->setViewer($viewer) 16 16 ->withPHIDs($phids) 17 17 ->execute(); 18 + 18 19 $objects = id(new PhabricatorObjectQuery()) 19 20 ->setViewer($viewer) 20 21 ->withPHIDs($phids) 21 22 ->execute(); 23 + $objects = mpull($objects, null, 'getPHID'); 22 24 23 - $cards = array(); 25 + $extensions = 26 + PhabricatorHovercardEngineExtension::getAllEnabledExtensions(); 27 + 28 + $extension_maps = array(); 29 + foreach ($extensions as $key => $extension) { 30 + $extension->setViewer($viewer); 31 + 32 + $extension_phids = array(); 33 + foreach ($objects as $phid => $object) { 34 + if ($extension->canRenderObjectHovercard($object)) { 35 + $extension_phids[$phid] = $phid; 36 + } 37 + } 38 + 39 + $extension_maps[$key] = $extension_phids; 40 + } 41 + 42 + $extension_data = array(); 43 + foreach ($extensions as $key => $extension) { 44 + $extension_phids = $extension_maps[$key]; 45 + if (!$extension_phids) { 46 + unset($extensions[$key]); 47 + continue; 48 + } 49 + 50 + $extension_data[$key] = $extension->willRenderHovercards( 51 + array_select_keys($objects, $extension_phids)); 52 + } 24 53 54 + $cards = array(); 25 55 foreach ($phids as $phid) { 26 56 $handle = $handles[$phid]; 27 57 $object = $objects[$phid]; ··· 32 62 33 63 if ($object) { 34 64 $hovercard->setObject($object); 35 - } 36 65 37 - // Send it to the other side of the world, thanks to PhutilEventEngine 38 - $event = new PhabricatorEvent( 39 - PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD, 40 - array( 41 - 'hovercard' => $hovercard, 42 - 'handle' => $handle, 43 - 'object' => $object, 44 - )); 45 - $event->setUser($viewer); 46 - PhutilEventEngine::dispatchEvent($event); 66 + foreach ($extension_maps as $key => $extension_phids) { 67 + if (isset($extension_phids[$phid])) { 68 + $extensions[$key]->renderHovercard( 69 + $hovercard, 70 + $handle, 71 + $object, 72 + $extension_data[$key]); 73 + } 74 + } 75 + } 47 76 48 77 $cards[$phid] = $hovercard; 49 78 } 50 79 51 - // Browser-friendly for non-Ajax requests 52 - if (!$request->isAjax()) { 53 - foreach ($cards as $key => $hovercard) { 54 - $cards[$key] = phutil_tag('div', 55 - array( 56 - 'class' => 'ml', 57 - ), 58 - $hovercard); 59 - } 60 - 61 - return $this->buildApplicationPage( 62 - $cards, 63 - array( 64 - 'device' => false, 65 - )); 66 - } else { 80 + if ($request->isAjax()) { 67 81 return id(new AphrontAjaxResponse())->setContent( 68 82 array( 69 83 'cards' => $cards, 70 84 )); 71 85 } 86 + 87 + foreach ($cards as $key => $hovercard) { 88 + $cards[$key] = phutil_tag('div', 89 + array( 90 + 'class' => 'ml', 91 + ), 92 + $hovercard); 93 + } 94 + 95 + return $this->newPage() 96 + ->appendChild($cards) 97 + ->setShowFooter(false); 72 98 } 73 99 74 100 }
+60
src/applications/search/engineextension/PhabricatorHovercardEngineExtension.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorHovercardEngineExtension extends Phobject { 4 + 5 + private $viewer; 6 + 7 + final public function getExtensionKey() { 8 + return $this->getPhobjectClassConstant('EXTENSIONKEY'); 9 + } 10 + 11 + final public function setViewer($viewer) { 12 + $this->viewer = $viewer; 13 + return $this; 14 + } 15 + 16 + final public function getViewer() { 17 + return $this->viewer; 18 + } 19 + 20 + abstract public function isExtensionEnabled(); 21 + 22 + abstract public function getExtensionName(); 23 + 24 + abstract public function canRenderObjectHovercard($object); 25 + 26 + public function getExtensionOrder() { 27 + return 5000; 28 + } 29 + 30 + public function willRenderHovercards(array $objects) { 31 + return null; 32 + } 33 + 34 + abstract public function renderHovercard( 35 + PhabricatorHovercardView $hovercard, 36 + PhabricatorObjectHandle $handle, 37 + $object, 38 + $data); 39 + 40 + final public static function getAllExtensions() { 41 + return id(new PhutilClassMapQuery()) 42 + ->setAncestorClass(__CLASS__) 43 + ->setUniqueMethod('getExtensionKey') 44 + ->setSortMethod('getExtensionOrder') 45 + ->execute(); 46 + } 47 + 48 + final public static function getAllEnabledExtensions() { 49 + $extensions = self::getAllExtensions(); 50 + 51 + foreach ($extensions as $key => $extension) { 52 + if (!$extension->isExtensionEnabled()) { 53 + unset($extensions[$key]); 54 + } 55 + } 56 + 57 + return $extensions; 58 + } 59 + 60 + }
+55
src/applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php
··· 1 + <?php 2 + 3 + final class PhabricatorHovercardEngineExtensionModule 4 + extends PhabricatorConfigModule { 5 + 6 + public function getModuleKey() { 7 + return 'hovercardengine'; 8 + } 9 + 10 + public function getModuleName() { 11 + return pht('Engine: Hovercards'); 12 + } 13 + 14 + public function renderModuleStatus(AphrontRequest $request) { 15 + $viewer = $request->getViewer(); 16 + 17 + $extensions = PhabricatorHovercardEngineExtension::getAllExtensions(); 18 + 19 + $rows = array(); 20 + foreach ($extensions as $extension) { 21 + $rows[] = array( 22 + $extension->getExtensionOrder(), 23 + $extension->getExtensionKey(), 24 + get_class($extension), 25 + $extension->getExtensionName(), 26 + $extension->isExtensionEnabled() 27 + ? pht('Yes') 28 + : pht('No'), 29 + ); 30 + } 31 + 32 + $table = id(new AphrontTableView($rows)) 33 + ->setHeaders( 34 + array( 35 + pht('Order'), 36 + pht('Key'), 37 + pht('Class'), 38 + pht('Name'), 39 + pht('Enabled'), 40 + )) 41 + ->setColumnClasses( 42 + array( 43 + null, 44 + null, 45 + null, 46 + 'wide pri', 47 + null, 48 + )); 49 + 50 + return id(new PHUIObjectBoxView()) 51 + ->setHeaderText(pht('HovercardEngine Extensions')) 52 + ->setTable($table); 53 + } 54 + 55 + }
-2
src/infrastructure/events/constant/PhabricatorEventType.php
··· 20 20 const TYPE_UI_DIDRENDEROBJECTS = 'ui.didRenderObjects'; 21 21 const TYPE_UI_WILLRENDERPROPERTIES = 'ui.willRenderProperties'; 22 22 23 - const TYPE_UI_DIDRENDERHOVERCARD = 'ui.didRenderHovercard'; 24 - 25 23 const TYPE_PEOPLE_DIDRENDERMENU = 'people.didRenderMenu'; 26 24 const TYPE_AUTH_WILLREGISTERUSER = 'auth.willRegisterUser'; 27 25 const TYPE_AUTH_WILLLOGINUSER = 'auth.willLoginUser';
+1 -1
webroot/rsrc/js/core/Hovercard.js
··· 15 15 _activeRoot : null, 16 16 _visiblePHID : null, 17 17 18 - fetchUrl : '/search/hovercard/retrieve/', 18 + fetchUrl : '/search/hovercard/', 19 19 20 20 /** 21 21 * Hovercard storage. {"PHID-XXXX-YYYY":"<...>", ...}