mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 06:42:42 +01:00
Add "Mute/Unmute" for subscribable objects
Summary: Ref T13053. See PHI126. Add an explicit "Mute" action to kill mail and notifications for a particular object. Test Plan: Muted and umuted an object while interacting with it. Saw mail route appropriately. Maniphest Tasks: T13053 Differential Revision: https://secure.phabricator.com/D19033
This commit is contained in:
parent
0402a79e0e
commit
ab04d2179b
13 changed files with 254 additions and 24 deletions
|
@ -9,7 +9,7 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => 'e68cf1fa',
|
||||
'conpherence.pkg.js' => '15191c65',
|
||||
'core.pkg.css' => '51debec3',
|
||||
'core.pkg.css' => 'ce8c2a58',
|
||||
'core.pkg.js' => '4c79d74f',
|
||||
'darkconsole.pkg.js' => '1f9a31bc',
|
||||
'differential.pkg.css' => '45951e9e',
|
||||
|
@ -136,7 +136,7 @@ return array(
|
|||
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6',
|
||||
'rsrc/css/phui/object-item/phui-oi-list-view.css' => '6ae18df0',
|
||||
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea',
|
||||
'rsrc/css/phui/phui-action-list.css' => 'f7f61a34',
|
||||
'rsrc/css/phui/phui-action-list.css' => '0bcd9a45',
|
||||
'rsrc/css/phui/phui-action-panel.css' => 'b4798122',
|
||||
'rsrc/css/phui/phui-badge.css' => '22c0cf4f',
|
||||
'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3',
|
||||
|
@ -766,7 +766,7 @@ return array(
|
|||
'path-typeahead' => 'f7fc67ec',
|
||||
'people-picture-menu-item-css' => 'a06f7f34',
|
||||
'people-profile-css' => '4df76faf',
|
||||
'phabricator-action-list-view-css' => 'f7f61a34',
|
||||
'phabricator-action-list-view-css' => '0bcd9a45',
|
||||
'phabricator-busy' => '59a7976a',
|
||||
'phabricator-chatlog-css' => 'd295b020',
|
||||
'phabricator-content-source-view-css' => '4b8b05d4',
|
||||
|
|
|
@ -3291,6 +3291,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMultiFactorSettingsPanel' => 'applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php',
|
||||
'PhabricatorMultimeterApplication' => 'applications/multimeter/application/PhabricatorMultimeterApplication.php',
|
||||
'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php',
|
||||
'PhabricatorMutedByEdgeType' => 'applications/transactions/edges/PhabricatorMutedByEdgeType.php',
|
||||
'PhabricatorMutedEdgeType' => 'applications/transactions/edges/PhabricatorMutedEdgeType.php',
|
||||
'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php',
|
||||
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
|
||||
'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php',
|
||||
|
@ -4240,6 +4242,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php',
|
||||
'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php',
|
||||
'PhabricatorSubscriptionsMailEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsMailEngineExtension.php',
|
||||
'PhabricatorSubscriptionsMuteController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php',
|
||||
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php',
|
||||
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php',
|
||||
'PhabricatorSubscriptionsSearchEngineAttachment' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineAttachment.php',
|
||||
|
@ -8808,6 +8811,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMultiFactorSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorMultimeterApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController',
|
||||
'PhabricatorMutedByEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorMutedEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
|
||||
'PhabricatorMySQLSearchHost' => 'PhabricatorSearchHost',
|
||||
|
@ -9960,6 +9965,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction',
|
||||
'PhabricatorSubscriptionsListController' => 'PhabricatorController',
|
||||
'PhabricatorSubscriptionsMailEngineExtension' => 'PhabricatorMailEngineExtension',
|
||||
'PhabricatorSubscriptionsMuteController' => 'PhabricatorController',
|
||||
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
|
||||
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
|
||||
'PhabricatorSubscriptionsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
|
|
|
@ -21,6 +21,7 @@ final class PhabricatorMetaMTAActor extends Phobject {
|
|||
const REASON_ROUTE_AS_NOTIFICATION = 'route-as-notification';
|
||||
const REASON_ROUTE_AS_MAIL = 'route-as-mail';
|
||||
const REASON_UNVERIFIED = 'unverified';
|
||||
const REASON_MUTED = 'muted';
|
||||
|
||||
private $phid;
|
||||
private $emailAddress;
|
||||
|
@ -116,6 +117,7 @@ final class PhabricatorMetaMTAActor extends Phobject {
|
|||
self::REASON_ROUTE_AS_NOTIFICATION => pht('Route as Notification'),
|
||||
self::REASON_ROUTE_AS_MAIL => pht('Route as Mail'),
|
||||
self::REASON_UNVERIFIED => pht('Address Not Verified'),
|
||||
self::REASON_MUTED => pht('Muted'),
|
||||
);
|
||||
|
||||
return idx($names, $reason, pht('Unknown ("%s")', $reason));
|
||||
|
@ -172,6 +174,8 @@ final class PhabricatorMetaMTAActor extends Phobject {
|
|||
'in Herald.'),
|
||||
self::REASON_UNVERIFIED => pht(
|
||||
'This recipient does not have a verified primary email address.'),
|
||||
self::REASON_MUTED => pht(
|
||||
'This recipient has muted notifications for this object.'),
|
||||
);
|
||||
|
||||
return idx($descriptions, $reason, pht('Unknown Reason ("%s")', $reason));
|
||||
|
|
|
@ -160,6 +160,15 @@ final class PhabricatorMetaMTAMail
|
|||
return $this->getParam('exclude', array());
|
||||
}
|
||||
|
||||
public function setMutedPHIDs(array $muted) {
|
||||
$this->setParam('muted', $muted);
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getMutedPHIDs() {
|
||||
return $this->getParam('muted', array());
|
||||
}
|
||||
|
||||
public function setForceHeraldMailRecipientPHIDs(array $force) {
|
||||
$this->setParam('herald-force-recipients', $force);
|
||||
return $this;
|
||||
|
@ -1113,6 +1122,18 @@ final class PhabricatorMetaMTAMail
|
|||
}
|
||||
}
|
||||
|
||||
// Exclude muted recipients. We're doing this after saving deliverability
|
||||
// so that Herald "Send me an email" actions can still punch through a
|
||||
// mute.
|
||||
|
||||
foreach ($this->getMutedPHIDs() as $muted_phid) {
|
||||
$muted_actor = idx($actors, $muted_phid);
|
||||
if (!$muted_actor) {
|
||||
continue;
|
||||
}
|
||||
$muted_actor->setUndeliverable(PhabricatorMetaMTAActor::REASON_MUTED);
|
||||
}
|
||||
|
||||
// For the rest of the rules, order matters. We're going to run all the
|
||||
// possible rules in order from weakest to strongest, and let the strongest
|
||||
// matching rule win. The weaker rules leave annotations behind which help
|
||||
|
|
|
@ -24,7 +24,10 @@ final class PhabricatorSubscriptionsApplication extends PhabricatorApplication {
|
|||
return array(
|
||||
'/subscriptions/' => array(
|
||||
'(?P<action>add|delete)/'.
|
||||
'(?P<phid>[^/]+)/' => 'PhabricatorSubscriptionsEditController',
|
||||
'(?P<phid>[^/]+)/' => 'PhabricatorSubscriptionsEditController',
|
||||
'mute/' => array(
|
||||
'(?P<phid>[^/]+)/' => 'PhabricatorSubscriptionsMuteController',
|
||||
),
|
||||
'list/(?P<phid>[^/]+)/' => 'PhabricatorSubscriptionsListController',
|
||||
'transaction/(?P<type>add|rem)/(?<phid>[^/]+)/'
|
||||
=> 'PhabricatorSubscriptionsTransactionController',
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSubscriptionsMuteController
|
||||
extends PhabricatorController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$phid = $request->getURIData('phid');
|
||||
|
||||
$handle = id(new PhabricatorHandleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($phid))
|
||||
->executeOne();
|
||||
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($phid))
|
||||
->executeOne();
|
||||
|
||||
if (!($object instanceof PhabricatorSubscribableInterface)) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
$muted_type = PhabricatorMutedByEdgeType::EDGECONST;
|
||||
|
||||
$edge_query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs(array($object->getPHID()))
|
||||
->withEdgeTypes(array($muted_type))
|
||||
->withDestinationPHIDs(array($viewer->getPHID()));
|
||||
|
||||
$edge_query->execute();
|
||||
|
||||
$is_mute = !$edge_query->getDestinationPHIDs();
|
||||
$object_uri = $handle->getURI();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($is_mute) {
|
||||
$xaction_value = array(
|
||||
'+' => array_fuse(array($viewer->getPHID())),
|
||||
);
|
||||
} else {
|
||||
$xaction_value = array(
|
||||
'-' => array_fuse(array($viewer->getPHID())),
|
||||
);
|
||||
}
|
||||
|
||||
$muted_type = PhabricatorMutedByEdgeType::EDGECONST;
|
||||
|
||||
$xaction = id($object->getApplicationTransactionTemplate())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $muted_type)
|
||||
->setNewValue($xaction_value);
|
||||
|
||||
$editor = id($object->getApplicationTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request);
|
||||
|
||||
$editor->applyTransactions(
|
||||
$object->getApplicationTransactionObject(),
|
||||
array($xaction));
|
||||
|
||||
return id(new AphrontReloadResponse())->setURI($object_uri);
|
||||
}
|
||||
|
||||
$dialog = $this->newDialog()
|
||||
->addCancelButton($object_uri);
|
||||
|
||||
if ($is_mute) {
|
||||
$dialog
|
||||
->setTitle(pht('Mute Notifications'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Mute this object? You will no longer receive notifications or '.
|
||||
'email about it.'))
|
||||
->addSubmitButton(pht('Mute'));
|
||||
} else {
|
||||
$dialog
|
||||
->setTitle(pht('Unmute Notifications'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Unmute this object? You will receive notifications and email '.
|
||||
'again.'))
|
||||
->addSubmitButton(pht('Unmute'));
|
||||
}
|
||||
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -42,6 +42,28 @@ final class PhabricatorSubscriptionsUIEventListener
|
|||
return;
|
||||
}
|
||||
|
||||
$src_phid = $object->getPHID();
|
||||
$subscribed_type = PhabricatorObjectHasSubscriberEdgeType::EDGECONST;
|
||||
$muted_type = PhabricatorMutedByEdgeType::EDGECONST;
|
||||
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs(array($src_phid))
|
||||
->withEdgeTypes(
|
||||
array(
|
||||
$subscribed_type,
|
||||
$muted_type,
|
||||
))
|
||||
->withDestinationPHIDs(array($user_phid))
|
||||
->execute();
|
||||
|
||||
if ($user_phid) {
|
||||
$is_subscribed = isset($edges[$src_phid][$subscribed_type][$user_phid]);
|
||||
$is_muted = isset($edges[$src_phid][$muted_type][$user_phid]);
|
||||
} else {
|
||||
$is_subscribed = false;
|
||||
$is_muted = false;
|
||||
}
|
||||
|
||||
if ($user_phid && $object->isAutomaticallySubscribed($user_phid)) {
|
||||
$sub_action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
|
@ -51,22 +73,9 @@ final class PhabricatorSubscriptionsUIEventListener
|
|||
->setName(pht('Automatically Subscribed'))
|
||||
->setIcon('fa-check-circle lightgreytext');
|
||||
} else {
|
||||
$subscribed = false;
|
||||
if ($user->isLoggedIn()) {
|
||||
$src_phid = $object->getPHID();
|
||||
$edge_type = PhabricatorObjectHasSubscriberEdgeType::EDGECONST;
|
||||
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs(array($src_phid))
|
||||
->withEdgeTypes(array($edge_type))
|
||||
->withDestinationPHIDs(array($user_phid))
|
||||
->execute();
|
||||
$subscribed = isset($edges[$src_phid][$edge_type][$user_phid]);
|
||||
}
|
||||
|
||||
$can_interact = PhabricatorPolicyFilter::canInteract($user, $object);
|
||||
|
||||
if ($subscribed) {
|
||||
if ($is_subscribed) {
|
||||
$sub_action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setRenderAsForm(true)
|
||||
|
@ -89,8 +98,26 @@ final class PhabricatorSubscriptionsUIEventListener
|
|||
}
|
||||
}
|
||||
|
||||
$mute_action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setHref('/subscriptions/mute/'.$object->getPHID().'/')
|
||||
->setDisabled(!$user_phid);
|
||||
|
||||
if (!$is_muted) {
|
||||
$mute_action
|
||||
->setName(pht('Mute Notifications'))
|
||||
->setIcon('fa-volume-up');
|
||||
} else {
|
||||
$mute_action
|
||||
->setName(pht('Unmute Notifications'))
|
||||
->setIcon('fa-volume-off')
|
||||
->setColor(PhabricatorActionView::RED);
|
||||
}
|
||||
|
||||
|
||||
$actions = $event->getValue('actions');
|
||||
$actions[] = $sub_action;
|
||||
$actions[] = $mute_action;
|
||||
$event->setValue('actions', $actions);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMutedByEdgeType
|
||||
extends PhabricatorEdgeType {
|
||||
|
||||
const EDGECONST = 68;
|
||||
|
||||
public function getInverseEdgeConstant() {
|
||||
return PhabricatorMutedEdgeType::EDGECONST;
|
||||
}
|
||||
|
||||
public function shouldWriteInverseTransactions() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMutedEdgeType
|
||||
extends PhabricatorEdgeType {
|
||||
|
||||
const EDGECONST = 67;
|
||||
|
||||
public function getInverseEdgeConstant() {
|
||||
return PhabricatorMutedByEdgeType::EDGECONST;
|
||||
}
|
||||
|
||||
public function shouldWriteInverseTransactions() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -78,6 +78,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
private $oldCC = array();
|
||||
private $mailRemovedPHIDs = array();
|
||||
private $mailUnexpandablePHIDs = array();
|
||||
private $mailMutedPHIDs = array();
|
||||
|
||||
private $transactionQueue = array();
|
||||
|
||||
|
@ -1211,6 +1212,14 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
// but were removed by this change.
|
||||
$this->applyOldRecipientLists();
|
||||
|
||||
if ($object instanceof PhabricatorSubscribableInterface) {
|
||||
$this->mailMutedPHIDs = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$object->getPHID(),
|
||||
PhabricatorMutedByEdgeType::EDGECONST);
|
||||
} else {
|
||||
$this->mailMutedPHIDs = array();
|
||||
}
|
||||
|
||||
$mail_xactions = $this->getTransactionsForMail($object, $xactions);
|
||||
$stamps = $this->newMailStamps($object, $xactions);
|
||||
foreach ($stamps as $stamp) {
|
||||
|
@ -2662,6 +2671,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
$mail_xactions);
|
||||
}
|
||||
|
||||
$muted_phids = $this->mailMutedPHIDs;
|
||||
if (!is_array($muted_phids)) {
|
||||
$muted_phids = array();
|
||||
}
|
||||
|
||||
$mail
|
||||
->setSensitiveContent(false)
|
||||
->setFrom($this->getActingAsPHID())
|
||||
|
@ -2670,6 +2684,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
->setThreadID($this->getMailThreadID($object), $this->getIsNewObject())
|
||||
->setRelatedPHID($object->getPHID())
|
||||
->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs())
|
||||
->setMutedPHIDs($muted_phids)
|
||||
->setForceHeraldMailRecipientPHIDs($this->heraldForcedEmailPHIDs)
|
||||
->setMailTags($mail_tags)
|
||||
->setIsBulk(true)
|
||||
|
@ -3186,6 +3201,18 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
$related_phids = $this->feedRelatedPHIDs;
|
||||
$subscribed_phids = $this->feedNotifyPHIDs;
|
||||
|
||||
// Remove muted users from the subscription list so they don't get
|
||||
// notifications, either.
|
||||
$muted_phids = $this->mailMutedPHIDs;
|
||||
if (!is_array($muted_phids)) {
|
||||
$muted_phids = array();
|
||||
}
|
||||
$subscribed_phids = array_fuse($subscribed_phids);
|
||||
foreach ($muted_phids as $muted_phid) {
|
||||
unset($subscribed_phids[$muted_phid]);
|
||||
}
|
||||
$subscribed_phids = array_values($subscribed_phids);
|
||||
|
||||
$story_type = $this->getFeedStoryType();
|
||||
$story_data = $this->getFeedStoryData($object, $xactions);
|
||||
|
||||
|
@ -3632,6 +3659,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
'mustEncrypt',
|
||||
'mailStamps',
|
||||
'mailUnexpandablePHIDs',
|
||||
'mailMutedPHIDs',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -643,6 +643,8 @@ abstract class PhabricatorApplicationTransaction
|
|||
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
|
||||
case ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST:
|
||||
case ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST:
|
||||
case PhabricatorMutedEdgeType::EDGECONST:
|
||||
case PhabricatorMutedByEdgeType::EDGECONST:
|
||||
return true;
|
||||
break;
|
||||
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
|
||||
|
|
|
@ -21,6 +21,7 @@ final class PhabricatorActionView extends AphrontView {
|
|||
private $order;
|
||||
private $color;
|
||||
private $type;
|
||||
private $highlight;
|
||||
|
||||
const TYPE_DIVIDER = 'type-divider';
|
||||
const TYPE_LABEL = 'label';
|
||||
|
@ -72,6 +73,15 @@ final class PhabricatorActionView extends AphrontView {
|
|||
return $this->href;
|
||||
}
|
||||
|
||||
public function setHighlight($highlight) {
|
||||
$this->highlight = $highlight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHighlight() {
|
||||
return $this->highlight;
|
||||
}
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
return $this;
|
||||
|
|
|
@ -95,15 +95,20 @@
|
|||
color: {$sky};
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-action-view-href.action-item-red:hover
|
||||
.phabricator-action-view-item {
|
||||
background-color: {$sh-redbackground};
|
||||
color: {$sh-redtext};
|
||||
.phabricator-action-view.action-item-red {
|
||||
background-color: {$sh-redbackground};
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-action-view-href.action-item-red:hover
|
||||
.phabricator-action-view.action-item-red .phabricator-action-view-item,
|
||||
.phabricator-action-view.action-item-red .phabricator-action-view-icon {
|
||||
color: {$sh-redtext};
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-action-view.action-item-red:hover
|
||||
.phabricator-action-view-item,
|
||||
.device-desktop .phabricator-action-view.action-item-red:hover
|
||||
.phabricator-action-view-icon {
|
||||
color: {$red};
|
||||
color: {$red};
|
||||
}
|
||||
|
||||
.phabricator-action-view-label .phabricator-action-view-item,
|
||||
|
|
Loading…
Reference in a new issue