diff --git a/.gitignore b/.gitignore index df6c16cde6..d9f44b6bab 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Diviner /docs/ /.divinercache/ +/src/.cache/ # libphutil /src/.phutil_module_cache diff --git a/resources/celerity/map.php b/resources/celerity/map.php index be152c24b9..5763349c0e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -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', ), diff --git a/resources/sprite/manifest/tokens.json b/resources/sprite/manifest/tokens.json index 6096050a30..c9768db11f 100644 --- a/resources/sprite/manifest/tokens.json +++ b/resources/sprite/manifest/tokens.json @@ -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", diff --git a/resources/sprite/tokens_1x/emoji-1.png b/resources/sprite/tokens_1x/emoji-1.png new file mode 100644 index 0000000000..dfc2ec784a Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-1.png differ diff --git a/resources/sprite/tokens_1x/emoji-2.png b/resources/sprite/tokens_1x/emoji-2.png new file mode 100644 index 0000000000..124b5a84d1 Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-2.png differ diff --git a/resources/sprite/tokens_1x/emoji-3.png b/resources/sprite/tokens_1x/emoji-3.png new file mode 100644 index 0000000000..8d3b9f10e0 Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-3.png differ diff --git a/resources/sprite/tokens_1x/emoji-4.png b/resources/sprite/tokens_1x/emoji-4.png new file mode 100644 index 0000000000..92ff339ce2 Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-4.png differ diff --git a/resources/sprite/tokens_1x/emoji-5.png b/resources/sprite/tokens_1x/emoji-5.png new file mode 100644 index 0000000000..66c746910d Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-5.png differ diff --git a/resources/sprite/tokens_1x/emoji-6.png b/resources/sprite/tokens_1x/emoji-6.png new file mode 100644 index 0000000000..4509a81684 Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-6.png differ diff --git a/resources/sprite/tokens_1x/emoji-7.png b/resources/sprite/tokens_1x/emoji-7.png new file mode 100644 index 0000000000..cb4780d99b Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-7.png differ diff --git a/resources/sprite/tokens_1x/emoji-8.png b/resources/sprite/tokens_1x/emoji-8.png new file mode 100644 index 0000000000..eb50ae85cb Binary files /dev/null and b/resources/sprite/tokens_1x/emoji-8.png differ diff --git a/resources/sprite/tokens_2x/emoji-1.png b/resources/sprite/tokens_2x/emoji-1.png new file mode 100644 index 0000000000..ba8dfc6fa3 Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-1.png differ diff --git a/resources/sprite/tokens_2x/emoji-2.png b/resources/sprite/tokens_2x/emoji-2.png new file mode 100644 index 0000000000..42ade697bd Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-2.png differ diff --git a/resources/sprite/tokens_2x/emoji-3.png b/resources/sprite/tokens_2x/emoji-3.png new file mode 100644 index 0000000000..1a5cbc1947 Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-3.png differ diff --git a/resources/sprite/tokens_2x/emoji-4.png b/resources/sprite/tokens_2x/emoji-4.png new file mode 100644 index 0000000000..5ac7e115ae Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-4.png differ diff --git a/resources/sprite/tokens_2x/emoji-5.png b/resources/sprite/tokens_2x/emoji-5.png new file mode 100644 index 0000000000..4284870eda Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-5.png differ diff --git a/resources/sprite/tokens_2x/emoji-6.png b/resources/sprite/tokens_2x/emoji-6.png new file mode 100644 index 0000000000..13771dd348 Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-6.png differ diff --git a/resources/sprite/tokens_2x/emoji-7.png b/resources/sprite/tokens_2x/emoji-7.png new file mode 100644 index 0000000000..b7df3d7e95 Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-7.png differ diff --git a/resources/sprite/tokens_2x/emoji-8.png b/resources/sprite/tokens_2x/emoji-8.png new file mode 100644 index 0000000000..f468e6112a Binary files /dev/null and b/resources/sprite/tokens_2x/emoji-8.png differ diff --git a/resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql b/resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql new file mode 100644 index 0000000000..0fa4f17197 --- /dev/null +++ b/resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_blog + MODIFY parentDomain VARCHAR(128) NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql b/resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql new file mode 100644 index 0000000000..6c2f2e2483 --- /dev/null +++ b/resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_blog + MODIFY parentSite VARCHAR(128) NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 051ab9c41c..791c4f8e5e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index e010d4491f..82b094c311 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -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; } } diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php index 03bbb3c52d..6a08ec68ae 100644 --- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php @@ -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( diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index 7f7b323956..a70cde04c4 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -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(); diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php index ad15d41b9d..85c2f2f87e 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php @@ -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); diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index 3e67f76f07..a79cf133a3 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -40,6 +40,8 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', 'create/' => 'PhabricatorDashboardPanelEditController', + $this->getEditRoutePattern('editpro/') + => 'PhabricatorDashboardPanelEditproController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardPanelEditController', 'render/(?P\d+)/' => 'PhabricatorDashboardPanelRenderController', 'archive/(?P\d+)/' diff --git a/src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php b/src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php new file mode 100644 index 0000000000..5fa80da5a6 --- /dev/null +++ b/src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php @@ -0,0 +1,20 @@ +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(); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php new file mode 100644 index 0000000000..b40dfd85b3 --- /dev/null +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php @@ -0,0 +1,105 @@ +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); + } + +} diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php index cf045265b9..6657e9cc41 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php @@ -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); diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php new file mode 100644 index 0000000000..48940dde60 --- /dev/null +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php @@ -0,0 +1,96 @@ +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()), + ); + } + +} diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php index 4e69fba636..5dae421674 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php +++ b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php @@ -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'); diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index 60985e364d..fa12659915 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -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, diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index b8892a94ea..d3801fa206 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -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) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index c22b888ac5..2ff0854a89 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -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()); diff --git a/src/applications/notification/builder/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/PhabricatorNotificationBuilder.php index a8af4ff00e..12e4b57bfb 100644 --- a/src/applications/notification/builder/PhabricatorNotificationBuilder.php +++ b/src/applications/notification/builder/PhabricatorNotificationBuilder.php @@ -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)); } diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index bfaebaf606..41dade2747 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -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(); diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index d1d6de760d..7f911d29d9 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -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, diff --git a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php index 2dccc57385..75cf8559d1 100644 --- a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php @@ -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())); } } diff --git a/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php b/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php index a313e45eab..b6a2d2b50d 100644 --- a/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php @@ -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() { diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index d385b65215..f56702ed62 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -31,13 +31,10 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( - '/phame/' => array( + '/J(?P[1-9]\d*)' => 'PhamePostViewController', + '/phame/' => array( '' => 'PhameHomeController', // NOTE: The live routes include an initial "/", so leave it off @@ -46,17 +43,16 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { 'post/' => array( '(?:query/(?P[^/]+)/)?' => 'PhamePostListController', 'blogger/(?P[\w\.-_]+)/' => 'PhamePostListController', - 'edit/(?:(?P[^/]+)/)?' => 'PhamePostEditController', + $this->getEditRoutePattern('edit/') + => 'PhamePostEditController', 'history/(?P\d+)/' => 'PhamePostHistoryController', 'view/(?P\d+)/(?:(?P[^/]+)/)?' => 'PhamePostViewController', '(?Ppublish|unpublish)/(?P\d+)/' => 'PhamePostPublishController', 'preview/(?P\d+)/' => 'PhamePostPreviewController', 'preview/' => 'PhabricatorMarkupPreviewController', - 'framed/(?P\d+)/' => 'PhamePostFramedController', 'move/(?P\d+)/' => 'PhamePostMoveController', 'archive/(?P\d+)/' => 'PhamePostArchiveController', - 'comment/(?P[1-9]\d*)/' => 'PhamePostCommentController', ), 'blog/' => array( '(?:query/(?P[^/]+)/)?' => '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( diff --git a/src/applications/phame/controller/PhameLiveController.php b/src/applications/phame/controller/PhameLiveController.php index 1287a33f35..2d8b2ee45f 100644 --- a/src/applications/phame/controller/PhameLiveController.php +++ b/src/applications/phame/controller/PhameLiveController.php @@ -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; diff --git a/src/applications/phame/controller/post/PhamePostCommentController.php b/src/applications/phame/controller/post/PhamePostCommentController.php deleted file mode 100644 index 54c041ef94..0000000000 --- a/src/applications/phame/controller/post/PhamePostCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -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); - } - } - -} diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index f7dddebe53..a056502349 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -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); } diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index d075a750ea..ea1c132f64 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -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(); diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index 1c58722e2e..382ef3f8ff 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -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(); diff --git a/src/applications/phame/mail/PhamePostMailReceiver.php b/src/applications/phame/mail/PhamePostMailReceiver.php index 975d64a668..3655e73906 100644 --- a/src/applications/phame/mail/PhamePostMailReceiver.php +++ b/src/applications/phame/mail/PhamePostMailReceiver.php @@ -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) { diff --git a/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php b/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php index 4250c91949..7c86d89d41 100644 --- a/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php +++ b/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php @@ -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); diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index 8f81c9362e..d1d9a791ec 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -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()) diff --git a/src/applications/phame/remarkup/PhamePostRemarkupRule.php b/src/applications/phame/remarkup/PhamePostRemarkupRule.php new file mode 100644 index 0000000000..92a2f0d350 --- /dev/null +++ b/src/applications/phame/remarkup/PhamePostRemarkupRule.php @@ -0,0 +1,19 @@ +getEngine()->getConfig('viewer'); + + return id(new PhamePostQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + } + +} diff --git a/src/applications/phame/site/PhameBlogSite.php b/src/applications/phame/site/PhameBlogSite.php index 6a5eebaf31..20bdfd5230 100644 --- a/src/applications/phame/site/PhameBlogSite.php +++ b/src/applications/phame/site/PhameBlogSite.php @@ -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() { diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 23ab671552..dc91672465 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -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?', diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 4c4acf4dc1..351bcb5dc0 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -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(); diff --git a/src/applications/phid/view/PHUIHandleView.php b/src/applications/phid/view/PHUIHandleView.php index f4bce39662..6d968bd7ee 100644 --- a/src/applications/phid/view/PHUIHandleView.php +++ b/src/applications/phid/view/PHUIHandleView.php @@ -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 { diff --git a/src/applications/phriction/application/PhabricatorPhrictionApplication.php b/src/applications/phriction/application/PhabricatorPhrictionApplication.php index c996365c67..d0eb46a069 100644 --- a/src/applications/phriction/application/PhabricatorPhrictionApplication.php +++ b/src/applications/phriction/application/PhabricatorPhrictionApplication.php @@ -59,7 +59,7 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication { 'new/' => 'PhrictionNewController', 'move/(?P[1-9]\d*)/' => 'PhrictionMoveController', - 'preview/(?P.+/)' => 'PhrictionMarkupPreviewController', + 'preview/(?P.*/)' => 'PhrictionMarkupPreviewController', 'diff/(?P[1-9]\d*)/' => 'PhrictionDiffController', ), ); diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index fd8c57e9b4..a4f697da0c 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -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); diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php index f912a551c5..a15cb3d79f 100644 --- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php +++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php @@ -30,6 +30,7 @@ final class PhabricatorWorkingCopyDiscoveryTestCase $this->assertEqual( array( '763d4ab372445551c95fb5cccd1a7a223f5b2ac8', + '41fa35914aa19c1aa6e57004d9745c05929c3563', ), mpull($refs, 'getIdentifier')); } diff --git a/src/applications/repository/engine/__tests__/data/GT.git.tgz b/src/applications/repository/engine/__tests__/data/GT.git.tgz index b53e9a2d1c..afd7e04a96 100644 Binary files a/src/applications/repository/engine/__tests__/data/GT.git.tgz and b/src/applications/repository/engine/__tests__/data/GT.git.tgz differ diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 4a7e3d4681..654ea42c3e 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -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(), ); } diff --git a/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php b/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php index c96c0717a9..e2285e2543 100644 --- a/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php +++ b/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php @@ -33,7 +33,6 @@ final class PhabricatorProfilePanelPHIDType $config = $objects[$phid]; $handle->setName(pht('Profile Panel')); - $handle->setURI($config->getURI()); } } diff --git a/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php b/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php index 5e9d16f391..4b1f135c6a 100644 --- a/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php +++ b/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php @@ -27,7 +27,7 @@ final class PhabricatorUserPreferencesSearchEngine } protected function getURI($path) { - return '/settings/list/'.$path; + return '/settings/'.$path; } protected function getBuiltinQueryNames() { diff --git a/src/applications/tokens/controller/PhabricatorTokenGiveController.php b/src/applications/tokens/controller/PhabricatorTokenGiveController.php index 83f9a6417a..c5d172ee64 100644 --- a/src/applications/tokens/controller/PhabricatorTokenGiveController.php +++ b/src/applications/tokens/controller/PhabricatorTokenGiveController.php @@ -90,7 +90,7 @@ final class PhabricatorTokenGiveController extends PhabricatorTokenController { $aural, $token->renderIcon(), )); - if ((++$ii % 4) == 0) { + if ((++$ii % 6) == 0) { $buttons[] = phutil_tag('br'); } } diff --git a/src/applications/tokens/query/PhabricatorTokenQuery.php b/src/applications/tokens/query/PhabricatorTokenQuery.php index 8992e719fb..07e15d4b03 100644 --- a/src/applications/tokens/query/PhabricatorTokenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenQuery.php @@ -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; diff --git a/src/applications/transactions/storage/PhabricatorModularTransaction.php b/src/applications/transactions/storage/PhabricatorModularTransaction.php index 5dc6229a77..8ea6248563 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransaction.php +++ b/src/applications/transactions/storage/PhabricatorModularTransaction.php @@ -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; diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index b37bf0d61d..a76f734c14 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -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(), diff --git a/src/docs/user/installation_guide.diviner b/src/docs/user/installation_guide.diviner index 8feb8ae1d0..749ca9a63d 100644 --- a/src/docs/user/installation_guide.diviner +++ b/src/docs/user/installation_guide.diviner @@ -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: diff --git a/src/docs/user/userguide/diffusion_existing.diviner b/src/docs/user/userguide/diffusion_existing.diviner new file mode 100644 index 0000000000..56226a84a1 --- /dev/null +++ b/src/docs/user/userguide/diffusion_existing.diviner @@ -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}. diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 08be97b6b8..d1f4b541bc 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -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. diff --git a/src/docs/user/userguide/phame.diviner b/src/docs/user/userguide/phame.diviner index 0de225c49f..d42ddc7d23 100644 --- a/src/docs/user/userguide/phame.diviner +++ b/src/docs/user/userguide/phame.diviner @@ -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. diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php index f8ca7a79a8..02777fb7a7 100644 --- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php @@ -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,14 +336,19 @@ final class PhabricatorDatabaseRef } if ($is_replica) { - $latency = (int)idx($replica_status, 'Seconds_Behind_Master'); - $ref->setReplicaDelay($latency); - if ($latency > 30) { - $ref->setReplicaStatus(self::REPLICATION_SLOW); - $ref->setReplicaMessage( - pht( - 'This replica is lagging far behind the master. Data is at '. - 'risk!')); + $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); + $ref->setReplicaMessage( + pht( + 'This replica is lagging far behind the master. Data is at '. + 'risk!')); + } } } } diff --git a/src/infrastructure/graph/DifferentialRevisionGraph.php b/src/infrastructure/graph/DifferentialRevisionGraph.php index 3b5d638ec7..892540f610 100644 --- a/src/infrastructure/graph/DifferentialRevisionGraph.php +++ b/src/infrastructure/graph/DifferentialRevisionGraph.php @@ -54,6 +54,8 @@ final class DifferentialRevisionGraph $link = $viewer->renderHandle($phid); } + $link = AphrontTableView::renderSingleDisplayLine($link); + return array( $trace, $status, diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index b793493cd3..1568c8c656 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -67,6 +67,8 @@ final class ManiphestTaskGraph $link = $viewer->renderHandle($phid); } + $link = AphrontTableView::renderSingleDisplayLine($link); + return array( $trace, $status, diff --git a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php index 7714ad81ab..1da2e16e27 100644 --- a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php +++ b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php @@ -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; - $data = Filesystem::readFile($full_path); - $futures[$full_path] = PhutilXHPASTBinary::getParserFuture($data); + 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)).'/'; } } - $console->writeErr( - "%s\n", - pht('Found %s file(s)...', phutil_count($futures))); + $targets = array_unique($targets); - $results = array(); + foreach ($targets as $library) { + echo tsprintf( + "** %s ** %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( + "** %s ** %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( + "** %s ** %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); + + $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,24 +165,27 @@ final class PhabricatorInternationalizationManagementExtractWorkflow $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); foreach ($calls as $call) { $name = $call->getChildByIndex(0)->getConcreteString(); - if ($name == 'pht') { - $params = $call->getChildByIndex(1, 'n_CALL_PARAMETER_LIST'); - $string_node = $params->getChildByIndex(0); - $string_line = $string_node->getLineNumber(); - try { - $string_value = $string_node->evalStatic(); + if ($name != 'pht') { + continue; + } - $results[$string_value][] = array( - 'file' => Filesystem::readablePath($full_path), - 'line' => $string_line, - ); - } catch (Exception $ex) { - $messages[] = pht( - 'WARNING: Failed to evaluate pht() call on line %d in "%s": %s', - $call->getLineNumber(), - $full_path, - $ex->getMessage()); - } + $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[$hash][] = array( + 'string' => $string_value, + 'file' => Filesystem::readablePath($full_path, $root_path), + 'line' => $string_line, + ); + } catch (Exception $ex) { + $messages[] = pht( + 'WARNING: Failed to evaluate pht() call on line %d in "%s": %s', + $call->getLineNumber(), + $full_path, + $ex->getMessage()); } } @@ -93,28 +194,109 @@ final class PhabricatorInternationalizationManagementExtractWorkflow $bar->done(); foreach ($messages as $message) { - $console->writeErr("%s\n", $message); + echo tsprintf( + "** %s ** %s\n", + pht('WARNING'), + $message); } - ksort($results); + return $results; + } - $out = array(); - $out[] = ' $locations) { - foreach ($locations as $location) { - $out[] = ' // '.$location['file'].':'.$location['line']; + 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; } } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 69ff522d5b..25dba1871a 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -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()) diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index 8267fa0197..fbba4a3f46 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -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,20 +146,25 @@ final class PHUIFeedStoryView extends AphrontView { if (!$this->viewed) { $classes[] = 'phabricator-notification-unread'; } - if ($this->epoch) { - if ($user) { - $foot = phabricator_datetime($this->epoch, $user); - $foot = phutil_tag( - 'span', - array( - 'class' => 'phabricator-notification-date', - ), - $foot); + + if ($this->getShowTimestamp()) { + if ($this->epoch) { + if ($user) { + $foot = phabricator_datetime($this->epoch, $user); + $foot = phutil_tag( + 'span', + array( + 'class' => 'phabricator-notification-date', + ), + $foot); + } else { + $foot = null; + } } else { - $foot = null; + $foot = pht('No time specified.'); } } else { - $foot = pht('No time specified.'); + $foot = null; } return javelin_tag( diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 4af02e572e..3a4909a1ee 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -29,8 +29,18 @@ final class PHUIListItemView extends AphrontTagView { private $indented; private $hideInApplicationMenu; private $icons = array(); + private $openInNewWindow = false; - public function setHideInApplicationMenu($hide) { + 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; return $this; } @@ -294,6 +304,7 @@ final class PHUIListItemView extends AphrontTagView { 'class' => implode(' ', $classes), 'meta' => $meta, 'sigil' => $sigil, + 'target' => $this->getOpenInNewWindow() ? '_blank' : null, ), array( $aural, diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index eca45b7741..75f1326af7 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -308,3 +308,7 @@ font-family: 'Aleo', {$fontfamily}; padding-top: 8px; } + +.phame-comment-view .aphront-form-control.aphront-form-control-select { + display: none; +} diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index e6f43f70ad..9460fbac2f 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -256,6 +256,11 @@ color: {$lightgreytext}; } +.phui-timeline-title .phui-timeline-value { + font-style: italic; + color: black; +} + .device-desktop .phui-timeline-extra { float: right; } diff --git a/webroot/rsrc/css/sprite-tokens.css b/webroot/rsrc/css/sprite-tokens.css index 2e51031199..5e61d6c9be 100644 --- a/webroot/rsrc/css/sprite-tokens.css +++ b/webroot/rsrc/css/sprite-tokens.css @@ -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; +} diff --git a/webroot/rsrc/image/sprite-tokens-X2.png b/webroot/rsrc/image/sprite-tokens-X2.png index 3596bf5374..2833fc9373 100644 Binary files a/webroot/rsrc/image/sprite-tokens-X2.png and b/webroot/rsrc/image/sprite-tokens-X2.png differ diff --git a/webroot/rsrc/image/sprite-tokens.png b/webroot/rsrc/image/sprite-tokens.png index fc02173e40..acc7f1106e 100644 Binary files a/webroot/rsrc/image/sprite-tokens.png and b/webroot/rsrc/image/sprite-tokens.png differ diff --git a/webroot/rsrc/js/core/KeyboardShortcutManager.js b/webroot/rsrc/js/core/KeyboardShortcutManager.js index 3acef196a7..c9a1fbe64e 100644 --- a/webroot/rsrc/js/core/KeyboardShortcutManager.js +++ b/webroot/rsrc/js/core/KeyboardShortcutManager.js @@ -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)) { diff --git a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js index a2f7ca172b..e1c6fa97d8 100644 --- a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js +++ b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js @@ -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(); + } + }); diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index 801fa36ffb..94f37dc413 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -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; }