3 final class PHUITimelineEventView
extends AphrontView
{
5 const DELIMITER
= " \xC2\xB7 ";
11 private $classes = array();
12 private $contentSource;
18 private $transactionPHID;
20 private $eventGroup = array();
21 private $hideByDefault;
23 private $tokenRemoved;
24 private $quoteTargetID;
25 private $isNormalComment;
27 private $reallyMajorEvent;
28 private $hideCommentOptions = false;
30 private $badges = array();
31 private $pinboardItems = array();
34 private $isLockOverride;
37 public function setAuthorPHID($author_phid) {
38 $this->authorPHID
= $author_phid;
42 public function getAuthorPHID() {
43 return $this->authorPHID
;
46 public function setQuoteRef($quote_ref) {
47 $this->quoteRef
= $quote_ref;
51 public function getQuoteRef() {
52 return $this->quoteRef
;
55 public function setQuoteTargetID($quote_target_id) {
56 $this->quoteTargetID
= $quote_target_id;
60 public function getQuoteTargetID() {
61 return $this->quoteTargetID
;
64 public function setIsNormalComment($is_normal_comment) {
65 $this->isNormalComment
= $is_normal_comment;
69 public function getIsNormalComment() {
70 return $this->isNormalComment
;
73 public function setHideByDefault($hide_by_default) {
74 $this->hideByDefault
= $hide_by_default;
78 public function getHideByDefault() {
79 return $this->hideByDefault
;
82 public function setTransactionPHID($transaction_phid) {
83 $this->transactionPHID
= $transaction_phid;
87 public function getTransactionPHID() {
88 return $this->transactionPHID
;
91 public function setIsEdited($is_edited) {
92 $this->isEdited
= $is_edited;
96 public function getIsEdited() {
97 return $this->isEdited
;
100 public function setIsPreview($is_preview) {
101 $this->isPreview
= $is_preview;
105 public function getIsPreview() {
106 return $this->isPreview
;
109 public function setIsEditable($is_editable) {
110 $this->isEditable
= $is_editable;
114 public function getIsEditable() {
115 return $this->isEditable
;
118 public function setCanInteract($can_interact) {
119 $this->canInteract
= $can_interact;
123 public function getCanInteract() {
124 return $this->canInteract
;
127 public function setIsRemovable($is_removable) {
128 $this->isRemovable
= $is_removable;
132 public function getIsRemovable() {
133 return $this->isRemovable
;
136 public function setDateCreated($date_created) {
137 $this->dateCreated
= $date_created;
141 public function getDateCreated() {
142 return $this->dateCreated
;
145 public function setContentSource(PhabricatorContentSource
$content_source) {
146 $this->contentSource
= $content_source;
150 public function getContentSource() {
151 return $this->contentSource
;
154 public function setUserHandle(PhabricatorObjectHandle
$handle) {
155 $this->userHandle
= $handle;
159 public function setAnchor($anchor) {
160 $this->anchor
= $anchor;
164 public function getAnchor() {
165 return $this->anchor
;
168 public function setTitle($title) {
169 $this->title
= $title;
173 public function addClass($class) {
174 $this->classes
[] = $class;
178 public function addBadge(PHUIBadgeMiniView
$badge) {
179 $this->badges
[] = $badge;
183 public function setIcon($icon) {
188 public function setColor($color) {
189 $this->color
= $color;
193 public function setIsSilent($is_silent) {
194 $this->isSilent
= $is_silent;
198 public function getIsSilent() {
199 return $this->isSilent
;
202 public function setIsMFA($is_mfa) {
203 $this->isMFA
= $is_mfa;
207 public function getIsMFA() {
211 public function setIsLockOverride($is_override) {
212 $this->isLockOverride
= $is_override;
216 public function getIsLockOverride() {
217 return $this->isLockOverride
;
220 public function setReallyMajorEvent($me) {
221 $this->reallyMajorEvent
= $me;
225 public function setHideCommentOptions($hide_comment_options) {
226 $this->hideCommentOptions
= $hide_comment_options;
230 public function getHideCommentOptions() {
231 return $this->hideCommentOptions
;
234 public function addPinboardItem(PHUIPinboardItemView
$item) {
235 $this->pinboardItems
[] = $item;
239 public function setToken($token, $removed = false) {
240 $this->token
= $token;
241 $this->tokenRemoved
= $removed;
245 public function getEventGroup() {
246 return array_merge(array($this), $this->eventGroup
);
249 public function addEventToGroup(PHUITimelineEventView
$event) {
250 $this->eventGroup
[] = $event;
254 protected function shouldRenderEventTitle() {
255 if ($this->title
=== null) {
262 protected function renderEventTitle($force_icon, $has_menu, $extra) {
263 $title = $this->title
;
265 $title_classes = array();
266 $title_classes[] = 'phui-timeline-title';
269 if ($this->icon ||
$force_icon) {
270 $title_classes[] = 'phui-timeline-title-with-icon';
274 $title_classes[] = 'phui-timeline-title-with-menu';
278 $fill_classes = array();
279 $fill_classes[] = 'phui-timeline-icon-fill';
281 $fill_classes[] = 'fill-has-color';
282 $fill_classes[] = 'phui-timeline-icon-fill-'.$this->color
;
285 $icon = id(new PHUIIconView())
286 ->setIcon($this->icon
)
287 ->addClass('phui-timeline-icon');
292 'class' => implode(' ', $fill_classes),
299 $token = id(new PHUIIconView())
300 ->addClass('phui-timeline-token')
301 ->setSpriteSheet(PHUIIconView
::SPRITE_TOKENS
)
302 ->setSpriteIcon($this->token
);
303 if ($this->tokenRemoved
) {
304 $token->addClass('strikethrough');
311 'class' => implode(' ', $title_classes),
313 array($icon, $token, $title, $extra));
318 public function render() {
320 $events = $this->getEventGroup();
322 // Move events with icons first.
323 $icon_keys = array();
324 foreach ($this->getEventGroup() as $key => $event) {
329 $events = array_select_keys($events, $icon_keys) +
$events;
330 $force_icon = (bool)$icon_keys;
334 if (!$this->getIsPreview() && !$this->getHideCommentOptions()) {
335 foreach ($this->getEventGroup() as $event) {
336 $items[] = $event->getMenuItems($this->anchor
);
338 $items = array_mergev($items);
342 $icon = id(new PHUIIconView())
343 ->setIcon('fa-caret-down');
344 $aural = javelin_tag(
349 pht('Comment Actions'));
352 $sigil = 'phui-dropdown-menu';
353 Javelin
::initBehavior('phui-dropdown-menu');
358 $action_list = id(new PhabricatorActionListView())
359 ->setUser($this->getUser());
360 foreach ($items as $item) {
361 $action_list->addAction($item);
365 $items ?
'a' : 'span',
368 'class' => 'phui-timeline-menu',
370 'aria-haspopup' => 'true',
371 'aria-expanded' => 'false',
372 'meta' => $action_list->getDropdownMenuMetadata(),
384 // Render "extra" information (timestamp, etc).
385 $extra = $this->renderExtra($events);
387 $show_badges = false;
389 $group_titles = array();
390 $group_items = array();
391 $group_children = array();
392 foreach ($events as $event) {
393 if ($event->shouldRenderEventTitle()) {
395 // Render the group anchor here, outside the title box. If we render
396 // it inside the title box it ends up completely hidden and Chrome 55
397 // refuses to jump to it. See T11997 for discussion.
399 if ($extra && $this->anchor
) {
400 $group_titles[] = id(new PhabricatorAnchorView())
401 ->setAnchorName($this->anchor
)
405 $group_titles[] = $event->renderEventTitle(
410 // Don't render this information more than once.
414 if ($event->hasChildren()) {
415 $group_children[] = $event->renderChildren();
420 $image_uri = $this->userHandle
->getImageURI();
425 'class' => 'phui-timeline-wedge',
426 'style' => (nonempty($image_uri)) ?
'' : 'display: none;',
433 $image = javelin_tag(
434 ($this->userHandle
->getURI()) ?
'a' : 'div',
436 'style' => 'background-image: url('.$image_uri.')',
437 'class' => 'phui-timeline-image',
438 'href' => $this->userHandle
->getURI(),
442 if ($this->badges
&& $show_badges) {
443 $flex = new PHUIBadgeBoxView();
444 $flex->addItems($this->badges
);
445 $flex->setCollapsed(true);
446 $badges = phutil_tag(
449 'class' => 'phui-timeline-badges',
455 $content_classes = array();
456 $content_classes[] = 'phui-timeline-content';
459 $classes[] = 'phui-timeline-event-view';
460 if ($group_children) {
461 $classes[] = 'phui-timeline-major-event';
462 $content = phutil_tag(
465 'class' => 'phui-timeline-inner-content',
473 'class' => 'phui-timeline-core-content',
478 $classes[] = 'phui-timeline-minor-event';
479 $content = $group_titles;
482 $content = phutil_tag(
485 'class' => 'phui-timeline-group',
491 if ($this->pinboardItems
) {
492 $pinboard = new PHUIPinboardView();
493 foreach ($this->pinboardItems
as $item) {
494 $pinboard->addItem($item);
498 $content = phutil_tag(
501 'class' => implode(' ', $content_classes),
503 array($image, $badges, $wedge, $content, $pinboard));
505 $outer_classes = $this->classes
;
506 $outer_classes[] = 'phui-timeline-shell';
508 foreach ($this->getEventGroup() as $event) {
510 $color = $event->color
;
516 $outer_classes[] = 'phui-timeline-'.$color;
521 if ($this->getTransactionPHID()) {
522 $sigils[] = 'transaction';
524 'phid' => $this->getTransactionPHID(),
525 'anchor' => $this->anchor
,
530 if ($this->reallyMajorEvent
) {
531 $major_event = phutil_tag(
534 'class' => 'phui-timeline-event-view '.
535 'phui-timeline-spacer '.
536 'phui-timeline-spacer-bold',
540 $sigils[] = 'anchor-container';
546 'class' => implode(' ', $outer_classes),
547 'sigil' => implode(' ', $sigils),
553 'class' => implode(' ', $classes),
560 private function renderExtra(array $events) {
563 if ($this->getIsPreview()) {
564 $extra[] = pht('PREVIEW');
566 foreach ($events as $event) {
567 if ($event->getIsEdited()) {
568 $extra[] = pht('Edited');
573 $source = $this->getContentSource();
574 $content_source = null;
576 $content_source = id(new PhabricatorContentSourceView())
577 ->setContentSource($source)
578 ->setUser($this->getUser());
579 $content_source = pht('Via %s', $content_source->getSourceName());
582 $date_created = null;
583 foreach ($events as $event) {
584 if ($event->getDateCreated()) {
585 if ($date_created === null) {
586 $date_created = $event->getDateCreated();
588 $date_created = min($event->getDateCreated(), $date_created);
594 $date = phabricator_datetime(
598 Javelin
::initBehavior('phabricator-watch-anchor');
599 Javelin
::initBehavior('phabricator-tooltips');
605 'href' => '#'.$this->anchor
,
606 'sigil' => 'has-tooltip',
608 'tip' => $content_source,
617 // If this edit was applied silently, give user a hint that they should
618 // not expect to have received any mail or notifications.
619 if ($this->getIsSilent()) {
620 $extra[] = id(new PHUIIconView())
621 ->setIcon('fa-bell-slash', 'white')
622 ->setEmblemColor('red')
623 ->setTooltip(pht('Silent Edit'));
626 // If this edit was applied while the actor was in high-security mode,
627 // provide a hint that it was extra authentic.
628 if ($this->getIsMFA()) {
629 $extra[] = id(new PHUIIconView())
630 ->setIcon('fa-vcard', 'white')
631 ->setEmblemColor('pink')
632 ->setTooltip(pht('MFA Authenticated'));
635 if ($this->getIsLockOverride()) {
636 $extra[] = id(new PHUIIconView())
637 ->setIcon('fa-chain-broken', 'white')
638 ->setEmblemColor('violet')
639 ->setTooltip(pht('Lock Overridden'));
643 $extra = javelin_tag(
646 'class' => 'phui-timeline-extra',
660 private function getMenuItems($anchor) {
661 $xaction_phid = $this->getTransactionPHID();
663 $can_interact = $this->getCanInteract();
664 $viewer = $this->getViewer();
665 $is_admin = $viewer->getIsAdmin();
669 if ($this->getIsEditable()) {
670 $items[] = id(new PhabricatorActionView())
671 ->setIcon('fa-pencil')
672 ->setHref('/transactions/edit/'.$xaction_phid.'/')
673 ->setName(pht('Edit Comment'))
674 ->addSigil('transaction-edit')
675 ->setDisabled(!$can_interact)
682 if ($this->getQuoteTargetID()) {
684 if ($this->getQuoteRef()) {
685 $ref = $this->getQuoteRef();
687 $ref = $ref.'#'.$anchor;
691 $items[] = id(new PhabricatorActionView())
692 ->setIcon('fa-quote-left')
693 ->setName(pht('Quote Comment'))
695 ->addSigil('transaction-quote')
698 'targetID' => $this->getQuoteTargetID(),
699 'uri' => '/transactions/quote/'.$xaction_phid.'/',
704 if ($this->getIsNormalComment()) {
705 $items[] = id(new PhabricatorActionView())
707 ->setHref('/transactions/raw/'.$xaction_phid.'/')
708 ->setName(pht('View Remarkup'))
709 ->addSigil('transaction-raw')
715 $content_source = $this->getContentSource();
716 $source_email = PhabricatorEmailContentSource
::SOURCECONST
;
717 if ($content_source->getSource() == $source_email) {
718 $source_id = $content_source->getContentSourceParameter('id');
720 $items[] = id(new PhabricatorActionView())
721 ->setIcon('fa-envelope-o')
722 ->setHref('/transactions/raw/'.$xaction_phid.'/?email')
723 ->setName(pht('View Email Body'))
724 ->addSigil('transaction-raw')
733 if ($this->getIsEdited()) {
734 $items[] = id(new PhabricatorActionView())
736 ->setHref('/transactions/history/'.$xaction_phid.'/')
737 ->setName(pht('View Edit History'))
741 if ($this->getIsRemovable()) {
742 $items[] = id(new PhabricatorActionView())
743 ->setType(PhabricatorActionView
::TYPE_DIVIDER
);
745 $remove_item = id(new PhabricatorActionView())
746 ->setIcon('fa-trash-o')
747 ->setHref('/transactions/remove/'.$xaction_phid.'/')
748 ->setName(pht('Remove Comment'))
749 ->addSigil('transaction-remove')
755 if (!$is_admin && !$can_interact) {
756 $remove_item->setDisabled(!$is_admin && !$can_interact);
758 $remove_item->setColor(PhabricatorActionView
::RED
);
761 $items[] = $remove_item;