@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.

Make chatlog a bit less awful

Summary:
- Default to showing the newest page of chat.
- Reformat for greater readability.
- Add permalinks to specific lines.
- Enable jump-to-date.

Test Plan: {F12200}

Reviewers: Koolvin, vrana, btrahan

Reviewed By: btrahan

CC: kdeggelman, aran

Maniphest Tasks: T837, T1065

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

+271 -61
+1 -1
src/__celerity_resource_map__.php
··· 1986 1986 ), 1987 1987 'phabricator-chatlog-css' => 1988 1988 array( 1989 - 'uri' => '/res/f674f526/rsrc/css/application/chatlog/chatlog.css', 1989 + 'uri' => '/res/f6631adc/rsrc/css/application/chatlog/chatlog.css', 1990 1990 'type' => 'css', 1991 1991 'requires' => 1992 1992 array(
+6 -2
src/__phutil_library_map__.php
··· 1542 1542 'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController', 1543 1543 'PhabricatorChatLogController' => 'PhabricatorController', 1544 1544 'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO', 1545 - 'PhabricatorChatLogEvent' => 'PhabricatorChatLogDAO', 1545 + 'PhabricatorChatLogEvent' => 1546 + array( 1547 + 0 => 'PhabricatorChatLogDAO', 1548 + 1 => 'PhabricatorPolicyInterface', 1549 + ), 1546 1550 'PhabricatorChatLogEventType' => 'PhabricatorChatLogConstants', 1547 - 'PhabricatorChatLogQuery' => 'PhabricatorOffsetPagedQuery', 1551 + 'PhabricatorChatLogQuery' => 'PhabricatorIDPagedPolicyQuery', 1548 1552 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 1549 1553 'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO', 1550 1554 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
+23 -9
src/applications/chatlog/PhabricatorChatLogQuery.php
··· 16 16 * limitations under the License. 17 17 */ 18 18 19 - final class PhabricatorChatLogQuery extends PhabricatorOffsetPagedQuery { 19 + final class PhabricatorChatLogQuery extends PhabricatorIDPagedPolicyQuery { 20 + 20 21 private $channels; 22 + private $maximumEpoch; 21 23 22 24 public function withChannels(array $channels) { 23 25 $this->channels = $channels; 24 26 return $this; 25 27 } 26 28 27 - public function execute() { 29 + public function withMaximumEpoch($epoch) { 30 + $this->maximumEpoch = $epoch; 31 + return $this; 32 + } 33 + 34 + public function loadPage() { 28 35 $table = new PhabricatorChatLogEvent(); 29 36 $conn_r = $table->establishConnection('r'); 30 37 31 - $where_clause = $this->buildWhereClause($conn_r); 32 - $limit_clause = $this->buildLimitClause($conn_r); 33 - 34 38 $data = queryfx_all( 35 39 $conn_r, 36 - 'SELECT * FROM %T e %Q ORDER BY epoch ASC %Q', 40 + 'SELECT * FROM %T e %Q %Q %Q', 37 41 $table->getTableName(), 38 - $where_clause, 39 - $limit_clause); 42 + $this->buildWhereClause($conn_r), 43 + $this->buildOrderClause($conn_r), 44 + $this->buildLimitClause($conn_r)); 40 45 41 46 $logs = $table->loadAllFromArray($data); 42 47 43 - return $logs; 48 + return $this->processResults($logs); 44 49 } 45 50 46 51 private function buildWhereClause($conn_r) { 47 52 $where = array(); 53 + 54 + $where[] = $this->buildPagingClause($conn_r); 55 + 56 + if ($this->maximumEpoch) { 57 + $where[] = qsprintf( 58 + $conn_r, 59 + 'epoch <= %d', 60 + $this->maximumEpoch); 61 + } 48 62 49 63 if ($this->channels) { 50 64 $where[] = qsprintf(
+185 -38
src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
··· 26 26 } 27 27 28 28 public function processRequest() { 29 + $request = $this->getRequest(); 30 + $user = $request->getUser(); 29 31 30 - $request = $this->getRequest(); 31 - $user = $request->getUser(); 32 - $offset = $request->getInt('offset', 0); 33 - $page_size = 1000; 34 - $pager = new AphrontPagerView(); 35 - $request_uri = $request->getRequestURI(); 36 - $pager->setURI($request_uri, 'offset'); 37 - $pager->setPageSize($page_size); 38 - $pager->setOffset($offset); 32 + $uri = clone $request->getRequestURI(); 33 + $uri->setQueryParams(array()); 39 34 40 - $query = new PhabricatorChatLogQuery(); 41 - $query->withChannels(array($this->channel)); 35 + $pager = new AphrontIDPagerView(); 36 + $pager->setURI($uri); 37 + $pager->setPageSize(250); 38 + 39 + $query = id(new PhabricatorChatLogQuery()) 40 + ->setViewer($user) 41 + ->withChannels(array($this->channel)); 42 + 43 + 44 + list($after, $before, $map) = $this->getPagingParameters($request, $query); 45 + 46 + $pager->setAfterID($after); 47 + $pager->setBeforeID($before); 48 + 42 49 $logs = $query->executeWithPager($pager); 43 50 44 - require_celerity_resource('phabricator-chatlog-css'); 51 + // Show chat logs oldest-first. 52 + $logs = array_reverse($logs); 45 53 46 - $last_author = null; 47 - $last_epoch = null; 48 54 49 - $row_idx = 0; 50 - $row_colors = array( 51 - 'normal', 52 - 'alternate', 53 - ); 55 + // Divide all the logs into blocks, where a block is the same author saying 56 + // several things in a row. A block ends when another user speaks, or when 57 + // two minutes pass without the author speaking. 54 58 55 - $out = array(); 56 - $out[] = '<table class="phabricator-chat-log">'; 59 + $blocks = array(); 60 + $block = null; 61 + 62 + $last_author = null; 63 + $last_epoch = null; 57 64 foreach ($logs as $log) { 58 65 $this_author = $log->getAuthor(); 59 66 $this_epoch = $log->getEpoch(); 60 67 61 - if (($this_author !== $last_author) || 62 - ($this_epoch - (60 * 5) > $last_epoch)) { 63 - ++$row_idx; 64 - $out[] = '<tr class="initial '.$row_colors[$row_idx % 2].'">'; 65 - $out[] = '<td class="timestamp">'. 66 - phabricator_datetime($log->getEpoch(), $user).'</td>'; 68 + // Decide whether we should start a new block or not. 69 + $new_block = ($this_author !== $last_author) || 70 + ($this_epoch - (60 * 2) > $last_epoch); 67 71 68 - $author = $log->getAuthor(); 69 - $author = phutil_utf8_shorten($author, 18); 70 - $out[] = '<td class="author">'. 71 - phutil_escape_html($author).'</td>'; 72 + if ($new_block) { 73 + if ($block) { 74 + $blocks[] = $block; 75 + } 76 + $block = array( 77 + 'id' => $log->getID(), 78 + 'epoch' => $this_epoch, 79 + 'author' => $this_author, 80 + 'logs' => array($log), 81 + ); 72 82 } else { 73 - $out[] = '<tr class="'.$row_colors[$row_idx % 2].'">'; 74 - $out[] = '<td class="similar" colspan="2"></td>'; 83 + $block['logs'][] = $log; 75 84 } 76 - $out[] = '<td class="message">'. 77 - phutil_escape_html($log->getMessage()).'</td>'; 78 - $out[] = '</tr>'; 79 85 80 86 $last_author = $this_author; 81 - $last_epoch = $this_epoch; 87 + $last_epoch = $this_epoch; 88 + } 89 + if ($block) { 90 + $blocks[] = $block; 91 + } 92 + 93 + // Figure out CSS classes for the blocks. We alternate colors between 94 + // lines, and highlight the entire block which contains the target ID or 95 + // date, if applicable. 96 + 97 + foreach ($blocks as $key => $block) { 98 + $classes = array(); 99 + if ($key % 2) { 100 + $classes[] = 'alternate'; 101 + } 102 + $ids = mpull($block['logs'], 'getID', 'getID'); 103 + if (array_intersect_key($ids, $map)) { 104 + $classes[] = 'highlight'; 105 + } 106 + $blocks[$key]['class'] = $classes ? implode(' ', $classes) : null; 107 + } 108 + 109 + 110 + require_celerity_resource('phabricator-chatlog-css'); 111 + 112 + $out = array(); 113 + $out[] = '<table class="phabricator-chat-log">'; 114 + foreach ($blocks as $block) { 115 + $author = $block['author']; 116 + $author = phutil_utf8_shorten($author, 18); 117 + $author = phutil_escape_html($author); 118 + $author = phutil_render_tag('td', array('class' => 'author'), $author); 119 + 120 + $message = mpull($block['logs'], 'getMessage'); 121 + $message = implode("\n", $message); 122 + $message = phutil_escape_html($message); 123 + $message = phutil_render_tag('td', array('class' => 'message'), $message); 124 + 125 + $href = $uri->alter('at', $block['id']); 126 + $timestamp = $block['epoch']; 127 + $timestamp = phabricator_datetime($timestamp, $user); 128 + $timestamp = phutil_render_tag('a', array('href' => $href), $timestamp); 129 + $timestamp = phutil_render_tag( 130 + 'td', 131 + array( 132 + 'class' => 'timestamp', 133 + ), 134 + $timestamp); 135 + 136 + $out[] = phutil_render_tag( 137 + 'tr', 138 + array( 139 + 'class' => $block['class'], 140 + ), 141 + $author.$message.$timestamp); 82 142 } 83 143 $out[] = '</table>'; 84 144 145 + $form = id(new AphrontFormView()) 146 + ->setUser($user) 147 + ->setMethod('GET') 148 + ->setAction($uri) 149 + ->appendChild( 150 + id(new AphrontFormTextControl()) 151 + ->setLabel('Date') 152 + ->setName('date') 153 + ->setValue($request->getStr('date'))) 154 + ->appendChild( 155 + id(new AphrontFormSubmitControl()) 156 + ->setValue('Jump')); 157 + 85 158 86 159 return $this->buildStandardPageResponse( 87 160 array( 161 + '<div class="phabricator-chat-log-panel">', 162 + $form, 163 + '<br />', 88 164 implode("\n", $out), 89 - $pager 165 + $pager, 166 + '</div>', 90 167 ), 91 168 array( 92 169 'title' => 'Channel Log', 93 170 )); 94 171 } 172 + 173 + /** 174 + * From request parameters, figure out where we should jump to in the log. 175 + * We jump to either a date or log ID, but load a few lines of context before 176 + * it so the user can see the nearby conversation. 177 + */ 178 + private function getPagingParameters( 179 + AphrontRequest $request, 180 + PhabricatorChatLogQuery $query) { 181 + 182 + $user = $request->getUser(); 183 + 184 + $at_id = $request->getInt('at'); 185 + $at_date = $request->getStr('date'); 186 + 187 + $context_log = null; 188 + $map = array(); 189 + 190 + $query = clone $query; 191 + $query->setLimit(8); 192 + 193 + if ($at_id) { 194 + // Jump to the log in question, and load a few lines of context before 195 + // it. 196 + $context_logs = $query 197 + ->setAfterID($at_id) 198 + ->execute(); 199 + 200 + $context_log = last($context_logs); 201 + 202 + $map = array( 203 + $at_id => true, 204 + ); 205 + 206 + } else if ($at_date) { 207 + $timezone = new DateTimeZone($user->getTimezoneIdentifier()); 208 + try { 209 + $date = new DateTime($at_date, $timezone); 210 + $timestamp = $date->format('U'); 211 + } catch (Exception $e) { 212 + $timestamp = null; 213 + } 214 + 215 + if ($timestamp) { 216 + $context_logs = $query 217 + ->withMaximumEpoch($timestamp) 218 + ->execute(); 219 + 220 + $context_log = last($context_logs); 221 + 222 + $target_log = head($context_logs); 223 + if ($target_log) { 224 + $map = array( 225 + $target_log->getID() => true, 226 + ); 227 + } 228 + } 229 + } 230 + 231 + if ($context_log) { 232 + $after = null; 233 + $before = $context_log->getID() - 1; 234 + } else { 235 + $after = $request->getInt('after'); 236 + $before = $request->getInt('before'); 237 + } 238 + 239 + return array($after, $before, $map); 240 + } 241 + 95 242 }
+20 -1
src/applications/chatlog/storage/PhabricatorChatLogEvent.php
··· 16 16 * limitations under the License. 17 17 */ 18 18 19 - final class PhabricatorChatLogEvent extends PhabricatorChatLogDAO { 19 + final class PhabricatorChatLogEvent 20 + extends PhabricatorChatLogDAO 21 + implements PhabricatorPolicyInterface { 20 22 21 23 protected $channel; 22 24 protected $epoch; ··· 24 26 protected $type; 25 27 protected $message; 26 28 protected $loggedByPHID; 29 + 30 + public function getCapabilities() { 31 + return array( 32 + PhabricatorPolicyCapability::CAN_VIEW, 33 + ); 34 + } 35 + 36 + public function getPolicy($capability) { 37 + // TODO: This is sort of silly and mostly just so that we can use 38 + // IDPagedPolicyQuery; once we implement Channel objects we should 39 + // just delegate policy to them. 40 + return PhabricatorPolicies::POLICY_PUBLIC; 41 + } 42 + 43 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 44 + return false; 45 + } 27 46 28 47 public function getConfiguration() { 29 48 return array(
+36 -10
webroot/rsrc/css/application/chatlog/chatlog.css
··· 2 2 * @provides phabricator-chatlog-css 3 3 */ 4 4 5 + .phabricator-chat-log-panel { 6 + margin: 1em auto; 7 + width: 80em; 8 + } 9 + 5 10 .phabricator-chat-log { 6 - margin: 1em 2em; 7 11 font-size: 12px; 12 + width: 100%; 13 + border: 1px solid #bbbbbb; 14 + } 15 + 16 + .phabricator-chat-log td { 17 + padding: 4px 8px; 8 18 } 9 19 10 - .phabricator-chat-log tr.initial { 11 - border-top: 4px solid white; 20 + .phabricator-chat-log tr { 21 + background: #fafafa; 12 22 } 13 23 14 - .phabricator-chat-log tr.normal { 15 - background: #e9e9e9; 24 + .phabricator-chat-log tr td.author { 25 + background: #dfdfdf; 16 26 } 17 27 18 28 .phabricator-chat-log tr.alternate { 19 - background: #f6f6f6; 29 + background: #e9e9e9; 20 30 } 21 31 22 - .phabricator-chat-log td { 23 - padding: 2px 4px; 32 + .phabricator-chat-log tr.alternate td.author { 33 + background: #d9d9d9; 34 + } 35 + 36 + .phabricator-chat-log tr.highlight td { 37 + background: #ffff88; 38 + } 39 + 40 + .phabricator-chat-log tr.highlight td.author { 41 + background: #eeee88; 24 42 } 25 43 26 44 .phabricator-chat-log td.timestamp { 27 45 white-space: nowrap; 28 - color: #666666; 46 + text-align: right; 47 + width: 12em; 48 + } 49 + 50 + .phabricator-chat-log td.timestamp a { 51 + color: #555555; 52 + font-size: 11px; 29 53 } 30 54 31 55 .phabricator-chat-log td.author { 32 56 white-space: nowrap; 33 57 text-align: right; 34 58 font-weight: bold; 59 + width: 12em; 60 + color: #555555; 35 61 } 36 62 37 63 .phabricator-chat-log td.message { 38 - width: 100%; 64 + white-space: pre-wrap; 39 65 }