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

Update the diff table of contents to use hierarchical views and edit distance renames

Summary:
Ref T13520. Generally, make the table of contents look and more like the paths panel:

- Show a hierarchy, with compression for single-sibling children.
- Use the same icons, instead of "M D" and "(img)" stuff.
- Use EditDistanceMatrix to do a piece-by-piece diff of paths changes.
- Show path changes within the path list.

I'm not entirely sold on this, but it was complicated to write and I've never heard the term "sunk cost fallacy". I think this is mostly a net improvement, but may need some adjustments and followup.

Test Plan: Viewed various changes in Differential and Diffusion, saw a more usable table of contents.

Maniphest Tasks: T13520

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

+470 -206
+3 -3
resources/celerity/map.php
··· 12 12 'core.pkg.css' => '589cd2fe', 13 13 'core.pkg.js' => '49814bac', 14 14 'dark-console.pkg.js' => '187792c2', 15 - 'differential.pkg.css' => '8e1a7922', 15 + 'differential.pkg.css' => '73cf6fb1', 16 16 'differential.pkg.js' => 'c8f88d74', 17 17 'diffusion.pkg.css' => '42c75c37', 18 18 'diffusion.pkg.js' => 'a98c0bf7', ··· 69 69 'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d', 70 70 'rsrc/css/application/differential/revision-history.css' => '8aa3eac5', 71 71 'rsrc/css/application/differential/revision-list.css' => '93d2df7d', 72 - 'rsrc/css/application/differential/table-of-contents.css' => '0e3364c7', 72 + 'rsrc/css/application/differential/table-of-contents.css' => 'e7f8c50e', 73 73 'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b', 74 74 'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4', 75 75 'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c', ··· 566 566 'differential-revision-comment-css' => '7dbc8d1d', 567 567 'differential-revision-history-css' => '8aa3eac5', 568 568 'differential-revision-list-css' => '93d2df7d', 569 - 'differential-table-of-contents-css' => '0e3364c7', 569 + 'differential-table-of-contents-css' => 'e7f8c50e', 570 570 'diffusion-css' => 'b54c77b0', 571 571 'diffusion-icons-css' => '23b31a1b', 572 572 'diffusion-readme-css' => 'b68a76e4',
-44
src/applications/differential/constants/DifferentialChangeType.php
··· 22 22 const FILE_NORMAL = 7; 23 23 const FILE_SUBMODULE = 8; 24 24 25 - public static function getSummaryCharacterForChangeType($type) { 26 - static $types = array( 27 - self::TYPE_ADD => 'A', 28 - self::TYPE_CHANGE => 'M', 29 - self::TYPE_DELETE => 'D', 30 - self::TYPE_MOVE_AWAY => 'V', 31 - self::TYPE_COPY_AWAY => 'P', 32 - self::TYPE_MOVE_HERE => 'V', 33 - self::TYPE_COPY_HERE => 'P', 34 - self::TYPE_MULTICOPY => 'P', 35 - self::TYPE_MESSAGE => 'Q', 36 - self::TYPE_CHILD => '@', 37 - ); 38 - return idx($types, coalesce($type, '?'), '~'); 39 - } 40 - 41 - public static function getSummaryColorForChangeType($type) { 42 - static $types = array( 43 - self::TYPE_ADD => 'green', 44 - self::TYPE_CHANGE => 'black', 45 - self::TYPE_DELETE => 'red', 46 - self::TYPE_MOVE_AWAY => 'orange', 47 - self::TYPE_COPY_AWAY => 'black', 48 - self::TYPE_MOVE_HERE => 'green', 49 - self::TYPE_COPY_HERE => 'green', 50 - self::TYPE_MULTICOPY => 'orange', 51 - self::TYPE_MESSAGE => 'black', 52 - self::TYPE_CHILD => 'black', 53 - ); 54 - return idx($types, coalesce($type, '?'), 'black'); 55 - } 56 - 57 - public static function getShortNameForFileType($type) { 58 - static $names = array( 59 - self::FILE_TEXT => null, 60 - self::FILE_DIRECTORY => 'dir', 61 - self::FILE_IMAGE => 'img', 62 - self::FILE_BINARY => 'bin', 63 - self::FILE_SYMLINK => 'sym', 64 - self::FILE_SUBMODULE => 'sub', 65 - ); 66 - return idx($names, coalesce($type, '?'), '???'); 67 - } 68 - 69 25 public static function getIconForFileType($type) { 70 26 static $icons = array( 71 27 self::FILE_TEXT => 'fa-file-text-o',
+26 -10
src/applications/differential/storage/DifferentialChangeset.php
··· 315 315 return $this->assertAttached($this->diff); 316 316 } 317 317 318 + public function getOldStatePathVector() { 319 + $path = $this->getOldFile(); 320 + if (!strlen($path)) { 321 + $path = $this->getFilename(); 322 + } 323 + 324 + $path = trim($path, '/'); 325 + $path = explode('/', $path); 326 + 327 + return $path; 328 + } 329 + 330 + public function getNewStatePathVector() { 331 + if (!$this->hasNewState()) { 332 + return null; 333 + } 334 + 335 + $path = $this->getFilename(); 336 + $path = trim($path, '/'); 337 + $path = explode('/', $path); 338 + 339 + return $path; 340 + } 341 + 318 342 public function newFileTreeIcon() { 319 343 $icon = $this->getPathIconIcon(); 320 344 $color = $this->getPathIconColor(); ··· 335 359 } 336 360 337 361 public function getIsLowImportanceChangeset() { 338 - $change_type = $this->getChangeType(); 339 - 340 - $change_map = array( 341 - DifferentialChangeType::TYPE_DELETE => true, 342 - DifferentialChangeType::TYPE_MOVE_AWAY => true, 343 - DifferentialChangeType::TYPE_MULTICOPY => true, 344 - ); 345 - 346 - if (isset($change_map[$change_type])) { 347 - return $change_map[$change_type]; 362 + if (!$this->hasNewState()) { 363 + return true; 348 364 } 349 365 350 366 if ($this->isGeneratedChangeset()) {
+14 -114
src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php
··· 81 81 82 82 $cells[] = $this->getContext(); 83 83 84 - $cells[] = $this->renderPathChangeCharacter(); 85 - $cells[] = $this->renderPropertyChangeCharacter(); 86 - $cells[] = $this->renderPropertyChangeDescription(); 84 + $cells[] = $changeset->newFileTreeIcon(); 87 85 88 86 $link = $this->renderChangesetLink(); 89 87 $lines = $this->renderChangesetLines(); ··· 103 101 return $cells; 104 102 } 105 103 106 - private function renderPathChangeCharacter() { 107 - $changeset = $this->getChangeset(); 108 - $type = $changeset->getChangeType(); 109 - 110 - $color = DifferentialChangeType::getSummaryColorForChangeType($type); 111 - $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); 112 - $title = DifferentialChangeType::getFullNameForChangeType($type); 113 - 114 - return javelin_tag( 115 - 'span', 116 - array( 117 - 'sigil' => 'has-tooltip', 118 - 'meta' => array( 119 - 'tip' => $title, 120 - 'align' => 'E', 121 - ), 122 - 'class' => 'phui-text-'.$color, 123 - ), 124 - $char); 125 - } 126 - 127 - private function renderPropertyChangeCharacter() { 128 - $changeset = $this->getChangeset(); 129 - 130 - $old = $changeset->getOldProperties(); 131 - $new = $changeset->getNewProperties(); 132 - 133 - if ($old === $new) { 134 - return null; 135 - } 136 - 137 - return javelin_tag( 138 - 'span', 139 - array( 140 - 'sigil' => 'has-tooltip', 141 - 'meta' => array( 142 - 'tip' => pht('Properties Modified'), 143 - 'align' => 'E', 144 - 'size' => 200, 145 - ), 146 - ), 147 - 'M'); 148 - } 149 - 150 - private function renderPropertyChangeDescription() { 151 - $changeset = $this->getChangeset(); 152 - 153 - $file_type = $changeset->getFileType(); 154 - 155 - $desc = DifferentialChangeType::getShortNameForFileType($file_type); 156 - if ($desc === null) { 157 - return null; 158 - } 159 - 160 - return pht('(%s)', $desc); 161 - } 162 - 163 - private function renderChangesetLink() { 104 + public function newLink() { 164 105 $anchor = $this->getAnchor(); 165 106 166 107 $changeset = $this->getChangeset(); 167 108 $name = $changeset->getDisplayFilename(); 168 - 169 - $change_type = $changeset->getChangeType(); 170 - if (DifferentialChangeType::isOldLocationChangeType($change_type)) { 171 - $away = $changeset->getAwayPaths(); 172 - if (count($away) == 1) { 173 - if ($change_type == DifferentialChangeType::TYPE_MOVE_AWAY) { 174 - $right_arrow = "\xE2\x86\x92"; 175 - $name = $this->renderRename($name, head($away), $right_arrow); 176 - } 177 - } 178 - } else if ($change_type == DifferentialChangeType::TYPE_MOVE_HERE) { 179 - $left_arrow = "\xE2\x86\x90"; 180 - $name = $this->renderRename($name, $changeset->getOldFile(), $left_arrow); 181 - } 109 + $name = basename($name); 182 110 183 111 return javelin_tag( 184 112 'a', ··· 192 120 $name); 193 121 } 194 122 195 - private function renderChangesetLines() { 123 + public function renderChangesetLines() { 196 124 $changeset = $this->getChangeset(); 197 125 126 + if ($changeset->getIsLowImportanceChangeset()) { 127 + return null; 128 + } 129 + 198 130 $line_count = $changeset->getAffectedLineCount(); 199 131 if (!$line_count) { 200 132 return null; 201 133 } 202 134 203 - return ' '.pht('(%d line(s))', $line_count); 135 + return pht('%d line(s)', $line_count); 204 136 } 205 137 206 - private function renderCoverage() { 138 + public function renderCoverage() { 207 139 $not_applicable = '-'; 208 140 209 141 $coverage = $this->getCoverage(); ··· 221 153 return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered))); 222 154 } 223 155 224 - private function renderModifiedCoverage() { 156 + public function renderModifiedCoverage() { 225 157 $not_applicable = '-'; 226 158 227 159 $coverage = $this->getCoverage(); ··· 285 217 $meta); 286 218 } 287 219 288 - private function renderPackages() { 220 + public function renderPackages() { 289 221 $packages = $this->getPackages(); 222 + 290 223 if (!$packages) { 291 224 return null; 292 225 } 293 226 294 - $viewer = $this->getUser(); 227 + $viewer = $this->getViewer(); 295 228 $package_phids = mpull($packages, 'getPHID'); 296 229 297 230 return $viewer->renderHandleList($package_phids) 298 231 ->setGlyphLimit(48); 299 - } 300 - 301 - private function renderRename($self, $other, $arrow) { 302 - $old = explode('/', $self); 303 - $new = explode('/', $other); 304 - 305 - $start = count($old); 306 - foreach ($old as $index => $part) { 307 - if (!isset($new[$index]) || $part != $new[$index]) { 308 - $start = $index; 309 - break; 310 - } 311 - } 312 - 313 - $end = count($old); 314 - foreach (array_reverse($old) as $from_end => $part) { 315 - $index = count($new) - $from_end - 1; 316 - if (!isset($new[$index]) || $part != $new[$index]) { 317 - $end = $from_end; 318 - break; 319 - } 320 - } 321 - 322 - $rename = 323 - '{'. 324 - implode('/', array_slice($old, $start, count($old) - $end - $start)). 325 - ' '.$arrow.' '. 326 - implode('/', array_slice($new, $start, count($new) - $end - $start)). 327 - '}'; 328 - 329 - array_splice($new, $start, count($new) - $end - $start, $rename); 330 - 331 - return implode('/', $new); 332 232 } 333 233 334 234 }
+386 -35
src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php
··· 9 9 private $background; 10 10 private $bare; 11 11 12 + private $components = array(); 13 + 12 14 public function addItem(PHUIDiffTableOfContentsItemView $item) { 13 15 $this->items[] = $item; 14 16 return $this; ··· 61 63 } 62 64 63 65 $items = $this->items; 66 + $viewer = $this->getViewer(); 67 + 68 + $item_map = array(); 69 + 70 + $vector_tree = new ArcanistDiffVectorTree(); 71 + foreach ($items as $item) { 72 + $item->setViewer($viewer); 73 + 74 + $changeset = $item->getChangeset(); 75 + 76 + $old_vector = $changeset->getOldStatePathVector(); 77 + $new_vector = $changeset->getNewStatePathVector(); 78 + 79 + $tree_vector = $this->newTreeVector($old_vector, $new_vector); 80 + 81 + $item_map[implode("\n", $tree_vector)] = $item; 82 + 83 + $vector_tree->addVector($tree_vector); 84 + } 85 + $node_list = $vector_tree->newDisplayList(); 86 + 87 + $node_map = array(); 88 + foreach ($node_list as $node) { 89 + $path_vector = $node->getVector(); 90 + $path_vector = implode("\n", $path_vector); 91 + $node_map[$path_vector] = $node; 92 + } 93 + 94 + // Mark all nodes which contain at least one path which exists in the new 95 + // state. Nodes we don't mark contain only deleted or moved files, so they 96 + // can be rendered with a less-prominent style. 97 + 98 + foreach ($node_map as $node_key => $node) { 99 + $item = idx($item_map, $node_key); 100 + 101 + if (!$item) { 102 + continue; 103 + } 104 + 105 + $changeset = $item->getChangeset(); 106 + if (!$changeset->getIsLowImportanceChangeset()) { 107 + $node->setAncestralAttribute('important', true); 108 + } 109 + } 110 + 111 + $any_packages = false; 112 + $any_coverage = false; 113 + $any_context = false; 64 114 65 115 $rows = array(); 66 116 $rowc = array(); 67 - foreach ($items as $item) { 68 - $item->setUser($this->getUser()); 69 - $rows[] = $item->render(); 117 + foreach ($node_map as $node_key => $node) { 118 + $display_vector = $node->getDisplayVector(); 119 + $item = idx($item_map, $node_key); 70 120 71 - $have_authority = false; 121 + if ($item) { 122 + $changeset = $item->getChangeset(); 123 + $icon = $changeset->newFileTreeIcon(); 124 + } else { 125 + $changeset = null; 126 + $icon = id(new PHUIIconView()) 127 + ->setIcon('fa-folder-open-o grey'); 128 + } 129 + 130 + if ($node->getChildren()) { 131 + $old_dir = true; 132 + $new_dir = true; 133 + } else { 134 + // TODO: When properties are set on a directory in SVN directly, this 135 + // might be incorrect. 136 + $old_dir = false; 137 + $new_dir = false; 138 + } 139 + 140 + $display_view = $this->newComponentView( 141 + $icon, 142 + $display_vector, 143 + $old_dir, 144 + $new_dir, 145 + $item); 146 + 147 + $depth = $node->getDisplayDepth(); 148 + 149 + $style = sprintf('padding-left: %dpx;', $depth * 16); 150 + 151 + if ($item) { 152 + $packages = $item->renderPackages(); 153 + } else { 154 + $packages = null; 155 + } 72 156 73 - $packages = $item->getPackages(); 74 157 if ($packages) { 75 - if (array_intersect_key($packages, $authority)) { 76 - $have_authority = true; 158 + $any_packages = true; 159 + } 160 + 161 + if ($item) { 162 + if ($item->getCoverage()) { 163 + $any_coverage = true; 164 + } 165 + $coverage = $item->renderCoverage(); 166 + $modified_coverage = $item->renderModifiedCoverage(); 167 + } else { 168 + $coverage = null; 169 + $modified_coverage = null; 170 + } 171 + 172 + if ($item) { 173 + $context = $item->getContext(); 174 + if ($context) { 175 + $any_context = true; 77 176 } 177 + } else { 178 + $context = null; 78 179 } 79 180 80 - if ($have_authority) { 81 - $rowc[] = 'highlighted'; 181 + if ($item) { 182 + $lines = $item->renderChangesetLines(); 82 183 } else { 83 - $rowc[] = null; 184 + $lines = null; 185 + } 186 + 187 + $rows[] = array( 188 + $context, 189 + phutil_tag( 190 + 'div', 191 + array( 192 + 'style' => $style, 193 + ), 194 + $display_view), 195 + $lines, 196 + $coverage, 197 + $modified_coverage, 198 + $packages, 199 + ); 200 + 201 + $classes = array(); 202 + 203 + $have_authority = false; 204 + 205 + if ($item) { 206 + $packages = $item->getPackages(); 207 + if ($packages) { 208 + if (array_intersect_key($packages, $authority)) { 209 + $have_authority = true; 210 + } 211 + } 84 212 } 85 - } 86 213 87 - // Check if any item has content in these columns. If no item does, we'll 88 - // just hide them. 89 - $any_coverage = false; 90 - $any_context = false; 91 - $any_packages = false; 92 - foreach ($items as $item) { 93 - if ($item->getContext() !== null) { 94 - $any_context = true; 214 + if ($have_authority) { 215 + $classes[] = 'highlighted'; 95 216 } 96 217 97 - if (strlen($item->getCoverage())) { 98 - $any_coverage = true; 218 + if (!$node->getAttribute('important')) { 219 + $classes[] = 'diff-toc-low-importance-row'; 99 220 } 100 221 101 - if ($item->getPackages() !== null) { 102 - $any_packages = true; 222 + if ($changeset) { 223 + $classes[] = 'diff-toc-changeset-row'; 224 + } else { 225 + $classes[] = 'diff-toc-no-changeset-row'; 103 226 } 227 + 228 + $rowc[] = implode(' ', $classes); 104 229 } 105 230 106 231 $table = id(new AphrontTableView($rows)) 107 232 ->setRowClasses($rowc) 233 + ->setClassName('aphront-table-view-compact') 108 234 ->setHeaders( 109 235 array( 110 236 null, 111 - null, 112 - null, 113 - null, 114 237 pht('Path'), 238 + pht('Size'), 115 239 pht('Coverage (All)'), 116 240 pht('Coverage (Touched)'), 117 241 pht('Packages'), ··· 119 243 ->setColumnClasses( 120 244 array( 121 245 null, 122 - 'differential-toc-char center', 123 - 'differential-toc-prop center', 124 - 'differential-toc-ftype center', 125 - 'differential-toc-file wide', 246 + 'diff-toc-path wide', 247 + 'right', 126 248 'differential-toc-cov', 127 249 'differential-toc-cov', 128 250 null, ··· 132 254 $any_context, 133 255 true, 134 256 true, 135 - true, 136 - true, 137 257 $any_coverage, 138 258 $any_coverage, 139 259 $any_packages, 140 260 )) 141 261 ->setDeviceVisibility( 142 262 array( 143 - true, 144 263 true, 145 264 true, 146 - true, 147 - true, 265 + false, 148 266 false, 149 267 false, 150 268 true, ··· 174 292 if ($this->infoView) { 175 293 $box->setInfoView($this->infoView); 176 294 } 295 + 177 296 return $box; 297 + } 298 + 299 + private function newTreeVector($old, $new) { 300 + if ($old === null && $new === null) { 301 + throw new Exception(pht('Changeset has no path vectors!')); 302 + } 303 + 304 + $vector = null; 305 + if ($old === null) { 306 + $vector = $new; 307 + } else if ($new === null) { 308 + $vector = $old; 309 + } else if ($old === $new) { 310 + $vector = $new; 311 + } 312 + 313 + if ($vector) { 314 + foreach ($vector as $k => $v) { 315 + $vector[$k] = $this->newScalarComponent($v); 316 + } 317 + return $vector; 318 + } 319 + 320 + $matrix = id(new PhutilEditDistanceMatrix()) 321 + ->setSequences($old, $new) 322 + ->setComputeString(true); 323 + $edits = $matrix->getEditString(); 324 + 325 + // If the edit sequence contains deletions followed by edits, move 326 + // the deletions to the end to left-align the new path. 327 + $edits = preg_replace('/(d+)(x+)/', '\2\1', $edits); 328 + 329 + $vector = array(); 330 + $length = strlen($edits); 331 + 332 + $old_cursor = 0; 333 + $new_cursor = 0; 334 + 335 + for ($ii = 0; $ii < strlen($edits); $ii++) { 336 + $c = $edits[$ii]; 337 + switch ($c) { 338 + case 'i': 339 + $vector[] = $this->newPairComponent(null, $new[$new_cursor]); 340 + $new_cursor++; 341 + break; 342 + case 'd': 343 + $vector[] = $this->newPairComponent($old[$old_cursor], null); 344 + $old_cursor++; 345 + break; 346 + case 's': 347 + case 'x': 348 + case 't': 349 + $vector[] = $this->newPairComponent( 350 + $old[$old_cursor], 351 + $new[$new_cursor]); 352 + $old_cursor++; 353 + $new_cursor++; 354 + break; 355 + default: 356 + throw new Exception(pht('Unknown edit string "%s"!', $c)); 357 + } 358 + } 359 + 360 + return $vector; 361 + } 362 + 363 + private function newScalarComponent($v) { 364 + $key = sprintf('path(%s)', $v); 365 + 366 + if (!isset($this->components[$key])) { 367 + $this->components[$key] = $v; 368 + } 369 + 370 + return $key; 371 + } 372 + 373 + private function newPairComponent($u, $v) { 374 + if ($u === $v) { 375 + return $this->newScalarComponent($u); 376 + } 377 + 378 + $key = sprintf('pair(%s > %s)', $u, $v); 379 + 380 + if (!isset($this->components[$key])) { 381 + $this->components[$key] = array($u, $v); 382 + } 383 + 384 + return $key; 385 + } 386 + 387 + private function newComponentView( 388 + $icon, 389 + array $keys, 390 + $old_dir, 391 + $new_dir, 392 + $item) { 393 + 394 + $is_simple = true; 395 + 396 + $items = array(); 397 + foreach ($keys as $key) { 398 + $component = $this->components[$key]; 399 + 400 + if (is_array($component)) { 401 + $is_simple = false; 402 + } else { 403 + $component = array( 404 + $component, 405 + $component, 406 + ); 407 + } 408 + 409 + $items[] = $component; 410 + } 411 + 412 + $move_icon = id(new PHUIIconView()) 413 + ->setIcon('fa-angle-double-right pink'); 414 + 415 + $old_row = array( 416 + phutil_tag('td', array(), $move_icon), 417 + ); 418 + $new_row = array( 419 + phutil_tag('td', array(), $icon), 420 + ); 421 + 422 + $last_old_key = null; 423 + $last_new_key = null; 424 + 425 + foreach ($items as $key => $component) { 426 + if (!is_array($component)) { 427 + $last_old_key = $key; 428 + $last_new_key = $key; 429 + } else { 430 + if ($component[0] !== null) { 431 + $last_old_key = $key; 432 + } 433 + if ($component[1] !== null) { 434 + $last_new_key = $key; 435 + } 436 + } 437 + } 438 + 439 + foreach ($items as $key => $component) { 440 + if (!is_array($component)) { 441 + $old = $component; 442 + $new = $component; 443 + } else { 444 + $old = $component[0]; 445 + $new = $component[1]; 446 + } 447 + 448 + $old_classes = array(); 449 + $new_classes = array(); 450 + 451 + if ($old === $new) { 452 + // Do nothing. 453 + } else if ($old === null) { 454 + $new_classes[] = 'diff-path-component-new'; 455 + } else if ($new === null) { 456 + $old_classes[] = 'diff-path-component-old'; 457 + } else { 458 + $old_classes[] = 'diff-path-component-old'; 459 + $new_classes[] = 'diff-path-component-new'; 460 + } 461 + 462 + if ($old !== null) { 463 + if (($key === $last_old_key) && !$old_dir) { 464 + // Do nothing. 465 + } else { 466 + $old = $old.'/'; 467 + } 468 + } 469 + 470 + if ($new !== null) { 471 + if (($key === $last_new_key) && $item) { 472 + $new = $item->newLink(); 473 + } else if (($key === $last_new_key) && !$new_dir) { 474 + // Do nothing. 475 + } else { 476 + $new = $new.'/'; 477 + } 478 + } 479 + 480 + $old_row[] = phutil_tag( 481 + 'td', 482 + array(), 483 + phutil_tag( 484 + 'div', 485 + array( 486 + 'class' => implode(' ', $old_classes), 487 + ), 488 + $old)); 489 + $new_row[] = phutil_tag( 490 + 'td', 491 + array(), 492 + phutil_tag( 493 + 'div', 494 + array( 495 + 'class' => implode(' ', $new_classes), 496 + ), 497 + $new)); 498 + } 499 + 500 + $old_row = phutil_tag( 501 + 'tr', 502 + array( 503 + 'class' => 'diff-path-old', 504 + ), 505 + $old_row); 506 + 507 + $new_row = phutil_tag( 508 + 'tr', 509 + array( 510 + 'class' => 'diff-path-new', 511 + ), 512 + $new_row); 513 + 514 + $rows = array(); 515 + $rows[] = $new_row; 516 + if (!$is_simple) { 517 + $rows[] = $old_row; 518 + } 519 + 520 + $body = phutil_tag('tbody', array(), $rows); 521 + 522 + $table = phutil_tag( 523 + 'table', 524 + array( 525 + ), 526 + $body); 527 + 528 + return $table; 178 529 } 179 530 180 531 }
+41
webroot/rsrc/css/application/differential/table-of-contents.css
··· 88 88 margin: 4px 0; 89 89 border: 1px solid {$thinblueborder}; 90 90 } 91 + 92 + .diff-toc-path .phui-icon-view { 93 + margin-right: 2px; 94 + text-align: center; 95 + width: 20px; 96 + display: inline-block; 97 + } 98 + 99 + table.aphront-table-view-compact td { 100 + padding: 3px 8px; 101 + } 102 + 103 + td.diff-toc-path td { 104 + padding: 0; 105 + color: {$greytext}; 106 + } 107 + 108 + .diff-toc-path div.diff-path-component-new { 109 + padding: 0 4px; 110 + background: {$new-bright}; 111 + margin: 0 2px; 112 + } 113 + 114 + .diff-toc-path div.diff-path-component-old { 115 + padding: 0 4px; 116 + background: {$old-bright}; 117 + margin: 0 2px; 118 + } 119 + 120 + .diff-toc-path a { 121 + color: {$darkbluetext}; 122 + } 123 + 124 + td.diff-toc-path .diff-path-old td { 125 + color: {$lightgreytext}; 126 + } 127 + 128 + .diff-toc-low-importance-row, 129 + .alt-diff-toc-low-importance-row { 130 + opacity: 0.5; 131 + }