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

(stable) Promote 2016 Week 28

This commit is contained in:
epriestley 2016-07-09 04:36:27 -07:00
commit 156f719645
85 changed files with 1124 additions and 304 deletions

1
.gitignore vendored
View file

@ -4,6 +4,7 @@
# Diviner
/docs/
/.divinercache/
/src/.cache/
# libphutil
/src/.phutil_module_cache

View file

@ -7,8 +7,8 @@
*/
return array(
'names' => array(
'core.pkg.css' => '55d9bb83',
'core.pkg.js' => 'f2139810',
'core.pkg.css' => '4e7e9bde',
'core.pkg.js' => '1bcca0f3',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '3e81ae60',
'differential.pkg.js' => '634399e9',
@ -81,7 +81,7 @@ return array(
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/paste/paste.css' => '1898e534',
'rsrc/css/application/people/people-profile.css' => '2473d929',
'rsrc/css/application/phame/phame.css' => 'bf6a743f',
'rsrc/css/application/phame/phame.css' => '8efb0729',
'rsrc/css/application/pholio/pholio-edit.css' => '07676f51',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
@ -155,7 +155,7 @@ return array(
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => 'd5263e49',
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
'rsrc/css/phui/phui-timeline-view.css' => 'c3782437',
'rsrc/css/phui/phui-timeline-view.css' => 'bc523970',
'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
@ -163,7 +163,7 @@ return array(
'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373',
'rsrc/css/sprite-login.css' => '60e8560e',
'rsrc/css/sprite-menu.css' => '9dd65b92',
'rsrc/css/sprite-tokens.css' => '72b952bd',
'rsrc/css/sprite-tokens.css' => '9cdfd599',
'rsrc/css/syntax/syntax-default.css' => '9923583c',
'rsrc/externals/d3/d3.min.js' => 'a11a5ff2',
'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7',
@ -344,8 +344,8 @@ return array(
'rsrc/image/sprite-login.png' => '03d5af29',
'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5',
'rsrc/image/sprite-menu.png' => 'd7a99faa',
'rsrc/image/sprite-tokens-X2.png' => 'e991bb40',
'rsrc/image/sprite-tokens.png' => 'fe69d6ab',
'rsrc/image/sprite-tokens-X2.png' => '804a5232',
'rsrc/image/sprite-tokens.png' => 'b41d03da',
'rsrc/image/texture/card-gradient.png' => '815f26e8',
'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8',
'rsrc/image/texture/dark-menu.png' => '7e22296e',
@ -463,7 +463,7 @@ return array(
'rsrc/js/core/FileUpload.js' => '680ea2c8',
'rsrc/js/core/Hovercard.js' => '1bd28176',
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10',
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
'rsrc/js/core/Notification.js' => 'ccf1cbf8',
'rsrc/js/core/Prefab.js' => 'cfd23f37',
@ -491,7 +491,7 @@ return array(
'rsrc/js/core/behavior-history-install.js' => '7ee2b591',
'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64',
'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '7835f8c9',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7',
'rsrc/js/core/behavior-line-linker.js' => '1499a8cb',
'rsrc/js/core/behavior-more.js' => 'a80d0378',
@ -523,7 +523,7 @@ return array(
'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '6d86ce8b',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da',
'rsrc/js/phuix/PHUIXFormControl.js' => 'e15869a8',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
@ -654,7 +654,7 @@ return array(
'javelin-behavior-phabricator-gesture' => '3ab51e2c',
'javelin-behavior-phabricator-gesture-example' => '558829c2',
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
'javelin-behavior-phabricator-keyboard-shortcuts' => '7835f8c9',
'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0',
'javelin-behavior-phabricator-line-linker' => '1499a8cb',
'javelin-behavior-phabricator-nav' => '56a1ca03',
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
@ -780,7 +780,7 @@ return array(
'phabricator-filetree-view-css' => 'fccf9f82',
'phabricator-flag-css' => '5337623f',
'phabricator-keyboard-shortcut' => '1ae869f2',
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
'phabricator-keyboard-shortcut-manager' => '4a021c10',
'phabricator-main-menu-view' => 'b623169f',
'phabricator-nav-view-css' => 'ac79a758',
'phabricator-notification' => 'ccf1cbf8',
@ -811,7 +811,7 @@ return array(
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '5b6fcf3f',
'phame-css' => 'bf6a743f',
'phame-css' => '8efb0729',
'pholio-css' => 'ca89d380',
'pholio-edit-css' => '07676f51',
'pholio-inline-comments-css' => '8e545e49',
@ -863,7 +863,7 @@ return array(
'phui-status-list-view-css' => 'd5263e49',
'phui-tag-view-css' => '6bbd83e2',
'phui-theme-css' => '027ba77e',
'phui-timeline-view-css' => 'c3782437',
'phui-timeline-view-css' => 'bc523970',
'phui-two-column-view-css' => '9fb86c85',
'phui-workboard-color-css' => 'ac6fe6a7',
'phui-workboard-view-css' => 'e6d89647',
@ -871,7 +871,7 @@ return array(
'phui-workpanel-view-css' => '92197373',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '8cf6d262',
'phuix-autocomplete' => '9196fb06',
'phuix-autocomplete' => '6d86ce8b',
'phuix-dropdown-menu' => '82e270da',
'phuix-form-control-view' => 'e15869a8',
'phuix-icon-view' => 'bff6884b',
@ -888,7 +888,7 @@ return array(
'setup-issue-css' => 'db7e9c40',
'sprite-login-css' => '60e8560e',
'sprite-menu-css' => '9dd65b92',
'sprite-tokens-css' => '72b952bd',
'sprite-tokens-css' => '9cdfd599',
'syntax-default-css' => '9923583c',
'syntax-highlighting-css' => '769d3498',
'tokens-css' => '3d0f239e',
@ -921,6 +921,13 @@ return array(
'javelin-workflow',
'phabricator-draggable-list',
),
'01fca1f0' => array(
'javelin-behavior',
'javelin-workflow',
'javelin-json',
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'031cee25' => array(
'javelin-behavior',
'javelin-request',
@ -1222,6 +1229,13 @@ return array(
'javelin-dom',
'javelin-stratcom',
),
'4a021c10' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'4b700e9e' => array(
'javelin-behavior',
'javelin-dom',
@ -1441,6 +1455,12 @@ return array(
'javelin-typeahead',
'javelin-uri',
),
'6d86ce8b' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'70baed2f' => array(
'javelin-install',
'javelin-dom',
@ -1486,13 +1506,6 @@ return array(
'multirow-row-manager',
'javelin-json',
),
'7835f8c9' => array(
'javelin-behavior',
'javelin-workflow',
'javelin-json',
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'7927a7d3' => array(
'javelin-behavior',
'javelin-quicksand',
@ -1608,12 +1621,6 @@ return array(
'javelin-dom',
'javelin-request',
),
'9196fb06' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
92197373 => array(
'phui-workcard-view-css',
),
@ -1882,13 +1889,6 @@ return array(
'javelin-install',
'javelin-dom',
),
'c1700f6f' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'c587b80f' => array(
'javelin-install',
),

View file

@ -21,6 +21,46 @@
"rule": ".tokens-coin-4",
"hash": "75832b7e42df9287b3c35c6afed12a93"
},
"tokens-emoji-1": {
"name": "tokens-emoji-1",
"rule": ".tokens-emoji-1",
"hash": "17f57bdeb4078f9c05f1f037ccb1c162"
},
"tokens-emoji-2": {
"name": "tokens-emoji-2",
"rule": ".tokens-emoji-2",
"hash": "6877c6e0c63522d5819531aaf4aba787"
},
"tokens-emoji-3": {
"name": "tokens-emoji-3",
"rule": ".tokens-emoji-3",
"hash": "cc67534b0119d4cc385a93ed5aff86e4"
},
"tokens-emoji-4": {
"name": "tokens-emoji-4",
"rule": ".tokens-emoji-4",
"hash": "f2a6febd638670962dfb5fdd76b23cfb"
},
"tokens-emoji-5": {
"name": "tokens-emoji-5",
"rule": ".tokens-emoji-5",
"hash": "22bc23d162449fde492e0fd3eccc7301"
},
"tokens-emoji-6": {
"name": "tokens-emoji-6",
"rule": ".tokens-emoji-6",
"hash": "e3689840f410ff1bbf365f6b06043d3f"
},
"tokens-emoji-7": {
"name": "tokens-emoji-7",
"rule": ".tokens-emoji-7",
"hash": "a689b9fe7c9f6f300d757b5350e2cc4b"
},
"tokens-emoji-8": {
"name": "tokens-emoji-8",
"rule": ".tokens-emoji-8",
"hash": "26570ef132caea33307e1e7574d754e8"
},
"tokens-heart-1": {
"name": "tokens-heart-1",
"rule": ".tokens-heart-1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
MODIFY parentDomain VARCHAR(128) NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
MODIFY parentSite VARCHAR(128) NULL COLLATE {$COLLATE_TEXT};

View file

@ -2295,7 +2295,10 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php',
'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php',
'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php',
'PhabricatorDashboardPanelEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php',
'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php',
'PhabricatorDashboardPanelEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php',
'PhabricatorDashboardPanelEditproController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php',
'PhabricatorDashboardPanelHasDashboardEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardPanelHasDashboardEdgeType.php',
'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php',
'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php',
@ -3831,7 +3834,6 @@ phutil_register_library_map(array(
'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php',
'PhamePost' => 'applications/phame/storage/PhamePost.php',
'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php',
'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php',
'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php',
'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php',
'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php',
@ -3845,6 +3847,7 @@ phutil_register_library_map(array(
'PhamePostMoveController' => 'applications/phame/controller/post/PhamePostMoveController.php',
'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php',
'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php',
'PhamePostRemarkupRule' => 'applications/phame/remarkup/PhamePostRemarkupRule.php',
'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php',
'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php',
'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php',
@ -6942,7 +6945,6 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorFlaggableInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController',
@ -6951,7 +6953,10 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldInterface',
),
'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField',
'PhabricatorDashboardPanelEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelEditEngine' => 'PhabricatorEditEngine',
'PhabricatorDashboardPanelEditproController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelHasDashboardEdgeType' => 'PhabricatorEdgeType',
'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType',
@ -8755,7 +8760,6 @@ phutil_register_library_map(array(
'PhabricatorFulltextInterface',
),
'PhamePostArchiveController' => 'PhamePostController',
'PhamePostCommentController' => 'PhamePostController',
'PhamePostController' => 'PhameController',
'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhamePostEditController' => 'PhamePostController',
@ -8769,6 +8773,7 @@ phutil_register_library_map(array(
'PhamePostMoveController' => 'PhamePostController',
'PhamePostPublishController' => 'PhamePostController',
'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhamePostRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine',

View file

@ -183,7 +183,8 @@ abstract class PhabricatorApplication
$item = id(new PHUIListItemView())
->setName($article['name'])
->setIcon('fa-book')
->setHref($article['href']);
->setHref($article['href'])
->setOpenInNewWindow(true);
$items[] = $item;
}
@ -203,7 +204,8 @@ abstract class PhabricatorApplication
$item = id(new PHUIListItemView())
->setName($spec['name'])
->setIcon('fa-envelope-o')
->setHref($href);
->setHref($href)
->setOpenInNewWindow(true);
$items[] = $item;
}
}

View file

@ -11,6 +11,23 @@ final class PhabricatorPHPConfigSetupCheck extends PhabricatorSetupCheck {
}
protected function executeChecks() {
if (version_compare(phpversion(), 7, '>=')) {
$message = pht(
'This version of Phabricator does not support PHP 7. You '.
'are running PHP %s.',
phpversion());
$this->newIssue('php.version7')
->setIsFatal(true)
->setName(pht('PHP 7 Not Supported'))
->setMessage($message)
->addLink(
'https://phurl.io/u/php7',
pht('Phabricator PHP 7 Compatibility Information'));
return;
}
$safe_mode = ini_get('safe_mode');
if ($safe_mode) {
$message = pht(

View file

@ -190,6 +190,7 @@ final class PhabricatorDaemonConsoleController
$triggers = id(new PhabricatorWorkerTriggerQuery())
->setViewer($viewer)
->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)
->withNextEventBetween(0, null)
->needEvents(true)
->setLimit(10)
->execute();

View file

@ -15,9 +15,13 @@ final class PhabricatorWorkerTaskDetailController
$task = reset($tasks);
}
$header = new PHUIHeaderView();
if (!$task) {
$title = pht('Task Does Not Exist');
$header->setHeader(pht('Task %d Missing', $id));
$error_view = new PHUIInfoView();
$error_view->setTitle(pht('No Such Task'));
$error_view->appendChild(phutil_tag(
@ -30,11 +34,11 @@ final class PhabricatorWorkerTaskDetailController
} else {
$title = pht('Task %d', $task->getID());
$header = id(new PHUIHeaderView())
->setHeader(pht('Task %d: %s',
$header->setHeader(
pht(
'Task %d: %s',
$task->getID(),
$task->getTaskClass()))
->setHeaderIcon('fa-sort');
$task->getTaskClass()));
$properties = $this->buildPropertyListView($task);
@ -59,6 +63,8 @@ final class PhabricatorWorkerTaskDetailController
);
}
$header->setHeaderIcon('fa-sort');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);

View file

@ -40,6 +40,8 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorDashboardPanelListController',
'create/' => 'PhabricatorDashboardPanelEditController',
$this->getEditRoutePattern('editpro/')
=> 'PhabricatorDashboardPanelEditproController',
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardPanelEditController',
'render/(?P<id>\d+)/' => 'PhabricatorDashboardPanelRenderController',
'archive/(?P<id>\d+)/'

View file

@ -0,0 +1,20 @@
<?php
final class PhabricatorDashboardPanelEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'dashboard.panel.edit';
}
public function newEditEngine() {
return new PhabricatorDashboardPanelEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new dashboard panel or edit an '.
'existing one.');
}
}

View file

@ -51,10 +51,6 @@ final class PhabricatorDashboardPanelEditController
if (!$panel) {
return new Aphront404Response();
}
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$panel->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$v_projects = array_reverse($v_projects);
if ($dashboard) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
@ -84,7 +80,6 @@ final class PhabricatorDashboardPanelEditController
if (empty($types[$type])) {
return $this->processPanelTypeRequest($request);
}
$v_projects = array();
$panel->setPanelType($type);
}
@ -135,7 +130,6 @@ final class PhabricatorDashboardPanelEditController
$v_name = $request->getStr('name');
$v_view_policy = $request->getStr('viewPolicy');
$v_edit_policy = $request->getStr('editPolicy');
$v_projects = $request->getArr('projects');
$type_name = PhabricatorDashboardPanelTransaction::TYPE_NAME;
$type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
@ -155,12 +149,6 @@ final class PhabricatorDashboardPanelEditController
->setTransactionType($type_edit_policy)
->setNewValue($v_edit_policy);
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorDashboardPanelTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $proj_edge_type)
->setNewValue(array('=' => array_fuse($v_projects)));
$field_xactions = $field_list->buildFieldTransactionsFromRequest(
new PhabricatorDashboardPanelTransaction(),
$request);
@ -238,13 +226,6 @@ final class PhabricatorDashboardPanelEditController
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies));
$form->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Tags'))
->setName('projects')
->setValue($v_projects)
->setDatasource(new PhabricatorProjectDatasource()));
$field_list->appendFieldsToForm($form);
$crumbs = $this->buildApplicationCrumbs();

View file

@ -0,0 +1,105 @@
<?php
final class PhabricatorDashboardPanelEditproController
extends PhabricatorDashboardController {
public function handleRequest(AphrontRequest $request) {
$engine = id(new PhabricatorDashboardPanelEditEngine())
->setController($this);
$id = $request->getURIData('id');
if (!$id) {
$list_uri = $this->getApplicationURI('panel/');
$panel_type = $request->getStr('panelType');
$panel_types = PhabricatorDashboardPanelType::getAllPanelTypes();
if (empty($panel_types[$panel_type])) {
return $this->buildPanelTypeResponse($list_uri);
}
$engine
->addContextParameter('panelType', $panel_type)
->setPanelType($panel_type);
}
return $engine->buildResponse();
}
private function buildPanelTypeResponse($cancel_uri) {
$panel_types = PhabricatorDashboardPanelType::getAllPanelTypes();
$viewer = $this->getViewer();
$request = $this->getRequest();
$e_type = null;
$errors = array();
if ($request->isFormPost()) {
$e_type = pht('Required');
$errors[] = pht(
'To create a new dashboard panel, you must select a panel type.');
}
$type_control = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Panel Type'))
->setName('panelType')
->setError($e_type);
foreach ($panel_types as $key => $type) {
$type_control->addButton(
$key,
$type->getPanelTypeName(),
$type->getPanelTypeDescription());
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht('Choose the type of dashboard panel to create:'))
->appendChild($type_control);
if ($request->isAjax()) {
return $this->newDialog()
->setTitle(pht('Add New Panel'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->setErrors($errors)
->appendForm($form)
->addCancelButton($cancel_uri)
->addSubmitButton(pht('Continue'));
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Continue'))
->addCancelButton($cancel_uri));
$title = pht('Create Dashboard Panel');
$header_icon = 'fa-plus-square';
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Panels'),
$this->getApplicationURI('panel/'));
$crumbs->addTextCrumb(pht('New Panel'));
$crumbs->setBorder(true);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Panel'))
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon($header_icon);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}

View file

@ -9,6 +9,10 @@ final class PhabricatorDashboardPanelCoreCustomField
}
public function createFields($object) {
if (!$object->getPanelType()) {
return array();
}
$impl = $object->requireImplementation();
$specs = $impl->getFieldSpecifications();
return PhabricatorStandardCustomField::buildStandardFields($this, $specs);

View file

@ -0,0 +1,96 @@
<?php
final class PhabricatorDashboardPanelEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'dashboard.panel';
private $panelType;
public function setPanelType($panel_type) {
$this->panelType = $panel_type;
return $this;
}
public function getPanelType() {
return $this->panelType;
}
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Dashboard Panels');
}
public function getSummaryHeader() {
return pht('Edit Dashboard Panels');
}
public function getSummaryText() {
return pht('This engine is used to modify dashboard panels.');
}
public function getEngineApplicationClass() {
return 'PhabricatorSearchApplication';
}
protected function newEditableObject() {
$viewer = $this->getViewer();
$panel = PhabricatorDashboardPanel::initializeNewPanel($viewer);
if ($this->panelType) {
$panel->setPanelType($this->panelType);
}
return $panel;
}
protected function newObjectQuery() {
return new PhabricatorDashboardPanelQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Dashboard Panel');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Panel');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Panel: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Panel');
}
protected function getObjectCreateShortText() {
return pht('Edit Panel');
}
protected function getObjectName() {
return pht('Dashboard Panel');
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function buildCustomEditFields($object) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the panel.'))
->setConduitDescription(pht('Rename the panel.'))
->setConduitTypeDescription(pht('New panel name.'))
->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME)
->setIsRequired(true)
->setValue($object->getName()),
);
}
}

View file

@ -10,7 +10,6 @@ final class PhabricatorDashboardPanel
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface,
PhabricatorFlaggableInterface,
PhabricatorProjectInterface,
PhabricatorDestructibleInterface {
protected $name;
@ -72,6 +71,10 @@ final class PhabricatorDashboardPanel
return 'W'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
public function getPanelTypes() {
$panel_types = PhabricatorDashboardPanelType::getAllPanelTypes();
$panel_types = mpull($panel_types, 'getPanelTypeName', 'getPanelTypeKey');

View file

@ -173,7 +173,7 @@ final class DiffusionHistoryQueryConduitAPIMethod
}
$hash_list = array_reverse($hash_list);
$this->parents = $parent_map;
$this->parents = array_reverse($parent_map, true);
return DiffusionQuery::loadHistoryForCommitIdentifiers(
$hash_list,

View file

@ -56,9 +56,30 @@ final class DiffusionRepositoryEditController
$layout->addColumn($action);
}
$hints = id(new AphrontMultiColumnView())
->setFluidLayout(true);
$observe_href = PhabricatorEnv::getDoclink(
'Diffusion User Guide: Existing Repositories');
$hints->addColumn(
id(new PHUIActionPanelView())
->setIcon('fa-book')
->setHeader(pht('Import or Observe an Existing Repository'))
->setHref($observe_href)
->setSubheader(
pht(
'Review the documentation describing how to import or observe an '.
'existing repository.')));
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($layout);
->setFooter(
array(
$layout,
phutil_tag('br'),
$hints,
));
return $this->newPage()
->setTitle($title)

View file

@ -77,6 +77,11 @@ final class DiffusionURIEditor
$old_uri = $object->getEffectiveURI();
} else {
$old_uri = null;
// When creating a URI, we may not have processed the repository
// transaction yet. Attach the repository here to make sure we
// have it for the calls below.
$object->attachRepository($this->repository);
}
$object->setURI($xaction->getNewValue());

View file

@ -5,6 +5,7 @@ final class PhabricatorNotificationBuilder extends Phobject {
private $stories;
private $parsedStories;
private $user = null;
private $showTimestamps = true;
public function __construct(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
@ -16,6 +17,15 @@ final class PhabricatorNotificationBuilder extends Phobject {
return $this;
}
public function setShowTimestamps($show_timestamps) {
$this->showTimestamps = $show_timestamps;
return $this;
}
public function getShowTimestamps() {
return $this->showTimestamps;
}
private function parseStories() {
if ($this->parsedStories) {
@ -121,6 +131,9 @@ final class PhabricatorNotificationBuilder extends Phobject {
// TODO: Render a nice debuggable notice instead?
continue;
}
$view->setShowTimestamp($this->getShowTimestamps());
$null_view->appendChild($view->renderNotification($this->user));
}

View file

@ -31,7 +31,8 @@ final class PhabricatorNotificationIndividualController
}
$builder = id(new PhabricatorNotificationBuilder(array($story)))
->setUser($viewer);
->setUser($viewer)
->setShowTimestamps(false);
$content = $builder->buildView()->render();
$dict = $builder->buildDict();

View file

@ -82,7 +82,13 @@ final class PhabricatorOwnersPathsController
}
}
$repos = mpull($repos, 'getDisplayName', 'getPHID');
$repo_map = array();
foreach ($repos as $key => $repo) {
$monogram = $repo->getMonogram();
$name = $repo->getName();
$repo_map[$repo->getPHID()] = "{$monogram} {$name}";
}
asort($repos);
$template = new AphrontTypeaheadTemplateView();
@ -94,7 +100,7 @@ final class PhabricatorOwnersPathsController
'root' => 'path-editor',
'table' => 'paths',
'add_button' => 'addpath',
'repositories' => $repos,
'repositories' => $repo_map,
'input_template' => $template,
'pathRefs' => $path_refs,

View file

@ -15,15 +15,19 @@ final class PhabricatorPasteLanguageTransaction
public function getTitle() {
return pht(
"%s updated the paste's language.",
$this->renderAuthor());
"%s updated the paste's language from %s to %s.",
$this->renderAuthor(),
$this->renderValue($this->getOldValue()),
$this->renderValue($this->getNewValue()));
}
public function getTitleForFeed() {
return pht(
'%s updated the language for %s.',
'%s updated the language for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject());
$this->renderObject(),
$this->renderValue($this->getOldValue()),
$this->renderValue($this->getNewValue()));
}
}

View file

@ -14,7 +14,7 @@ final class PhabricatorPasteStatusTransaction
}
private function isActivate() {
return ($this->getNewValue() == PhabricatorPaste::STATUS_ARCHIVED);
return ($this->getNewValue() == PhabricatorPaste::STATUS_ACTIVE);
}
public function getIcon() {

View file

@ -31,12 +31,9 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
);
}
public function isPrototype() {
return true;
}
public function getRoutes() {
return array(
'/J(?P<id>[1-9]\d*)' => 'PhamePostViewController',
'/phame/' => array(
'' => 'PhameHomeController',
@ -46,17 +43,16 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
'post/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhamePostListController',
'blogger/(?P<bloggername>[\w\.-_]+)/' => 'PhamePostListController',
'edit/(?:(?P<id>[^/]+)/)?' => 'PhamePostEditController',
$this->getEditRoutePattern('edit/')
=> 'PhamePostEditController',
'history/(?P<id>\d+)/' => 'PhamePostHistoryController',
'view/(?P<id>\d+)/(?:(?P<slug>[^/]+)/)?' => 'PhamePostViewController',
'(?P<action>publish|unpublish)/(?P<id>\d+)/'
=> 'PhamePostPublishController',
'preview/(?P<id>\d+)/' => 'PhamePostPreviewController',
'preview/' => 'PhabricatorMarkupPreviewController',
'framed/(?P<id>\d+)/' => 'PhamePostFramedController',
'move/(?P<id>\d+)/' => 'PhamePostMoveController',
'archive/(?P<id>\d+)/' => 'PhamePostArchiveController',
'comment/(?P<id>[1-9]\d*)/' => 'PhamePostCommentController',
),
'blog/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhameBlogListController',
@ -107,6 +103,13 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
);
}
public function getRemarkupRules() {
return array(
new PhamePostRemarkupRule(),
);
}
protected function getCustomCapabilities() {
return array(
PhameBlogCreateCapability::CAPABILITY => array(

View file

@ -195,7 +195,9 @@ abstract class PhameLiveController extends PhameController {
}
if ($post) {
$crumbs->addTextCrumb($post->getTitle());
if (!$is_external) {
$crumbs->addTextCrumb('J'.$post->getID());
}
}
return $crumbs;

View file

@ -1,63 +0,0 @@
<?php
final class PhamePostCommentController
extends PhamePostController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if (!$request->isFormPost()) {
return new Aphront400Response();
}
$post = id(new PhamePostQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$is_preview = $request->isPreviewRequest();
$draft = PhabricatorDraft::buildFromRequest($request);
$view_uri = $this->getApplicationURI('post/view/'.$post->getID().'/');
$xactions = array();
$xactions[] = id(new PhamePostTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new PhamePostTransactionComment())
->setContent($request->getStr('comment')));
$editor = id(new PhamePostEditor())
->setActor($viewer)
->setContinueOnNoEffect($request->isContinueRequest())
->setContentSourceFromRequest($request)
->setIsPreview($is_preview);
try {
$xactions = $editor->applyTransactions($post, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
->setException($ex);
}
if ($draft) {
$draft->replaceOrDelete();
}
if ($request->isAjax() && $is_preview) {
return id(new PhabricatorApplicationTransactionResponse())
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview);
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
}

View file

@ -123,19 +123,22 @@ final class PhamePostViewController
->setImage($blogger->getProfileImageURI())
->setImageHref($author_uri);
$monogram = $post->getMonogram();
$timeline = $this->buildTransactionTimeline(
$post,
id(new PhamePostTransactionQuery())
->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)));
$timeline = phutil_tag_div('phui-document-view-pro-box', $timeline);
$timeline->setQuoteRef($monogram);
if ($is_external) {
$add_comment = null;
} else {
$add_comment = $this->buildCommentForm($post);
$add_comment = phutil_tag_div('mlb mlt', $add_comment);
$add_comment = $this->buildCommentForm($post, $timeline);
$add_comment = phutil_tag_div('mlb mlt phame-comment-view', $add_comment);
}
$timeline = phutil_tag_div('phui-document-view-pro-box', $timeline);
list($prev, $next) = $this->loadAdjacentPosts($post);
$properties = id(new PHUIPropertyListView())
@ -273,19 +276,13 @@ final class PhamePostViewController
return $actions;
}
private function buildCommentForm(PhamePost $post) {
private function buildCommentForm(PhamePost $post, $timeline) {
$viewer = $this->getViewer();
$draft = PhabricatorDraft::newFromUserAndKey(
$viewer, $post->getPHID());
$box = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($post->getPHID())
->setDraft($draft)
->setHeaderText(pht('Add Comment'))
->setAction($this->getApplicationURI('post/comment/'.$post->getID().'/'))
->setSubmitButtonName(pht('Add Comment'));
$box = id(new PhamePostEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($post)
->setTransactionTimeline($timeline);
return phutil_tag_div('phui-document-view-pro-box', $box);
}

View file

@ -66,6 +66,7 @@ final class PhameBlogEditor
case PhameBlogTransaction::TYPE_DESCRIPTION:
case PhameBlogTransaction::TYPE_STATUS:
case PhameBlogTransaction::TYPE_PARENTSITE:
case PhameBlogTransaction::TYPE_PARENTDOMAIN:
case PhameBlogTransaction::TYPE_PROFILEIMAGE:
case PhameBlogTransaction::TYPE_HEADERIMAGE:
return $xaction->getNewValue();

View file

@ -68,6 +68,10 @@ final class PhamePostEditEngine
return $object->getViewURI();
}
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('post/edit/');
}
protected function buildCustomEditFields($object) {
$blog_phid = $object->getBlog()->getPHID();

View file

@ -9,7 +9,7 @@ final class PhamePostMailReceiver
}
protected function getObjectPattern() {
return 'POST[1-9]\d*';
return 'J[1-9]\d*';
}
protected function loadObject($pattern, PhabricatorUser $viewer) {

View file

@ -32,8 +32,8 @@ final class PhabricatorPhamePostPHIDType extends PhabricatorPHIDType {
foreach ($handles as $phid => $handle) {
$post = $objects[$phid];
$handle->setName($post->getTitle());
$handle->setFullName($post->getTitle());
$handle->setURI('/phame/post/view/'.$post->getID().'/');
$handle->setFullName(pht('Blog Post: ').$post->getTitle());
$handle->setURI('/J'.$post->getID());
if ($post->isArchived()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);

View file

@ -108,6 +108,7 @@ final class PhamePostSearchEngine
$item = id(new PHUIObjectItemView())
->setUser($viewer)
->setObject($post)
->setObjectName($post->getMonogram())
->setHeader($post->getTitle())
->setStatusIcon('fa-star')
->setHref($post->getViewURI())

View file

@ -0,0 +1,19 @@
<?php
final class PhamePostRemarkupRule
extends PhabricatorObjectRemarkupRule {
protected function getObjectNamePrefix() {
return 'J';
}
protected function loadObjects(array $ids) {
$viewer = $this->getEngine()->getConfig('viewer');
return id(new PhamePostQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
}
}

View file

@ -18,9 +18,10 @@ final class PhameBlogSite extends PhameSite {
}
public function shouldRequireHTTPS() {
// TODO: We should probably provide options here eventually, but for now
// just never require HTTPS for external-domain blogs.
return false;
$full_uri = $this->getBlog()->getDomainFullURI();
$full_uri = new PhutilURI($full_uri);
return ($full_uri->getProtocol() == 'https');
}
public function getPriority() {

View file

@ -48,8 +48,8 @@ final class PhameBlog extends PhameDAO
'description' => 'text',
'domain' => 'text128?',
'domainFullURI' => 'text128?',
'parentSite' => 'text128',
'parentDomain' => 'text128',
'parentSite' => 'text128?',
'parentDomain' => 'text128?',
'status' => 'text32',
'mailKey' => 'bytes20',
'profileImagePHID' => 'phid?',

View file

@ -51,6 +51,10 @@ final class PhamePost extends PhameDAO
return $this->assertAttached($this->blog);
}
public function getMonogram() {
return 'J'.$this->getID();
}
public function getLiveURI() {
$blog = $this->getBlog();
$is_draft = $this->isDraft();

View file

@ -14,6 +14,7 @@ final class PHUIHandleView
private $handleList;
private $handlePHID;
private $asTag;
private $asText;
private $useShortName;
private $showHovercard;
@ -32,6 +33,11 @@ final class PHUIHandleView
return $this;
}
public function setAsText($as_text) {
$this->asText = $as_text;
return $this;
}
public function setUseShortName($short) {
$this->useShortName = $short;
return $this;
@ -55,6 +61,10 @@ final class PHUIHandleView
return $tag;
}
if ($this->asText) {
return $handle->getLinkName();
}
if ($this->useShortName) {
$name = $handle->getName();
} else {

View file

@ -59,7 +59,7 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication {
'new/' => 'PhrictionNewController',
'move/(?P<id>[1-9]\d*)/' => 'PhrictionMoveController',
'preview/(?P<slug>.+/)' => 'PhrictionMarkupPreviewController',
'preview/(?P<slug>.*/)' => 'PhrictionMarkupPreviewController',
'diff/(?P<id>[1-9]\d*)/' => 'PhrictionDiffController',
),
);

View file

@ -174,12 +174,13 @@ final class PhabricatorRepositoryDiscoveryEngine
continue;
}
// In Git, it's possible to tag a tag. We just skip these, we'll discover
// them when we process the target tag. See T11180.
// In Git, it's possible to tag anything. We just skip tags that don't
// point to a commit. See T11301.
$fields = $ref->getRawFields();
$ref_type = idx($fields, 'objecttype');
$tag_type = idx($fields, '*objecttype');
if ($tag_type == 'tag') {
$this->log(pht('Skipping, this is a tag of a tag.'));
if ($ref_type != 'commit' && $tag_type != 'commit') {
$this->log(pht('Skipping, this is not a commit.'));
continue;
}
@ -759,6 +760,13 @@ final class PhabricatorRepositoryDiscoveryEngine
'repositoryPHID = %s',
$repository->getPHID());
// If we don't have any refs to update, bail out before building a graph
// stream. In particular, this improves behavior in empty repositories,
// where `git log` exits with an error.
if (!$old_refs) {
return;
}
// We can share a single graph stream across all the checks we need to do.
$stream = new PhabricatorGitGraphStream($repository);

View file

@ -30,6 +30,7 @@ final class PhabricatorWorkingCopyDiscoveryTestCase
$this->assertEqual(
array(
'763d4ab372445551c95fb5cccd1a7a223f5b2ac8',
'41fa35914aa19c1aa6e57004d9745c05929c3563',
),
mpull($refs, 'getIdentifier'));
}

View file

@ -2400,6 +2400,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
->setKey('status')
->setType('string')
->setDescription(pht('Active or inactive status.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isImporting')
->setType('bool')
->setDescription(
pht(
'True if the repository is importing initial commits.')),
);
}
@ -2410,6 +2416,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'callsign' => $this->getCallsign(),
'shortName' => $this->getRepositorySlug(),
'status' => $this->getStatus(),
'isImporting' => (bool)$this->isImporting(),
);
}

View file

@ -33,7 +33,6 @@ final class PhabricatorProfilePanelPHIDType
$config = $objects[$phid];
$handle->setName(pht('Profile Panel'));
$handle->setURI($config->getURI());
}
}

View file

@ -27,7 +27,7 @@ final class PhabricatorUserPreferencesSearchEngine
}
protected function getURI($path) {
return '/settings/list/'.$path;
return '/settings/'.$path;
}
protected function getBuiltinQueryNames() {

View file

@ -90,7 +90,7 @@ final class PhabricatorTokenGiveController extends PhabricatorTokenController {
$aural,
$token->renderIcon(),
));
if ((++$ii % 4) == 0) {
if ((++$ii % 6) == 0) {
$buttons[] = phutil_tag('br');
}
}

View file

@ -43,6 +43,14 @@ final class PhabricatorTokenQuery
array('misc-2', pht('Evil Spooky Haunted Tree')),
array('misc-3', pht('Baby Tequila')),
array('misc-4', pht('The World Burns')),
array('emoji-1', pht('100')),
array('emoji-2', pht('Party Time')),
array('emoji-3', pht('Y So Serious')),
array('emoji-4', pht('Dat Boi')),
array('emoji-5', pht('Cup of Joe')),
array('emoji-6', pht('Hungry Hippo')),
array('emoji-7', pht('Burninate')),
array('emoji-8', pht('Pirate Logo')),
);
$type = PhabricatorTokenTokenPHIDType::TYPECONST;

View file

@ -98,6 +98,15 @@ abstract class PhabricatorModularTransaction
return parent::getTitle();
}
public function getTitleForMail() {
$old_target = $this->getRenderingTarget();
$new_target = self::TARGET_TEXT;
$this->setRenderingTarget($new_target);
$title = $this->getTitle();
$this->setRenderingTarget($old_target);
return $title;
}
final public function getTitleForFeed() {
$title = $this->getTransactionImplementation()->getTitleForFeed();
if ($title !== null) {
@ -116,6 +125,11 @@ abstract class PhabricatorModularTransaction
return parent::getColor();
}
public function attachViewer(PhabricatorUser $viewer) {
$this->getTransactionImplementation()->setViewer($viewer);
return parent::attachViewer($viewer);
}
final public function hasChangeDetails() {
if ($this->getTransactionImplementation()->hasChangeDetailView()) {
return true;

View file

@ -129,6 +129,32 @@ abstract class PhabricatorModularTransactionType
return $this->getStorage()->renderHandleLink($object_phid);
}
final protected function renderHandle($phid) {
$viewer = $this->getViewer();
$display = $viewer->renderHandle($phid);
$rendering_target = $this->getStorage()->getRenderingTarget();
if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) {
$display->setAsText(true);
}
return $display;
}
final protected function renderValue($value) {
$rendering_target = $this->getStorage()->getRenderingTarget();
if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) {
return sprintf('"%s"', $value);
}
return phutil_tag(
'span',
array(
'class' => 'phui-timeline-value',
),
$value);
}
final protected function newError($title, $message, $xaction = null) {
return new PhabricatorApplicationTransactionValidationError(
$this->getTransactionTypeConstant(),

View file

@ -70,7 +70,8 @@ Beyond an operating system, you will need **a webserver**.
You will also need:
- **MySQL**: You need MySQL. We strongly recommend MySQL 5.5 or newer.
- **PHP**: You need PHP 5.2 or newer.
- **PHP**: You need PHP 5.2 or newer, but note that PHP 7 is
**not supported**.
You'll probably also need a **domain name**. In particular, you should read this
note:

View file

@ -0,0 +1,68 @@
@title Diffusion User Guide: Existing Repositories
@group userguide
Quick guide for importing or observing existing repositories.
Overview
========
If you have an existing repository, you can observe or import it into
Diffusion.
Observing a repository creates a read-only copy in Phabricator that is kept
up to date by continuously importing new changes.
Importing a repository creates a read-write copy.
This document is a quick guide to getting started. For an overview of
Diffusion, see @{article:Diffusion User Guide}. For a more detailed guide
about managing repositories and URIs in Diffusion, see
@{article:Diffusion User Guide: URIs}.
Observing Repositories
======================
To observe an existing repository:
- Create a repository in Diffusion, but do not activate it yet.
- Add the URI for the existing repository you wish to observe in the
**URIs** section, in **Observe** mode.
- Activate the repository in Diffusion.
This creates a read-only copy of the repository in Phabricator. Phabricator
will keep its copy in sync with the remote by periodically polling the remote
for changes.
For more details, see @{article:Diffusion User Guide: URIs}.
Importing Repositories
======================
There are two primary ways to import an existing repository:
**Observe First**: In Git or Mercurial, you can observe the repository first.
Once the import completes, disable the **Observe** URI to automatically convert
it into a hosted repository.
**Push to Empty Repository**: Create an activate an empty repository, then push
all of your changes to empty the repository.
In Git and Mercurial, you can do this with `git push` or `hg push`.
In Subversion, you can do this with `svnsync`.
For more details, see @{article:Diffusion User Guide: URIs}.
Next Steps
==========
Continue by:
- reading an overview of Diffusion in
@{article:Diffusion User Guide}; or
- learning more about managing remote repository URIs in
@{article:Diffusion User Guide: URIs}.

View file

@ -135,10 +135,6 @@ Import a Repository
If you have an existing repository that you want to move so it is hosted on
Phabricator, there are three ways to do it:
**Push Everything**: //(Git, Mercurial)// Create a new empty hosted repository
according to the instructions above. Once the empty repository initializes,
push your entire existing repository to it.
**Observe First**: //(Git, Mercurial)// Observe the existing repository first,
according to the instructions above. Once Phabricator's copy of the repository
is fully synchronized, change the **I/O Type** for the **Observe** URI to
@ -149,6 +145,12 @@ writable, and you can begin pushing to it. If you've adjusted URI
configuration away from the defaults, you may need to set at least one URI
to **Read/Write** mode so you can push to it.
**Push Everything**: //(Git, Mercurial, Subversion)// Create a new empty hosted
repository according to the instructions above. Once the empty repository
initializes, push your entire existing repository to it.
In Subversion, you can do this with the `svnsync` tool.
**Copy on Disk**: //(Git, Mercurial, Subversion)// Create a new empty hosted
repository according to the instructions above, but do not activate it yet.

View file

@ -1,59 +1,121 @@
@title Phame User Guide
@group userguide
Journal about your thoughts and feelings. Share with others. Profit.
Phame is a blogging platform.
= Overview =
Overview
========
IMPORTANT: Phame is a prototype application.
Phame is a simple platform for writing blogs and blog posts. Content published
through Phame is integrated with other Phabricator applications (like Feed,
Herald and Dashboards).
Phame is a simple blogging platform. You can write drafts which only you can
see. Later, you can publish these drafts as posts which anyone who can access
the Phabricator instance can see. You can also add posts to blogs to increase
your distribution.
You can use Phame to write and publish posts on any topic. You might use it to
make announcements, hold discussions, or provide progress updates about a
project.
Overall, Phame is intended to help an individual spread their message. As
such, pertinent design decisions skew towards favoring the individual
rather than the collective.
In the upstream, we use several Phame blogs to discuss changes to Phabricator,
make company announcements, photograph food, and provide visionary thought
leadership.
= Drafts =
Drafts are completely private so draft away.
Blogs
=====
= Posts =
To get started with Phame, create a blog. Blogs can be personal or edited
by a group: the **Editable By** policy controls who is allowed to write new
posts.
Posts are accessible to anyone who has access to the Phabricator instance.
You can provide a title, subtitle, and description to help users understand
the role and purpose of the blog.
= Blogs =
After creating a blog, you can optionally provide a header image (a large
image shown on the main blog page, like a beautiful photograph of food) and
a picture (a small logo or profile image shown in various places in the UI to
help identify the blog).
Blogs are collections of posts. Each blog has associated metadata like
a name, description, and set of bloggers who can add posts to the blog.
Each blogger can also edit metadata about the blog and delete the blog
outright.
Blogs can also be hosted externally. See "External Blogs", below, for
more information.
NOTE: removing a blogger from a given blog does not remove their posts that
are already associated with the blog. Rather, it removes their ability to edit
metadata about and add posts to the blog.
Blogs can be useful for powering external websites, like
Posts
=====
blog.yourcompany.com
After creating a blog, you're ready to write your first post. You can navigate
to the blog and choose {nav Write Post} to get started.
by making pertinent configuration changes with your DNS authority and
Phabricator instance. For the Phabricator instance, you must
Posts have a **Visibility** field which controls who can see them. The options
are:
- Enable `policy.allow-public` in Phabricator configuration.
- Configure the blog to have the view policy `public`.
- **Published**: Anyone who can see the blog will be able to read the post.
- **Draft**: Allows you to work on posts before publishing them. Only users
who can edit the blog will be able to see the post.
- **Archived**: Allows you to remove old posts. Only users who can edit
the blog will be able to see the post, and it won't appear in the pending
drafts list.
For your DNS authority, simply point the pertinent domain name at your
Phabricator instance. e.g. by IP address.
After publishing a post, it will appear on the blog and on the Phame home page
for all users who can see it.
= Comment Widgets =
Phame supports comment widgets from Facebook and Disqus. The administrator
of the Phabricator instance must properly configure Phabricator to enable
this functionality.
Using Phame With Other Applications
===================================
A given comment widget is tied 1:1 with a given post. This means the same
instance of a given comment widget will appear for a given post regardless
of whether that post is being viewed in the context of a blog.
Phame integrates with other Phabricator applications, so you can do a few
interesting things:
**Dashboards**: You can create a dashboard panel which shows posts on a
particular blog, then put the panel on the homepage or a custom dashboard.
This is an easy way to create a list of recent announcements.
**Herald**: You can use Herald rules to make sure you get notified whenever
your favorite author publishes a new post.
**Remarkup**: You can reference a blog post in any other application using the
`J123` monogram for the post, or embed a more detailed link with `{J123}`.
(We ran out of letters a while ago, but thinking about **j**ournal may be
helpful in remembering this.)
External Blogs
==============
WARNING: This feature is still a prototype and has some known issues.
You can host a Phame blog on an external domain, like `blog.mycompany.com`. The
Phacility corporate blog is an example of an external Phame blog:
> https://blog.phacility.com/
External blogs are public (they do not require login) and are only supported if
your Phabricator install is also public. You can make an install public by
adjusting `policy.allow-public` in Config, but make sure you understand the
effects of adjusting this setting before touching it.
Once you've made your install public, configure the blog that you want to host
like this:
- **View Policy**: Set the "View Policy" for the blog to "Public". Blogs must
have a public view policy to be served from an external domain.
- **Full Domain URI**: Set this to the full URI of your external domain,
like `https://blog.mycompany.com/`. When users visit this URI, Phabricator
will serve the blog to them.
To configure the blog's navigation breadcrumbs so that it links back to the
right parent site, set these options:
- **Parent Site Name**: Put the parent site name here (like "MyCompany").
- **Parent Site URI**: Put the parent site URI here (like
`https://www.mycompany.com`).
Configuring these options will add a new breadcrumb to the navigation to let
users return to the blog's parent site. It will look something like this:
- {nav My Company > Blog Name}
Finally, configure DNS for `blog.mycompany.com` to point at Phabricator.
If everything is set up properly, visiting `blog.mycompany.com` should now
serve your blog.

View file

@ -12,6 +12,7 @@ final class PhabricatorDatabaseRef
const REPLICATION_MASTER_REPLICA = 'master-replica';
const REPLICATION_REPLICA_NONE = 'replica-none';
const REPLICATION_SLOW = 'replica-slow';
const REPLICATION_NOT_REPLICATING = 'not-replicating';
const KEY_REFS = 'cluster.db.refs';
const KEY_INDIVIDUAL = 'cluster.db.individual';
@ -196,13 +197,18 @@ final class PhabricatorDatabaseRef
self::REPLICATION_REPLICA_NONE => array(
'icon' => 'fa-download',
'color' => 'red',
'label' => pht('Not Replicating'),
'label' => pht('Not A Replica'),
),
self::REPLICATION_SLOW => array(
'icon' => 'fa-hourglass',
'color' => 'red',
'label' => pht('Slow Replication'),
),
self::REPLICATION_NOT_REPLICATING => array(
'icon' => 'fa-exclamation-triangle',
'color' => 'red',
'label' => pht('Not Replicating'),
),
);
}
@ -330,7 +336,11 @@ final class PhabricatorDatabaseRef
}
if ($is_replica) {
$latency = (int)idx($replica_status, 'Seconds_Behind_Master');
$latency = idx($replica_status, 'Seconds_Behind_Master');
if (!strlen($latency)) {
$ref->setReplicaStatus(self::REPLICATION_NOT_REPLICATING);
} else {
$latency = (int)$latency;
$ref->setReplicaDelay($latency);
if ($latency > 30) {
$ref->setReplicaStatus(self::REPLICATION_SLOW);
@ -342,6 +352,7 @@ final class PhabricatorDatabaseRef
}
}
}
}
return $refs;
}

View file

@ -54,6 +54,8 @@ final class DifferentialRevisionGraph
$link = $viewer->renderHandle($phid);
}
$link = AphrontTableView::renderSingleDisplayLine($link);
return array(
$trace,
$status,

View file

@ -67,6 +67,8 @@ final class ManiphestTaskGraph
$link = $viewer->renderHandle($phid);
}
$link = AphrontTableView::renderSingleDisplayLine($link);
return array(
$trace,
$status,

View file

@ -3,9 +3,13 @@
final class PhabricatorInternationalizationManagementExtractWorkflow
extends PhabricatorInternationalizationManagementWorkflow {
const CACHE_VERSION = 1;
protected function didConstruct() {
$this
->setName('extract')
->setExamples(
'**extract** [__options__] __library__')
->setSynopsis(pht('Extract translatable strings.'))
->setArguments(
array(
@ -13,44 +17,138 @@ final class PhabricatorInternationalizationManagementExtractWorkflow
'name' => 'paths',
'wildcard' => true,
),
array(
'name' => 'clean',
'help' => pht('Drop caches before extracting strings. Slow!'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$paths = $args->getArg('paths');
$futures = array();
$paths = $args->getArg('paths');
if (!$paths) {
$paths = array(getcwd());
}
$targets = array();
foreach ($paths as $path) {
$root = Filesystem::resolvePath($path);
$path_files = id(new FileFinder($root))
->withType('f')
->withSuffix('php')
->find();
foreach ($path_files as $file) {
$full_path = $root.DIRECTORY_SEPARATOR.$file;
if (!Filesystem::pathExists($root) || !is_dir($root)) {
throw new PhutilArgumentUsageException(
pht(
'Path "%s" does not exist, or is not a directory.',
$path));
}
$libraries = id(new FileFinder($path))
->withPath('*/__phutil_library_init__.php')
->find();
if (!$libraries) {
throw new PhutilArgumentUsageException(
pht(
'Path "%s" contains no libphutil libraries.',
$path));
}
foreach ($libraries as $library) {
$targets[] = Filesystem::resolvePath(dirname($library)).'/';
}
}
$targets = array_unique($targets);
foreach ($targets as $library) {
echo tsprintf(
"**<bg:blue> %s </bg>** %s\n",
pht('EXTRACT'),
pht(
'Extracting "%s"...',
Filesystem::readablePath($library)));
$this->extractLibrary($library);
}
return 0;
}
private function extractLibrary($root) {
$files = $this->loadLibraryFiles($root);
$cache = $this->readCache($root);
$modified = $this->getModifiedFiles($files, $cache);
$cache['files'] = $files;
if ($modified) {
echo tsprintf(
"**<bg:blue> %s </bg>** %s\n",
pht('MODIFIED'),
pht(
'Found %s modified file(s) (of %s total).',
phutil_count($modified),
phutil_count($files)));
$old_strings = idx($cache, 'strings');
$old_strings = array_select_keys($old_strings, $files);
$new_strings = $this->extractFiles($root, $modified);
$all_strings = $new_strings + $old_strings;
$cache['strings'] = $all_strings;
$this->writeStrings($root, $all_strings);
} else {
echo tsprintf(
"**<bg:blue> %s </bg>** %s\n",
pht('NOT MODIFIED'),
pht('Strings for this library are already up to date.'));
}
$cache = id(new PhutilJSON())->encodeFormatted($cache);
$this->writeCache($root, 'i18n_files.json', $cache);
}
private function getModifiedFiles(array $files, array $cache) {
$known = idx($cache, 'files', array());
$known = array_fuse($known);
$modified = array();
foreach ($files as $file => $hash) {
if (isset($known[$hash])) {
continue;
}
$modified[$file] = $hash;
}
return $modified;
}
private function extractFiles($root_path, array $files) {
$hashes = array();
$futures = array();
foreach ($files as $file => $hash) {
$full_path = $root_path.DIRECTORY_SEPARATOR.$file;
$data = Filesystem::readFile($full_path);
$futures[$full_path] = PhutilXHPASTBinary::getParserFuture($data);
}
}
$console->writeErr(
"%s\n",
pht('Found %s file(s)...', phutil_count($futures)));
$results = array();
$hashes[$full_path] = $hash;
}
$bar = id(new PhutilConsoleProgressBar())
->setTotal(count($futures));
$messages = array();
$results = array();
$futures = id(new FutureIterator($futures))
->limit(8);
foreach ($futures as $full_path => $future) {
$bar->update(1);
$hash = $hashes[$full_path];
try {
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
Filesystem::readFile($full_path),
@ -67,15 +165,19 @@ final class PhabricatorInternationalizationManagementExtractWorkflow
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
foreach ($calls as $call) {
$name = $call->getChildByIndex(0)->getConcreteString();
if ($name == 'pht') {
if ($name != 'pht') {
continue;
}
$params = $call->getChildByIndex(1, 'n_CALL_PARAMETER_LIST');
$string_node = $params->getChildByIndex(0);
$string_line = $string_node->getLineNumber();
try {
$string_value = $string_node->evalStatic();
$results[$string_value][] = array(
'file' => Filesystem::readablePath($full_path),
$results[$hash][] = array(
'string' => $string_value,
'file' => Filesystem::readablePath($full_path, $root_path),
'line' => $string_line,
);
} catch (Exception $ex) {
@ -86,35 +188,115 @@ final class PhabricatorInternationalizationManagementExtractWorkflow
$ex->getMessage());
}
}
}
$tree->dispose();
}
$bar->done();
foreach ($messages as $message) {
$console->writeErr("%s\n", $message);
echo tsprintf(
"**<bg:yellow> %s </bg>** %s\n",
pht('WARNING'),
$message);
}
ksort($results);
$out = array();
$out[] = '<?php';
$out[] = '// @no'.'lint';
$out[] = 'return array(';
foreach ($results as $string => $locations) {
foreach ($locations as $location) {
$out[] = ' // '.$location['file'].':'.$location['line'];
return $results;
}
private function writeStrings($root, array $strings) {
$map = array();
foreach ($strings as $hash => $string_list) {
foreach ($string_list as $string_info) {
$map[$string_info['string']]['uses'][] = array(
'file' => $string_info['file'],
'line' => $string_info['line'],
);
}
$out[] = " '".addcslashes($string, "\0..\37\\'\177..\377")."' => null,";
$out[] = null;
}
$out[] = ');';
$out[] = null;
echo implode("\n", $out);
ksort($map);
return 0;
$json = id(new PhutilJSON())->encodeFormatted($map);
$this->writeCache($root, 'i18n_strings.json', $json);
}
private function loadLibraryFiles($root) {
$files = id(new FileFinder($root))
->withType('f')
->withSuffix('php')
->excludePath('*/.*')
->setGenerateChecksums(true)
->find();
$map = array();
foreach ($files as $file => $hash) {
$file = Filesystem::readablePath($file, $root);
$file = ltrim($file, '/');
if (dirname($file) == '.') {
continue;
}
if (dirname($file) == 'extensions') {
continue;
}
$map[$file] = md5($hash.$file);
}
return $map;
}
private function readCache($root) {
$path = $this->getCachePath($root, 'i18n_files.json');
$default = array(
'version' => self::CACHE_VERSION,
'files' => array(),
'strings' => array(),
);
if ($this->getArgv()->getArg('clean')) {
return $default;
}
if (!Filesystem::pathExists($path)) {
return $default;
}
try {
$data = Filesystem::readFile($path);
} catch (Exception $ex) {
return $default;
}
try {
$cache = phutil_json_decode($data);
} catch (PhutilJSONParserException $e) {
return $default;
}
$version = idx($cache, 'version');
if ($version !== self::CACHE_VERSION) {
return $default;
}
return $cache;
}
private function writeCache($root, $file, $data) {
$path = $this->getCachePath($root, $file);
$cache_dir = dirname($path);
if (!Filesystem::pathExists($cache_dir)) {
Filesystem::createDirectory($cache_dir, 0755, true);
}
Filesystem::writeFile($path, $data);
}
private function getCachePath($root, $to_file) {
return $root.'/.cache/'.$to_file;
}
}

View file

@ -186,9 +186,17 @@ final class PhabricatorMainMenuView extends AphrontView {
}
$result = $search;
$keyboard_config['searchID'] = $search->getID();
}
Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config);
$keyboard_config['pht'] = array(
'/' => pht('Give keyboard focus to the search box.'),
'?' => pht('Show keyboard shortcut help for the current page.'),
);
Javelin::initBehavior(
'phabricator-keyboard-shortcuts',
$keyboard_config);
if ($result) {
$result = id(new PHUIListItemView())

View file

@ -17,6 +17,7 @@ final class PHUIFeedStoryView extends AphrontView {
private $chronologicalKey;
private $tags;
private $authorIcon;
private $showTimestamp = true;
public function setTags($tags) {
$this->tags = $tags;
@ -97,6 +98,15 @@ final class PHUIFeedStoryView extends AphrontView {
return $this;
}
public function setShowTimestamp($show_timestamp) {
$this->showTimestamp = $show_timestamp;
return $this;
}
public function getShowTimestamp() {
return $this->showTimestamp;
}
public function addProject($project) {
$this->projects[] = $project;
return $this;
@ -136,6 +146,8 @@ final class PHUIFeedStoryView extends AphrontView {
if (!$this->viewed) {
$classes[] = 'phabricator-notification-unread';
}
if ($this->getShowTimestamp()) {
if ($this->epoch) {
if ($user) {
$foot = phabricator_datetime($this->epoch, $user);
@ -151,6 +163,9 @@ final class PHUIFeedStoryView extends AphrontView {
} else {
$foot = pht('No time specified.');
}
} else {
$foot = null;
}
return javelin_tag(
'div',

View file

@ -29,6 +29,16 @@ final class PHUIListItemView extends AphrontTagView {
private $indented;
private $hideInApplicationMenu;
private $icons = array();
private $openInNewWindow = false;
public function setOpenInNewWindow($open_in_new_window) {
$this->openInNewWindow = $open_in_new_window;
return $this;
}
public function getOpenInNewWindow() {
return $this->openInNewWindow;
}
public function setHideInApplicationMenu($hide) {
$this->hideInApplicationMenu = $hide;
@ -294,6 +304,7 @@ final class PHUIListItemView extends AphrontTagView {
'class' => implode(' ', $classes),
'meta' => $meta,
'sigil' => $sigil,
'target' => $this->getOpenInNewWindow() ? '_blank' : null,
),
array(
$aural,

View file

@ -308,3 +308,7 @@
font-family: 'Aleo', {$fontfamily};
padding-top: 8px;
}
.phame-comment-view .aphront-form-control.aphront-form-control-select {
display: none;
}

View file

@ -256,6 +256,11 @@
color: {$lightgreytext};
}
.phui-timeline-title .phui-timeline-value {
font-style: italic;
color: black;
}
.device-desktop .phui-timeline-extra {
float: right;
}

View file

@ -14,7 +14,7 @@ only screen and (-webkit-min-device-pixel-ratio: 1.5),
only screen and (min-resolution: 1.5dppx) {
.sprite-tokens {
background-image: url(/rsrc/image/sprite-tokens-X2.png);
background-size: 76px 76px;
background-size: 95px 95px;
}
}
@ -35,50 +35,82 @@ only screen and (min-resolution: 1.5dppx) {
background-position: -57px 0px;
}
.tokens-heart-1 {
.tokens-emoji-1 {
background-position: -76px 0px;
}
.tokens-emoji-2 {
background-position: 0px -19px;
}
.tokens-heart-2 {
.tokens-emoji-3 {
background-position: -19px -19px;
}
.tokens-like-1 {
.tokens-emoji-4 {
background-position: -38px -19px;
}
.tokens-like-2 {
.tokens-emoji-5 {
background-position: -57px -19px;
}
.tokens-medal-1 {
.tokens-emoji-6 {
background-position: -76px -19px;
}
.tokens-emoji-7 {
background-position: 0px -38px;
}
.tokens-medal-2 {
.tokens-emoji-8 {
background-position: -19px -38px;
}
.tokens-medal-3 {
.tokens-heart-1 {
background-position: -38px -38px;
}
.tokens-medal-4 {
.tokens-heart-2 {
background-position: -57px -38px;
}
.tokens-misc-1 {
.tokens-like-1 {
background-position: -76px -38px;
}
.tokens-like-2 {
background-position: 0px -57px;
}
.tokens-misc-2 {
.tokens-medal-1 {
background-position: -19px -57px;
}
.tokens-misc-3 {
.tokens-medal-2 {
background-position: -38px -57px;
}
.tokens-misc-4 {
.tokens-medal-3 {
background-position: -57px -57px;
}
.tokens-medal-4 {
background-position: -76px -57px;
}
.tokens-misc-1 {
background-position: 0px -76px;
}
.tokens-misc-2 {
background-position: -19px -76px;
}
.tokens-misc-3 {
background-position: -38px -76px;
}
.tokens-misc-4 {
background-position: -57px -76px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -32,6 +32,18 @@ JX.install('KeyboardShortcutManager', {
down: 1
},
/**
* Some keys require Alt to be pressed in order to type them on certain
* keyboard layouts.
*/
_altkeys: {
// "Alt+L" on German layouts.
'@': 1,
// "Alt+Shift+7" on German layouts.
'\\': 1
},
getInstance : function() {
if (!JX.KeyboardShortcutManager._instance) {
JX.KeyboardShortcutManager._instance = new JX.KeyboardShortcutManager();
@ -119,14 +131,24 @@ JX.install('KeyboardShortcutManager', {
}
},
_onkeyhit : function(e) {
var self = JX.KeyboardShortcutManager;
var raw = e.getRawEvent();
if (raw.altKey || raw.ctrlKey || raw.metaKey) {
if (raw.ctrlKey || raw.metaKey) {
// Never activate keyboard shortcuts if modifier keys are also
// depressed.
return;
}
// For most keystrokes, don't activate keyboard shortcuts if the Alt
// key is depressed. However, we continue if the character requires the
// use of Alt to type it on some keyboard layouts.
var key = this._getKey(e);
if (raw.altKey && !(key in self._altkeys)) {
return;
}
var target = e.getTarget();
var ignore = ['input', 'select', 'textarea', 'object', 'embed'];
if (JX.DOM.isType(target, ignore)) {

View file

@ -11,10 +11,10 @@
* Define global keyboard shortcuts.
*/
JX.behavior('phabricator-keyboard-shortcuts', function(config) {
var pht = JX.phtize(config.pht);
var workflow = null;
var desc = 'Show keyboard shortcut help for the current page.';
new JX.KeyboardShortcut('?', desc)
new JX.KeyboardShortcut('?', pht('?'))
.setHandler(function(manager) {
if (workflow) {
// Already showing the dialog.
@ -30,4 +30,14 @@ JX.behavior('phabricator-keyboard-shortcuts', function(config) {
})
.register();
if (config.searchID) {
new JX.KeyboardShortcut('/', pht('/'))
.setHandler(function() {
var search = JX.$(config.searchID);
search.focus();
search.select();
})
.register();
}
});

View file

@ -197,7 +197,11 @@ JX.install('PHUIXAutocomplete', {
_onkeypress: function(e) {
var r = e.getRawEvent();
if (r.metaKey || r.altKey || r.ctrlKey) {
// NOTE: We allow events to continue with "altKey", because you need
// to press Alt to type characters like "@" on a German keyboard layout.
// The cost of misfiring autocompleters is very small since we do not
// eat the keystroke. See T10252.
if (r.metaKey || r.ctrlKey) {
return;
}