1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

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
This commit is contained in:
epriestley 2015-12-24 11:20:18 -08:00
parent 3ec07c4987
commit bdc517485c
21 changed files with 407 additions and 384 deletions

View file

@ -8,7 +8,7 @@
return array(
'names' => array(
'core.pkg.css' => 'a419cf4b',
'core.pkg.js' => 'cf262309',
'core.pkg.js' => 'b826f522',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => '64e69521',
@ -452,7 +452,7 @@ return array(
'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac',
'rsrc/js/core/DraggableList.js' => 'a16ec1c6',
'rsrc/js/core/FileUpload.js' => '477359c8',
'rsrc/js/core/Hovercard.js' => '14ac66f5',
'rsrc/js/core/Hovercard.js' => '6914d0dd',
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
@ -747,7 +747,7 @@ return array(
'phabricator-file-upload' => '477359c8',
'phabricator-filetree-view-css' => 'fccf9f82',
'phabricator-flag-css' => '5337623f',
'phabricator-hovercard' => '14ac66f5',
'phabricator-hovercard' => '6914d0dd',
'phabricator-hovercard-view-css' => '1239cd52',
'phabricator-keyboard-shortcut' => '1ae869f2',
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
@ -935,13 +935,6 @@ return array(
'javelin-dom',
'javelin-history',
),
'14ac66f5' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
),
'1ad0a787' => array(
'javelin-install',
'javelin-reactor',
@ -1311,6 +1304,13 @@ return array(
'6882e80a' => array(
'javelin-dom',
),
'6914d0dd' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
),
'69adf288' => array(
'javelin-install',
),

View file

@ -262,7 +262,6 @@ phutil_register_library_map(array(
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php',
'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php',
'ConpherenceHovercardEventListener' => 'applications/conpherence/events/ConpherenceHovercardEventListener.php',
'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php',
'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php',
'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php',
@ -420,7 +419,7 @@ phutil_register_library_map(array(
'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php',
'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php',
'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php',
'DifferentialHovercardEventListener' => 'applications/differential/event/DifferentialHovercardEventListener.php',
'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php',
'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php',
'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php',
'DifferentialHunkParserTestCase' => 'applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php',
@ -620,7 +619,7 @@ phutil_register_library_map(array(
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php',
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
'DiffusionHovercardEventListener' => 'applications/diffusion/events/DiffusionHovercardEventListener.php',
'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
@ -1292,7 +1291,7 @@ phutil_register_library_map(array(
'ManiphestExcelFormatTestCase' => 'applications/maniphest/export/__tests__/ManiphestExcelFormatTestCase.php',
'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php',
'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php',
'ManiphestHovercardEventListener' => 'applications/maniphest/event/ManiphestHovercardEventListener.php',
'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php',
'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php',
'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php',
'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php',
@ -2370,6 +2369,8 @@ phutil_register_library_map(array(
'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php',
'PhabricatorHomePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorHomePreferencesSettingsPanel.php',
'PhabricatorHomeQuickCreateController' => 'applications/home/controller/PhabricatorHomeQuickCreateController.php',
'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php',
'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php',
'PhabricatorHovercardUIExample' => 'applications/uiexample/examples/PhabricatorHovercardUIExample.php',
'PhabricatorHovercardView' => 'view/widget/hovercard/PhabricatorHovercardView.php',
'PhabricatorHunksManagementMigrateWorkflow' => 'applications/differential/management/PhabricatorHunksManagementMigrateWorkflow.php',
@ -2711,7 +2712,7 @@ phutil_register_library_map(array(
'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php',
'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php',
'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php',
'PhabricatorPeopleHovercardEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php',
'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php',
'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php',
'PhabricatorPeopleInviteSendController' => 'applications/people/controller/PhabricatorPeopleInviteSendController.php',
@ -4172,7 +4173,6 @@ phutil_register_library_map(array(
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl',
'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery',
'ConpherenceHovercardEventListener' => 'PhabricatorEventListener',
'ConpherenceImageData' => 'ConpherenceConstants',
'ConpherenceIndex' => 'ConpherenceDAO',
'ConpherenceLayoutView' => 'AphrontView',
@ -4347,7 +4347,7 @@ phutil_register_library_map(array(
'DifferentialHostField' => 'DifferentialCustomField',
'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy',
'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy',
'DifferentialHovercardEventListener' => 'PhabricatorEventListener',
'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DifferentialHunk' => array(
'DifferentialDAO',
'PhabricatorPolicyInterface',
@ -4568,7 +4568,7 @@ phutil_register_library_map(array(
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionHistoryTableView' => 'DiffusionView',
'DiffusionHovercardEventListener' => 'PhabricatorEventListener',
'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
'DiffusionLastModifiedController' => 'DiffusionController',
@ -5361,7 +5361,7 @@ phutil_register_library_map(array(
'ManiphestExcelFormatTestCase' => 'PhabricatorTestCase',
'ManiphestExportController' => 'ManiphestController',
'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestHovercardEventListener' => 'PhabricatorEventListener',
'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestNameIndex' => 'ManiphestDAO',
'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType',
@ -6624,6 +6624,8 @@ phutil_register_library_map(array(
'PhabricatorHomeMainController' => 'PhabricatorHomeController',
'PhabricatorHomePreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorHomeQuickCreateController' => 'PhabricatorHomeController',
'PhabricatorHovercardEngineExtension' => 'Phobject',
'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorHovercardUIExample' => 'PhabricatorUIExample',
'PhabricatorHovercardView' => 'AphrontView',
'PhabricatorHunksManagementMigrateWorkflow' => 'PhabricatorHunksManagementWorkflow',
@ -7010,7 +7012,7 @@ phutil_register_library_map(array(
'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController',
'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPeopleHovercardEventListener' => 'PhabricatorEventListener',
'PhabricatorPeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController',
'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController',
'PhabricatorPeopleInviteSendController' => 'PhabricatorPeopleInviteController',

View file

@ -28,12 +28,6 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication {
);
}
public function getEventListeners() {
return array(
new ConpherenceHovercardEventListener(),
);
}
public function getRoutes() {
return array(
'/Z(?P<id>[1-9]\d*)' => 'ConpherenceViewController',

View file

@ -1,41 +0,0 @@
<?php
/**
* This event listener is tasked with probably one of the most important
* missions in this world: Adding a Conpherence button to a hovercard.
*
* Handle with care when modifying!
*
* @task event
*/
final class ConpherenceHovercardEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
}
private function handleHovercardEvent($event) {
$hovercard = $event->getValue('hovercard');
$user = $event->getValue('object');
if (!($user instanceof PhabricatorUser)) {
return;
}
$conpherence_uri = id(new PhutilURI('/conpherence/new/'))
->setQueryParam('participant', $user->getPHID());
$name = pht('Send a Message');
$hovercard->addAction($name, $conpherence_uri, true);
$event->setValue('hovercard', $hovercard);
}
}

View file

@ -44,7 +44,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
public function getEventListeners() {
return array(
new DifferentialActionMenuEventListener(),
new DifferentialHovercardEventListener(),
new DifferentialLandingActionMenuEventListener(),
);
}

View file

@ -0,0 +1,77 @@
<?php
final class DifferentialHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
const EXTENSIONKEY = 'differential';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorDifferentialApplication');
}
public function getExtensionName() {
return pht('Differential Revisions');
}
public function canRenderObjectHovercard($object) {
return ($object instanceof DifferentialRevision);
}
public function willRenderHovercards(array $objects) {
$viewer = $this->getViewer();
$phids = mpull($objects, 'getPHID');
$revisions = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withPHIDs($phids)
->needReviewerStatus(true)
->execute();
$revisions = mpull($revisions, null, 'getPHID');
return array(
'revisions' => $revisions,
);
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data) {
$viewer = $this->getViewer();
$revision = idx($data['revisions'], $object->getPHID());
if (!$revision) {
return;
}
$hovercard->setTitle('D'.$revision->getID());
$hovercard->setDetail($revision->getTitle());
$hovercard->addField(
pht('Author'),
$viewer->renderHandle($revision->getAuthorPHID()));
$reviewer_phids = $revision->getReviewerStatus();
$reviewer_phids = mpull($reviewer_phids, 'getReviewerPHID');
$hovercard->addField(
pht('Reviewers'),
$viewer->renderHandleList($reviewer_phids)->setAsInline(true));
$summary = $revision->getSummary();
if (strlen($summary)) {
$summary = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(120)
->truncateString($summary);
$hovercard->addField(pht('Summary'), $summary);
}
$tag = DifferentialRevisionDetailView::renderTagForRevision($revision);
$hovercard->addTag($tag);
}
}

View file

@ -1,71 +0,0 @@
<?php
final class DifferentialHovercardEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
}
private function handleHovercardEvent($event) {
$viewer = $event->getUser();
$hovercard = $event->getValue('hovercard');
$object_handle = $event->getValue('handle');
$phid = $object_handle->getPHID();
$rev = $event->getValue('object');
if (!($rev instanceof DifferentialRevision)) {
return;
}
$rev->loadRelationships();
$reviewer_phids = $rev->getReviewers();
$e_task = DifferentialRevisionHasTaskEdgeType::EDGECONST;
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($phid))
->withEdgeTypes(
array(
$e_task,
));
$edge_query->execute();
$tasks = $edge_query->getDestinationPHIDs();
$hovercard->setTitle('D'.$rev->getID());
$hovercard->setDetail($rev->getTitle());
$hovercard->addField(
pht('Author'),
$viewer->renderHandle($rev->getAuthorPHID()));
$hovercard->addField(
pht('Reviewers'),
$viewer->renderHandleList($reviewer_phids)->setAsInline(true));
if ($tasks) {
$hovercard->addField(
pht('Tasks'),
$viewer->renderHandleList($tasks)->setAsInline(true));
}
if ($rev->getSummary()) {
$hovercard->addField(pht('Summary'),
id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(120)
->truncateString($rev->getSummary()));
}
$hovercard->addTag(
DifferentialRevisionDetailView::renderTagForRevision($rev));
$event->setValue('hovercard', $hovercard);
}
}

View file

@ -37,12 +37,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
);
}
public function getEventListeners() {
return array(
new DiffusionHovercardEventListener(),
);
}
public function getRemarkupRules() {
return array(
new DiffusionCommitRemarkupRule(),

View file

@ -0,0 +1,53 @@
<?php
final class DiffusionHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
const EXTENSIONKEY = 'diffusion';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorDiffusionApplication');
}
public function getExtensionName() {
return pht('Diffusion Commits');
}
public function canRenderObjectHovercard($object) {
return ($object instanceof PhabricatorRepositoryCommit);
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$commit,
$data) {
$viewer = $this->getViewer();
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$author = $viewer->loadHandle($author)->renderLink();
} else {
$commit_data = $commit->loadCommitData();
$author = phutil_tag('em', array(), $commit_data->getAuthorName());
}
$hovercard->setTitle($handle->getName());
$hovercard->setDetail($commit->getSummary());
$hovercard->addField(pht('Author'), $author);
$hovercard->addField(pht('Date'),
phabricator_date($commit->getEpoch(), $viewer));
if ($commit->getAuditStatus() !=
PhabricatorAuditCommitStatusConstants::NONE) {
$hovercard->addField(pht('Audit Status'),
PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus()));
}
}
}

View file

@ -1,75 +0,0 @@
<?php
final class DiffusionHovercardEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
}
private function handleHovercardEvent($event) {
$viewer = $event->getUser();
$hovercard = $event->getValue('hovercard');
$object_handle = $event->getValue('handle');
$commit = $event->getValue('object');
if (!($commit instanceof PhabricatorRepositoryCommit)) {
return;
}
$commit_data = $commit->loadCommitData();
$revision = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit->getPHID(),
DiffusionCommitHasRevisionEdgeType::EDGECONST);
$revision = reset($revision);
$author = $commit->getAuthorPHID();
$phids = array_filter(array(
$revision,
$author,
));
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
if ($author) {
$author = $handles[$author]->renderLink();
} else {
$author = phutil_tag('em', array(), $commit_data->getAuthorName());
}
$hovercard->setTitle($object_handle->getName());
$hovercard->setDetail($commit->getSummary());
$hovercard->addField(pht('Author'), $author);
$hovercard->addField(pht('Date'),
phabricator_date($commit->getEpoch(), $viewer));
if ($commit->getAuditStatus() !=
PhabricatorAuditCommitStatusConstants::NONE) {
$hovercard->addField(pht('Audit Status'),
PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus()));
}
if ($revision) {
$rev_handle = $handles[$revision];
$hovercard->addField(pht('Revision'), $rev_handle->renderLink());
}
$event->setValue('hovercard', $hovercard);
}
}

View file

@ -36,12 +36,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
);
}
public function getEventListeners() {
return array(
new ManiphestHovercardEventListener(),
);
}
public function getRemarkupRules() {
return array(
new ManiphestRemarkupRule(),

View file

@ -0,0 +1,47 @@
<?php
final class ManiphestHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
const EXTENSIONKEY = 'maniphest';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorManiphestApplication');
}
public function getExtensionName() {
return pht('Maniphest Tasks');
}
public function canRenderObjectHovercard($object) {
return ($object instanceof ManiphestTask);
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$task,
$data) {
$viewer = $this->getViewer();
$hovercard
->setTitle($task->getMonogram())
->setDetail($task->getTitle());
$owner_phid = $task->getOwnerPHID();
if ($owner_phid) {
$owner = $viewer->renderHandle($owner_phid);
} else {
$owner = phutil_tag('em', array(), pht('None'));
}
$hovercard->addField(pht('Assigned To'), $owner);
$hovercard->addField(
pht('Priority'),
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()));
$hovercard->addTag(ManiphestView::renderTagForTask($task));
}
}

View file

@ -1,93 +0,0 @@
<?php
final class ManiphestHovercardEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
}
private function handleHovercardEvent(PhutilEvent $event) {
$viewer = $event->getUser();
$hovercard = $event->getValue('hovercard');
$handle = $event->getValue('handle');
$phid = $handle->getPHID();
$task = $event->getValue('object');
if (!($task instanceof ManiphestTask)) {
return;
}
$e_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
// Fun with "Unbeta Pholio", hua hua
$e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($phid))
->withEdgeTypes(
array(
$e_project,
$e_dep_on,
$e_dep_by,
));
$edges = idx($edge_query->execute(), $phid);
$edge_phids = $edge_query->getDestinationPHIDs();
$owner_phid = $task->getOwnerPHID();
$hovercard
->setTitle(pht('T%d', $task->getID()))
->setDetail($task->getTitle());
if ($owner_phid) {
$owner = $viewer->renderHandle($owner_phid);
} else {
$owner = phutil_tag('em', array(), pht('None'));
}
$hovercard->addField(pht('Assigned To'), $owner);
$hovercard->addField(
pht('Priority'),
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()));
if ($edge_phids) {
$edge_types = array(
$e_project => pht('Projects'),
$e_dep_by => pht('Blocks'),
$e_dep_on => pht('Blocked By'),
);
$max_count = 6;
foreach ($edge_types as $edge_type => $edge_name) {
if ($edges[$edge_type]) {
// TODO: This can be made more sophisticated. We still load all
// edges into memory. Only load the ones we need.
$edge_overflow = array();
if (count($edges[$edge_type]) > $max_count) {
$edges[$edge_type] = array_slice($edges[$edge_type], 0, 6, true);
$edge_overflow = ', ...';
}
$hovercard->addField(
$edge_name,
array(
$viewer->renderHandleList(array_keys($edges[$edge_type])),
$edge_overflow,
));
}
}
}
$hovercard->addTag(ManiphestView::renderTagForTask($task));
$event->setValue('hovercard', $hovercard);
}
}

View file

@ -34,12 +34,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
return false;
}
public function getEventListeners() {
return array(
new PhabricatorPeopleHovercardEventListener(),
);
}
public function getRoutes() {
return array(
'/people/' => array(

View file

@ -1,45 +1,58 @@
<?php
final class PhabricatorPeopleHovercardEventListener
extends PhabricatorEventListener {
final class PhabricatorPeopleHovercardEngineExtension
extends PhabricatorHovercardEngineExtension {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
const EXTENSIONKEY = 'people';
public function isExtensionEnabled() {
return true;
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
public function getExtensionName() {
return pht('User Accounts');
}
private function handleHovercardEvent($event) {
$viewer = $event->getUser();
$hovercard = $event->getValue('hovercard');
$object_handle = $event->getValue('handle');
$phid = $object_handle->getPHID();
$user = $event->getValue('object');
if (!($user instanceof PhabricatorUser)) {
return;
public function canRenderObjectHovercard($object) {
return ($object instanceof PhabricatorUser);
}
// Reload to get availability.
$user = id(new PhabricatorPeopleQuery())
public function willRenderHovercards(array $objects) {
$viewer = $this->getViewer();
$phids = mpull($objects, 'getPHID');
$users = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($user->getID()))
->withPHIDs($phids)
->needAvailability(true)
->needProfile(true)
->needBadges(true)
->executeOne();
->execute();
$users = mpull($users, null, 'getPHID');
return array(
'users' => $users,
);
}
public function renderHovercard(
PhabricatorHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data) {
$viewer = $this->getViewer();
$user = idx($data['users'], $object->getPHID());
if (!$user) {
return;
}
$hovercard->setTitle($user->getUsername());
$profile = $user->getUserProfile();
$detail = $user->getRealName();
if ($profile->getTitle()) {
$detail .= ' - '.$profile->getTitle().'.';
$detail .= ' - '.$profile->getTitle();
}
$hovercard->setDetail($detail);
@ -70,8 +83,6 @@ final class PhabricatorPeopleHovercardEventListener
foreach ($badges as $badge) {
$hovercard->addBadge($badge);
}
$event->setValue('hovercard', $hovercard);
}
private function buildBadges(
@ -101,5 +112,4 @@ final class PhabricatorPeopleHovercardEventListener
return $items;
}
}

View file

@ -35,7 +35,7 @@ final class PhabricatorSearchApplication extends PhabricatorApplication {
'select/(?P<type>\w+)/(?:(?P<action>\w+)/)?'
=> 'PhabricatorSearchSelectController',
'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController',
'hovercard/(?P<mode>retrieve|test)/'
'hovercard/'
=> 'PhabricatorSearchHovercardController',
'edit/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController',
'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'

View file

@ -15,13 +15,43 @@ final class PhabricatorSearchHovercardController
->setViewer($viewer)
->withPHIDs($phids)
->execute();
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
$extensions =
PhabricatorHovercardEngineExtension::getAllEnabledExtensions();
$extension_maps = array();
foreach ($extensions as $key => $extension) {
$extension->setViewer($viewer);
$extension_phids = array();
foreach ($objects as $phid => $object) {
if ($extension->canRenderObjectHovercard($object)) {
$extension_phids[$phid] = $phid;
}
}
$extension_maps[$key] = $extension_phids;
}
$extension_data = array();
foreach ($extensions as $key => $extension) {
$extension_phids = $extension_maps[$key];
if (!$extension_phids) {
unset($extensions[$key]);
continue;
}
$extension_data[$key] = $extension->willRenderHovercards(
array_select_keys($objects, $extension_phids));
}
$cards = array();
foreach ($phids as $phid) {
$handle = $handles[$phid];
$object = $objects[$phid];
@ -32,24 +62,28 @@ final class PhabricatorSearchHovercardController
if ($object) {
$hovercard->setObject($object);
}
// Send it to the other side of the world, thanks to PhutilEventEngine
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD,
array(
'hovercard' => $hovercard,
'handle' => $handle,
'object' => $object,
));
$event->setUser($viewer);
PhutilEventEngine::dispatchEvent($event);
foreach ($extension_maps as $key => $extension_phids) {
if (isset($extension_phids[$phid])) {
$extensions[$key]->renderHovercard(
$hovercard,
$handle,
$object,
$extension_data[$key]);
}
}
}
$cards[$phid] = $hovercard;
}
// Browser-friendly for non-Ajax requests
if (!$request->isAjax()) {
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())->setContent(
array(
'cards' => $cards,
));
}
foreach ($cards as $key => $hovercard) {
$cards[$key] = phutil_tag('div',
array(
@ -58,17 +92,9 @@ final class PhabricatorSearchHovercardController
$hovercard);
}
return $this->buildApplicationPage(
$cards,
array(
'device' => false,
));
} else {
return id(new AphrontAjaxResponse())->setContent(
array(
'cards' => $cards,
));
}
return $this->newPage()
->appendChild($cards)
->setShowFooter(false);
}
}

View file

@ -0,0 +1,60 @@
<?php
abstract class PhabricatorHovercardEngineExtension extends Phobject {
private $viewer;
final public function getExtensionKey() {
return $this->getPhobjectClassConstant('EXTENSIONKEY');
}
final public function setViewer($viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
abstract public function isExtensionEnabled();
abstract public function getExtensionName();
abstract public function canRenderObjectHovercard($object);
public function getExtensionOrder() {
return 5000;
}
public function willRenderHovercards(array $objects) {
return null;
}
abstract public function renderHovercard(
PhabricatorHovercardView $hovercard,
PhabricatorObjectHandle $handle,
$object,
$data);
final public static function getAllExtensions() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getExtensionKey')
->setSortMethod('getExtensionOrder')
->execute();
}
final public static function getAllEnabledExtensions() {
$extensions = self::getAllExtensions();
foreach ($extensions as $key => $extension) {
if (!$extension->isExtensionEnabled()) {
unset($extensions[$key]);
}
}
return $extensions;
}
}

View file

@ -0,0 +1,55 @@
<?php
final class PhabricatorHovercardEngineExtensionModule
extends PhabricatorConfigModule {
public function getModuleKey() {
return 'hovercardengine';
}
public function getModuleName() {
return pht('Engine: Hovercards');
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
$extensions = PhabricatorHovercardEngineExtension::getAllExtensions();
$rows = array();
foreach ($extensions as $extension) {
$rows[] = array(
$extension->getExtensionOrder(),
$extension->getExtensionKey(),
get_class($extension),
$extension->getExtensionName(),
$extension->isExtensionEnabled()
? pht('Yes')
: pht('No'),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Order'),
pht('Key'),
pht('Class'),
pht('Name'),
pht('Enabled'),
))
->setColumnClasses(
array(
null,
null,
null,
'wide pri',
null,
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('HovercardEngine Extensions'))
->setTable($table);
}
}

View file

@ -20,8 +20,6 @@ final class PhabricatorEventType extends PhutilEventType {
const TYPE_UI_DIDRENDEROBJECTS = 'ui.didRenderObjects';
const TYPE_UI_WILLRENDERPROPERTIES = 'ui.willRenderProperties';
const TYPE_UI_DIDRENDERHOVERCARD = 'ui.didRenderHovercard';
const TYPE_PEOPLE_DIDRENDERMENU = 'people.didRenderMenu';
const TYPE_AUTH_WILLREGISTERUSER = 'auth.willRegisterUser';
const TYPE_AUTH_WILLLOGINUSER = 'auth.willLoginUser';

View file

@ -15,7 +15,7 @@ JX.install('Hovercard', {
_activeRoot : null,
_visiblePHID : null,
fetchUrl : '/search/hovercard/retrieve/',
fetchUrl : '/search/hovercard/',
/**
* Hovercard storage. {"PHID-XXXX-YYYY":"<...>", ...}