1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Merge branch 'master' into phutil_tag

(Sync.)
This commit is contained in:
epriestley 2013-02-04 06:19:16 -08:00
commit 0f1bdbe147
59 changed files with 876 additions and 346 deletions

3
.gitignore vendored
View file

@ -24,3 +24,6 @@
/conf/local/local.json
/conf/local/ENVIRONMENT
/conf/local/VERSION
# Impact Font
/resources/font/impact.ttf

View file

@ -798,13 +798,11 @@ return array(
// behalf, silencing the warning.
'phabricator.timezone' => null,
// When unhandled exceptions occur, stack traces are hidden by default.
// You can enable traces for development to make it easier to debug problems.
'phabricator.show-stack-traces' => false,
// Shows an error callout if a page generated PHP errors, warnings or notices.
// This makes it harder to miss problems while developing Phabricator.
'phabricator.show-error-callout' => false,
// Show stack traces when unhandled exceptions occur, force reloading of
// static resources (skipping the cache), show an error callout if a page
// generated PHP errors, warnings, or notices, force disk reads when
// reloading. This option should not be enabled in production.
'phabricator.developer-mode' => false,
// When users write comments which have URIs, they'll be automatically linked
// if the protocol appears in this set. This whitelist is primarily to prevent
@ -1220,15 +1218,6 @@ return array(
// unlikely that you need to modify this.
'celerity.resource-hash' => 'd9455ea150622ee044f7931dabfa52aa',
// In a development environment, it is desirable to force static resources
// (CSS and JS) to be read from disk on every request, so that edits to them
// appear when you reload the page even if you haven't updated the resource
// maps. This setting ensures requests will be verified against the state on
// disk. Generally, you should leave this off in production (caching behavior
// and performance improve with it off) but turn it on in development. (These
// settings are the defaults.)
'celerity.force-disk-reads' => false,
// Minify static resources by removing whitespace and comments. You should
// enable this in production, but disable it in development.
'celerity.minify' => false,

View file

@ -2,10 +2,7 @@
return array(
'phabricator.developer-mode' => true,
'darkconsole.enabled' => true,
'celerity.force-disk-reads' => true,
'phabricator.show-stack-traces' => true,
'phabricator.show-error-callout' => true,
'celerity.minify' => false,
) + phabricator_read_config_file('default');

View file

@ -134,16 +134,17 @@ JX.install('Event', {
return r.which == 3 || r.button == 2;
},
/**
* Determine if a click event is a normal click (left mouse button, no
* Determine if a mouse event is a normal event (left mouse button, no
* modifier keys).
*
* @return bool
* @task info
*/
isNormalClick : function() {
if (this.getType() != 'click') {
isNormalMouseEvent : function() {
var supportedEvents = ['click','mouseup','mousedown'];
if (supportedEvents.indexOf(this.getType()) == -1) {
return false;
}
@ -164,6 +165,22 @@ JX.install('Event', {
},
/**
* Determine if a click event is a normal click (left mouse button, no
* modifier keys).
*
* @return bool
* @task info
*/
isNormalClick : function() {
if (this.getType() != 'click') {
return false;
}
return this.isNormalMouseEvent();
},
/**
* Get the node corresponding to the specified key in this event's node map.
* This is a simple helper method that makes the API for accessing nodes

View file

@ -978,6 +978,7 @@ phutil_register_library_map(array(
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
'PhabricatorMenuItemView' => 'view/layout/PhabricatorMenuItemView.php',
'PhabricatorMenuView' => 'view/layout/PhabricatorMenuView.php',
'PhabricatorMenuViewTestCase' => 'view/layout/__tests__/PhabricatorMenuViewTestCase.php',
'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php',
'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
@ -2395,6 +2396,7 @@ phutil_register_library_map(array(
'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
'PhabricatorMenuItemView' => 'AphrontTagView',
'PhabricatorMenuView' => 'AphrontTagView',
'PhabricatorMenuViewTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorMetaMTAController' => 'PhabricatorController',
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',

View file

@ -228,6 +228,26 @@ final class AphrontRequest {
$more_info = "(This was a web request, {$token_info}.)";
}
// Give a more detailed explanation of how to avoid the exception
// in developer mode.
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$more_info = $more_info .
"To avoid this error, use phabricator_form() to construct forms. " .
"If you are already using phabricator_form(), make sure the form " .
"'action' uses a relative URI (i.e., begins with a '/'). Forms " .
"using absolute URIs do not include CSRF tokens, to prevent " .
"leaking tokens to external sites.\n\n" .
"If this page performs writes which do not require CSRF " .
"protection (usually, filling caches or logging), you can use " .
"AphrontWriteGuard::beginScopedUnguardedWrites() to temporarily " .
"bypass CSRF protection while writing. You should use this only " .
"for writes which can not be protected with normal CSRF " .
"mechanisms.\n\n" .
"Some UI elements (like PhabricatorActionListView) also have " .
"methods which will allow you to render links as forms (like " .
"setRenderAsForm(true)).";
}
// This should only be able to happen if you load a form, pull your
// internet for 6 hours, and then reconnect and immediately submit,
// but give the user some indication of what happened since the workflow

View file

@ -238,7 +238,7 @@ class AphrontDefaultApplicationConfiguration
"schema is up to date.";
}
if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) {
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$trace = $this->renderStackTrace($ex->getTrace(), $user);
} else {
$trace = null;

View file

@ -22,7 +22,6 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication {
$item->setIcon('power');
$item->setWorkflow(true);
$item->setHref('/logout/');
$item->setSortOrder(2.0);
$item->setSelected(($controller instanceof PhabricatorLogoutController));
$items[] = $item;
}

View file

@ -70,9 +70,10 @@ final class PhabricatorOAuthFailureView extends AphrontView {
$provider_key = $provider->getProviderKey();
$diagnose = hsprintf(
'<a href="/oauth/'.$provider_key.'/diagnose/" class="button green">'.
'<a href="/oauth/%s/diagnose/" class="button green">'.
'Diagnose %s OAuth Problems'.
'</a>',
$provider_key,
$provider_name);
}

View file

@ -66,7 +66,9 @@ abstract class PhabricatorApplication {
$uninstalled =
PhabricatorEnv::getEnvConfig('phabricator.uninstalled-applications');
if (isset($uninstalled[get_class($this)])) {
if (!$this->canUninstall()) {
return true;
} else if (isset($uninstalled[get_class($this)])) {
return false;
} else {
return true;
@ -227,8 +229,21 @@ abstract class PhabricatorApplication {
/* -( Application Management )--------------------------------------------- */
public static function getAllApplications() {
public static function getByClass($class_name) {
$selected = null;
$applications = PhabricatorApplication::getAllApplications();
foreach ($applications as $application) {
if (get_class($application) == $class_name) {
$selected = $application;
break;
}
}
return $selected;
}
public static function getAllApplications() {
$classes = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
@ -241,6 +256,13 @@ abstract class PhabricatorApplication {
$apps[] = $app;
}
// Reorder the applications into "application order". Notably, this ensures
// their event handlers register in application order.
$apps = msort($apps, 'getApplicationOrder');
$apps = mgroup($apps, 'getApplicationGroup');
$apps = array_select_keys($apps, self::getApplicationGroups()) + $apps;
$apps = array_mergev($apps);
return $apps;
}
@ -254,31 +276,24 @@ abstract class PhabricatorApplication {
PhabricatorEnv::getEnvConfig('phabricator.uninstalled-applications');
if (empty($applications)) {
$classes = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
->selectAndLoadSymbols();
$all_applications = self::getAllApplications();
$apps = array();
foreach ($classes as $class) {
if (isset($uninstalled[$class['name']])) {
continue;
}
$app = newv($class['name'], array());
if (!$app->isEnabled()) {
foreach ($all_applications as $app) {
$class = get_class($app);
if (isset($uninstalled[$class])) {
continue;
}
}
if (!$show_beta && $app->isBeta()) {
if (!$app->isEnabled()) {
continue;
}
}
$apps[] = $app;
if (!$show_beta && $app->isBeta()) {
continue;
}
$apps[] = $app;
}
$applications = $apps;

View file

@ -86,48 +86,18 @@ final class PhabricatorDeveloperConfigOptions
"data to look at eventually). In development, it may be useful to ".
"set it to 1 in order to debug performance problems.\n\n".
"NOTE: You must install XHProf for profiling to work.")),
$this->newOption('phabricator.show-stack-traces', 'bool', false)
$this->newOption('phabricator.developer-mode', 'bool', false)
->setBoolOptions(
array(
pht('Show stack traces'),
pht('Hide stack traces'),
pht('Enable developer mode'),
pht('Disable developer mode'),
))
->setSummary(pht("Show stack traces when unhandled exceptions occur."))
->setDescription(
pht(
"When unhandled exceptions occur, stack traces are hidden by ".
"default. You can enable traces for development to make it easier ".
"to debug problems.")),
$this->newOption('phabricator.show-error-callout', 'bool', false)
->setBoolOptions(
array(
pht('Show error callout'),
pht('Hide error callout'),
))
->setSummary(pht("Show error callout."))
->setDescription(
pht(
"Shows an error callout if a page generated PHP errors, warnings ".
"or notices. This makes it harder to miss problems while ".
"developing Phabricator. A callout is simply a red error at the ".
"top of the page.")),
$this->newOption('celerity.force-disk-reads', 'bool', false)
->setBoolOptions(
array(
pht('Force disk reads'),
pht("Don't force disk reads"),
))
->setSummary(pht("Force Celerity to read from disk on every request."))
->setDescription(
pht(
"In a development environment, it is desirable to force static ".
"resources (CSS and JS) to be read from disk on every request, so ".
"that edits to them appear when you reload the page even if you ".
"haven't updated the resource maps. This setting ensures requests ".
"will be verified against the state on disk. Generally, you ".
"should leave this off in production (caching behavior and ".
"performance improve with it off) but turn it on in development. ".
"(These settings are the defaults.)")),
->setSummary(pht("Enable verbose error reporting and disk reads."))
->setDescription(
pht(
"This option enables verbose error reporting (stack traces, ".
"error callouts) and forces disk reads of static assets on ".
"every reload.")),
$this->newOption('celerity.minify', 'bool', false)
->setBoolOptions(
array(

View file

@ -10,6 +10,15 @@ final class PhabricatorConfigResponse extends AphrontHTMLResponse {
}
public function buildResponseString() {
// Check to make sure we aren't requesting this via ajax or conduit
if (isset($_REQUEST['__ajax__']) || isset($_REQUEST['__conduit__'])) {
// We don't want to flood the console with html, just return a simple
// message for now.
return pht(
"This install has a fatal setup error, access the internet web ".
"version to view details and resolve it.");
}
$resources = $this->buildResources();
$view = $this->view->render();

View file

@ -44,6 +44,7 @@ final class ConpherenceUpdateController extends
'ip' => $request->getRemoteAddr()
));
$editor = id(new ConpherenceEditor())
->setContinueOnNoEffect($request->isContinueRequest())
->setContentSource($content_source)
->setActor($user);
@ -55,28 +56,6 @@ final class ConpherenceUpdateController extends
$conpherence,
$message
);
$time = time();
$conpherence->openTransaction();
$xactions = $editor->applyTransactions($conpherence, $xactions);
$last_xaction = end($xactions);
$xaction_phid = $last_xaction->getPHID();
$behind = ConpherenceParticipationStatus::BEHIND;
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
$participants = $conpherence->getParticipants();
foreach ($participants as $phid => $participant) {
if ($phid != $user->getPHID()) {
if ($participant->getParticipationStatus() != $behind) {
$participant->setBehindTransactionPHID($xaction_phid);
}
$participant->setParticipationStatus($behind);
$participant->setDateTouched($time);
} else {
$participant->setParticipationStatus($up_to_date);
$participant->setDateTouched($time);
}
$participant->save();
}
$updated = $conpherence->saveTransaction();
break;
case 'metadata':
$xactions = array();
@ -112,23 +91,25 @@ final class ConpherenceUpdateController extends
->setTransactionType(ConpherenceTransactionType::TYPE_TITLE)
->setNewValue($title);
}
if ($xactions) {
$conpherence->openTransaction();
$xactions = $editor
->setContinueOnNoEffect(true)
->applyTransactions($conpherence, $xactions);
$updated = $conpherence->saveTransaction();
} else if (empty($errors)) {
$errors[] = pht(
'That was a non-update. Try cancel.'
);
}
break;
default:
throw new Exception('Unknown action: '.$action);
break;
}
if ($xactions) {
try {
$xactions = $editor->applyTransactions($conpherence, $xactions);
$updated = true;
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($this->getApplicationURI($conpherence_id.'/'))
->setException($ex);
}
} else if (empty($errors)) {
$errors[] = pht(
'That was a non-update. Try cancel.'
);
}
}
if ($updated) {

View file

@ -128,6 +128,7 @@ final class ConpherenceViewController extends
$form =
id(new AphrontFormView())
->setWorkflow(true)
->setAction($this->getApplicationURI('update/'.$conpherence->getID().'/'))
->setFlexible(true)
->setUser($user)

View file

@ -118,6 +118,27 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
);
}
$editor->save();
// fallthrough
case PhabricatorTransactions::TYPE_COMMENT:
$xaction_phid = $xaction->getPHID();
$behind = ConpherenceParticipationStatus::BEHIND;
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
$participants = $object->getParticipants();
$user = $this->getActor();
$time = time();
foreach ($participants as $phid => $participant) {
if ($phid != $user->getPHID()) {
if ($participant->getParticipationStatus() != $behind) {
$participant->setBehindTransactionPHID($xaction_phid);
}
$participant->setParticipationStatus($behind);
$participant->setDateTouched($time);
} else {
$participant->setParticipationStatus($up_to_date);
$participant->setDateTouched($time);
}
$participant->save();
}
break;
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
foreach ($xaction->getNewValue() as $participant) {

View file

@ -363,7 +363,7 @@ final class DifferentialRevisionListController extends DifferentialController {
array_select_keys($handles, $params['participants']),
'getFullName');
return id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/allmailable/')
->setDatasource('/typeahead/common/accounts/')
->setLabel($label)
->setName('participants')
->setValue($value);

View file

@ -182,10 +182,9 @@ final class DifferentialRevisionViewController extends DifferentialController {
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->appendChild(
pht(
'This diff is very large and affects %2$s files. Load each file '.
'This diff is very large and affects %s files. Load each file '.
'individually.',
$count,
PhutilTranslator::getInstance()->formatNumber($count)).
new PhutilNumber($count)).
" <strong>".
phutil_tag(
'a',
@ -499,7 +498,6 @@ final class DifferentialRevisionViewController extends DifferentialController {
'href' => "/differential/subscribe/{$action}/{$revision_id}/",
'name' => $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'),
'instant' => true,
'sigil' => 'workflow',
);
} else {
$links[] = array(

View file

@ -188,6 +188,8 @@ final class DifferentialDiff extends DifferentialDAO {
'id' => $this->getID(),
'parent' => $this->getParentRevisionID(),
'revisionID' => $this->getRevisionID(),
'dateCreated' => $this->getDateCreated(),
'dateModified' => $this->getDateModified(),
'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(),
'sourceControlPath' => $this->getSourceControlPath(),
'sourceControlSystem' => $this->getSourceControlSystem(),

View file

@ -229,7 +229,7 @@ final class DifferentialChangesetListView extends AphrontView {
$template =
'<table><tr>'.
'<th></th><td>%s</td>'.
'<th></th><td colspan="2">%s</td>'.
'<th></th><td colspan="3">%s</td>'.
'</tr></table>';
return array(

View file

@ -52,6 +52,7 @@ final class DifferentialRevisionDetailView extends AphrontView {
->setName($action['name'])
->setHref(idx($action, 'href'))
->setWorkflow(idx($action, 'sigil') == 'workflow')
->setRenderAsForm(!empty($action['instant']))
->setUser($user)
->setDisabled(idx($action, 'disabled', false));
$actions->addAction($obj);

View file

@ -814,9 +814,9 @@ final class DiffusionBrowseFileController extends DiffusionController {
$size = strlen($data);
$properties->addTextContent(
pht('This is a binary file. It is %2$s byte(s) in length.',
$size,
PhutilTranslator::getInstance()->formatNumber($size)));
pht(
'This is a binary file. It is %s byte(s) in length.',
new PhutilNumber($size)));
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())

View file

@ -116,10 +116,18 @@ final class DiffusionCommitController extends DiffusionController {
$content[] = $this->buildAuditTable($commit, $audit_requests);
$content[] = $this->buildComments($commit);
$hard_limit = 1000;
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
$changes = array_slice($changes, 0, $hard_limit);
}
$content[] = $this->buildMergesTable($commit);
$owners_paths = array();
@ -151,7 +159,6 @@ final class DiffusionCommitController extends DiffusionController {
'r'.$callsign.$commit->getCommitIdentifier());
}
$pane_id = null;
if ($bad_commit) {
$error_panel = new AphrontErrorView();
$error_panel->setTitle('Bad Commit');
@ -175,11 +182,20 @@ final class DiffusionCommitController extends DiffusionController {
"This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths).");
$content[] = $no_changes;
} else if ($was_limited) {
$huge_commit = new AphrontErrorView();
$huge_commit->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$huge_commit->setTitle(pht('Enormous Commit'));
$huge_commit->appendChild(
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
$content[] = $huge_commit;
} else {
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT) {
$show_all_button = phutil_tag(
'a',
@ -296,28 +312,11 @@ final class DiffusionCommitController extends DiffusionController {
}
$change_table->setRenderingReferences($change_references);
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
$pane_id = celerity_generate_unique_node_id();
$add_comment_view = $this->renderAddCommentPanel($commit,
$audit_requests,
$pane_id);
$main_pane = phutil_render_tag(
'div',
array(
'id' => $pane_id
),
$change_list->render().
id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render().
$add_comment_view);
$content[] = $main_pane;
$content[] = $change_list->render();
}
$content[] = $this->renderAddCommentPanel($commit, $audit_requests);
$commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
$short_name = DiffusionView::nameCommit(
$repository,
@ -577,13 +576,13 @@ final class DiffusionCommitController extends DiffusionController {
private function renderAddCommentPanel(
PhabricatorRepositoryCommit $commit,
array $audit_requests,
$pane_id = null) {
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
@ -693,14 +692,26 @@ final class DiffusionCommitController extends DiffusionController {
</div>
</div>';
return
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
return phutil_render_tag(
'div',
array(
'id' => $pane_id,
),
phutil_render_tag(
'div',
array(
'class' => 'differential-add-comment-panel',
),
id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render().
$panel->render().
$preview_panel);
$preview_panel));
}
/**

View file

@ -3,6 +3,16 @@
final class DiffusionPathChangeQuery {
private $request;
private $limit;
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function getLimit() {
return $this->limit;
}
final private function __construct() {
// <private>
@ -31,20 +41,36 @@ final class DiffusionPathChangeQuery {
$commit = $drequest->loadCommit();
$conn_r = $repository->establishConnection('r');
$limit = '';
if ($this->limit) {
$limit = qsprintf(
$conn_r,
'LIMIT %d',
$this->limit + 1);
}
$raw_changes = queryfx_all(
$repository->establishConnection('r'),
$conn_r,
'SELECT c.*, p.path pathName, t.path targetPathName,
i.commitIdentifier targetCommitIdentifier
FROM %T c
LEFT JOIN %T p ON c.pathID = p.id
LEFT JOIN %T t ON c.targetPathID = t.id
LEFT JOIN %T i ON c.targetCommitID = i.id
WHERE c.commitID = %d AND isDirect = 1',
WHERE c.commitID = %d AND isDirect = 1 %Q',
PhabricatorRepository::TABLE_PATHCHANGE,
PhabricatorRepository::TABLE_PATH,
PhabricatorRepository::TABLE_PATH,
$commit->getTableName(),
$commit->getID());
$commit->getID(),
$limit);
$limited = $this->limit && (count($raw_changes) > $this->limit);
if ($limited) {
$raw_changes = array_slice($raw_changes, 0, $this->limit);
}
$changes = array();
@ -68,20 +94,22 @@ final class DiffusionPathChangeQuery {
$changes[$id] = $change;
}
// Deduce the away paths by examining all the changes.
// Deduce the away paths by examining all the changes, if we loaded them
// all.
$away = array();
foreach ($changes as $change) {
if ($change->getTargetPath()) {
$away[$change->getTargetPath()][] = $change->getPath();
if (!$limited) {
$away = array();
foreach ($changes as $change) {
if ($change->getTargetPath()) {
$away[$change->getTargetPath()][] = $change->getPath();
}
}
foreach ($changes as $change) {
if (isset($away[$change->getPath()])) {
$change->setAwayPaths($away[$change->getPath()]);
}
}
}
foreach ($changes as $change) {
if (isset($away[$change->getPath()])) {
$change->setAwayPaths($away[$change->getPath()]);
}
}
return $changes;
}

View file

@ -95,22 +95,21 @@ final class DiffusionBrowseTableView extends DiffusionView {
$conn = $drequest->getRepository()->establishConnection('r');
$where = '';
$path = '/'.$drequest->getPath();
$where = (substr($path, -1) == '/'
? qsprintf($conn, 'AND path LIKE %>', $path)
: qsprintf($conn, 'AND path = %s', $path));
if ($drequest->getLint()) {
$where = qsprintf(
$conn,
'AND code = %s',
$drequest->getLint());
$where .= qsprintf($conn, ' AND code = %s', $drequest->getLint());
}
$like = (substr($drequest->getPath(), -1) == '/' ? 'LIKE %>' : '= %s');
return head(queryfx_one(
$conn,
'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q AND path '.$like,
'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q',
PhabricatorRepository::TABLE_LINTMESSAGE,
$branch->getID(),
$where,
'/'.$drequest->getPath()));
$where));
}
public function render() {

View file

@ -65,8 +65,6 @@ abstract class PhabricatorDirectoryController extends PhabricatorController {
continue;
}
$tile_group = msort($tile_group, 'getApplicationOrder');
$is_small_tiles = ($tile_display == PhabricatorApplication::TILE_SHOW) ||
($tile_display == PhabricatorApplication::TILE_HIDE);

View file

@ -44,7 +44,6 @@ final class PhabricatorApplicationDiviner extends PhabricatorApplication {
$item->setName(pht('%s Help', $application->getName()));
$item->setIcon('help');
$item->setHref($application->getHelpURI());
$item->setSortOrder(0.1);
$items[] = $item;
}

View file

@ -116,7 +116,7 @@ final class PhabricatorImageTransformer {
return $dst;
}
private function generatePreview(PhabricatorFile $file, $size) {
public static function getPreviewDimensions(PhabricatorFile $file, $size) {
$data = $file->loadFileData();
$src = imagecreatefromstring($data);
@ -128,13 +128,35 @@ final class PhabricatorImageTransformer {
$dx = max($size / 4, $scale * $x);
$dy = max($size / 4, $scale * $y);
$sdx = $scale * $x;
$sdy = $scale * $y;
return array(
'x' => $x,
'y' => $y,
'dx' => $dx,
'dy' => $dy,
'sdx' => $sdx,
'sdy' => $sdy
);
}
private function generatePreview(PhabricatorFile $file, $size) {
$data = $file->loadFileData();
$src = imagecreatefromstring($data);
$dimensions = self::getPreviewDimensions($file, $size);
$x = $dimensions['x'];
$y = $dimensions['y'];
$dx = $dimensions['dx'];
$dy = $dimensions['dy'];
$sdx = $dimensions['sdx'];
$sdy = $dimensions['sdy'];
$dst = imagecreatetruecolor($dx, $dy);
imagesavealpha($dst, true);
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
$sdx = $scale * $x;
$sdy = $scale * $y;
imagecopyresampled(
$dst,
$src,
@ -153,10 +175,14 @@ final class PhabricatorImageTransformer {
$data = $file->loadFileData();
$img = imagecreatefromstring($data);
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
$font_path = $phabricator_root.'/resources/font/tuffy.ttf';
$white = imagecolorallocate($img, 255, 255, 255);
$black = imagecolorallocate($img, 0, 0, 0);
$border_width = 3;
$font_root = $phabricator_root.'/resources/font/';
$font_path = $font_root.'tuffy.ttf';
if (Filesystem::pathExists($font_root.'impact.ttf')) {
$font_path = $font_root.'impact.ttf';
}
$text_color = imagecolorallocate($img, 255, 255, 255);
$border_color = imagecolorallocatealpha($img, 0, 0, 0, 110);
$border_width = 4;
$font_max = 200;
$font_min = 5;
for ($i = $font_max; $i > $font_min; $i--) {
@ -172,8 +198,8 @@ final class PhabricatorImageTransformer {
$i,
$x,
$y,
$white,
$black,
$text_color,
$border_color,
$border_width,
$font_path,
$upper_text);
@ -191,8 +217,8 @@ final class PhabricatorImageTransformer {
$i,
$x,
$y,
$white,
$black,
$text_color,
$border_color,
$border_width,
$font_path,
$lower_text);

View file

@ -39,10 +39,10 @@ final class PhabricatorMacroEditController
if (!strlen($macro->getName())) {
$errors[] = pht('Macro name is required.');
$e_name = pht('Required');
} else if (!preg_match('/^[a-z0-9_-]{3,}$/', $macro->getName())) {
$errors[] = pht('Macro must be at least three characters long and '.
'contain only lowercase letters, digits, hyphen and '.
'underscore.');
} else if (!preg_match('/^[a-z0-9:_-]{3,}$/', $macro->getName())) {
$errors[] = pht(
'Macro must be at least three characters long and contain only '.
'lowercase letters, digits, hyphens, colons and underscores.');
$e_name = pht('Invalid');
} else {
$e_name = null;

View file

@ -22,8 +22,8 @@ final class PhabricatorApplicationApplications extends PhabricatorApplication {
return "\xE0\xBC\x84";
}
public function shouldAppearInLaunchView() {
return false;
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function getRoutes() {

View file

@ -13,15 +13,7 @@ final class PhabricatorApplicationDetailViewController
$request = $this->getRequest();
$user = $request->getUser();
$selected = null;
$applications = PhabricatorApplication::getAllApplications();
foreach ($applications as $application) {
if (get_class($application) == $this->application) {
$selected = $application;
break;
}
}
$selected = PhabricatorApplication::getByClass($this->application);
if (!$selected) {
return new Aphront404Response();
@ -35,36 +27,53 @@ final class PhabricatorApplicationDetailViewController
->setName(pht('Applications'))
->setHref($this->getApplicationURI()));
$properties = $this->buildPropertyView($selected);
$actions = $this->buildActionView($user, $selected);
$header = id(new PhabricatorHeaderView())
->setHeader($title);
return $this->buildApplicationPage(
array(
$crumbs,
id(new PhabricatorHeaderView())->setHeader($title),
$actions,
$properties,
),
array(
'title' => $title,
'device' => true,
));
$status_tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE);
if ($selected->isInstalled()) {
$status_tag->setName(pht('Installed'));
$status_tag->setBackgroundColor(PhabricatorTagView::COLOR_GREEN);
} else {
$status_tag->setName(pht('Uninstalled'));
$status_tag->setBackgroundColor(PhabricatorTagView::COLOR_RED);
}
if ($selected->isBeta()) {
$beta_tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setName(pht('Beta'))
->setBackgroundColor(PhabricatorTagView::COLOR_GREY);
$header->addTag($beta_tag);
}
$header->addTag($status_tag);
$properties = $this->buildPropertyView($selected);
$actions = $this->buildActionView($user, $selected);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
),
array(
'title' => $title,
'device' => true,
));
}
private function buildPropertyView(PhabricatorApplication $selected) {
$properties = new PhabricatorPropertyListView();
if ($selected->isInstalled()) {
$properties->addProperty(
pht('Status'), pht('Installed'));
} else {
$properties->addProperty(
pht('Status'), pht('Uninstalled'));
}
$properties->addProperty(
pht('Description'), $selected->getShortDescription());
$properties = id(new PhabricatorPropertyListView())
->addProperty(
pht('Description'), $selected->getShortDescription()
);
return $properties;
}
@ -72,30 +81,41 @@ final class PhabricatorApplicationDetailViewController
private function buildActionView(
PhabricatorUser $user, PhabricatorApplication $selected) {
$view = id(new PhabricatorActionListView())
->setUser($user);
if ($selected->canUninstall()) {
if ($selected->isInstalled()) {
return id(new PhabricatorActionListView())
->setUser($user)
->addAction(
id(new PhabricatorActionView())
->setName(pht('Uninstall'))
->setIcon('delete')
->setHref(
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Uninstall'))
->setIcon('delete')
->setWorkflow(true)
->setHref(
$this->getApplicationURI(get_class($selected).'/uninstall/'))
);
);
} else {
return id(new PhabricatorActionListView())
->setUser($user)
->addAction(
id(new PhabricatorActionView())
->setName(pht('Install'))
->setIcon('new')
->setHref(
$this->getApplicationURI(get_class($selected).'/install/'))
);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Install'))
->setIcon('new')
->setWorkflow(true)
->setHref(
$this->getApplicationURI(get_class($selected).'/install/'))
);
}
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Uninstall'))
->setIcon('delete')
->setWorkflow(true)
->setDisabled(true)
->setHref(
$this->getApplicationURI(get_class($selected).'/uninstall/'))
);
}
return $view;
}
}

View file

@ -14,30 +14,51 @@ final class PhabricatorApplicationUninstallController
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$app_name = substr($this->application, strlen('PhabricatorApplication'));
$selected = PhabricatorApplication::getByClass($this->application);
if (!$selected) {
return new Aphront404Response();
}
$view_uri = $this->getApplicationURI('view/'.$this->application);
if ($request->isDialogFormPost()) {
$this->manageApplication();
return id(new AphrontRedirectResponse())->setURI('/applications/');
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
$dialog = id(new AphrontDialogView())
->setUser($user)
->addCancelButton($view_uri);
if ($this->action == 'install') {
if ($selected->canUninstall()) {
$dialog->setTitle('Confirmation')
->appendChild(
'Install '. $selected->getName(). ' application ?'
)
->addSubmitButton('Install');
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle('Confirmation')
->appendChild('Install '. $app_name. ' application ?')
->addSubmitButton('Install')
->addCancelButton('/applications/view/'.$this->application);
} else {
$dialog->setTitle('Information')
->appendChild('You cannot install a installed application.');
}
} else {
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle('Confirmation')
->appendChild('Really Uninstall '. $app_name. ' application ?')
->addSubmitButton('Uninstall')
->addCancelButton('/applications/view/'.$this->application);
if ($selected->canUninstall()) {
$dialog->setTitle('Confirmation')
->appendChild(
'Really Uninstall '. $selected->getName(). ' application ?'
)
->addSubmitButton('Uninstall');
} else {
$dialog->setTitle('Information')
->appendChild(
'This application cannot be uninstalled,
because it is required for Phabricator to work.'
);
}
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}

View file

@ -47,12 +47,24 @@ final class PhabricatorApplicationsListController
private function buildInstalledApplicationsList(array $applications) {
$list = new PhabricatorObjectItemListView();
$applications = msort($applications, 'getName');
foreach ($applications as $application) {
$item = id(new PhabricatorObjectItemView())
->setHeader($application->getName())
->setHref('/applications/view/'.get_class($application).'/')
->addAttribute($application->getShortDescription());
if (!$application->isInstalled()) {
$item->addIcon('delete', pht('Uninstalled'));
}
if ($application->isBeta()) {
$item->addIcon('lint-warning', pht('Beta'));
}
$list->addItem($item);
}
return $list;
}

View file

@ -95,9 +95,8 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
$line_count = count(explode("\n", $paste->getContent()));
$line_count = pht(
'%2$s Line(s)',
$line_count,
PhutilTranslator::getInstance()->formatNumber($line_count));
'%s Line(s)',
new PhutilNumber($line_count));
$item = id(new PhabricatorObjectItemView())
->setHeader($paste->getFullName())

View file

@ -58,7 +58,6 @@ final class PhabricatorApplicationPeople extends PhabricatorApplication {
$item = new PhabricatorMenuItemView();
$item->setName($user->getUsername());
$item->setHref('/p/'.$user->getUsername().'/');
$item->setSortOrder(0.0);
$item->addClass('phabricator-core-menu-item-profile');
$classes = array(

View file

@ -13,19 +13,37 @@ final class PholioMockImagesView extends AphrontView {
throw new Exception("Call setMock() before render()!");
}
$mockview = array();
$file = head($this->mock->getImages())->getFile();
$main_image_id = celerity_generate_unique_node_id();
require_celerity_resource('javelin-behavior-pholio-mock-view');
$config = array('mainID' => $main_image_id);
Javelin::initBehavior('pholio-mock-view', $config);
$main_image_tag = phutil_tag(
$mockview = "";
$main_image = head($this->mock->getImages());
$main_image_tag = javelin_tag(
'img',
array(
'src' => $file->getBestURI(),
'class' => 'pholio-mock-image',
'id' => $main_image_id,
));
'src' => $main_image->getFile()->getBestURI(),
'sigil' => 'mock-image',
'class' => 'pholio-mock-image',
'meta' => array(
'fullSizeURI' => $main_image->getFile()->getBestURI(),
'imageID' => $main_image->getID(),
),
));
$main_image_tag = javelin_tag(
'div',
array(
'id' => 'mock-wrapper',
'sigil' => 'mock-wrapper',
'class' => 'pholio-mock-wrapper'
),
$main_image_tag
);
$mockview[] = phutil_tag(
'div',
@ -35,10 +53,6 @@ final class PholioMockImagesView extends AphrontView {
$main_image_tag);
if (count($this->mock->getImages()) > 1) {
require_celerity_resource('javelin-behavior-pholio-mock-view');
$config = array('mainID' => $main_image_id);
Javelin::initBehavior('pholio-mock-view', $config);
$thumbnails = array();
foreach ($this->mock->getImages() as $image) {
$thumbfile = $image->getFile();
@ -51,7 +65,7 @@ final class PholioMockImagesView extends AphrontView {
'class' => 'pholio-mock-carousel-thumbnail',
'meta' => array(
'fullSizeURI' => $thumbfile->getBestURI(),
'imageID' => $image->getID(),
'imageID' => $image->getID()
),
));
$thumbnails[] = $tag;
@ -67,5 +81,4 @@ final class PholioMockImagesView extends AphrontView {
return $this->renderHTMLView($mockview);
}
}

View file

@ -266,11 +266,14 @@ final class PhabricatorRepositorySvnCommitChangeParserWorker
if (empty($raw_paths[$full_from]) &&
empty($effects[$full_from])) {
if ($other_type == DifferentialChangeType::TYPE_COPY_AWAY) {
// Add an indirect effect for the copied file, if we
// don't already have an entry for it (e.g., a separate
// change).
$effects[$full_from] = array(
'rawPath' => $full_from,
'rawTargetPath' => null,
'rawTargetCommit' => null,
'rawDirect' => true,
'rawDirect' => false,
'changeType' => $other_type,
'fileType' => $from_file_type,

View file

@ -161,7 +161,8 @@ final class PhabricatorSearchEngineMySQL extends PhabricatorSearchEngine {
if (strlen($q)) {
$join[] = qsprintf(
$conn_r,
"{$t_field} field ON field.phid = document.phid");
'%T field ON field.phid = document.phid',
$t_field);
$where[] = qsprintf(
$conn_r,
'MATCH(corpus) AGAINST (%s IN BOOLEAN MODE)',

View file

@ -44,7 +44,6 @@ final class PhabricatorApplicationSettings extends PhabricatorApplication {
$item->setIcon('settings');
$item->setSelected($selected);
$item->setHref('/settings/');
$item->setSortOrder(0.90);
$items[] = $item;
}

View file

@ -7,7 +7,7 @@ arguments.
= Overview =
Phabricator uses a lightweight markup language called "Remarkup", similar to
other lightweight markup langauges like Markdown and Wiki markup.
other lightweight markup languages like Markdown and Wiki markup.
This document describes how to format text using Remarkup.

View file

@ -35,7 +35,9 @@ final class CelerityPhabricatorResourceController
protected function buildResourceTransformer() {
$xformer = new CelerityResourceTransformer();
$xformer->setMinify(PhabricatorEnv::getEnvConfig('celerity.minify'));
$xformer->setMinify(
!PhabricatorEnv::getEnvConfig('phabricator.developer-mode') &&
PhabricatorEnv::getEnvConfig('celerity.minify'));
$xformer->setCelerityMap(CelerityResourceMap::getInstance());
return $xformer;
}

View file

@ -35,7 +35,7 @@ abstract class CelerityResourceController extends PhabricatorController {
}
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
!PhabricatorEnv::getEnvConfig('celerity.force-disk-reads')) {
!PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
// Return a "304 Not Modified". We don't care about the value of this
// field since we never change what resource is served by a given URI.
return $this->makeResponseCacheable(new Aphront304Response());

View file

@ -74,7 +74,7 @@ final class CelerityResourceTransformer {
$bin = $root.'/externals/javelin/support/jsxmin/jsxmin';
if (@file_exists($bin)) {
$future = new ExecFuture("{$bin} __DEV__:0");
$future = new ExecFuture('%s __DEV__:0', $bin);
$future->write($data);
list($err, $result) = $future->resolve();
if (!$err) {

View file

@ -167,9 +167,9 @@ abstract class PhabricatorBaseEnglishTranslation
'%d Lint Messages',
),
'This is a binary file. It is %2$s byte(s) in length.' => array(
'This is a binary file. It is %2$s byte in length.',
'This is a binary file. It is %2$s bytes in length.',
'This is a binary file. It is %s byte(s) in length.' => array(
'This is a binary file. It is %s byte in length.',
'This is a binary file. It is %s bytes in length.',
),
'%d Action(s) Have No Effect' => array(
@ -226,9 +226,9 @@ abstract class PhabricatorBaseEnglishTranslation
),
),
'%2$s Line(s)' => array(
'%2$s Line',
'%2$s Lines',
'%s Line(s)' => array(
'%s Line',
'%s Lines',
),
"Indexing %d object(s) of type %s." => array(

View file

@ -1,5 +1,8 @@
<?php
/**
* @deprecated Use javelin_tag().
*/
function javelin_render_tag(
$tag,
array $attributes = array(),

View file

@ -187,9 +187,7 @@ final class PhabricatorJavelinLinter extends ArcanistLinter {
}
private function newSymbolsFuture($path) {
$javelinsymbols = 'javelinsymbols';
$future = new ExecFuture($javelinsymbols.' # '.escapeshellarg($path));
$future = new ExecFuture('javelinsymbols # %s', $path);
$future->write($this->getData($path));
return $future;
}

View file

@ -68,7 +68,10 @@ final class PhabricatorRemarkupRuleEmbedFile
case 'thumb':
default:
$attrs['src'] = $file->getPreview220URI();
$attrs['width'] = '220';
$dimensions =
PhabricatorImageTransformer::getPreviewDimensions($file, 220);
$attrs['width'] = $dimensions['sdx'];
$attrs['height'] = $dimensions['sdy'];
$options['image_class'] = 'phabricator-remarkup-embed-image';
break;
}

View file

@ -10,7 +10,7 @@ final class PhabricatorRemarkupRuleImageMacro
public function apply($text) {
return preg_replace_callback(
'@^([a-zA-Z0-9_\-]+)$@m',
'@^([a-zA-Z0-9:_\-]+)$@m',
array($this, 'markupImageMacro'),
$text);
}

View file

@ -121,7 +121,10 @@ final class AphrontSideNavFilterView extends AphrontView {
}
public function addCustomBlock($block) {
$this->menu->appendChild($block);
$this->menu->addMenuItem(
id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_CUSTOM)
->appendChild($block));
return $this;
}

View file

@ -6,13 +6,13 @@ final class PhabricatorMenuItemView extends AphrontTagView {
const TYPE_SPACER = 'type-spacer';
const TYPE_LABEL = 'type-label';
const TYPE_BUTTON = 'type-button';
const TYPE_CUSTOM = 'type-custom';
private $name;
private $href;
private $type = self::TYPE_LINK;
private $isExternal;
private $key;
private $sortOrder = 1.0;
private $icon;
private $selected;
@ -88,15 +88,6 @@ final class PhabricatorMenuItemView extends AphrontTagView {
return $this->isExternal;
}
public function setSortOrder($order) {
$this->sortOrder = $order;
return $this;
}
public function getSortOrder() {
return $this->sortOrder;
}
protected function getTagName() {
return $this->href ? 'a' : 'div';
}

View file

@ -4,6 +4,10 @@ final class PhabricatorMenuView extends AphrontTagView {
private $items = array();
protected function canAppendChild() {
return false;
}
public function newLabel($name) {
$item = id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_LABEL)
@ -37,13 +41,83 @@ final class PhabricatorMenuView extends AphrontTagView {
}
public function addMenuItem(PhabricatorMenuItemView $item) {
$key = $item->getKey();
$this->items[] = $item;
$this->appendChild($item);
return $this->addMenuItemAfter(null, $item);
}
public function addMenuItemAfter($key, PhabricatorMenuItemView $item) {
if ($key === null) {
$this->items[] = $item;
return $this;
}
if (!$this->getItem($key)) {
throw new Exception("No such key '{$key}' to add menu item after!");
}
$result = array();
foreach ($this->items as $other) {
$result[] = $other;
if ($other->getKey() == $key) {
$result[] = $item;
}
}
$this->items = $result;
return $this;
}
public function addMenuItemBefore($key, PhabricatorMenuItemView $item) {
if ($key === null) {
array_unshift($this->items, $item);
return $this;
}
$this->requireKey($key);
$result = array();
foreach ($this->items as $other) {
if ($other->getKey() == $key) {
$result[] = $item;
}
$result[] = $other;
}
$this->items = $result;
return $this;
}
public function addMenuItemToLabel($key, PhabricatorMenuItemView $item) {
$this->requireKey($key);
$other = $this->getItem($key);
if ($other->getType() != PhabricatorMenuItemView::TYPE_LABEL) {
throw new Exception("Menu item '{$key}' is not a label!");
}
$seen = false;
$after = null;
foreach ($this->items as $other) {
if (!$seen) {
if ($other->getKey() == $key) {
$seen = true;
}
} else {
if ($other->getType() == PhabricatorMenuItemView::TYPE_LABEL) {
break;
}
}
$after = $other->getKey();
}
return $this->addMenuItemAfter($after, $item);
}
private function requireKey($key) {
if (!$this->getItem($key)) {
throw new Exception("No menu item with key '{$key}' exists!");
}
}
public function getItem($key) {
$key = (string)$key;
@ -83,4 +157,8 @@ final class PhabricatorMenuView extends AphrontTagView {
'class' => 'phabricator-menu-view',
);
}
protected function getTagContent() {
return $this->renderSingleView($this->items);
}
}

View file

@ -0,0 +1,141 @@
<?php
final class PhabricatorMenuViewTestCase extends PhabricatorTestCase {
public function testAppend() {
$menu = $this->newABCMenu();
$this->assertMenuKeys(
array(
'a',
'b',
'c',
),
$menu);
}
public function testAppendAfter() {
$menu = $this->newABCMenu();
$caught = null;
try {
$menu->addMenuItemAfter('x', $this->newLink('test1'));
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertEqual(true, $caught instanceof Exception);
$menu->addMenuItemAfter('a', $this->newLink('test2'));
$menu->addMenuItemAfter(null, $this->newLink('test3'));
$menu->addMenuItemAfter('a', $this->newLink('test4'));
$menu->addMenuItemAfter('test3', $this->newLink('test5'));
$this->assertMenuKeys(
array(
'a',
'test4',
'test2',
'b',
'c',
'test3',
'test5',
),
$menu);
}
public function testAppendBefore() {
$menu = $this->newABCMenu();
$caught = null;
try {
$menu->addMenuItemBefore('x', $this->newLink('test1'));
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertEqual(true, $caught instanceof Exception);
$menu->addMenuItemBefore('b', $this->newLink('test2'));
$menu->addMenuItemBefore(null, $this->newLink('test3'));
$menu->addMenuItemBefore('a', $this->newLink('test4'));
$menu->addMenuItemBefore('test3', $this->newLink('test5'));
$this->assertMenuKeys(
array(
'test5',
'test3',
'test4',
'a',
'test2',
'b',
'c',
),
$menu);
}
public function testAppendLabel() {
$menu = new PhabricatorMenuView();
$menu->addMenuItem($this->newLabel('fruit'));
$menu->addMenuItem($this->newLabel('animals'));
$caught = null;
try {
$menu->addMenuItemToLabel('x', $this->newLink('test1'));
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertEqual(true, $caught instanceof Exception);
$menu->addMenuItemToLabel('fruit', $this->newLink('apple'));
$menu->addMenuItemToLabel('fruit', $this->newLink('banana'));
$menu->addMenuItemToLabel('animals', $this->newLink('dog'));
$menu->addMenuItemToLabel('animals', $this->newLink('cat'));
$menu->addMenuItemToLabel('fruit', $this->newLink('cherry'));
$this->assertMenuKeys(
array(
'fruit',
'apple',
'banana',
'cherry',
'animals',
'dog',
'cat',
),
$menu);
}
private function newLink($key) {
return id(new PhabricatorMenuItemView())
->setKey($key)
->setHref('#')
->setName('Link');
}
private function newLabel($key) {
return id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setKey($key)
->setName('Label');
}
private function newABCMenu() {
$menu = new PhabricatorMenuView();
$menu->addMenuItem($this->newLink('a'));
$menu->addMenuItem($this->newLink('b'));
$menu->addMenuItem($this->newLink('c'));
return $menu;
}
private function assertMenuKeys(array $expect, PhabricatorMenuView $menu) {
$items = $menu->getItems();
$keys = mpull($items, 'getKey');
$keys = array_values($keys);
$this->assertEqual($expect, $keys);
}
}

View file

@ -83,7 +83,11 @@ class PhabricatorBarePageView extends AphrontPageView {
'<script type="text/javascript">'.
$framebust.
'window.__DEV__=1;'.
'window.__DEV__='.
(PhabricatorEnv::getEnvConfig('phabricator.developer-mode')
? '1'
: '0').
';'.
'</script>',
$response->renderResourcesOfType('css'),

View file

@ -256,7 +256,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
}
$developer_warning = null;
if (PhabricatorEnv::getEnvConfig('phabricator.show-error-callout') &&
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') &&
DarkConsoleErrorLogPluginAPI::getErrors()) {
$developer_warning = phutil_tag(
'div',

View file

@ -5,7 +5,6 @@ final class PhabricatorMainMenuIconView extends AphrontView {
private $classes = array();
private $href;
private $name;
private $sortOrder = 0.5;
private $workflow;
private $style;
@ -42,22 +41,6 @@ final class PhabricatorMainMenuIconView extends AphrontView {
return $this;
}
/**
* Provide a float, where 0.0 is the profile item and 1.0 is the logout
* item. Normally you should pick something between the two.
*
* @param float Sort order.
* @return this
*/
public function setSortOrder($sort_order) {
$this->sortOrder = $sort_order;
return $this;
}
public function getSortOrder() {
return $this->sortOrder;
}
public function render() {
$name = $this->getName();
$href = $this->getHref();

View file

@ -164,7 +164,6 @@ final class PhabricatorMainMenuView extends AphrontView {
$controller = $this->getController();
$applications = PhabricatorApplication::getAllInstalledApplications();
$applications = msort($applications, 'getName');
$core = array();
$more = array();
@ -184,7 +183,7 @@ final class PhabricatorMainMenuView extends AphrontView {
if ($application->getApplicationGroup() == $group_core) {
$core[] = $item;
} else {
$more[] = $item;
$more[$application->getName()] = $item;
}
}
@ -200,7 +199,9 @@ final class PhabricatorMainMenuView extends AphrontView {
$view->addClass('phabricator-core-menu');
$search = $this->renderSearch();
$view->appendChild($search);
if ($search) {
$view->addMenuItem($search);
}
$view
->newLabel(pht('Home'))
@ -235,7 +236,6 @@ final class PhabricatorMainMenuView extends AphrontView {
}
if ($actions) {
$actions = msort($actions, 'getSortOrder');
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
@ -260,6 +260,7 @@ final class PhabricatorMainMenuView extends AphrontView {
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('More Applications')));
ksort($more);
foreach ($more as $item) {
$item->addClass('phabricator-core-item-device');
$view->addMenuItem($item);

View file

@ -18,6 +18,19 @@
}
.pholio-mock-image {
margin: 10px 0px;
display: inline-block;
}
.pholio-mock-select {
border: 1px solid #FF0000;
position: absolute;
}
.pholio-mock-wrapper {
position: relative;
display: inline-block;
cursor: crosshair;
padding: 0px;
margin: 10px 0px;
}

View file

@ -24,6 +24,10 @@
}
.phabricator-header-tags {
margin-left: 1em;
margin-left: 12px;
font-size: 13px;
}
.phabricator-header-tags .phabricator-tag-view {
margin-left: 4px;
}

View file

@ -6,13 +6,55 @@
* javelin-dom
* javelin-stratcom
* javelin-behavior-device
* javelin-vector
* phabricator-tooltip
*/
JX.behavior('differential-populate', function(config) {
function onresponse(target, response) {
JX.DOM.replace(JX.$(target), JX.$H(response.changeset));
function onresponse(target_id, response) {
// As we populate the diff, we try to hold the document scroll position
// steady, so that, e.g., users who want to leave a comment on a diff with a
// large number of changes don't constantly have the text area scrolled off
// the bottom of the screen until the entire diff loads.
//
// There are two three major cases here:
//
// - If we're near the top of the document, never scroll.
// - If we're near the bottom of the document, always scroll.
// - Otherwise, scroll if the changes were above the midline of the
// viewport.
var target = JX.$(target_id);
var old_pos = JX.Vector.getScroll();
var old_view = JX.Vector.getViewport();
var old_dim = JX.Vector.getDocument();
// Number of pixels away from the top or bottom of the document which
// count as "nearby".
var sticky = 480;
var near_top = (old_pos.y <= sticky);
var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky));
var target_pos = JX.Vector.getPos(target);
var target_dim = JX.Vector.getDim(target);
var target_mid = (target_pos.y + (target_dim.y / 2));
var view_mid = (old_pos.y + (old_view.y / 2));
var above_mid = (target_mid < view_mid);
JX.DOM.replace(target, JX.$H(response.changeset));
if (!near_top) {
if (near_bot || above_mid) {
// Figure out how much taller the document got.
var delta = (JX.Vector.getDocument().y - old_dim.y);
window.scrollTo(old_pos.x, old_pos.y + delta);
}
}
if (response.coverage) {
for (var k in response.coverage) {
try {

View file

@ -2,8 +2,19 @@
* @provides javelin-behavior-pholio-mock-view
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* javelin-vector
* javelin-event
*/
JX.behavior('pholio-mock-view', function(config) {
var is_dragging = false;
var wrapper = JX.$('mock-wrapper');
var image;
var imageData;
var startPos;
var endPos;
var selection;
JX.Stratcom.listen(
'click', // Listen for clicks...
'mock-thumbnail', // ...on nodes with sigil "mock-thumbnail".
@ -11,7 +22,106 @@ JX.behavior('pholio-mock-view', function(config) {
var data = e.getNodeData('mock-thumbnail');
var main = JX.$(config.mainID);
JX.Stratcom.addData(
main,
{
fullSizeURI: data['fullSizeURI'],
imageID: data['imageID']
});
main.src = data.fullSizeURI;
JX.DOM.setContent(wrapper,main);
});
function draw_rectangle(node, current, init) {
JX.$V(
Math.abs(current.x-init.x),
Math.abs(current.y-init.y))
.setDim(node);
JX.$V(
(current.x-init.x < 0) ? current.x:init.x,
(current.y-init.y < 0) ? current.y:init.y)
.setPos(node);
}
function getRealXY(parent, point) {
var pos = {x: (point.x - parent.x), y: (point.y - parent.y)};
if (pos.x < 0) pos.x = 0;
else if (pos.x > image.clientWidth) pos.x = image.clientWidth - 1;
if (pos.y < 0) pos.y = 0;
else if (pos.y > image.clientHeight) pos.y = image.clientHeight - 2;
return pos;
}
JX.Stratcom.listen('mousedown', 'mock-wrapper', function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
image = JX.$(config.mainID);
imageData = JX.Stratcom.getData(image);
e.getRawEvent().target.draggable = false;
is_dragging = true;
startPos = getRealXY(JX.$V(wrapper),JX.$V(e));
selection = JX.$N(
'div',
{className: 'pholio-mock-select'}
);
JX.$V(startPos.x,startPos.y).setPos(selection);
JX.DOM.appendContent(wrapper, selection);
});
JX.enableDispatch(document.body, 'mousemove');
JX.Stratcom.listen('mousemove',null, function(e) {
if (!is_dragging) {
return;
}
draw_rectangle(selection, getRealXY(JX.$V(wrapper), JX.$V(e)), startPos);
});
JX.Stratcom.listen(
'mouseup',
null,
function(e) {
if (!is_dragging) {
return;
}
is_dragging = false;
endPos = getRealXY(JX.$V(wrapper), JX.$V(e));
comment = window.prompt("Add your comment");
if (comment == null || comment == "") {
selection.remove();
return;
}
selection.title = comment;
console.log("ImageID: " + imageData['imageID'] +
", coords: (" + Math.min(startPos.x, endPos.x) + "," +
Math.min(startPos.y, endPos.y) + ") -> (" +
Math.max(startPos.x,endPos.x) + "," + Math.max(startPos.y,endPos.y) +
"), comment: " + comment);
});
});