Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891242
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
56 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php
index dc09fd6db5..3d7454c229 100644
--- a/src/applications/differential/controller/DifferentialChangesetViewController.php
+++ b/src/applications/differential/controller/DifferentialChangesetViewController.php
@@ -1,350 +1,354 @@
<?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());
+ // TODO: [HTML] Clean up DifferentialChangesetParser output, but it's
+ // undergoing like six kinds of refactoring anyway.
+ $output = phutil_safe_html($output);
+
$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' => pht('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/DifferentialChangesetHTMLRenderer.php b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
index 1a60831ac9..4efe69250a 100644
--- a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
@@ -1,388 +1,396 @@
<?php
abstract class DifferentialChangesetHTMLRenderer
extends DifferentialChangesetRenderer {
protected function renderChangeTypeHeader($force) {
$changeset = $this->getChangeset();
$change = $changeset->getChangeType();
$file = $changeset->getFileType();
$message = null;
if ($change == DifferentialChangeType::TYPE_CHANGE &&
$file == DifferentialChangeType::FILE_TEXT) {
if ($force) {
// We have to force something to render because there were no changes
// of other kinds.
$message = pht('This file was not modified.');
} else {
// Default case of changes to a text file, no metadata.
return null;
}
} else {
switch ($change) {
case DifferentialChangeType::TYPE_ADD:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was <strong>added</strong>.');
break;
}
break;
case DifferentialChangeType::TYPE_DELETE:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was <strong>deleted</strong>.');
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_HERE:
$from =
"<strong>".
phutil_escape_html($changeset->getOldFile()).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was moved from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_HERE:
$from =
"<strong>".
phutil_escape_html($changeset->getOldFile()).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was copied from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_AWAY:
$paths =
"<strong>".
phutil_escape_html(implode(', ', $changeset->getAwayPaths())).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was moved to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_AWAY:
$paths =
"<strong>".
phutil_escape_html(implode(', ', $changeset->getAwayPaths())).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was copied to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_MULTICOPY:
$paths =
"<strong>".
phutil_escape_html(implode(', ', $changeset->getAwayPaths())).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht(
'This file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht(
'This image was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht(
'This symlink was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was deleted after being copied to %s.',
$paths);
break;
}
break;
default:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This is a file.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This is an image.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This is a directory.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This is a binary file.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This is a symlink.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This is a submodule.');
break;
}
break;
}
}
return
'<div class="differential-meta-notice">'.
$message.
'</div>';
}
protected function renderPropertyChangeHeader() {
$changeset = $this->getChangeset();
$old = $changeset->getOldProperties();
$new = $changeset->getNewProperties();
$keys = array_keys($old + $new);
sort($keys);
$rows = array();
foreach ($keys as $key) {
$oval = idx($old, $key);
$nval = idx($new, $key);
if ($oval !== $nval) {
if ($oval === null) {
$oval = '<em>null</em>';
} else {
$oval = nl2br(phutil_escape_html($oval));
}
if ($nval === null) {
$nval = '<em>null</em>';
} else {
$nval = nl2br(phutil_escape_html($nval));
}
$rows[] =
'<tr>'.
'<th>'.phutil_escape_html($key).'</th>'.
'<td class="oval">'.$oval.'</td>'.
'<td class="nval">'.$nval.'</td>'.
'</tr>';
}
}
return
'<table class="differential-property-table">'.
'<tr class="property-table-header">'.
'<th>'.pht('Property Changes').'</th>'.
'<td class="oval">'.pht('Old Value').'</td>'.
'<td class="nval">'.pht('New Value').'</td>'.
'</tr>'.
implode('', $rows).
'</table>';
}
public function renderShield($message, $force = 'default') {
$end = count($this->getOldLines());
$reference = $this->getRenderingReference();
if ($force !== 'text' &&
$force !== 'whitespace' &&
$force !== 'none' &&
$force !== 'default') {
throw new Exception("Invalid 'force' parameter '{$force}'!");
}
$range = "0-{$end}";
if ($force == 'text') {
// If we're forcing text, force the whole file to be rendered.
$range = "{$range}/0-{$end}";
}
$meta = array(
'ref' => $reference,
'range' => $range,
);
if ($force == 'whitespace') {
$meta['whitespace'] = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
}
- $more = null;
+ $content = array();
+ $content[] = $message;
if ($force !== 'none') {
- $more = ' '.javelin_tag(
+ $content[] = ' ';
+ $content[] = javelin_tag(
'a',
array(
'mustcapture' => true,
'sigil' => 'show-more',
'class' => 'complete',
'href' => '#',
'meta' => $meta,
),
pht('Show File Contents'));
}
return $this->wrapChangeInTable(
- javelin_render_tag(
+ javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
- '<td class="differential-shield" colspan="6">'.
- phutil_escape_html($message).
- $more.
- '</td>'));
+ phutil_tag(
+ 'td',
+ array(
+ 'class' => 'differential-shield',
+ 'colspan' => 6,
+ ),
+ $content)));
}
protected function wrapChangeInTable($content) {
if (!$content) {
return null;
}
- return javelin_render_tag(
+ // TODO: [HTML] After TwoUpRenderer gets refactored, fix this.
+ $content = phutil_safe_html($content);
+
+ return javelin_tag(
'table',
array(
'class' => 'differential-diff remarkup-code PhabricatorMonospaced',
'sigil' => 'differential-diff',
),
$content);
}
protected function renderInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
return $this->buildInlineComment($comment, $on_right)->render();
}
protected function buildInlineComment(
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);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
index cb22b44341..7a9bb3a1af 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -1,426 +1,446 @@
<?php
final class DifferentialChangesetTwoUpRenderer
extends DifferentialChangesetHTMLRenderer {
public function isOneUpRenderer() {
return false;
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$hunk_starts = $this->getHunkStartLines();
$context_not_available = null;
if ($hunk_starts) {
$context_not_available = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'colspan' => 6,
'class' => 'show-more'
),
pht('Context not available.')
)
);
}
$html = array();
$old_lines = $this->getOldLines();
$new_lines = $this->getNewLines();
$gaps = $this->getGaps();
$reference = $this->getRenderingReference();
$left_id = $this->getOldChangesetID();
$right_id = $this->getNewChangesetID();
// "N" stands for 'new' and means the comment should attach to the new file
// when stored, i.e. DifferentialInlineComment->setIsNewFile().
// "O" stands for 'old' and means the comment should attach to the old file.
$left_char = $this->getOldAttachesToNewFile()
? 'N'
: 'O';
$right_char = $this->getNewAttachesToNewFile()
? 'N'
: 'O';
$changeset = $this->getChangeset();
$copy_lines = idx($changeset->getMetadata(), 'copy:lines', array());
$highlight_old = $this->getHighlightOld();
$highlight_new = $this->getHighlightNew();
$old_render = $this->getOldRender();
$new_render = $this->getNewRender();
$original_left = $this->getOriginalOld();
$original_right = $this->getOriginalNew();
$depths = $this->getDepths();
$mask = $this->getMask();
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) {
// If we aren't going to show this line, we've just entered a gap.
// Pop information about the next gap off the $gaps stack and render
// an appropriate "Show more context" element. This branch eventually
// increments $ii by the entire size of the gap and then continues
// the loop.
$gap = array_pop($gaps);
$top = $gap[0];
$len = $gap[1];
$end = $top + $len - 20;
$contents = array();
if ($len > 40) {
$is_first_block = false;
if ($ii == 0) {
$is_first_block = true;
}
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-20",
),
),
$is_first_block
? pht("Show First 20 Lines")
: pht("\xE2\x96\xB2 Show 20 Lines"));
}
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'type' => 'all',
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-{$len}",
),
),
pht('Show All %d Lines', $len));
$is_last_block = false;
if ($ii + $len >= $rows) {
$is_last_block = true;
}
if ($len > 40) {
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$end}-20",
),
),
$is_last_block
? pht("Show Last 20 Lines")
: pht("\xE2\x96\xBC Show 20 Lines"));
}
$context = null;
$context_line = null;
if (!$is_last_block && $depths[$ii + $len]) {
for ($l = $ii + $len - 1; $l >= $ii; $l--) {
$line = $new_lines[$l]['text'];
if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') {
$context = $new_render[$l];
$context_line = $new_lines[$l]['line'];
break;
}
}
}
- $container = javelin_render_tag(
+ $container = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
- '<td colspan="2" class="show-more">'.
- implode(' • ', $contents).
- '</td>'.
- '<th class="show-context-line">'.$context_line.'</td>'.
- '<td colspan="3" class="show-context">'.$context.'</td>');
+ array(
+ phutil_tag(
+ 'td',
+ array(
+ 'colspan' => 2,
+ 'class' => 'show-more',
+ ),
+ array_interleave(
+ " \xE2\x80\xA2 ", // Bullet
+ $contents)),
+ phutil_tag(
+ 'th',
+ array(
+ 'class' => 'show-context-line',
+ ),
+ $context_line ? (int)$context_line : null),
+ phutil_tag(
+ 'td',
+ array(
+ 'colspan' => 3,
+ 'class' => 'show-context',
+ ),
+ // TODO: [HTML] Escaping model here isn't ideal.
+ phutil_safe_html($context)),
+ ));
$html[] = $container;
$ii += ($len - 1);
continue;
}
$o_num = null;
$o_classes = 'left';
$o_text = null;
if (isset($old_lines[$ii])) {
$o_num = $old_lines[$ii]['line'];
$o_text = isset($old_render[$ii]) ? $old_render[$ii] : null;
if ($old_lines[$ii]['type']) {
if ($old_lines[$ii]['type'] == '\\') {
$o_text = $old_lines[$ii]['text'];
$o_classes .= ' comment';
} else if ($original_left && !isset($highlight_old[$o_num])) {
$o_classes .= ' old-rebase';
} else if (empty($new_lines[$ii])) {
$o_classes .= ' old old-full';
} else {
$o_classes .= ' old';
}
}
}
$n_copy = '<td class="copy" />';
$n_cov = null;
$n_colspan = 2;
$n_classes = '';
$n_num = null;
$n_text = null;
if (isset($new_lines[$ii])) {
$n_num = $new_lines[$ii]['line'];
$n_text = isset($new_render[$ii]) ? $new_render[$ii] : null;
$coverage = $this->getCodeCoverage();
if ($coverage !== null) {
if (empty($coverage[$n_num - 1])) {
$cov_class = 'N';
} else {
$cov_class = $coverage[$n_num - 1];
}
$cov_class = 'cov-'.$cov_class;
$n_cov = '<td class="cov '.$cov_class.'"></td>';
$n_colspan--;
}
if ($new_lines[$ii]['type']) {
if ($new_lines[$ii]['type'] == '\\') {
$n_text = $new_lines[$ii]['text'];
$n_class = 'comment';
} else if ($original_right && !isset($highlight_new[$n_num])) {
$n_class = 'new-rebase';
} else if (empty($old_lines[$ii])) {
$n_class = 'new new-full';
} else {
$n_class = 'new';
}
$n_classes = $n_class;
if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) {
$n_copy = '<td class="copy '.$n_class.'"></td>';
} else {
list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
$title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
if ($orig_file == '') {
$title .= "line {$orig_line}";
} else {
$title .=
basename($orig_file).
":{$orig_line} in dir ".
dirname('/'.$orig_file);
}
$class = ($orig_type == '-' ? 'new-move' : 'new-copy');
$n_copy = javelin_tag(
'td',
array(
'meta' => array(
'msg' => $title,
),
'class' => 'copy '.$class,
),
'');
}
}
}
$n_classes .= ' right'.$n_colspan;
if (isset($hunk_starts[$o_num])) {
$html[] = $context_not_available;
}
if ($o_num && $left_id) {
$o_id = ' id="C'.$left_id.$left_char.'L'.$o_num.'"';
} else {
$o_id = null;
}
if ($n_num && $right_id) {
$n_id = ' id="C'.$right_id.$right_char.'L'.$n_num.'"';
} else {
$n_id = null;
}
// NOTE: The Javascript is sensitive to whitespace changes in this
// block!
$html[] =
'<tr>'.
'<th'.$o_id.'>'.$o_num.'</th>'.
'<td class="'.$o_classes.'">'.$o_text.'</td>'.
'<th'.$n_id.'>'.$n_num.'</th>'.
$n_copy.
// NOTE: This is a unicode zero-width space, which we use as a hint
// when intercepting 'copy' events to make sure sensible text ends
// up on the clipboard. See the 'phabricator-oncopy' behavior.
'<td class="'.$n_classes.'" colspan="'.$n_colspan.'">'.
"\xE2\x80\x8B".$n_text.
'</td>'.
$n_cov.
'</tr>';
if ($context_not_available && ($ii == $rows - 1)) {
$html[] = $context_not_available;
}
$old_comments = $this->getOldComments();
$new_comments = $this->getNewComments();
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = false);
$new = '';
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$new = $this->renderInlineComment($new_comment,
$on_right = true);
unset($new_comments[$n_num][$key]);
}
}
}
$html[] =
'<tr class="inline">'.
'<th />'.
'<td class="left">'.$comment_html.'</td>'.
'<th />'.
'<td colspan="3" class="right3">'.$new.'</td>'.
'</tr>';
}
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = true);
$html[] =
'<tr class="inline">'.
'<th />'.
'<td class="left" />'.
'<th />'.
'<td colspan="3" class="right3">'.$comment_html.'</td>'.
'</tr>';
}
}
}
return $this->wrapChangeInTable(implode('', $html));
}
public function renderFileChange($old_file = null,
$new_file = null,
$id = 0,
$vs = 0) {
$old = null;
if ($old_file) {
$old = phutil_tag(
'div',
array(
'class' => 'differential-image-stage'
),
phutil_tag(
'img',
array(
'src' => $old_file->getBestURI(),
)
)
);
}
$new = null;
if ($new_file) {
$new = phutil_tag(
'div',
array(
'class' => 'differential-image-stage'
),
phutil_tag(
'img',
array(
'src' => $new_file->getBestURI(),
)
)
);
}
$html_old = array();
$html_new = array();
foreach ($this->getOldComments() as $on_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = false);
$html_old[] =
'<tr class="inline">'.
'<th />'.
'<td class="left">'.$comment_html.'</td>'.
'<th />'.
'<td class="right3" colspan="3" />'.
'</tr>';
}
}
foreach ($this->getNewComments() as $lin_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = true);
$html_new[] =
'<tr class="inline">'.
'<th />'.
'<td class="left" />'.
'<th />'.
'<td class="right3" colspan="3">'.$comment_html.'</td>'.
'</tr>';
}
}
if (!$old) {
$th_old = '<th></th>';
} else {
$th_old = '<th id="C'.$vs.'OL1">1</th>';
}
if (!$new) {
$th_new = '<th></th>';
} else {
$th_new = '<th id="C'.$id.'NL1">1</th>';
}
$output =
'<tr class="differential-image-diff">'.
$th_old.
'<td class="left differential-old-image">'.$old.'</td>'.
$th_new.
'<td class="right3 differential-new-image" colspan="3">'.
$new.
'</td>'.
'</tr>'.
implode('', $html_old).
implode('', $html_new);
$output = $this->wrapChangeInTable($output);
return $this->renderChangesetTable($output);
}
}
diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php
index 364fe35701..f77b5377d3 100644
--- a/src/applications/differential/view/DifferentialChangesetDetailView.php
+++ b/src/applications/differential/view/DifferentialChangesetDetailView.php
@@ -1,106 +1,108 @@
<?php
final class DifferentialChangesetDetailView extends AphrontView {
private $changeset;
private $buttons = array();
private $editable;
private $symbolIndex;
private $id;
private $vsChangesetID;
public function setChangeset($changeset) {
$this->changeset = $changeset;
return $this;
}
public function addButton($button) {
$this->buttons[] = $button;
return $this;
}
public function setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setSymbolIndex($symbol_index) {
$this->symbolIndex = $symbol_index;
return $this;
}
public function getID() {
if (!$this->id) {
$this->id = celerity_generate_unique_node_id();
}
return $this->id;
}
public function setVsChangesetID($vs_changeset_id) {
$this->vsChangesetID = $vs_changeset_id;
return $this;
}
public function getVsChangesetID() {
return $this->vsChangesetID;
}
public function render() {
require_celerity_resource('differential-changeset-view-css');
require_celerity_resource('syntax-highlighting-css');
Javelin::initBehavior('phabricator-oncopy', array());
$changeset = $this->changeset;
$class = 'differential-changeset';
if (!$this->editable) {
$class .= ' differential-changeset-immutable';
}
$buttons = null;
if ($this->buttons) {
- $buttons =
- '<div class="differential-changeset-buttons">'.
- implode('', $this->buttons).
- '</div>';
+ $buttons = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'differential-changeset-buttons',
+ ),
+ $this->buttons);
}
$id = $this->getID();
if ($this->symbolIndex) {
Javelin::initBehavior(
'repository-crossreference',
array(
'container' => $id,
) + $this->symbolIndex);
}
$display_filename = $changeset->getDisplayFilename();
- $output = javelin_render_tag(
+ return javelin_tag(
'div',
array(
'sigil' => 'differential-changeset',
'meta' => array(
'left' => nonempty(
$this->getVsChangesetID(),
$this->changeset->getID()),
'right' => $this->changeset->getID(),
),
'class' => $class,
'id' => $id,
),
- id(new PhabricatorAnchorView())
- ->setAnchorName($changeset->getAnchorName())
- ->setNavigationMarker(true)
- ->render().
- $buttons.
- '<h1>'.phutil_escape_html($display_filename).'</h1>'.
- '<div style="clear: both;"></div>'.
- $this->renderChildren());
-
-
- return $output;
+ $this->renderHTMLView(
+ array(
+ id(new PhabricatorAnchorView())
+ ->setAnchorName($changeset->getAnchorName())
+ ->setNavigationMarker(true)
+ ->render(),
+ $buttons,
+ phutil_tag('h1', array(), $display_filename),
+ phutil_tag('div', array('style' => 'clear: both'), ''),
+ $this->renderHTMLChildren(),
+ )));
}
}
diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php
index abbca39906..00094164f4 100644
--- a/src/applications/differential/view/DifferentialChangesetListView.php
+++ b/src/applications/differential/view/DifferentialChangesetListView.php
@@ -1,317 +1,319 @@
<?php
final class DifferentialChangesetListView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $inlineURI;
private $renderURI = '/differential/changeset/';
private $whitespace;
private $standaloneURI;
private $leftRawFileURI;
private $rightRawFileURI;
private $symbolIndexes = array();
private $repository;
private $branch;
private $diff;
private $vsMap = array();
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
private function getTitle() {
return $this->title;
}
public function setBranch($branch) {
$this->branch = $branch;
return $this;
}
private function getBranch() {
return $this->branch;
}
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setInlineCommentControllerURI($uri) {
$this->inlineURI = $uri;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setSymbolIndexes(array $indexes) {
$this->symbolIndexes = $indexes;
return $this;
}
public function setRenderURI($render_uri) {
$this->renderURI = $render_uri;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function setVsMap(array $vs_map) {
$this->vsMap = $vs_map;
return $this;
}
public function getVsMap() {
return $this->vsMap;
}
public function setStandaloneURI($uri) {
$this->standaloneURI = $uri;
return $this;
}
public function setRawFileURIs($l, $r) {
$this->leftRawFileURI = $l;
$this->rightRawFileURI = $r;
return $this;
}
public function render() {
require_celerity_resource('differential-changeset-view-css');
$changesets = $this->changesets;
Javelin::initBehavior('differential-toggle-files', array());
$output = array();
$mapping = array();
foreach ($changesets as $key => $changeset) {
$file = $changeset->getFilename();
$class = 'differential-changeset';
if (!$this->inlineURI) {
$class .= ' differential-changeset-noneditable';
}
$ref = $this->references[$key];
$detail = new DifferentialChangesetDetailView();
$view_options = $this->renderViewOptionsDropdown(
$detail,
$ref,
$changeset);
$prefs = $this->user->loadPreferences();
$pref_symbols = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS);
$detail->setChangeset($changeset);
$detail->addButton($view_options);
if ($pref_symbols != 'disabled') {
$detail->setSymbolIndex(idx($this->symbolIndexes, $key));
}
$detail->setVsChangesetID(idx($this->vsMap, $changeset->getID()));
$detail->setEditable(true);
$uniq_id = 'diff-'.$changeset->getAnchorName();
if (isset($this->visibleChangesets[$key])) {
$load = 'Loading...';
$mapping[$uniq_id] = $ref;
} else {
$load = javelin_tag(
'a',
array(
'href' => '#'.$uniq_id,
'meta' => array(
'id' => $uniq_id,
'ref' => $ref,
'kill' => true,
),
'sigil' => 'differential-load',
'mustcapture' => true,
),
pht('Load'));
}
$detail->appendChild(
phutil_tag(
'div',
array(
'id' => $uniq_id,
),
phutil_tag('div', array('class' => 'differential-loading'), $load)));
$output[] = $detail->render();
}
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('differential-populate', array(
'registry' => $mapping,
'whitespace' => $this->whitespace,
'uri' => $this->renderURI,
));
Javelin::initBehavior('differential-show-more', array(
'uri' => $this->renderURI,
'whitespace' => $this->whitespace,
));
Javelin::initBehavior('differential-comment-jump', array());
if ($this->inlineURI) {
$undo_templates = $this->renderUndoTemplates();
Javelin::initBehavior('differential-edit-inline-comments', array(
'uri' => $this->inlineURI,
'undo_templates' => $undo_templates,
'stage' => 'differential-review-stage',
));
}
- return
- id(new PhabricatorHeaderView())
- ->setHeader($this->getTitle())
- ->render().
- phutil_render_tag(
- 'div',
+ return $this->renderHTMLView(
array(
- 'class' => 'differential-review-stage',
- 'id' => 'differential-review-stage',
- ),
- implode("\n", $output));
+ id(new PhabricatorHeaderView())
+ ->setHeader($this->getTitle())
+ ->render(),
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'differential-review-stage',
+ 'id' => 'differential-review-stage',
+ ),
+ $output),
+ ));
}
/**
* Render the "Undo" markup for the inline comment undo feature.
*/
private function renderUndoTemplates() {
$link = javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'differential-inline-comment-undo',
),
pht('Undo'));
$div = phutil_tag(
'div',
array(
'class' => 'differential-inline-undo',
),
array('Changes discarded. ', $link));
$template =
'<table><tr>'.
'<th></th><td>%s</td>'.
'<th></th><td colspan="2">%s</td>'.
'</tr></table>';
return array(
'l' => sprintf($template, $div, ''),
'r' => sprintf($template, '', $div),
);
}
private function renderViewOptionsDropdown(
DifferentialChangesetDetailView $detail,
$ref,
DifferentialChangeset $changeset) {
$meta = array();
$qparams = array(
'ref' => $ref,
'whitespace' => $this->whitespace,
);
if ($this->standaloneURI) {
$uri = new PhutilURI($this->standaloneURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['standaloneURI'] = (string)$uri;
}
$repository = $this->repository;
if ($repository) {
$meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
idx($changeset->getMetadata(), 'line:first'),
$this->getBranch());
}
$change = $changeset->getChangeType();
if ($this->leftRawFileURI) {
if ($change != DifferentialChangeType::TYPE_ADD) {
$uri = new PhutilURI($this->leftRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['leftURI'] = (string)$uri;
}
}
if ($this->rightRawFileURI) {
if ($change != DifferentialChangeType::TYPE_DELETE &&
$change != DifferentialChangeType::TYPE_MULTICOPY) {
$uri = new PhutilURI($this->rightRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['rightURI'] = (string)$uri;
}
}
$user = $this->user;
if ($user && $repository) {
$path = ltrim(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
'/');
$line = idx($changeset->getMetadata(), 'line:first', 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
if ($editor_link) {
$meta['editor'] = $editor_link;
} else {
$meta['editorConfigure'] = '/settings/panel/display/';
}
}
$meta['containerID'] = $detail->getID();
Javelin::initBehavior(
'differential-dropdown-menus',
array());
return javelin_tag(
'a',
array(
'class' => 'button small grey',
'meta' => $meta,
'href' => idx($meta, 'detailURI', '#'),
'target' => '_blank',
'sigil' => 'differential-view-options',
),
pht("View Options \xE2\x96\xBC"));
}
}
diff --git a/webroot/rsrc/js/application/core/behavior-dark-console.js b/webroot/rsrc/js/application/core/behavior-dark-console.js
index 0c72d0469c..99370a9342 100644
--- a/webroot/rsrc/js/application/core/behavior-dark-console.js
+++ b/webroot/rsrc/js/application/core/behavior-dark-console.js
@@ -1,231 +1,230 @@
/**
* @provides javelin-behavior-dark-console
* @requires javelin-behavior
* javelin-stratcom
* javelin-util
* javelin-dom
* javelin-request
* phabricator-keyboard-shortcut
*/
JX.behavior('dark-console', function(config, statics) {
var root = statics.root || setup_console();
config.key = config.key || root.getAttribute('data-console-key');
add_request(config);
// Do first-time setup.
function setup_console() {
statics.root = JX.$('darkconsole');
statics.req = {all: {}, current: null};
statics.tab = {all: {}, current: null};
statics.el = {};
statics.el.reqs = JX.$N('div', {className: 'dark-console-requests'});
statics.root.appendChild(statics.el.reqs);
statics.el.tabs = JX.$N('div', {className: 'dark-console-tabs'});
statics.root.appendChild(statics.el.tabs);
statics.el.panel = JX.$N('div', {className: 'dark-console-panel'});
statics.root.appendChild(statics.el.panel);
statics.el.load = JX.$N('div', {className: 'dark-console-load'});
statics.root.appendChild(statics.el.load);
statics.cache = {};
statics.visible = config.visible;
statics.selected = config.selected;
return statics.root;
}
// Add a new request to the console (initial page load, or new Ajax response).
function add_request(config) {
// Ignore DarkConsole data requests.
if (config.uri.match(new RegExp('^/~/data/'))) {
return;
}
var attr = {
className: 'dark-console-request',
sigil: 'dark-console-request',
title: config.uri,
meta: config,
href: '#'
};
var link = JX.$N('a', attr, config.uri);
statics.el.reqs.appendChild(link);
statics.req.all[config.key] = link;
if (!statics.req.current) {
select_request(config.key);
}
}
// Select a request (on load, or when the user clicks one).
function select_request(key) {
var req = statics.req;
if (req.current) {
JX.DOM.alterClass(req.all[req.current], 'dark-selected', false);
}
statics.req.current = key;
JX.DOM.alterClass(req.all[req.current], 'dark-selected', true);
if (statics.visible) {
- JX.log('visible!');
draw_request(key);
}
}
// When the user clicks a request, select it.
JX.Stratcom.listen('click', 'dark-console-request', function(e) {
e.kill();
select_request(e.getNodeData('dark-console-request').key);
});
// After the user selects a request, draw its tabs.
function draw_request(key) {
var cache = statics.cache;
if (cache[key]) {
render_request(key);
return;
}
new JX.Request(
'/~/data/' + key + '/',
function(r) {
cache[key] = r;
if (statics.req.current == key) {
render_request(key);
}
})
.send();
show_loading();
}
// Show the loading indicator.
function show_loading() {
JX.DOM.hide(statics.el.tabs);
JX.DOM.hide(statics.el.panel);
JX.DOM.show(statics.el.load);
}
// Hide the loading indicator.
function hide_loading() {
JX.DOM.show(statics.el.tabs);
JX.DOM.show(statics.el.panel);
JX.DOM.hide(statics.el.load);
}
function render_request(key) {
var data = statics.cache[key];
statics.tab.all = {};
var links = [];
var first = null;
for (var ii = 0; ii < data.tabs.length; ii++) {
var tab = data.tabs[ii];
var attr = {
className: 'dark-console-tab',
sigil: 'dark-console-tab',
meta: tab,
href: '#'
};
var bullet = null;
if (tab.color) {
bullet = JX.$N('span', {style: {color: tab.color}}, "\u2022");
}
var link = JX.$N('a', attr, [bullet, ' ', tab.name]);
links.push(link);
statics.tab.all[tab['class']] = link;
first = first || tab['class'];
}
JX.DOM.setContent(statics.el.tabs, links);
if (statics.tab.current in statics.tab.all) {
select_tab(statics.tab.current);
} else if (statics.selected in statics.tab.all) {
select_tab(statics.selected);
} else {
select_tab(first);
}
hide_loading();
}
function select_tab(tclass) {
var tabs = statics.tab;
if (tabs.current) {
JX.DOM.alterClass(tabs.current, 'dark-selected', false);
}
tabs.current = tabs.all[tclass];
JX.DOM.alterClass(tabs.current, 'dark-selected', true);
if (tclass != statics.selected) {
// Save user preference.
new JX.Request('/~/', JX.bag)
.setData({ tab : tclass })
.send();
}
draw_panel();
}
// When the user clicks a tab, select it.
JX.Stratcom.listen('click', 'dark-console-tab', function(e) {
e.kill();
select_tab(e.getNodeData('dark-console-tab')['class']);
});
function draw_panel() {
var data = statics.cache[statics.req.current];
var tclass = JX.Stratcom.getData(statics.tab.current)['class'];
var html = data.panel[tclass];
var div = JX.$N('div', {className: 'dark-console-panel-core'}, JX.$H(html));
JX.DOM.setContent(statics.el.panel, div);
}
// Install keyboard shortcut.
var desc = 'Toggle visibility of DarkConsole.';
new JX.KeyboardShortcut('`', desc)
.setHandler(function(manager) {
statics.visible = !statics.visible;
if (statics.visible) {
JX.DOM.show(root);
if (statics.req.current) {
draw_request(statics.req.current);
}
} else {
JX.DOM.hide(root);
}
// Save user preference.
new JX.Request('/~/', JX.bag)
.setData({visible: statics.visible ? 1 : 0})
.send();
// Force resize listeners to take effect.
JX.Stratcom.invoke('resize');
})
.register();
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:54 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125696
Default Alt Text
(56 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment