1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-10 23:01:04 +01:00

(stable) Promote 2018 Week 19

This commit is contained in:
epriestley 2018-05-14 10:00:59 -07:00
commit 121a18de8c
22 changed files with 451 additions and 46 deletions

View file

@ -9,8 +9,8 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => 'cb8ae4dc',
'core.pkg.js' => 'e1f0f7bd',
'core.pkg.css' => '8be474cc',
'core.pkg.js' => 'e452721e',
'differential.pkg.css' => '06dc617c',
'differential.pkg.js' => 'c2ca903a',
'diffusion.pkg.css' => 'a2d17c7d',
@ -144,7 +144,7 @@ return array(
'rsrc/css/phui/phui-cms.css' => '504b4b23',
'rsrc/css/phui/phui-comment-form.css' => 'ac68149f',
'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad',
'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb',
'rsrc/css/phui/phui-crumbs-view.css' => '10728aaa',
'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026',
'rsrc/css/phui/phui-document-pro.css' => '8af7ea27',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
@ -259,7 +259,7 @@ return array(
'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783',
'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '8d3bc1b2',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'dfaf006b',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '185bbd53',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
@ -710,7 +710,7 @@ return array(
'javelin-scrollbar' => '9065f639',
'javelin-sound' => '949c0fe5',
'javelin-stratcom' => '327f418a',
'javelin-tokenizer' => '8d3bc1b2',
'javelin-tokenizer' => 'dfaf006b',
'javelin-typeahead' => '70baed2f',
'javelin-typeahead-composite-source' => '503e17fd',
'javelin-typeahead-normalizer' => '185bbd53',
@ -810,7 +810,7 @@ return array(
'phui-cms-css' => '504b4b23',
'phui-comment-form-css' => 'ac68149f',
'phui-comment-panel-css' => 'f50152ad',
'phui-crumbs-view-css' => '6ece3bbb',
'phui-crumbs-view-css' => '10728aaa',
'phui-curtain-view-css' => '2bdaf026',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '878c2f52',
@ -1573,12 +1573,6 @@ return array(
'javelin-stratcom',
'javelin-behavior',
),
'8d3bc1b2' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
),
'8d4a8c72' => array(
'javelin-install',
'javelin-dom',
@ -2025,6 +2019,12 @@ return array(
'phuix-icon-view',
'phabricator-prefab',
),
'dfaf006b' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
),
'e1d25dfb' => array(
'javelin-behavior',
'javelin-stratcom',

View file

@ -816,6 +816,7 @@ phutil_register_library_map(array(
'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php',
'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
@ -2525,6 +2526,7 @@ phutil_register_library_map(array(
'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php',
'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php',
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
'PhabricatorCheckboxesEditField' => 'applications/transactions/editfield/PhabricatorCheckboxesEditField.php',
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php',
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
@ -2881,6 +2883,7 @@ phutil_register_library_map(array(
'PhabricatorDocumentRef' => 'applications/files/document/PhabricatorDocumentRef.php',
'PhabricatorDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorDocumentRenderingEngine.php',
'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php',
'PhabricatorDoubleExportField' => 'infrastructure/export/field/PhabricatorDoubleExportField.php',
'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php',
'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php',
'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
@ -6147,6 +6150,7 @@ phutil_register_library_map(array(
'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
@ -8140,6 +8144,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
),
'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCheckboxesEditField' => 'PhabricatorEditField',
'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorClassConfigType' => 'PhabricatorTextConfigType',
'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -8538,6 +8543,7 @@ phutil_register_library_map(array(
'PhabricatorDocumentRef' => 'Phobject',
'PhabricatorDocumentRenderingEngine' => 'Phobject',
'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication',
'PhabricatorDoubleExportField' => 'PhabricatorExportField',
'PhabricatorDraft' => 'PhabricatorDraftDAO',
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorDraftEngine' => 'Phobject',

View file

@ -53,6 +53,10 @@ final class PhabricatorCommitSearchEngine
$query->withUnreachable($map['unreachable']);
}
if ($map['ancestorsOf']) {
$query->withAncestorsOf($map['ancestorsOf']);
}
return $query;
}
@ -103,6 +107,13 @@ final class PhabricatorCommitSearchEngine
pht(
'Find or exclude unreachable commits which are not ancestors of '.
'any branch, tag, or ref.')),
id(new PhabricatorSearchStringListField())
->setLabel(pht('Ancestors Of'))
->setKey('ancestorsOf')
->setDescription(
pht(
'Find commits which are ancestors of a particular ref, '.
'like "master".')),
);
}

View file

@ -182,25 +182,31 @@ final class DifferentialRevisionViewController
if ($large_warning) {
$count = $this->getChangesetCount();
$warning = new PHUIInfoView();
$warning->setTitle(pht('Large Diff'));
$warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
$warning->appendChild(hsprintf(
'%s <strong>%s</strong>',
$expand_uri = $request_uri
->alter('large', 'true')
->setFragment('toc');
$message = array(
pht(
'This diff is large and affects %s files. '.
'You may load each file individually or ',
'This large diff affects %s files. Files without inline '.
'comments have been collapsed.',
new PhutilNumber($count)),
' ',
phutil_tag(
'strong',
array(),
phutil_tag(
'a',
array(
'class' => 'button button-grey',
'href' => $request_uri
->alter('large', 'true')
->setFragment('toc'),
'href' => $expand_uri,
),
pht('Show All Files Inline'))));
$warning = $warning->render();
pht('Expand All Files'))),
);
$warning = id(new PHUIInfoView())
->setTitle(pht('Large Diff'))
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->appendChild($message);
$old = array_select_keys($changesets, $old_ids);
$new = array_select_keys($changesets, $new_ids);

View file

@ -682,8 +682,13 @@ final class DifferentialTransactionEditor
if ($config_inline || $config_attach) {
$body_limit = PhabricatorEnv::getEnvConfig('metamta.email-body-limit');
$patch = $this->buildPatchForMail($diff);
if ($config_inline) {
try {
$patch = $this->buildPatchForMail($diff, $body_limit);
} catch (ArcanistDiffByteSizeException $ex) {
$patch = null;
}
if (($patch !== null) && $config_inline) {
$lines = substr_count($patch, "\n");
$bytes = strlen($patch);
@ -706,7 +711,7 @@ final class DifferentialTransactionEditor
}
}
if ($config_attach) {
if (($patch !== null) && $config_attach) {
// See T12033, T11767, and PHI55. This is a crude fix to stop the
// major concrete problems that lackluster email size limits cause.
if (strlen($patch) < $body_limit) {
@ -1411,13 +1416,14 @@ final class DifferentialTransactionEditor
array('style' => 'font-family: monospace;'), $patch);
}
private function buildPatchForMail(DifferentialDiff $diff) {
private function buildPatchForMail(DifferentialDiff $diff, $byte_limit) {
$format = PhabricatorEnv::getEnvConfig('metamta.differential.patch-format');
return id(new DifferentialRawDiffRenderer())
->setViewer($this->getActor())
->setFormat($format)
->setChangesets($diff->getChangesets())
->setByteLimit($byte_limit)
->buildPatch();
}

View file

@ -5,6 +5,7 @@ final class DifferentialRawDiffRenderer extends Phobject {
private $changesets;
private $format = 'unified';
private $viewer;
private $byteLimit;
public function setFormat($format) {
$this->format = $format;
@ -35,6 +36,15 @@ final class DifferentialRawDiffRenderer extends Phobject {
return $this->viewer;
}
public function setByteLimit($byte_limit) {
$this->byteLimit = $byte_limit;
return $this;
}
public function getByteLimit() {
return $this->byteLimit;
}
public function buildPatch() {
$diff = new DifferentialDiff();
$diff->attachChangesets($this->getChangesets());
@ -52,15 +62,18 @@ final class DifferentialRawDiffRenderer extends Phobject {
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setLoadFileDataCallback(array($loader, 'loadFileData'));
$byte_limit = $this->getByteLimit();
if ($byte_limit) {
$bundle->setByteLimit($byte_limit);
}
$format = $this->getFormat();
switch ($format) {
case 'git':
return $bundle->toGitPatch();
break;
case 'unified':
default:
return $bundle->toUnifiedDiff();
break;
}
}
}

View file

@ -0,0 +1,51 @@
<?php
final class DiffusionInternalAncestorsConduitAPIMethod
extends DiffusionQueryConduitAPIMethod {
public function isInternalAPI() {
return true;
}
public function getAPIMethodName() {
return 'diffusion.internal.ancestors';
}
public function getMethodDescription() {
return pht('Internal method for filtering ref ancestors.');
}
protected function defineReturnType() {
return 'list<string>';
}
protected function defineCustomParamTypes() {
return array(
'ref' => 'required string',
'commits' => 'required list<string>',
);
}
protected function getResult(ConduitAPIRequest $request) {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$commits = $request->getValue('commits');
$ref = $request->getValue('ref');
$graph = new PhabricatorGitGraphStream($repository, $ref);
$keep = array();
foreach ($commits as $identifier) {
try {
$graph->getCommitDate($identifier);
$keep[] = $identifier;
} catch (Exception $ex) {
// Not an ancestor.
}
}
return $keep;
}
}

View file

@ -22,10 +22,14 @@ final class DiffusionCommitQuery
private $epochMin;
private $epochMax;
private $importing;
private $ancestorsOf;
private $needCommitData;
private $needDrafts;
private $mustFilterRefs = false;
private $refRepository;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
@ -92,7 +96,7 @@ final class DiffusionCommitQuery
}
public function withRepositoryIDs(array $repository_ids) {
$this->repositoryIDs = $repository_ids;
$this->repositoryIDs = array_unique($repository_ids);
return $this;
}
@ -152,6 +156,11 @@ final class DiffusionCommitQuery
return $this;
}
public function withAncestorsOf(array $refs) {
$this->ancestorsOf = $refs;
return $this;
}
public function getIdentifierMap() {
if ($this->identifierMap === null) {
throw new Exception(
@ -307,6 +316,54 @@ final class DiffusionCommitQuery
protected function didFilterPage(array $commits) {
$viewer = $this->getViewer();
if ($this->mustFilterRefs) {
// If this flag is set, the query has an "Ancestors Of" constraint and
// at least one of the constraining refs had too many ancestors for us
// to apply the constraint with a big "commitIdentifier IN (%Ls)" clause.
// We're going to filter each page and hope we get a full result set
// before the query overheats.
$ancestor_list = mpull($commits, 'getCommitIdentifier');
$ancestor_list = array_values($ancestor_list);
foreach ($this->ancestorsOf as $ref) {
try {
$ancestor_list = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
DiffusionRequest::newFromDictionary(
array(
'repository' => $this->refRepository,
'user' => $viewer,
)),
'diffusion.internal.ancestors',
array(
'ref' => $ref,
'commits' => $ancestor_list,
));
} catch (ConduitClientException $ex) {
throw new PhabricatorSearchConstraintException(
$ex->getMessage());
}
if (!$ancestor_list) {
break;
}
}
$ancestor_list = array_fuse($ancestor_list);
foreach ($commits as $key => $commit) {
$identifier = $commit->getCommitIdentifier();
if (!isset($ancestor_list[$identifier])) {
$this->didRejectResult($commit);
unset($commits[$key]);
}
}
if (!$commits) {
return $commits;
}
}
if ($this->needCommitData) {
$data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID in (%Ld)',
@ -364,6 +421,95 @@ final class DiffusionCommitQuery
$this->withRepositoryIDs($repository_ids);
}
if ($this->ancestorsOf !== null) {
if (count($this->repositoryIDs) !== 1) {
throw new PhabricatorSearchConstraintException(
pht(
'To search for commits which are ancestors of particular refs, '.
'you must constrain the search to exactly one repository.'));
}
$repository_id = head($this->repositoryIDs);
$history_limit = $this->getRawResultLimit() * 32;
$viewer = $this->getViewer();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIDs(array($repository_id))
->executeOne();
if (!$repository) {
throw new PhabricatorEmptyQueryException();
}
if ($repository->isSVN()) {
throw new PhabricatorSearchConstraintException(
pht(
'Subversion does not support searching for ancestors of '.
'a particular ref. This operation is not meaningful in '.
'Subversion.'));
}
if ($repository->isHg()) {
throw new PhabricatorSearchConstraintException(
pht(
'Mercurial does not currently support searching for ancestors of '.
'a particular ref.'));
}
$can_constrain = true;
$history_identifiers = array();
foreach ($this->ancestorsOf as $key => $ref) {
try {
$raw_history = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
DiffusionRequest::newFromDictionary(
array(
'repository' => $repository,
'user' => $viewer,
)),
'diffusion.historyquery',
array(
'commit' => $ref,
'limit' => $history_limit,
));
} catch (ConduitClientException $ex) {
throw new PhabricatorSearchConstraintException(
$ex->getMessage());
}
$ref_identifiers = array();
foreach ($raw_history['pathChanges'] as $change) {
$ref_identifiers[] = $change['commitIdentifier'];
}
// If this ref had fewer total commits than the limit, we're safe to
// apply the constraint as a large `IN (...)` query for a list of
// commit identifiers. This is efficient.
if ($history_limit) {
if (count($ref_identifiers) >= $history_limit) {
$can_constrain = false;
break;
}
}
$history_identifiers += array_fuse($ref_identifiers);
}
// If all refs had a small number of ancestors, we can just put the
// constraint into the query here and we're done. Otherwise, we need
// to filter each page after it comes out of the MySQL layer.
if ($can_constrain) {
$where[] = qsprintf(
$conn,
'commit.commitIdentifier IN (%Ls)',
$history_identifiers);
} else {
$this->mustFilterRefs = true;
$this->refRepository = $repository;
}
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,

View file

@ -516,7 +516,7 @@ final class ManiphestTaskSearchEngine
);
if (ManiphestTaskPoints::getIsEnabled()) {
$fields[] = id(new PhabricatorIntExportField())
$fields[] = id(new PhabricatorDoubleExportField())
->setKey('points')
->setLabel('Points');
}

View file

@ -162,13 +162,17 @@ EOTEXT
->setIsConduitOnly(true)
->setValue($object->getStatus())
->setOptions($object->getStatusNameMap()),
id(new PhabricatorStringListEditField())
id(new PhabricatorCheckboxesEditField())
->setKey('ignored')
->setLabel(pht('Ignored Attributes'))
->setDescription(pht('Ignore paths with any of these attributes.'))
->setTransactionType(
PhabricatorOwnersPackageIgnoredTransaction::TRANSACTIONTYPE)
->setValue(array_keys($object->getIgnoredPathAttributes())),
->setValue(array_keys($object->getIgnoredPathAttributes()))
->setOptions(
array(
'generated' => pht('Ignore generated files (review only).'),
)),
id(new PhabricatorConduitEditField())
->setKey('paths.set')
->setLabel(pht('Paths'))

View file

@ -64,9 +64,12 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod {
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
if (strlen($language)) {
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setTransactionType(
PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setNewValue($language);
}
$editor = id(new PhabricatorPasteEditor())
->setActor($viewer)

View file

@ -38,4 +38,20 @@ final class PhabricatorPasteLanguageTransaction
$this->renderLanguageValue($this->getNewValue()));
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
if ($new !== null && !strlen($new)) {
$errors[] = $this->newInvalidError(
pht('Paste language must be null or a nonempty string.'),
$xaction);
}
}
return $errors;
}
}

View file

@ -364,7 +364,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
if (!strlen($name)) {
$name = $this->getName();
$name = phutil_utf8_strtolower($name);
$name = preg_replace('@[/ -:<>]+@', '-', $name);
$name = preg_replace('@[ -/:->]+@', '-', $name);
$name = trim($name, '-');
if (!strlen($name)) {
$name = $this->getCallsign();

View file

@ -0,0 +1,36 @@
<?php
final class PhabricatorCheckboxesEditField
extends PhabricatorEditField {
private $options;
protected function newControl() {
$options = $this->getOptions();
return id(new AphrontFormCheckboxControl())
->setOptions($options);
}
protected function newConduitParameterType() {
return new ConduitStringListParameterType();
}
protected function newHTTPParameterType() {
return new AphrontStringListHTTPParameterType();
}
public function setOptions(array $options) {
$this->options = $options;
return $this;
}
public function getOptions() {
if ($this->options === null) {
throw new PhutilInvalidStateException('setOptions');
}
return $this->options;
}
}

View file

@ -302,6 +302,14 @@ abstract class PhabricatorEditField extends Phobject {
}
public function getPreviewPanel() {
if ($this->getIsHidden()) {
return null;
}
if ($this->getIsLocked()) {
return null;
}
return $this->previewPanel;
}

View file

@ -132,6 +132,21 @@ behavior. If you want more powerful auditing behavior, you can use Herald to
write more sophisticated rules.
Ignored Attributes
==================
You can automatically exclude certain types of files, like generated files,
with **Ignored Attributes**.
When a package is marked as ignoring files with a particular attribute, and
a file in a particular change has that attribute, the file will be ignored when
computing ownership.
(This feature is currently rough, only works for Differential revisions, and
may not always compute the correct set of owning packages in some complex
cases where it interacts with dominion rules.)
Files in Multiple Packages
==========================

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorDoubleExportField
extends PhabricatorExportField {
public function getNaturalValue($value) {
if ($value === null) {
return $value;
}
return (double)$value;
}
/**
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function formatPHPExcelCell($cell, $style) {
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_NUMERIC);
}
public function getCharacterWidth() {
return 8;
}
}

View file

@ -155,8 +155,12 @@ EOHELP
return $this->sheet;
}
/**
* @phutil-external-symbol class PHPExcel_Cell
*/
private function getCellName($col, $row = null) {
$col_name = chr(ord('A') + $col);
$col_name = PHPExcel_Cell::stringFromColumnIndex($col);
if ($row === null) {
return $col_name;

View file

@ -34,6 +34,20 @@ final class AphrontFormCheckboxControl extends AphrontFormControl {
return 'aphront-form-control-checkbox';
}
public function setOptions(array $options) {
$boxes = array();
foreach ($options as $key => $value) {
$boxes[] = array(
'value' => $key,
'label' => $value,
);
}
$this->boxes = $boxes;
return $this;
}
protected function renderInput() {
$rows = array();
foreach ($this->boxes as $box) {
@ -41,14 +55,28 @@ final class AphrontFormCheckboxControl extends AphrontFormControl {
if ($id === null) {
$id = celerity_generate_unique_node_id();
}
$name = idx($box, 'name');
if ($name === null) {
$name = $this->getName().'[]';
}
$value = $box['value'];
if (array_key_exists('checked', $box)) {
$checked = $box['checked'];
} else {
$checked = in_array($value, $this->getValue());
}
$checkbox = phutil_tag(
'input',
array(
'id' => $id,
'type' => 'checkbox',
'name' => $box['name'],
'name' => $name,
'value' => $box['value'],
'checked' => $box['checked'] ? 'checked' : null,
'checked' => $checked ? 'checked' : null,
'disabled' => $this->getDisabled() ? 'disabled' : null,
));
$label = phutil_tag(

View file

@ -8,6 +8,7 @@ final class PHUICrumbView extends AphrontView {
private $isLastCrumb;
private $workflow;
private $aural;
private $alwaysVisible;
public function setAural($aural) {
$this->aural = $aural;
@ -18,6 +19,22 @@ final class PHUICrumbView extends AphrontView {
return $this->aural;
}
/**
* Make this crumb always visible, even on devices where it would normally
* be hidden.
*
* @param bool True to make the crumb always visible.
* @return this
*/
public function setAlwaysVisible($always_visible) {
$this->alwaysVisible = $always_visible;
return $this;
}
public function getAlwaysVisible() {
return $this->alwaysVisible;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
@ -98,6 +115,10 @@ final class PHUICrumbView extends AphrontView {
$classes[] = 'phabricator-last-crumb';
}
if ($this->getAlwaysVisible()) {
$classes[] = 'phui-crumb-always-visible';
}
$tag = javelin_tag(
$this->href ? 'a' : 'span',
array(

View file

@ -55,6 +55,8 @@
}
.device-phone .phui-crumb-view.phabricator-last-crumb .phui-crumb-name,
.device-phone .phui-crumb-view.phui-crumb-always-visible .phui-crumb-name,
.device-phone .phui-crumb-view.phui-crumb-always-visible + .phui-crumb-divider,
.device-phone .phui-crumb-view.phui-crumb-has-icon,
.device-phone .phui-crumb-has-icon + .phui-crumb-divider {
display: inline-block;

View file

@ -395,8 +395,12 @@ JX.install('Tokenizer', {
break;
case 'delete':
if (!this._focus.value.length) {
// In unusual cases, it's possible for us to end up with a token
// that has the empty string ("") as a value. Support removal of
// this unusual token.
var tok;
while ((tok = this._tokens.pop())) {
while ((tok = this._tokens.pop()) !== null) {
if (this._remove(tok, true)) {
break;
}