diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 7151c0fcc4..033e47f690 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -166,7 +166,7 @@ abstract class ConpherenceController extends PhabricatorController { private function getNoConpherencesBlock() { - return phutil_render_tag( + return phutil_tag( 'div', array( 'class' => 'no-conpherences-menu-item' diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index 916d044e86..58aabb58fb 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -65,51 +65,55 @@ final class ConpherenceListController extends private function renderEmptyMainPane() { $this->initJavelinBehaviors(); - return phutil_render_tag( + return phutil_tag( 'div', array( 'id' => 'conpherence-main-pane' ), - phutil_render_tag( - 'div', - array( - 'class' => 'conpherence-header-pane', - 'id' => 'conpherence-header-pane', - ), - '' - ). - phutil_render_tag( - 'div', - array( - 'class' => 'conpherence-widget-pane', - 'id' => 'conpherence-widget-pane' - ), - '' - ). - javelin_render_tag( - 'div', - array( - 'class' => 'conpherence-message-pane', - 'id' => 'conpherence-message-pane' - ), - phutil_render_tag( + array( + phutil_tag( 'div', array( - 'class' => 'conpherence-messages', - 'id' => 'conpherence-messages' + 'class' => 'conpherence-header-pane', + 'id' => 'conpherence-header-pane', ), '' - ). - phutil_render_tag( + ), + phutil_tag( 'div', array( - 'id' => 'conpherence-form' + 'class' => 'conpherence-widget-pane', + 'id' => 'conpherence-widget-pane' ), '' + ), + javelin_tag( + 'div', + array( + 'class' => 'conpherence-message-pane', + 'id' => 'conpherence-message-pane' + ), + array( + phutil_tag( + 'div', + array( + 'class' => 'conpherence-messages', + 'id' => 'conpherence-messages' + ), + '' + ), + phutil_tag( + 'div', + array( + 'id' => 'conpherence-form' + ), + '' + ) + ) ) ) ); } -} + } diff --git a/src/applications/conpherence/controller/ConpherenceNewController.php b/src/applications/conpherence/controller/ConpherenceNewController.php index 43f1ae87f9..623d681535 100644 --- a/src/applications/conpherence/controller/ConpherenceNewController.php +++ b/src/applications/conpherence/controller/ConpherenceNewController.php @@ -92,7 +92,7 @@ final class ConpherenceNewController extends ConpherenceController { ->setTitle('Success') ->addCancelButton('#', 'Okay') ->appendChild( - phutil_render_tag('p', + phutil_tag('p', array(), pht('Message sent successfully.') ) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 1f984529c2..2d8deee715 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -135,7 +135,7 @@ final class ConpherenceUpdateController extends ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Image')) - ->setValue(phutil_render_tag( + ->setValue(phutil_tag( 'img', array( 'src' => $conpherence->loadImageURI(), diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 11cdc8d586..f082ccf78c 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -71,7 +71,7 @@ final class ConpherenceViewController extends $edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/'); $header = - javelin_render_tag( + javelin_tag( 'a', array( 'class' => 'edit', @@ -80,7 +80,7 @@ final class ConpherenceViewController extends ), '' ). - phutil_render_tag( + phutil_tag( 'div', array( 'class' => 'header-image', @@ -88,19 +88,19 @@ final class ConpherenceViewController extends ), '' ). - phutil_render_tag( + phutil_tag( 'div', array( 'class' => 'title', ), - phutil_escape_html($display_data['title']) + $display_data['title'] ). - phutil_render_tag( + phutil_tag( 'div', array( 'class' => 'subtitle', ), - phutil_escape_html($display_data['subtitle']) + $display_data['subtitle'] ); return array('header' => $header); @@ -152,6 +152,7 @@ final class ConpherenceViewController extends private function renderWidgetPaneContent() { require_celerity_resource('conpherence-widget-pane-css'); + require_celerity_resource('sprite-conpher-css'); Javelin::initBehavior( 'conpherence-widget-pane', array( @@ -165,37 +166,54 @@ final class ConpherenceViewController extends $conpherence = $this->getConpherence(); - $widgets = phutil_render_tag( + $widgets = phutil_tag( 'div', array( 'class' => 'widgets-header' ), - javelin_render_tag( - 'a', - array( - 'sigil' => 'conpherence-change-widget', - 'meta' => array('widget' => 'widgets-files') + array( + javelin_tag( + 'a', + array( + 'sigil' => 'conpherence-change-widget', + 'meta' => array( + 'widget' => 'widgets-files', + 'toggleClass' => 'conpher_files_on' + ), + 'id' => 'widgets-files-toggle', + 'class' => 'sprite-conpher conpher_files_off first-icon' + ), + '' ), - pht('Files') - ).' | '. - javelin_render_tag( - 'a', - array( - 'sigil' => 'conpherence-change-widget', - 'meta' => array('widget' => 'widgets-tasks') + javelin_tag( + 'a', + array( + 'sigil' => 'conpherence-change-widget', + 'meta' => array( + 'widget' => 'widgets-tasks', + 'toggleClass' => 'conpher_list_on' + ), + 'id' => 'widgets-tasks-toggle', + 'class' => 'sprite-conpher conpher_list_off conpher_list_on', + ), + '' ), - pht('Tasks') - ).' | '. - javelin_render_tag( - 'a', - array( - 'sigil' => 'conpherence-change-widget', - 'meta' => array('widget' => 'widgets-calendar') - ), - pht('Calendar') + javelin_tag( + 'a', + array( + 'sigil' => 'conpherence-change-widget', + 'meta' => array( + 'widget' => 'widgets-calendar', + 'toggleClass' => 'conpher_calendar_on' + ), + 'id' => 'widgets-calendar-toggle', + 'class' => 'sprite-conpher conpher_calendar_off', + ), + '' + ) ) ). - phutil_render_tag( + phutil_tag( 'div', array( 'class' => 'widgets-body', @@ -204,7 +222,7 @@ final class ConpherenceViewController extends ), $this->renderFilesWidgetPaneContent() ). - phutil_render_tag( + phutil_tag( 'div', array( 'class' => 'widgets-body', @@ -212,7 +230,7 @@ final class ConpherenceViewController extends ), $this->renderTaskWidgetPaneContent() ). - phutil_render_tag( + phutil_tag( 'div', array( 'class' => 'widgets-body', @@ -234,7 +252,7 @@ final class ConpherenceViewController extends foreach ($files as $file) { $thumb = $file->getThumb60x45URI(); $table_data[] = array( - phutil_render_tag( + phutil_tag( 'img', array( 'src' => $thumb @@ -250,7 +268,7 @@ final class ConpherenceViewController extends ->setNoDataString(pht('No files attached to conpherence.')) ->setHeaders(array('', pht('Name'))) ->setColumnClasses(array('', 'wide')); - return $header->render() . $table->render(); + return new PhutilSafeHTML($header->render() . $table->render()); } private function renderTaskWidgetPaneContent() { @@ -271,12 +289,12 @@ final class ConpherenceViewController extends foreach ($actual_tasks as $task) { $data[] = array( idx($priority_map, $task->getPriority(), pht('???')), - phutil_render_tag( + phutil_tag( 'a', array( 'href' => '/T'.$task->getID() ), - phutil_escape_html($task->getTitle()) + $task->getTitle() ) ); } @@ -286,13 +304,110 @@ final class ConpherenceViewController extends ->setColumnClasses(array('', 'wide')); $content[] = $table->render(); } - return implode('', $content); + return new PhutilSafeHTML(implode('', $content)); } private function renderCalendarWidgetPaneContent() { - $header = id(new PhabricatorHeaderView()) - ->setHeader(pht('Calendar')); - return $header->render() . 'TODO'; + $user = $this->getRequest()->getUser(); + + $conpherence = $this->getConpherence(); + $widget_data = $conpherence->getWidgetData(); + $statuses = $widget_data['statuses']; + $handles = $conpherence->getHandles(); + $content = array(); + $timestamps = $this->getCalendarWidgetWeekTimestamps(); + $one_day = 24 * 60 * 60; + foreach ($timestamps as $time => $day) { + // build a header for the new day + $content[] = id(new PhabricatorHeaderView()) + ->setHeader($day->format('l')) + ->render(); + + $day->setTime(0, 0, 0); + $epoch_start = $day->format('U'); + $day->modify('+1 day'); + $epoch_end = $day->format('U'); + + // keep looking through statuses where we last left off + foreach ($statuses as $status) { + if ($status->getDateFrom() >= $epoch_end) { + // This list is sorted, so we can stop looking. + break; + } + if ($status->getDateFrom() < $epoch_end && + $status->getDateTo() > $epoch_start) { + $timespan = $status->getDateTo() - $status->getDateFrom(); + if ($timespan > $one_day) { + $time_str = 'm/d'; + } else { + $time_str = 'h:i A'; + } + $epoch_range = phabricator_format_local_time( + $status->getDateFrom(), + $user, + $time_str + ) . ' - ' . phabricator_format_local_time( + $status->getDateTo(), + $user, + $time_str + ); + + $content[] = phutil_tag( + 'div', + array( + 'class' => 'user-status '.$status->getTextStatus(), + ), + array( + phutil_tag( + 'div', + array( + 'class' => 'epoch-range' + ), + $epoch_range + ), + phutil_tag( + 'div', + array( + 'class' => 'icon', + ), + '' + ), + phutil_tag( + 'div', + array( + 'class' => 'description' + ), + $status->getTerseSummary($user) + ), + phutil_tag( + 'div', + array( + 'class' => 'participant' + ), + $handles[$status->getUserPHID()]->getName() + ) + ) + ); + } + } + } + + return new PhutilSafeHTML(implode('', $content)); + } + + private function getCalendarWidgetWeekTimestamps() { + $user = $this->getRequest()->getUser(); + $timezone = new DateTimeZone($user->getTimezoneIdentifier()); + + $timestamps = array(); + for ($day = 0; $day < 7; $day++) { + $timestamps[] = new DateTime( + sprintf('today +%d days', $day), + $timezone + ); + } + + return $timestamps; } } diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 18fc8ec1b5..d22fd03820 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -141,17 +141,22 @@ final class ConpherenceThreadQuery $tasks = mgroup($tasks, 'getOwnerPHID'); // statuses of everyone currently in the conpherence - // until the beginning of the next work week. - // NOTE: this is a bit boring on the weekends. + // for a rolling one week window + $start_of_week = phabricator_format_local_time( + strtotime('today'), + $this->getViewer(), + 'U' + ); $end_of_week = phabricator_format_local_time( - strtotime('Monday midnight'), + strtotime('midnight +1 week'), $this->getViewer(), 'U' ); $statuses = id(new PhabricatorUserStatus()) ->loadAllWhere( - 'userPHID in (%Ls) AND dateTo <= %d', + 'userPHID in (%Ls) AND dateTo >= %d AND dateFrom <= %d', $participant_phids, + $start_of_week, $end_of_week ); $statuses = mgroup($statuses, 'getUserPHID'); @@ -168,9 +173,12 @@ final class ConpherenceThreadQuery foreach ($conpherences as $phid => $conpherence) { $participant_phids = array_keys($conpherence->getParticipants()); + $statuses = array_select_keys($statuses, $participant_phids); + $statuses = array_mergev($statuses); + $statuses = msort($statuses, 'getDateFrom'); $widget_data = array( 'tasks' => array_select_keys($tasks, $participant_phids), - 'statuses' => array_select_keys($statuses, $participant_phids), + 'statuses' => $statuses, 'files' => array_select_keys($files, $conpherence->getFilePHIDs()), ); $conpherence->attachWidgetData($widget_data); diff --git a/src/applications/conpherence/view/ConpherenceMenuItemView.php b/src/applications/conpherence/view/ConpherenceMenuItemView.php index b2e7cbccd0..6cc4d7a88a 100644 --- a/src/applications/conpherence/view/ConpherenceMenuItemView.php +++ b/src/applications/conpherence/view/ConpherenceMenuItemView.php @@ -85,7 +85,7 @@ final class ConpherenceMenuItemView extends AphrontTagView { protected function getTagContent() { $image = null; if ($this->imageURI) { - $image = phutil_render_tag( + $image = phutil_tag( 'span', array( 'class' => 'conpherence-menu-item-image', @@ -95,34 +95,34 @@ final class ConpherenceMenuItemView extends AphrontTagView { } $title = null; if ($this->title) { - $title = phutil_render_tag( + $title = phutil_tag( 'span', array( 'class' => 'conpherence-menu-item-title', ), - phutil_escape_html($this->title)); + $this->title); } $subtitle = null; if ($this->subtitle) { - $subtitle = phutil_render_tag( + $subtitle = phutil_tag( 'span', array( 'class' => 'conpherence-menu-item-subtitle', ), - phutil_escape_html($this->subtitle)); + $this->subtitle); } $message = null; if ($this->messageText) { - $message = phutil_render_tag( + $message = phutil_tag( 'span', array( 'class' => 'conpherence-menu-item-message-text' ), - phutil_escape_html($this->messageText)); + $this->messageText); } $epoch = null; if ($this->epoch) { - $epoch = phutil_render_tag( + $epoch = phutil_tag( 'span', array( 'class' => 'conpherence-menu-item-date', @@ -131,7 +131,7 @@ final class ConpherenceMenuItemView extends AphrontTagView { } $unread_count = null; if ($this->unreadCount) { - $unread_count = phutil_render_tag( + $unread_count = phutil_tag( 'span', array( 'class' => 'conpherence-menu-item-unread-count' diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index d60f648414..d8163b3ecd 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -47,7 +47,7 @@ final class ConpherenceTransactionView extends AphrontView { case ConpherenceTransactionType::TYPE_PICTURE: $img = $transaction->getHandle($transaction->getNewValue()); $content = $transaction->getTitle() . - phutil_render_tag( + phutil_tag( 'img', array( 'src' => $img->getImageURI() @@ -85,12 +85,12 @@ final class ConpherenceTransactionView extends AphrontView { } $transaction_view - ->appendChild(phutil_render_tag( + ->appendChild(phutil_tag( 'div', array( 'class' => $content_class ), - $content) + new PhutilSafeHTML($content)) ); return $transaction_view->render(); diff --git a/webroot/rsrc/css/application/conpherence/widget-pane.css b/webroot/rsrc/css/application/conpherence/widget-pane.css index 224adc2db5..6cc2c4b19e 100644 --- a/webroot/rsrc/css/application/conpherence/widget-pane.css +++ b/webroot/rsrc/css/application/conpherence/widget-pane.css @@ -12,9 +12,88 @@ border-color: #CCC; border-style: solid; background: url('/rsrc/image/texture/dust_background.jpg'); + overflow-y: auto; } .conpherence-widget-pane .aphront-form-input { margin: 0; width: 100%; } + +.conpherence-widget-pane .widgets-header { + height: 40px; + width: 127px; + margin: 0px auto 0px auto; +} + +.conpherence-widget-pane .widgets-header .sprite-conpher { + display: block; + width: 29px; + height: 33px; + margin: 4px 0px 0px 20px; + float: left; + clear: none; +} +.conpherence-widget-pane .widgets-header .first-icon { + margin-left: 0px; +} + +.conpherence-widget-pane .widgets-body { + position: fixed; + overflow-y: auto; + top: 165px; + bottom: 0px; + width: 320px; +} + +/* calendar widget */ + +.conpherence-widget-pane #widgets-calendar { +} + +.conpherence-widget-pane #widgets-calendar .user-status { + height: 60px; +} + +.conpherence-widget-pane #widgets-calendar .user-status .icon { + border-radius: 10px; + position: relative; + top: 24px; + left: 12px; + height: 16px; + width: 16px; + box-shadow: 0px 0px 1px #000; +} + +.conpherence-widget-pane #widgets-calendar .sporadic .icon { + background-color: rgb(222, 226, 232); +} + +.conpherence-widget-pane #widgets-calendar .away .icon { + background-color: rgb(102, 204, 255); +} + +.conpherence-widget-pane #widgets-calendar .user-status .epoch-range { + float: right; + font-style: italic; + position: relative; + top: 24px; + right: 8px; + font-size: 11px; +} + +.conpherence-widget-pane #widgets-calendar .user-status .description { + position: relative; + left: 40px; + top: 0px; + width: 260px; +} + +.conpherence-widget-pane #widgets-calendar .user-status .participant { + position: relative; + left: 40px; + top: 0px; + font-style: italic; + font-size: 11px; + width: 260px; +} diff --git a/webroot/rsrc/css/sprite-conph.css b/webroot/rsrc/css/sprite-conph.css index d11ae477c5..8f2d3cdec1 100644 --- a/webroot/rsrc/css/sprite-conph.css +++ b/webroot/rsrc/css/sprite-conph.css @@ -4,7 +4,7 @@ */ .sprite-conpher { - background-image: url(/rsrc/image/sprite-conpher.png); + background-image: url(/rsrc/image/sprite-conph.png); background-repeat: no-repeat; } @@ -12,7 +12,7 @@ only screen and (min-device-pixel-ratio: 1.5), only screen and (-webkit-min-device-pixel-ratio: 1.5) { .sprite-conpher { - background-image: url(/rsrc/image/sprite-conpher-X2.png); + background-image: url(/rsrc/image/sprite-conph-X2.png); background-size: 132px 132px; } } diff --git a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js index badbe128f6..00def66f7c 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js +++ b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js @@ -16,8 +16,15 @@ JX.behavior('conpherence-widget-pane', function(config) { for (var widget in config.widgetRegistery) { if (widget == data.widget) { JX.$(widget).style.display = 'block'; + JX.DOM.alterClass(e.getTarget(), data.toggleClass, true); } else { JX.$(widget).style.display = 'none'; + var cur_toggle = JX.$(widget + '-toggle'); + JX.DOM.alterClass( + cur_toggle, + JX.Stratcom.getData(cur_toggle).toggleClass, + false + ); } } }