diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index 437cd66d22..3a3e7883ca 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -1,345 +1,350 @@ <?php final class DifferentialChangesetViewController extends DifferentialController { public function shouldRequireLogin() { return !$this->allowsAnonymousAccess(); } public function processRequest() { $request = $this->getRequest(); $author_phid = $request->getUser()->getPHID(); $rendering_reference = $request->getStr('ref'); $parts = explode('/', $rendering_reference); if (count($parts) == 2) { list($id, $vs) = $parts; } else { $id = $parts[0]; $vs = 0; } $id = (int)$id; $vs = (int)$vs; $changeset = id(new DifferentialChangeset())->load($id); if (!$changeset) { return new Aphront404Response(); } $view = $request->getStr('view'); if ($view) { $changeset->attachHunks($changeset->loadHunks()); $phid = idx($changeset->getMetadata(), "$view:binary-phid"); if ($phid) { return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/"); } switch ($view) { case 'new': return $this->buildRawFileResponse($changeset, $is_new = true); case 'old': if ($vs && ($vs != -1)) { $vs_changeset = id(new DifferentialChangeset())->load($vs); if ($vs_changeset) { $vs_changeset->attachHunks($vs_changeset->loadHunks()); return $this->buildRawFileResponse($vs_changeset, $is_new = true); } } return $this->buildRawFileResponse($changeset, $is_new = false); default: return new Aphront400Response(); } } if ($vs && ($vs != -1)) { $vs_changeset = id(new DifferentialChangeset())->load($vs); if (!$vs_changeset) { return new Aphront404Response(); } } if (!$vs) { $right = $changeset; $left = null; $right_source = $right->getID(); $right_new = true; $left_source = $right->getID(); $left_new = false; $render_cache_key = $right->getID(); } else if ($vs == -1) { $right = null; $left = $changeset; $right_source = $left->getID(); $right_new = false; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; } else { $right = $changeset; $left = $vs_changeset; $right_source = $right->getID(); $right_new = true; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; } if ($left) { $left->attachHunks($left->loadHunks()); } if ($right) { $right->attachHunks($right->loadHunks()); } if ($left) { $left_data = $left->makeNewFile(); if ($right) { $right_data = $right->makeNewFile(); } else { $right_data = $left->makeOldFile(); } $engine = new PhabricatorDifferenceEngine(); $synthetic = $engine->generateChangesetFromFileContent( $left_data, $right_data); $choice = clone nonempty($left, $right); $choice->attachHunks($synthetic->getHunks()); $changeset = $choice; } $coverage = null; if ($right && $right->getDiffID()) { $unit = id(new DifferentialDiffProperty())->loadOneWhere( 'diffID = %d AND name = %s', $right->getDiffID(), 'arc:unit'); if ($unit) { $coverage = array(); foreach ($unit->getData() as $result) { $result_coverage = idx($result, 'coverage'); if (!$result_coverage) { continue; } $file_coverage = idx($result_coverage, $right->getFileName()); if (!$file_coverage) { continue; } $coverage[] = $file_coverage; } $coverage = ArcanistUnitTestResult::mergeCoverage($coverage); } } $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $parser = new DifferentialChangesetParser(); $parser->setCoverage($coverage); $parser->setChangeset($changeset); $parser->setRenderingReference($rendering_reference); $parser->setRenderCacheKey($render_cache_key); $parser->setRightSideCommentMapping($right_source, $right_new); $parser->setLeftSideCommentMapping($left_source, $left_new); $parser->setWhitespaceMode($request->getStr('whitespace')); + if ($request->getStr('renderer') == '1up') { + $parser->setRenderer(new DifferentialChangesetOneUpRenderer()); + } + + if ($left && $right) { $parser->setOriginals($left, $right); } // Load both left-side and right-side inline comments. $inlines = $this->loadInlineComments( array($left_source, $right_source), $author_phid); if ($left_new) { $inlines = array_merge( $inlines, $this->buildLintInlineComments($left)); } if ($right_new) { $inlines = array_merge( $inlines, $this->buildLintInlineComments($right)); } $phids = array(); foreach ($inlines as $inline) { $parser->parseInlineComment($inline); if ($inline->getAuthorPHID()) { $phids[$inline->getAuthorPHID()] = true; } } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); $parser->setHandles($handles); $engine = new PhabricatorMarkupEngine(); $engine->setViewer($request->getUser()); foreach ($inlines as $inline) { $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); $parser->setMarkupEngine($engine); if ($request->isAjax()) { // TODO: This is sort of lazy, the effect is just to not render "Edit" // and "Reply" links on the "standalone view". $parser->setUser($request->getUser()); } $output = $parser->render($range_s, $range_e, $mask); $mcov = $parser->renderModifiedCoverage(); if ($request->isAjax()) { $coverage = array( 'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov, ); return id(new PhabricatorChangesetResponse()) ->setRenderedChangeset($output) ->setCoverage($coverage); } Javelin::initBehavior('differential-show-more', array( 'uri' => '/differential/changeset/', 'whitespace' => $request->getStr('whitespace'), )); Javelin::initBehavior('differential-comment-jump', array()); $detail = new DifferentialChangesetDetailView(); $detail->setChangeset($changeset); $detail->appendChild($output); $detail->setVsChangesetID($left_source); $panel = new DifferentialPrimaryPaneView(); $panel->appendChild(phutil_render_tag('div', array( 'class' => 'differential-review-stage', 'id' => 'differential-review-stage', ), $detail->render()) ); return $this->buildStandardPageResponse( array( $panel ), array( 'title' => 'Changeset View', )); } private function loadInlineComments(array $changeset_ids, $author_phid) { $changeset_ids = array_unique(array_filter($changeset_ids)); if (!$changeset_ids) { return; } return id(new DifferentialInlineComment())->loadAllWhere( 'changesetID IN (%Ld) AND (commentID IS NOT NULL OR authorPHID = %s)', $changeset_ids, $author_phid); } private function buildRawFileResponse( DifferentialChangeset $changeset, $is_new) { if ($is_new) { $key = 'raw:new:phid'; } else { $key = 'raw:old:phid'; } $metadata = $changeset->getMetadata(); $file = null; $phid = idx($metadata, $key); if ($phid) { $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $phid); } if (!$file) { // This is just building a cache of the changeset content in the file // tool, and is safe to run on a read pathway. $unguard = AphrontWriteGuard::beginScopedUnguardedWrites(); if ($is_new) { $data = $changeset->makeNewFile(); } else { $data = $changeset->makeOldFile(); } $file = PhabricatorFile::newFromFileData( $data, array( 'name' => $changeset->getFilename(), 'mime-type' => 'text/plain', )); $metadata[$key] = $file->getPHID(); $changeset->setMetadata($metadata); $changeset->save(); unset($unguard); } return id(new AphrontRedirectResponse()) ->setURI($file->getBestURI()); } private function buildLintInlineComments($changeset) { $lint = id(new DifferentialDiffProperty())->loadOneWhere( 'diffID = %d AND name = %s', $changeset->getDiffID(), 'arc:lint'); if (!$lint) { return array(); } $lint = $lint->getData(); $inlines = array(); foreach ($lint as $msg) { if ($msg['path'] != $changeset->getFilename()) { continue; } $inline = new DifferentialInlineComment(); $inline->setChangesetID($changeset->getID()); $inline->setIsNewFile(true); $inline->setSyntheticAuthor('Lint: '.$msg['name']); $inline->setLineNumber($msg['line']); $inline->setLineLength(0); $inline->setContent('%%%'.$msg['description'].'%%%'); $inlines[] = $inline; } return $inlines; } } diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index 919f753231..5ab1fa07b2 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -1,28 +1,74 @@ <?php final class DifferentialChangesetOneUpRenderer extends DifferentialChangesetHTMLRenderer { public function isOneUpRenderer() { return true; } - public function renderChangesetTable($contents) { - throw new Exception("Not implemented!"); - } - public function renderTextChange( $range_start, $range_len, $rows) { - throw new Exception("Not implemented!"); + + $primitives = $this->buildPrimitives($range_start, $range_len); + + $out = array(); + foreach ($primitives as $p) { + $type = $p['type']; + switch ($type) { + case 'old': + case 'new': + $out[] = '<tr>'; + if ($type == 'old') { + if ($p['htype']) { + $class = 'left old'; + } else { + $class = 'left'; + } + $out[] = '<th>'.$p['line'].'</th>'; + $out[] = '<th></th>'; + $out[] = '<td class="'.$class.'">'.$p['render'].'</td>'; + } else if ($type == 'new') { + if ($p['htype']) { + $class = 'right new'; + $out[] = '<th />'; + } else { + $class = 'right'; + $out[] = '<th>'.$p['oline'].'</th>'; + } + $out[] = '<th>'.$p['line'].'</th>'; + $out[] = '<td class="'.$class.'">'.$p['render'].'</td>'; + } + $out[] = '</tr>'; + break; + case 'inline': + $out[] = '<tr><th /><th />'; + $out[] = '<td style="background: #ddd; color: #888;">'; + + $out[] = 'INLINE COMMENT<br />'; + $out[] = phutil_escape_html($p['comment']->getContent()); + + $out[] = '</td></tr>'; + break; + default: + $out[] = '<tr><th /><th /><td>'.$type.'</td></tr>'; + break; + } + } + + if ($out) { + return $this->wrapChangeInTable(implode('', $out)); + } + return null; } public function renderFileChange($old_file = null, $new_file = null, $id = 0, $vs = 0) { throw new Exception("Not implemented!"); } } diff --git a/src/applications/differential/render/DifferentialChangesetRenderer.php b/src/applications/differential/render/DifferentialChangesetRenderer.php index 6424d96da9..b592e988eb 100644 --- a/src/applications/differential/render/DifferentialChangesetRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetRenderer.php @@ -1,495 +1,501 @@ <?php abstract class DifferentialChangesetRenderer { private $user; private $changeset; private $renderingReference; private $renderPropertyChangeHeader; private $hunkStartLines; private $oldLines; private $newLines; private $oldComments; private $newComments; private $oldChangesetID; private $newChangesetID; private $oldAttachesToNewFile; private $newAttachesToNewFile; private $highlightOld = array(); private $highlightNew = array(); private $codeCoverage; private $handles; private $markupEngine; private $oldRender; private $newRender; private $originalOld; private $originalNew; private $gaps; private $mask; private $depths; public function setDepths($depths) { $this->depths = $depths; return $this; } protected function getDepths() { return $this->depths; } public function setMask($mask) { $this->mask = $mask; return $this; } protected function getMask() { return $this->mask; } public function setGaps($gaps) { $this->gaps = $gaps; return $this; } protected function getGaps() { return $this->gaps; } public function setOriginalNew($original_new) { $this->originalNew = $original_new; return $this; } protected function getOriginalNew() { return $this->originalNew; } public function setOriginalOld($original_old) { $this->originalOld = $original_old; return $this; } protected function getOriginalOld() { return $this->originalOld; } public function setNewRender($new_render) { $this->newRender = $new_render; return $this; } protected function getNewRender() { return $this->newRender; } public function setOldRender($old_render) { $this->oldRender = $old_render; return $this; } protected function getOldRender() { return $this->oldRender; } public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) { $this->markupEngine = $markup_engine; return $this; } public function getMarkupEngine() { return $this->markupEngine; } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } protected function getHandles() { return $this->handles; } public function setCodeCoverage($code_coverage) { $this->codeCoverage = $code_coverage; return $this; } protected function getCodeCoverage() { return $this->codeCoverage; } public function setHighlightNew($highlight_new) { $this->highlightNew = $highlight_new; return $this; } protected function getHighlightNew() { return $this->highlightNew; } public function setHighlightOld($highlight_old) { $this->highlightOld = $highlight_old; return $this; } protected function getHighlightOld() { return $this->highlightOld; } public function setNewAttachesToNewFile($attaches) { $this->newAttachesToNewFile = $attaches; return $this; } protected function getNewAttachesToNewFile() { return $this->newAttachesToNewFile; } public function setOldAttachesToNewFile($attaches) { $this->oldAttachesToNewFile = $attaches; return $this; } protected function getOldAttachesToNewFile() { return $this->oldAttachesToNewFile; } public function setNewChangesetID($new_changeset_id) { $this->newChangesetID = $new_changeset_id; return $this; } protected function getNewChangesetID() { return $this->newChangesetID; } public function setOldChangesetID($old_changeset_id) { $this->oldChangesetID = $old_changeset_id; return $this; } protected function getOldChangesetID() { return $this->oldChangesetID; } public function setNewComments(array $new_comments) { foreach ($new_comments as $line_number => $comments) { assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); } $this->newComments = $new_comments; return $this; } protected function getNewComments() { return $this->newComments; } public function setOldComments(array $old_comments) { foreach ($old_comments as $line_number => $comments) { assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); } $this->oldComments = $old_comments; return $this; } protected function getOldComments() { return $this->oldComments; } public function setNewLines(array $new_lines) { $this->newLines = $new_lines; return $this; } protected function getNewLines() { return $this->newLines; } public function setOldLines(array $old_lines) { $this->oldLines = $old_lines; return $this; } protected function getOldLines() { return $this->oldLines; } public function setHunkStartLines(array $hunk_start_lines) { $this->hunkStartLines = $hunk_start_lines; return $this; } protected function getHunkStartLines() { return $this->hunkStartLines; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } protected function getUser() { return $this->user; } public function setChangeset(DifferentialChangeset $changeset) { $this->changeset = $changeset; return $this; } protected function getChangeset() { return $this->changeset; } public function setRenderingReference($rendering_reference) { $this->renderingReference = $rendering_reference; return $this; } protected function getRenderingReference() { return $this->renderingReference; } public function setRenderPropertyChangeHeader($should_render) { $this->renderPropertyChangeHeader = $should_render; return $this; } private function shouldRenderPropertyChangeHeader() { return $this->renderPropertyChangeHeader; } final public function renderChangesetTable($content) { $props = null; if ($this->shouldRenderPropertyChangeHeader()) { $props = $this->renderPropertyChangeHeader(); } $force = (!$content && !$props); $notice = $this->renderChangeTypeHeader($force); $result = $notice.$props.$content; // TODO: Let the user customize their tab width / display style. // TODO: We should possibly post-process "\r" as well. // TODO: Both these steps should happen earlier. $result = str_replace("\t", ' ', $result); return $result; } abstract public function isOneUpRenderer(); abstract public function renderTextChange( $range_start, $range_len, $rows ); abstract public function renderFileChange( $old = null, $new = null, $id = 0, $vs = 0 ); abstract protected function renderChangeTypeHeader($force); protected function didRenderChangesetTableContents($contents) { return $contents; } /** * Render a "shield" over the diff, with a message like "This file is * generated and does not need to be reviewed." or "This file was completely * deleted." This UI element hides unimportant text so the reviewer doesn't * need to scroll past it. * * The shield includes a link to view the underlying content. This link * may force certain rendering modes when the link is clicked: * * - `"default"`: Render the diff normally, as though it was not * shielded. This is the default and appropriate if the underlying * diff is a normal change, but was hidden for reasons of not being * important (e.g., generated code). * - `"text"`: Force the text to be shown. This is probably only relevant * when a file is not changed. * - `"whitespace"`: Force the text to be shown, and the diff to be * rendered with all whitespace shown. This is probably only relevant * when a file is changed only by altering whitespace. * - `"none"`: Don't show the link (e.g., text not available). * * @param string Message explaining why the diff is hidden. * @param string|null Force mode, see above. * @return string Shield markup. */ abstract protected function renderShield($message, $force = 'default'); abstract protected function renderPropertyChangeHeader(); protected function renderInlineComment( PhabricatorInlineCommentInterface $comment, $on_right = false) { $user = $this->getUser(); $edit = $user && ($comment->getAuthorPHID() == $user->getPHID()) && ($comment->isDraft()); $allow_reply = (bool)$user; return id(new DifferentialInlineCommentView()) ->setInlineComment($comment) ->setOnRight($on_right) ->setHandles($this->getHandles()) ->setMarkupEngine($this->getMarkupEngine()) ->setEditable($edit) ->setAllowReply($allow_reply) ->render(); } protected function buildPrimitives($range_start, $range_len) { $primitives = array(); $hunk_starts = $this->getHunkStartLines(); $mask = $this->getMask(); $gaps = $this->getGaps(); $old = $this->getOldLines(); $new = $this->getNewLines(); $old_render = $this->getOldRender(); $new_render = $this->getNewRender(); $old_comments = $this->getOldComments(); $new_comments = $this->getNewComments(); $size = count($old); for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { list($top, $len) = array_pop($gaps); $primitives[] = array( 'type' => 'context', 'top' => $top, 'len' => $len, ); $ii += ($len - 1); continue; } $ospec = array( 'type' => 'old', 'htype' => null, 'cursor' => $ii, 'line' => null, + 'oline' => null, 'render' => null, ); $nspec = array( 'type' => 'new', 'htype' => null, 'cursor' => $ii, 'line' => null, + 'oline' => null, 'render' => null, 'copy' => null, 'coverage' => null, ); if (isset($old[$ii])) { - $ospec['line'] = $old[$ii]['line']; + $ospec['line'] = (int)$old[$ii]['line']; + $nspec['oline'] = (int)$old[$ii]['line']; $ospec['htype'] = $old[$ii]['type']; if (isset($old_render[$ii])) { $ospec['render'] = $old_render[$ii]; } } if (isset($new[$ii])) { - $nspec['line'] = $new[$ii]['line']; + $nspec['line'] = (int)$new[$ii]['line']; + $ospec['oline'] = (int)$new[$ii]['line']; $nspec['htype'] = $new[$ii]['type']; if (isset($new_render[$ii])) { $nspec['render'] = $new_render[$ii]; } } if (isset($hunk_starts[$ospec['line']])) { $primitives[] = array( 'type' => 'no-context', ); } $primitives[] = $ospec; $primitives[] = $nspec; if ($ospec['line'] !== null && isset($old_comments[$ospec['line']])) { foreach ($old_comments[$ospec['line']] as $comment) { $primitives[] = array( 'type' => 'inline', 'comment' => $comment, 'right' => false, ); } } if ($nspec['line'] !== null && isset($new_comments[$nspec['line']])) { foreach ($new_comments[$nspec['line']] as $comment) { $primitives[] = array( 'type' => 'inline', - 'comment' => 'comment', + 'comment' => $comment, 'right' => true, ); } } if ($hunk_starts && ($ii == $size - 1)) { $primitives[] = array( 'type' => 'no-context', ); } } if ($this->isOneUpRenderer()) { $primitives = $this->processPrimitivesForOneUp($primitives); } return $primitives; } private function processPrimitivesForOneUp(array $primitives) { // Primitives come out of buildPrimitives() in two-up format, because it // is the most general, flexible format. To put them into one-up format, // we need to filter and reorder them. In particular: // // - We discard unchanged lines in the old file; in one-up format, we // render them only once. // - We group contiguous blocks of old-modified and new-modified lines, so // they render in "block of old, block of new" order instead of // alternating old and new lines. $out = array(); $old_buf = array(); $new_buf = array(); foreach ($primitives as $primitive) { $type = $primitive['type']; + if ($type == 'old') { if (!$primitive['htype']) { // This is a line which appears in both the old file and the new // file, or the spacer corresponding to a line added in the new file. // Ignore it when rendering a one-up diff. continue; } if ($new_buf) { $out[] = $new_buf; $new_buf = array(); } $old_buf[] = $primitive; } else if ($type == 'new') { if ($primitive['line'] === null) { // This is an empty spacer corresponding to a line removed from the // old file. Ignore it when rendering a one-up diff. continue; } if ($old_buf) { $out[] = $old_buf; $old_buf = array(); } $new_buf[] = $primitive; } else if ($type == 'context' || $type == 'no-context') { $out[] = $old_buf; $out[] = $new_buf; $old_buf = array(); $new_buf = array(); $out[] = array($primitive); } else if ($type == 'inline') { - if ($primitive['right']) { - $new_buf[] = $primitive; - } else { - $old_buf[] = $primitive; - } + $out[] = $old_buf; + $out[] = $new_buf; + $old_buf = array(); + $new_buf = array(); + + $out[] = array($primitive); } else { throw new Exception("Unknown primitive type '{$primitive}'!"); } } $out[] = $old_buf; $out[] = $new_buf; $out = array_mergev($out); return $out; } } diff --git a/webroot/rsrc/js/application/core/behavior-device.js b/webroot/rsrc/js/application/core/behavior-device.js index 40eca13e9c..df6d9c6cd5 100644 --- a/webroot/rsrc/js/application/core/behavior-device.js +++ b/webroot/rsrc/js/application/core/behavior-device.js @@ -1,49 +1,53 @@ /** * @provides javelin-behavior-device * @requires javelin-behavior * javelin-stratcom * javelin-dom * javelin-vector * javelin-install */ JX.install('Device', { statics : { _device : null, - getDevice : function() { - return JX.Device._device; - } - } -}); + recalculate: function() { + var v = JX.Vector.getViewport(); + var self = JX.Device; -JX.behavior('device', function() { + var device = 'desktop'; + if (v.x <= 768) { + device = 'tablet'; + } + if (v.x <= 480) { + device = 'phone'; + } - function onresize() { - var v = JX.Vector.getViewport(); + if (device == self._device) { + return; + } - var device = 'desktop'; - if (v.x <= 768) { - device = 'tablet'; - } - if (v.x <= 480) { - device = 'phone'; - } + self._device = device; - if (device == JX.Device.getDevice()) { - return; - } - - JX.Device._device = device; + var e = document.body; + JX.DOM.alterClass(e, 'device-phone', (device == 'phone')); + JX.DOM.alterClass(e, 'device-tablet', (device == 'tablet')); + JX.DOM.alterClass(e, 'device-desktop', (device == 'desktop')); + JX.DOM.alterClass(e, 'device', (device != 'desktop')); - var e = document.body; - JX.DOM.alterClass(e, 'device-phone', (device == 'phone')); - JX.DOM.alterClass(e, 'device-tablet', (device == 'tablet')); - JX.DOM.alterClass(e, 'device-desktop', (device == 'desktop')); - JX.DOM.alterClass(e, 'device', (device != 'desktop')); + JX.Stratcom.invoke('phabricator-device-change', null, device); + }, - JX.Stratcom.invoke('phabricator-device-change', null, device); + getDevice : function() { + var self = JX.Device; + if (self._device === null) { + self.recalculate(); + } + return self._device; + } } +}); - JX.Stratcom.listen('resize', null, onresize); - onresize(); +JX.behavior('device', function() { + JX.Stratcom.listen('resize', null, JX.Device.recalculate); + JX.Device.recalculate(); }); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 44b5786058..79c9732fa8 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -1,125 +1,132 @@ /** * @provides javelin-behavior-differential-populate * @requires javelin-behavior * javelin-workflow * javelin-util * javelin-dom * javelin-stratcom + * javelin-behavior-device * phabricator-tooltip */ JX.behavior('differential-populate', function(config) { function onresponse(target, response) { JX.DOM.replace(JX.$(target), JX.$H(response.changeset)); if (response.coverage) { for (var k in response.coverage) { try { JX.DOM.replace(JX.$(k), JX.$H(response.coverage[k])); } catch (ignored) { // Not terribly important. } } } } + // NOTE: If you load the page at one device resolution and then resize to + // a different one we don't re-render the diffs, because it's a complicated + // mess and you could lose inline comments, cursor positions, etc. + var renderer = (JX.Device.getDevice() == 'desktop') ? '2up' : '1up'; + for (var k in config.registry) { var data = { ref : config.registry[k], - whitespace: config.whitespace + whitespace: config.whitespace, + renderer: renderer }; new JX.Workflow(config.uri, data) .setHandler(JX.bind(null, onresponse, k)) .start(); } var highlighted = null; var highlight_class = null; JX.Stratcom.listen( 'click', 'differential-load', function(e) { var meta = e.getNodeData('differential-load'); var diff; try { diff = JX.$(meta.id); } catch (e) { // Already loaded. } if (diff) { JX.DOM.setContent( diff, JX.$H('<div class="differential-loading">Loading...</div>')); var data = { ref : meta.ref, whitespace : config.whitespace }; new JX.Workflow(config.uri, data) .setHandler(JX.bind(null, onresponse, meta.id)) .start(); } if (meta.kill) { e.kill(); } }); JX.Stratcom.listen( ['mouseover', 'mouseout'], ['differential-changeset', 'tag:td'], function(e) { var t = e.getTarget(); // NOTE: Using className is not best practice, but the diff UI is perf // sensitive. if (!t.className.match(/cov|copy/)) { return; } if (e.getType() == 'mouseout') { JX.Tooltip.hide(); if (highlighted) { JX.DOM.alterClass(highlighted, highlight_class, false); highlighted = null; } } else { highlight_class = null; var msg; var align = 'W'; var sibling = 'previousSibling'; var width = 120; if (t.className.match(/cov-C/)) { msg = 'Covered'; highlight_class = 'source-cov-C'; } else if (t.className.match(/cov-U/)) { msg = 'Not Covered'; highlight_class = 'source-cov-U'; } else if (t.className.match(/cov-N/)) { msg = 'Not Executable'; highlight_class = 'source-cov-N'; } else { var match = /new-copy|new-move/.exec(t.className); if (match) { sibling = 'nextSibling'; width = 500; msg = JX.Stratcom.getData(t).msg; highlight_class = match[0]; } } if (msg) { JX.Tooltip.show(t, width, align, msg); } if (highlight_class) { highlighted = t[sibling]; JX.DOM.alterClass(highlighted, highlight_class, true); } } }); });